/
DICOMPETSUVPlugin.py
263 lines (209 loc) · 9.32 KB
/
DICOMPETSUVPlugin.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
import os
import sys as SYS
from __main__ import vtk, qt, ctk, slicer
from DICOMLib import DICOMPlugin
from DICOMLib import DICOMLoadable
if slicer.app.majorVersion >= 5 or (slicer.app.majorVersion == 4 and slicer.app.minorVersion >= 11):
import pydicom
else:
import dicom
import DICOMLib
import math as math
#
# This is the plugin to handle PET SUV volumes
# from DICOM files into MRML nodes. It follows the DICOM module's
# plugin architecture.
#
class DICOMPETSUVPluginClass(DICOMPlugin):
""" PET specific interpretation code
"""
def __init__(self):
super(DICOMPETSUVPluginClass,self).__init__()
#print "DICOMPETSUVPlugin __init__()"
self.loadType = "PET SUV Plugin"
self.tags['patientID'] = "0010,0020"
self.tags['patientName'] = "0010,0010"
self.tags['patientBirthDate'] = "0010,0030"
self.tags['patientSex'] = "0010,0040"
self.tags['patientHeight'] = "0010,1020"
self.tags['patientWeight'] = "0010,1030"
self.tags['relatedSeriesSequence'] = "0008,1250"
self.tags['radioPharmaconStartTime'] = "0018,1072"
self.tags['decayCorrection'] = "0054,1102"
self.tags['decayFactor'] = "0054,1321"
self.tags['frameRefTime'] = "0054,1300"
self.tags['radionuclideHalfLife'] = "0018,1075"
self.tags['contentTime'] = "0008,0033"
self.tags['seriesTime'] = "0008,0031"
self.tags['seriesDescription'] = "0008,103e"
self.tags['seriesModality'] = "0008,0060"
self.tags['seriesInstanceUID'] = "0020,000E"
self.tags['sopInstanceUID'] = "0008,0018"
self.tags['seriesInstanceUID'] = "0020,000e"
self.tags['studyInstanceUID'] = "0020,000D"
self.tags['studyDate'] = "0008,0020"
self.tags['studyTime'] = "0008,0030"
self.tags['studyID'] = "0020,0010"
self.tags['rows'] = "0028,0010"
self.tags['columns'] = "0028,0011"
self.tags['spacing'] = "0028,0030"
self.tags['position'] = "0020,0032"
self.tags['orientation'] = "0020,0037"
self.tags['pixelData'] = "7fe0,0010"
self.tags['referencedImageRWVMappingSeq'] = "0008,1140"
self.fileLists = []
self.patientName = ""
self.patientBirthDate = ""
self.patientSex = ""
self.ctTerm = "CT"
self.petTerm = "PT"
self.scalarVolumePlugin = slicer.modules.dicomPlugins['DICOMScalarVolumePlugin']()
self.rwvPlugin = slicer.modules.dicomPlugins['DICOMRWVMPlugin']()
def __getDirectoryOfImageSeries(self, sopInstanceUID):
f = slicer.dicomDatabase.fileForInstance(sopInstanceUID)
return os.path.dirname(f)
def __getSeriesInformation(self,seriesFiles,dicomTag):
if seriesFiles:
return slicer.dicomDatabase.fileValue(seriesFiles[0],dicomTag)
def examine(self,fileLists):
""" Returns a list of DICOMLoadable instances
corresponding to ways of interpreting the
fileLists parameter.
"""
loadables = []
# get from cache or create new loadables
for fileList in fileLists:
cachedLoadables = self.getCachedLoadables(fileList)
if cachedLoadables:
loadables += cachedLoadables
else:
if slicer.dicomDatabase.fileValue(fileList[0],self.tags['seriesModality']) == "PT":
# check if PET series already has Real World Value Mapping
hasRWVM = False
if slicer.app.majorVersion >= 5 or (slicer.app.majorVersion == 4 and slicer.app.minorVersion >= 11):
ptFile = pydicom.dcmread(fileList[0])
else:
ptFile = dicom.read_file(fileList[0])
studyUID = slicer.dicomDatabase.fileValue(fileList[0],self.tags['studyInstanceUID'])
for series in slicer.dicomDatabase.seriesForStudy(studyUID):
if ptFile.SeriesInstanceUID != series:
for seriesFile in slicer.dicomDatabase.filesForSeries(series):
if slicer.dicomDatabase.fileValue(seriesFile,self.tags['seriesModality']) == "RWV":
if ptFile.SeriesInstanceUID == self.getReferencedSeriesInstanceUID(seriesFile):
hasRWVM = True
loadablesForFiles = self.rwvPlugin.getLoadablePetSeriesFromRWVMFile(seriesFile)
for loadable in loadablesForFiles:
loadable.confidence = 1.0
self.abbreviateLoadableName(loadable)
loadables += loadablesForFiles
self.cacheLoadables(fileList,loadablesForFiles)
if not hasRWVM:
# Call SUV Factor Calculator to create RWVM files for this PET series
rwvmFile = self.generateRWVMforFileList(fileList)
loadablesForFiles = self.rwvPlugin.getLoadablePetSeriesFromRWVMFile(rwvmFile)
for loadable in loadablesForFiles:
loadable.confidence = 0.95
self.abbreviateLoadableName(loadable)
self.cacheLoadables(fileList,loadablesForFiles)
# there may be multiple loadables per one RWV series, add it only
# once. Note we only add RWV to the DB if we create a new RWV
# instance.
loadablesForFiles[0].derivedItems = [rwvmFile]
loadables += loadablesForFiles
return loadables
def generateRWVMforFileList(self, fileList):
"""Return a list of loadables after generating Real World Value Mapping
objects for a PET series
"""
loadables = []
# Call SUV Factor Calculator module
sopInstanceUID = self.__getSeriesInformation(fileList, self.tags['sopInstanceUID'])
seriesDirectory = self.__getDirectoryOfImageSeries(sopInstanceUID)
# copy files to a temp location, since otherwise the command line can easily exceed
# the maximum on Windows (~8k characters)
import tempfile, shutil
cliTempDir = os.path.join(tempfile.mkdtemp())
for inputFilePath in fileList:
destFile = os.path.join(cliTempDir,os.path.split(inputFilePath)[1])
shutil.copyfile(inputFilePath, destFile)
parameters = {}
parameters['PETDICOMPath'] = cliTempDir
parameters['RWVDICOMPath'] = seriesDirectory
parameters['PETSeriesInstanceUID'] = self.__getSeriesInformation(fileList, self.tags['seriesInstanceUID'])
SUVFactorCalculator = None
SUVFactorCalculator = slicer.cli.run(slicer.modules.suvfactorcalculator, SUVFactorCalculator, parameters, wait_for_completion=True)
shutil.rmtree(cliTempDir)
if SUVFactorCalculator.GetStatusString() != 'Completed':
raise RuntimeError("SUVFactorCalculator CLI did not complete cleanly")
rwvFile = SUVFactorCalculator.GetParameterDefault(2,1)
return rwvFile
def getReferencedSeriesInstanceUID(self, rwvmFile):
"""Helper method to read the Referenced Series Instance UID from an RWVM file"""
if slicer.app.majorVersion >= 5 or (slicer.app.majorVersion == 4 and slicer.app.minorVersion >= 11):
dicomFile = pydicom.dcmread(rwvmFile)
else:
dicomFile = dicom.read_file(rwvmFile)
refSeriesSeq = dicomFile.ReferencedSeriesSequence
return refSeriesSeq[0].SeriesInstanceUID
def abbreviateLoadableName(self, loadable):
"""Helper method to shorten the name of the SUV conversion """
if "Standardized Uptake Value body weight" in loadable.name:
loadable.name = (loadable.name).replace('Standardized Uptake Value body weight','(SUVbw)')
loadable.selected = True
elif "Standardized Uptake Value ideal body weight" in loadable.name:
loadable.name = (loadable.name).replace('Standardized Uptake Value ideal body weight','(SUVibw)')
elif "Standardized Uptake Value lean body mass" in loadable.name:
loadable.name = (loadable.name).replace('Standardized Uptake Value lean body mass','(SUVlbm)')
elif "Standardized Uptake Value body surface area" in loadable.name:
loadable.name = (loadable.name).replace('Standardized Uptake Value body surface area','(SUVbsa)')
return
def load(self,loadable):
"""Load the series into Slicer"""
# Call the DICOMRWVMPlugin to get the image node
imageNode = self.rwvPlugin.loadPetSeries(loadable)
return imageNode
#
# DICOMPETSUVPlugin
#
class DICOMPETSUVPlugin:
"""
This class is the 'hook' for slicer to detect and recognize the plugin
as a loadable scripted module
"""
def __init__(self, parent):
parent.title = "DICOM PET SUV Volume Plugin"
parent.categories = ["Developer Tools.DICOM Plugins"]
parent.contributors = ["Ethan Ulrich (Univ. of Iowa)"]
parent.helpText = """
Plugin to the DICOM Module to parse and load PET volumes
from DICOM files. Provides options for standardized uptake values.
No module interface here, only in the DICOM module
"""
parent.acknowledgementText = """
This DICOM Plugin was developed by
Ethan Ulrich, Univ. of Iowa
and was partially funded by NIH grant U24 CA180918.
"""
# don't show this module - it only appears in the DICOM module
parent.hidden = True
# Add this extension to the DICOM module's list for discovery when the module
# is created. Since this module may be discovered before DICOM itself,
# create the list if it doesn't already exist.
try:
slicer.modules.dicomPlugins
except AttributeError:
slicer.modules.dicomPlugins = {}
slicer.modules.dicomPlugins['DICOMPETSUVPlugin'] = DICOMPETSUVPluginClass
#
# DICOMPETSUVWidget
#
class DICOMPETSUVWidget:
def __init__(self, parent = None):
self.parent = parent
def setup(self):
# don't display anything for this widget - it will be hidden anyway
pass
def enter(self):
pass
def exit(self):
pass