loadDicom.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351
  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 = ["LabKey"]
  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': {'tag':"0008,0020",'VR':'DA'},
  35. 'studyTime': {'tag':"0008,0030",'VR':'TM'},
  36. 'seriesTime': {'tag':"0008,0031",'VR':'TM'},
  37. 'modality': {'tag':"0008,0060",'VR':'CS'},
  38. 'presentationIntentType': {'tag':"0008,0068",'VR':'CS'},
  39. 'manufacturer': {'tag':"0008,0070",'VR':'LO'},
  40. 'institutionName': {'tag':"0008,0080",'VR':'LO'},
  41. 'studyDescription': {'tag':"0008,1030",'VR':'LO'},
  42. 'seriesDescription': {'tag':"0008,103e",'VR':'LO'},
  43. 'manufacturerModelName': {'tag':"0008,1090",'VR':'LO'},
  44. 'patientName': {'tag':"0010,0010",'VR':'PN'},
  45. 'patientId': {'tag':"0010,0020",'VR':'LO'},
  46. 'patientBirthDate': {'tag':"0010,0030",'VR':'DA'},
  47. 'patientSex': {'tag':"0010,0040",'VR':'CS'},
  48. 'patientAge': {'tag':"0010,1010",'VR':'AS'},
  49. 'patientComments': {'tag':"0010,4000",'VR':'LT'},
  50. 'sequenceName': {'tag':"0018,0024",'VR':'SH'},
  51. 'kVP': {'tag':"0018,0060",'VR':'DS'},
  52. 'percentPhaseFieldOfView': {'tag':"0018,0094",'VR':'DS'},
  53. 'xRayTubeCurrent': {'tag':"0018,1151",'VR':'IS'},
  54. 'exposure': {'tag':"0018,1152",'VR':'IS'},
  55. 'imagerPixelSpacing': {'tag':"0018,1164",'VR':'DS'},
  56. 'bodyPartThickness': {'tag':"0018,11a0",'VR':'DS'},
  57. 'compressionForce': {'tag':"0018,11a2",'VR':'DS'},
  58. 'viewPosition': {'tag':"0018,5101",'VR':'CS'},
  59. 'fieldOfViewHorizontalFlip': {'tag':"0018,7034",'VR':'CS'},
  60. 'studyInstanceUid': {'tag':"0020,000d",'VR':'UI'},
  61. 'seriesInstanceUid': {'tag':"0020,000e",'VR':'UI'},
  62. 'studyId': {'tag':"0020,0010",'VR':'SH'},
  63. 'seriesNumber': {'tag':"0020,0011",'VR':'IS'},
  64. 'instanceNumber': {'tag':"0020,0013",'VR':'IS'},
  65. 'frameOfReferenceInstanceUid': {'tag':"0020,0052",'seqTag':"3006,0010",'VR':'UI'},
  66. 'imageLaterality': {'tag':"0020,0062",'VR':'CS'},
  67. 'imagesInAcquisition': {'tag':"0020,1002",'VR':'IS'},
  68. 'photometricInterpretation': {'tag':"0028,0004",'VR':'CS'},
  69. 'reconstructionMethod': {'tag':"0054,1103",'VR':'LO'}
  70. }
  71. self.tagPyDicom={
  72. 'studyDate': 0x00080020,
  73. 'studyTime': 0x00080030,
  74. 'modality': 0x00080060,
  75. 'presentationIntentType': 0x00080068,
  76. 'manufacturer': 0x00080070,
  77. 'studyDescription': 0x00081030,
  78. 'seriesDescription': 0x0008103e,
  79. 'patientName': 0x00100010,
  80. 'patientId': 0x00100020,
  81. 'patientBirthDate': 0x00100030,
  82. 'patientSex': 0x00100040,
  83. 'patientComments': 0x00104000,
  84. 'sequenceName': 0x00180024,
  85. 'kVP': 0x00180060,
  86. 'percentPhaseFieldOfView': 0x00180094,
  87. 'xRayTubeCurrent': 0x00181151,
  88. 'exposure': 0x00181152,
  89. 'imagerPixelSpacing': 0x00181164,
  90. 'bodyPartThickness': 0x001811a0,
  91. 'compressionForce': 0x001811a2,
  92. 'viewPosition': 0x00185101,
  93. 'studyInstanceUid': 0x0020000d,
  94. 'seriesInstanceUid': 0x0020000e,
  95. 'studyId': 0x00200010,
  96. 'seriesNumber': 0x00200011,
  97. 'instanceNumber': 0x00200013,
  98. 'frameOfReferenceInstanceUid': 0x00200052
  99. }
  100. #new_dict_items={
  101. # 0x001811a0: ('DS','BodyPartThickness','Body Part Thickness')
  102. #}
  103. #dicom.datadict.add_dict_entries(new_dict_items)
  104. def getHex(self,key):
  105. #convert string to hex key;
  106. fv=key.split(",")
  107. return int(fv[0],16)*0x10000+int(fv[1],16)
  108. def load(self,sNet,dir,doRemove=True):
  109. print("Loading dir {}").format(dir)
  110. dicomFiles=sNet.listRelativeDir(dir)
  111. #filelist=[os.path.join(dir,f) for f in os.listdir(dir)]
  112. filelist=[]
  113. for f in dicomFiles:
  114. localPath=sNet.DownloadFileToCache(f)
  115. f0=localPath
  116. f1=f0+"1"
  117. if not dicomModify==None:
  118. subprocess.call(dicomModify+" "+f0+" "+f1+" && mv "+f1+" "+f0+";", shell=True)
  119. filelist.append(localPath)
  120. try:
  121. loadables=self.volumePlugin.examineForImport([filelist])
  122. except AttributeError:
  123. self.volumePlugin=slicer.modules.dicomPlugins['DICOMScalarVolumePlugin']()
  124. loadables=self.volumePlugin.examineForImport([filelist])
  125. for loadable in loadables:
  126. #TODO check if it makes sense to load a particular loadable
  127. fileBuffer = open(loadable.files[0])
  128. plan=dicom.read_file(fileBuffer)
  129. if loadable.name.find('imageOrientationPatient')>-1:
  130. continue
  131. volumeNode=self.volumePlugin.load(loadable)
  132. if volumeNode != None:
  133. vName='Series'+plan[0x00200011].value
  134. volumeNode.SetName(vName)
  135. try:
  136. loadableRTs=self.RTPlugin.examineForImport([filelist])
  137. except:
  138. self.RTPlugin=plugin=slicer.modules.dicomPlugins['DicomRtImportExportPlugin']()
  139. loadableRTs=self.RTPlugin.examineForImport([filelist])
  140. for loadable in loadableRTs:
  141. segmentationNode=self.RTPlugin.load(loadable)
  142. #if segmentationNode!= None:
  143. # segmentationNode.SetName('SegmentationBR')
  144. if not doRemove:
  145. return
  146. for f in filelist:
  147. os.remove(f)
  148. def applyFilter(self,loadable,filter,nodeMetadata):
  149. filterOK=True
  150. print("Opening {}").format(loadable.files[0]);
  151. fileBuffer = open(loadable.files[0])
  152. try:
  153. plan = dicom.read_file(fileBuffer)
  154. except:
  155. return False
  156. for key in filter:
  157. #try:
  158. # fileValue=plan[self.tag[key]].value
  159. #except KeyError:
  160. # fileValue=None
  161. try:
  162. fileValue=dicomValue(loadable.files[0],self.tag[key]['tag'],self.tag[key]['seqTag'])
  163. except KeyError:
  164. fileValue=dicomValue(loadable.files[0],self.tag[key]['tag'])
  165. if filter[key]=="SeriesLabel":
  166. nodeMetadata['seriesLabel']=fileValue
  167. continue
  168. if not filter[key]==None:
  169. if not fileValue==filter[key]:
  170. print("File {} failed for tag {}: {}/{}").format(
  171. loadable.files[0],key,fileValue,filter[key])
  172. filterOK=False
  173. break
  174. nodeMetadata[key]=fileValue
  175. return filterOK
  176. def loadVolumes(self,sNet,dir,filter,doRemove=True):
  177. #returns all series from the directory, each as a separate node in a node list
  178. #filter is a dictionary of speciifed dicom values, if filter(key)=None, that values
  179. #get set, if it isn't, the file gets checked for a match
  180. print("Loading dir {}").format(dir)
  181. dicomFiles=sNet.listRelativeDir(dir)
  182. #filelist=[os.path.join(dir,f) for f in os.listdir(dir)]
  183. filelist=[]
  184. for f in dicomFiles:
  185. localPath=sNet.DownloadFileToCache(f)
  186. f0=localPath
  187. f1=f0+"1"
  188. if not dicomModify==None:
  189. subprocess.call(dicomModify+" "+f0+" "+f1+" && mv "+f1+" "+f0+";", shell=False)
  190. filelist.append(localPath)
  191. try:
  192. loadables=self.volumePlugin.examineForImport([filelist])
  193. except AttributeError:
  194. self.volumePlugin=slicer.modules.dicomPlugins['DICOMScalarVolumePlugin']()
  195. loadables=self.volumePlugin.examineForImport([filelist])
  196. volumeNodes=[]
  197. print("Number of loadables:{}").format(len(loadables))
  198. for loadable in loadables:
  199. #TODO check if it makes sense to load a particular loadable
  200. print "Loading {} number of files: {}".format(loadable.name,len(loadable.files))
  201. for f in loadable.files:
  202. print "\t {}".format(f)
  203. #perform checks
  204. nodeMetadata={}
  205. filterOK=self.applyFilter(loadable,filter,nodeMetadata)
  206. if not filterOK:
  207. #skip this loadable
  208. continue
  209. volumeNode=self.volumePlugin.load(loadable,"DCMTK")
  210. if volumeNode != None:
  211. vName='Series'+nodeMetadata['seriesLabel']
  212. volumeNode.SetName(vName)
  213. volume={'node':volumeNode,'metadata':nodeMetadata}
  214. volumeNodes.append(volume)
  215. if doRemove:
  216. for f in filelist:
  217. os.remove(f)
  218. return volumeNodes
  219. def loadSegmentations(self,net,dir,filter,doRemove=True):
  220. print("Loading dir {}").format(dir)
  221. dicomFiles=net.listRelativeDir(dir)
  222. filelist=[net.DownloadFileToCache(f) for f in dicomFiles]
  223. segmentationNodes=[]
  224. try:
  225. loadableRTs=self.RTPlugin.examineForImport([filelist])
  226. except:
  227. self.RTPlugin=plugin=slicer.modules.dicomPlugins['DicomRtImportExportPlugin']()
  228. loadableRTs=self.RTPlugin.examineForImport([filelist])
  229. for loadable in loadableRTs:
  230. nodeMetadata={}
  231. filterOK=self.applyFilter(loadable,filter,nodeMetadata)
  232. if not filterOK:
  233. continue
  234. success=self.RTPlugin.load(loadable)
  235. if not success:
  236. print("Could not load RT structure set")
  237. return
  238. segNodes=slicer.util.getNodesByClass("vtkMRMLSegmentationNode")
  239. segmentationNode=segNodes[0]
  240. #assume we loaded the first node in list
  241. if segmentationNode != None:
  242. sName='Segmentation'+nodeMetadata['seriesLabel']
  243. segmentationNode.SetName(sName)
  244. segmentation={'node':segmentationNode,'metadata':nodeMetadata}
  245. segmentationNodes.append(segmentation)
  246. if not doRemove:
  247. return segmentationNodes
  248. for f in filelist:
  249. os.remove(f)
  250. return segmentationNodes
  251. def isDicom(file):
  252. try:
  253. f=open(file,'rb')
  254. except IOError:
  255. return False
  256. f.read(128)
  257. dt=f.read(4)
  258. f.close()
  259. return dt=='DICM'
  260. def dicomValue(file,tag,seqTag=None,shell=False):
  261. debug=False
  262. dcmdump=os.path.join(os.environ['SLICER_HOME'],"bin","dcmdump")
  263. try:
  264. out=subprocess.check_output([dcmdump,'+p','+P',tag,file],shell=shell)
  265. if debug:
  266. print("Tag {} Line '{}'").format(tag,out)
  267. if len(out)==0:
  268. return out
  269. tag1="^\({}\)".format(tag)
  270. if not seqTag==None:
  271. if debug:
  272. print("Tag:{} seqTag:{}").format(tag,seqTag)
  273. tag1="^\({}\).\({}\)".format(seqTag,tag)
  274. lst=out.split('\n')
  275. rpl=[re.sub(r'^.*\[(.*)\].*$',r'\1',f) for f in lst]
  276. mtch=[re.match(tag1,f) for f in lst]
  277. out=[x for y,x in zip(mtch,rpl) if not y==None]
  278. out=out[0]
  279. #out=re.sub(r'^.*\[(.*)\].*\n$',r'\1',out)
  280. #separate out series
  281. if debug:
  282. print("Tag {} Parsed value {}").format(tag,out)
  283. if out.find('\\')>-1:
  284. out=out.split('\\')
  285. return out
  286. except subprocess.CalledProcessError as e:
  287. return None
  288. def clearNodes():
  289. nodes=[]
  290. nodes.extend(slicer.util.getNodesByClass("vtkMRMLScalarVolumeNode"))
  291. nodes.extend(slicer.util.getNodesByClass("vtkMRMLScalarVolumeDisplayNode"))
  292. nodes.extend(slicer.util.getNodesByClass("vtkMRMLSegmentationNode"))
  293. nodes.extend(slicer.util.getNodesByClass("vtkMRMLSegmentationDisplayNode"))
  294. for node in nodes:
  295. slicer.mrmlScene.RemoveNode(node)