loadDicom.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  1. import slicer
  2. import os
  3. import subprocess
  4. import re
  5. import dicom
  6. dicomModify=os.getenv("HOME")
  7. if not dicomModify==None:
  8. dicomModify+="/software/install/"
  9. dicomModify+="dicomModify/bin/dicomModify"
  10. class loadDicom(slicer.ScriptedLoadableModule.ScriptedLoadableModule):
  11. def __init__(self,parent):
  12. slicer.ScriptedLoadableModule.ScriptedLoadableModule.__init__(self, parent)
  13. self.className="loadDicom"
  14. self.parent.title="loadDicom"
  15. self.parent.categories = ["Examples"]
  16. self.parent.dependencies = []
  17. self.parent.contributors = ["Andrej Studen (UL/FMF)"] # replace with "Firstname Lastname (Organization)"
  18. self.parent.helpText = """
  19. utilities for parsing dicom entries
  20. """
  21. self.parent.acknowledgementText = """
  22. Developed within the medical physics research programme of the Slovenian research agency.
  23. """ # replace with organization, grant and thanks.
  24. class loadDicomWidget(slicer.ScriptedLoadableModule.ScriptedLoadableModuleWidget):
  25. """Uses ScriptedLoadableModuleWidget base class, available at:
  26. https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py
  27. """
  28. def setup(self):
  29. slicer.ScriptedLoadableModule.ScriptedLoadableModuleWidget.setup(self)
  30. class loadDicomLogic(slicer.ScriptedLoadableModule.ScriptedLoadableModuleLogic):
  31. def __init__(self,parent):
  32. slicer.ScriptedLoadableModule.ScriptedLoadableModuleLogic.__init__(self, parent)
  33. self.tag={
  34. 'studyDate': "0008,0020",
  35. 'studyTime': "0008,0030",
  36. 'seriesTime':"0008,0031",
  37. 'modality': "0008,0060",
  38. 'presentationIntentType':"0008,0068",
  39. 'manufacturer':"0008,0070",
  40. 'studyDescription': "0008,1030",
  41. 'seriesDescription': "0008,103e",
  42. 'patientName':"0010,0010",
  43. 'patientId':"0010,0020",
  44. 'patientBirthDate': "0010,0030",
  45. 'patientSex': "0010,0040",
  46. 'patientAge': "0010,1010",
  47. 'patientComments': "0010,4000",
  48. 'sequenceName':"0018,0024",
  49. 'kVP':"0018,0060",
  50. 'percentPhaseFieldOfView':"0018,0094",
  51. 'xRayTubeCurrent':"0018,1151",
  52. 'exposure':"0018,1152",
  53. 'imagerPixelSpacing':"0018,1164",
  54. 'bodyPartThickness':"0018,11a0",
  55. 'compressionForce':"0018,11a2",
  56. 'viewPosition':"0018,5101",
  57. 'fieldOfViewHorizontalFlip':"0018,7034",
  58. 'studyInstanceUid':"0020,000d",
  59. 'seriesInstanceUid':"0020,000e",
  60. 'studyId': "0020,0010",
  61. 'seriesNumber':"0020,0011",
  62. 'instanceNumber':"0020,0013",
  63. 'frameOfReferenceInstanceUid':"0020,0052",
  64. 'imageLaterality':"0020,0060",
  65. 'imagesInAcquisition':"0020,1002",
  66. 'photometricInterpretation':"0028,0004"
  67. }
  68. self.tagPyDicom={
  69. 'studyDate': 0x00080020,
  70. 'studyTime': 0x00080030,
  71. 'modality': 0x00080060,
  72. 'presentationIntentType': 0x00080068,
  73. 'manufacturer': 0x00080070,
  74. 'studyDescription': 0x00081030,
  75. 'seriesDescription': 0x0008103e,
  76. 'patientName': 0x00100010,
  77. 'patientId': 0x00100020,
  78. 'patientBirthDate': 0x00100030,
  79. 'patientSex': 0x00100040,
  80. 'patientComments': 0x00104000,
  81. 'sequenceName': 0x00180024,
  82. 'kVP': 0x00180060,
  83. 'percentPhaseFieldOfView': 0x00180094,
  84. 'xRayTubeCurrent': 0x00181151,
  85. 'exposure': 0x00181152,
  86. 'imagerPixelSpacing': 0x00181164,
  87. 'bodyPartThickness': 0x001811a0,
  88. 'compressionForce': 0x001811a2,
  89. 'viewPosition': 0x00185101,
  90. 'studyInstanceUid': 0x0020000d,
  91. 'seriesInstanceUid': 0x0020000e,
  92. 'studyId': 0x00200010,
  93. 'seriesNumber': 0x00200011,
  94. 'instanceNumber': 0x00200013,
  95. 'frameOfReferenceInstanceUid': 0x00200052
  96. }
  97. #new_dict_items={
  98. # 0x001811a0: ('DS','BodyPartThickness','Body Part Thickness')
  99. #}
  100. #dicom.datadict.add_dict_entries(new_dict_items)
  101. def load(self,sNet,dir,doRemove=True):
  102. print("Loading dir {}").format(dir)
  103. dicomFiles=sNet.listRelativeDir(dir)
  104. #filelist=[os.path.join(dir,f) for f in os.listdir(dir)]
  105. filelist=[]
  106. for f in dicomFiles:
  107. localPath=sNet.DownloadFileToCache(f)
  108. f0=localPath
  109. f1=f0+"1"
  110. if not dicomModify==None:
  111. subprocess.call(dicomModify+" "+f0+" "+f1+" && mv "+f1+" "+f0+";", shell=True)
  112. filelist.append(localPath)
  113. try:
  114. loadables=self.volumePlugin.examineForImport([filelist])
  115. except AttributeError:
  116. self.volumePlugin=slicer.modules.dicomPlugins['DICOMScalarVolumePlugin']()
  117. loadables=self.volumePlugin.examineForImport([filelist])
  118. for loadable in loadables:
  119. #TODO check if it makes sense to load a particular loadable
  120. fileBuffer = open(loadable.files[0])
  121. plan=dicom.read_file(fileBuffer)
  122. if loadable.name.find('imageOrientationPatient')>-1:
  123. continue
  124. volumeNode=self.volumePlugin.load(loadable)
  125. if volumeNode != None:
  126. vName='Series'+plan[0x00200011].value
  127. volumeNode.SetName(vName)
  128. try:
  129. loadableRTs=self.RTPlugin.examineForImport([filelist])
  130. except:
  131. self.RTPlugin=plugin=slicer.modules.dicomPlugins['DicomRtImportExportPlugin']()
  132. loadableRTs=self.RTPlugin.examineForImport([filelist])
  133. for loadable in loadableRTs:
  134. segmentationNode=self.RTPlugin.load(loadable)
  135. #if segmentationNode!= None:
  136. # segmentationNode.SetName('SegmentationBR')
  137. if not doRemove:
  138. return
  139. for f in filelist:
  140. os.remove(f)
  141. def applyFilter(self,loadable,filter,nodeMetadata):
  142. filterOK=True
  143. fileBuffer = open(loadable.files[0])
  144. try:
  145. plan = dicom.read_file(fileBuffer)
  146. except:
  147. return False
  148. for key in filter:
  149. #try:
  150. # fileValue=plan[self.tag[key]].value
  151. #except KeyError:
  152. # fileValue=None
  153. fileValue=dicomValue(loadable.files[0],self.tag[key])
  154. if filter[key]=="SeriesLabel":
  155. nodeMetadata['seriesLabel']=fileValue
  156. continue
  157. if not filter[key]==None:
  158. if not fileValue==filter[key]:
  159. print("File {} failed for tag {}: {}/{}").format(
  160. loadable.files[0],key,fileValue,filter[key])
  161. filterOK=False
  162. break
  163. nodeMetadata[key]=fileValue
  164. return filterOK
  165. def loadVolumes(self,sNet,dir,filter,doRemove=True):
  166. #returns all series from the directory, each as a separate node in a node list
  167. #filter is a dictionary of speciifed dicom values, if filter(key)=None, that values
  168. #get set, if it isn't, the file gets checked for a match
  169. print("Loading dir {}").format(dir)
  170. dicomFiles=sNet.listRelativeDir(dir)
  171. #filelist=[os.path.join(dir,f) for f in os.listdir(dir)]
  172. filelist=[]
  173. for f in dicomFiles:
  174. localPath=sNet.DownloadFileToCache(f)
  175. f0=localPath
  176. f1=f0+"1"
  177. if not dicomModify==None:
  178. subprocess.call(dicomModify+" "+f0+" "+f1+" && mv "+f1+" "+f0+";", shell=True)
  179. filelist.append(localPath)
  180. try:
  181. loadables=self.volumePlugin.examineForImport([filelist])
  182. except AttributeError:
  183. self.volumePlugin=slicer.modules.dicomPlugins['DICOMScalarVolumePlugin']()
  184. loadables=self.volumePlugin.examineForImport([filelist])
  185. volumeNodes=[]
  186. print("Number of loadables:{}").format(len(loadables))
  187. for loadable in loadables:
  188. #TODO check if it makes sense to load a particular loadable
  189. print "Loading {} number of files: {}".format(loadable.name,len(loadable.files))
  190. for f in loadable.files:
  191. print "\t {}".format(f)
  192. #perform checks
  193. nodeMetadata={}
  194. filterOK=self.applyFilter(loadable,filter,nodeMetadata)
  195. if not filterOK:
  196. #skip this loadable
  197. continue
  198. volumeNode=self.volumePlugin.load(loadable,"DCMTK")
  199. if volumeNode != None:
  200. vName='Series'+nodeMetadata['seriesLabel']
  201. volumeNode.SetName(vName)
  202. volume={'node':volumeNode,'metadata':nodeMetadata}
  203. volumeNodes.append(volume)
  204. if doRemove:
  205. for f in filelist:
  206. os.remove(f)
  207. return volumeNodes
  208. def loadSegmentations(self,net,dir,filter,doRemove=True):
  209. print("Loading dir {}").format(dir)
  210. dicomFiles=net.listRelativeDir(dir)
  211. filelist=[net.DownloadFileToCache(f) for f in dicomFiles]
  212. segmentationNodes=[]
  213. try:
  214. loadableRTs=self.RTPlugin.examineForImport([filelist])
  215. except:
  216. self.RTPlugin=plugin=slicer.modules.dicomPlugins['DicomRtImportExportPlugin']()
  217. loadableRTs=self.RTPlugin.examineForImport([filelist])
  218. for loadable in loadableRTs:
  219. nodeMetadata={}
  220. filterOK=self.applyFilter(loadable,filter,nodeMetadata)
  221. if not filterOK:
  222. continue
  223. success=self.RTPlugin.load(loadable)
  224. if not success:
  225. print("Could not load RT structure set")
  226. return
  227. segNodes=slicer.util.getNodesByClass("vtkMRMLSegmentationNode")
  228. segmentationNode=segNodes[0]
  229. #assume we loaded the first node in list
  230. if segmentationNode != None:
  231. sName='Segmentation'+nodeMetadata['seriesLabel']
  232. segmentationNode.SetName(sName)
  233. segmentation={'node':segmentationNode,'metadata':nodeMetadata}
  234. segmentationNodes.append(segmentation)
  235. if not doRemove:
  236. return segmentationNodes
  237. for f in filelist:
  238. os.remove(f)
  239. return segmentationNodes
  240. def dicomValue(file,tag):
  241. dcmdump=os.path.join(os.environ['SLICER_HOME'],"bin")
  242. dcmdump=os.path.join(dcmdump,"dcmdump")
  243. try:
  244. out=subprocess.check_output([dcmdump,'+P',tag,file])
  245. out=re.sub(r'^.*\[(.*)\].*\n$',r'\1',out)
  246. #separate out series
  247. if out.find('\\')>-1:
  248. out=out.split('\\')
  249. return out
  250. except:
  251. return None
  252. def clearNodes():
  253. nodes=[]
  254. nodes.extend(slicer.util.getNodesByClass("vtkMRMLScalarVolumeNode"))
  255. nodes.extend(slicer.util.getNodesByClass("vtkMRMLScalarVolumeDisplayNode"))
  256. nodes.extend(slicer.util.getNodesByClass("vtkMRMLSegmentationNode"))
  257. nodes.extend(slicer.util.getNodesByClass("vtkMRMLSegmentationDisplayNode"))
  258. for node in nodes:
  259. slicer.mrmlScene.RemoveNode(node)