exportDicom.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415
  1. import DICOMLib
  2. from slicer.ScriptedLoadableModule import *
  3. import slicerNetwork
  4. import qt,vtk,ctk,slicer
  5. import datetime
  6. import re
  7. import os
  8. import slicer.cli
  9. import json
  10. class exportDicom(slicer.ScriptedLoadableModule.ScriptedLoadableModule):
  11. def __init__(self,parent):
  12. slicer.ScriptedLoadableModule.ScriptedLoadableModule.__init__(self, parent)
  13. self.className="exportDicom"
  14. self.parent.title="exportDicom"
  15. self.parent.categories = ["LabKey"]
  16. self.parent.dependencies = []
  17. self.parent.contributors = ["Andrej Studen (University of Ljubljana)"] # replace with "Firstname Lastname (Organization)"
  18. self.parent.helpText = """
  19. This is an example of scripted loadable module bundled in an extension.
  20. It adds hierarchy datat for reliable dicom export of a node
  21. """
  22. self.parent.helpText += self.getDefaultModuleDocumentationLink()
  23. self.parent.acknowledgementText = """
  24. This extension developed within Medical Physics research programe of ARRS
  25. """ # replace with organization, grant and thanks.
  26. #
  27. # dataExplorerWidget
  28. class exportDicomWidget(ScriptedLoadableModuleWidget):
  29. """Uses ScriptedLoadableModuleWidget base class, available at:
  30. https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py
  31. """
  32. def setup(self):
  33. ScriptedLoadableModuleWidget.setup(self)
  34. self.logic=exportDicomLogic(self)
  35. try:
  36. fhome=os.environ["HOME"]
  37. except:
  38. #in windows, the variable is called HOMEPATH
  39. fhome=os.environ['HOMEDRIVE']+os.environ['HOMEPATH']
  40. cfgPath=os.path.join(fhome,".labkey")
  41. cfgNet=os.path.join(cfgPath,"Remote.json")
  42. self.project='DICOM/Export'
  43. baseUUIDFile=os.path.join(cfgPath,"baseUUID.json")
  44. baseUUIDCandidate='0.0.0.0.0'
  45. try:
  46. print("Opening {}".format(baseUUIDFile))
  47. f=open(baseUUIDFile,'r')
  48. print("Parsing {}".format(baseUUIDFile))
  49. baseUUIDCfg=json.load(f)
  50. print("Decoding {}".format(baseUUIDCfg))
  51. baseUUIDCandidate=baseUUIDCfg['baseUUID']
  52. print("baseUUID {}".format(baseUUIDCandidate))
  53. self.project=baseUUIDCfg['project']
  54. print("project {}".format(self.project))
  55. except IOError:
  56. pass
  57. except JSONDecodeError:
  58. pass
  59. except KeyError:
  60. pass
  61. self.Net=slicerNetwork.labkeyURIHandler()
  62. self.Net.parseConfig(cfgNet)
  63. self.Net.initRemote()
  64. datasetCollapsibleButton = ctk.ctkCollapsibleButton()
  65. datasetCollapsibleButton.text = "Node data"
  66. self.layout.addWidget(datasetCollapsibleButton)
  67. # Layout within the dummy collapsible button
  68. datasetFormLayout = qt.QFormLayout(datasetCollapsibleButton)
  69. self.volumeNode=qt.QLineEdit("enter name of the volume Node")
  70. datasetFormLayout.addRow("volumeNode:",self.volumeNode)
  71. self.patientId=qt.QLineEdit("enter PatientID")
  72. datasetFormLayout.addRow("PatientId:",self.patientId)
  73. self.studyInstanceUid=qt.QLineEdit("enter study id")
  74. datasetFormLayout.addRow("StudyInstanceUid:",self.studyInstanceUid)
  75. self.studyDescription=qt.QLineEdit("enter study description")
  76. datasetFormLayout.addRow("StudyDescription:",self.studyDescription)
  77. self.addHierarchyButton=qt.QPushButton("Add hierarchy")
  78. self.addHierarchyButton.clicked.connect(self.onAddHierarchyButtonClicked)
  79. datasetFormLayout.addRow("Volume:",self.addHierarchyButton)
  80. self.baseUUIDText=qt.QLineEdit(baseUUIDCandidate)
  81. datasetFormLayout.addRow("Base UUID:",self.baseUUIDText)
  82. self.exportButton=qt.QPushButton("Export")
  83. self.exportButton.clicked.connect(self.onExportButtonClicked)
  84. datasetFormLayout.addRow("Export:",self.exportButton)
  85. def onAddHierarchyButtonClicked(self):
  86. metadata={'patientId':self.patientId.text,
  87. 'studyDescription':self.studyDescription.text,
  88. 'studyInstanceUid':self.studyInstanceUid.text,
  89. 'baseUUID':self.baseUUIDText.text}
  90. node=slicer.util.getFirstNodeByName(self.volumeNode.text)
  91. if node==None:
  92. return
  93. self.logic.addHierarchy(node,metadata)
  94. def onExportButtonClicked(self):
  95. metadata={'patientId':self.patientId.text,
  96. 'studyDescription':self.studyDescription.text,
  97. 'studyInstanceUid':self.studyInstanceUid.text,
  98. 'seriesInstanceUid':self.logic.generateSeriesUUID('volume',self.baseUUIDText.text),
  99. 'frameOfReferenceInstanceUid':self.logic.generateFrameOfReferenceUUID('volume',self.baseUUIDText.text)}
  100. node=slicer.util.getFirstNodeByName(self.volumeNode.text)
  101. if node==None:
  102. return
  103. self.logic.exportNodeAsDICOM(self.Net,self.project,node,metadata)
  104. class exportDicomLogic(slicer.ScriptedLoadableModule.ScriptedLoadableModuleLogic):
  105. def __init__(self,parent):
  106. slicer.ScriptedLoadableModule.ScriptedLoadableModuleLogic.__init__(self, parent)
  107. self.labelUUID={'series':'1','study':'2','instance':'3','frameOfReference':4}
  108. self.dataUUID={'volume':'1','segmentation':'2','transformation':'3'}
  109. try:
  110. fhome=os.environ["HOME"]
  111. except:
  112. #in windows, the variable is called HOMEPATH
  113. fhome=os.environ['HOMEDRIVE']+os.environ['HOMEPATH']
  114. self.basePath=os.path.join(fhome,'.dicom')
  115. if not os.path.isdir(self.basePath):
  116. os.mkdir(self.basePath)
  117. self.itemLabel={
  118. 'patientName': slicer.vtkMRMLSubjectHierarchyConstants.GetDICOMPatientNameAttributeName(),
  119. 'modality': slicer.vtkMRMLSubjectHierarchyConstants.GetDICOMSeriesModalityAttributeName(),
  120. 'patientId': slicer.vtkMRMLSubjectHierarchyConstants.GetDICOMPatientIDAttributeName(),
  121. 'patientSex': slicer.vtkMRMLSubjectHierarchyConstants.GetDICOMPatientSexAttributeName(),
  122. 'patientBirthDate': slicer.vtkMRMLSubjectHierarchyConstants.GetDICOMPatientBirthDateAttributeName(),
  123. 'patientComments': slicer.vtkMRMLSubjectHierarchyConstants.GetDICOMPatientCommentsAttributeName(),
  124. 'studyDescription': slicer.vtkMRMLSubjectHierarchyConstants.GetDICOMStudyDescriptionAttributeName(),
  125. 'studyInstanceUid': slicer.vtkMRMLSubjectHierarchyConstants.GetDICOMStudyInstanceUIDTagName(),
  126. 'studyDate': slicer.vtkMRMLSubjectHierarchyConstants.GetDICOMStudyDateAttributeName(),
  127. 'studyId': slicer.vtkMRMLSubjectHierarchyConstants.GetDICOMStudyIDTagName(),
  128. 'studyTime': slicer.vtkMRMLSubjectHierarchyConstants.GetDICOMStudyTimeAttributeName()}
  129. def generateStudyUUID(self,type,baseUUID):
  130. x=datetime.datetime.now()
  131. date=x.strftime("%Y%m%d")
  132. studyFile=os.path.join(self.basePath,'studyCount'+date+'.txt')
  133. try:
  134. f=open(studyFile,"r")
  135. id=int(f.readline())
  136. id=id+1
  137. f.close()
  138. except:
  139. id=0
  140. studyId="{}.{}.{}.{}.{}".format(baseUUID,self.labelUUID['study'],
  141. self.dataUUID[type],date,id)
  142. f=open(studyFile,"w")
  143. f.write("{}".format(id))
  144. f.close()
  145. return studyId
  146. def generateSOPInstanceUUID(self,type,baseUUID):
  147. x=datetime.datetime.now()
  148. date=x.strftime("%Y%m%d")
  149. instanceFile=os.path.join(self.basePath,'instanceCount'+date+'.txt')
  150. try:
  151. f=open(instanceFile,"r")
  152. id=int(f.readline())
  153. id=id+1
  154. f.close()
  155. except:
  156. id=0
  157. instanceId="{}.{}.{}.{}.{}".format(baseUUID,self.labelUUID['instance'],
  158. self.dataUUID[type],date,id)
  159. f=open(instanceFile,"w")
  160. f.write("{}".format(id))
  161. f.close()
  162. return instanceId
  163. def generateFrameOfReferenceUUID(self,type,baseUUID):
  164. x=datetime.datetime.now()
  165. date=x.strftime("%Y%m%d")
  166. forFile=os.path.join(self.basePath,'frameCount'+date+'.txt')
  167. try:
  168. f=open(studyFile,"r")
  169. id=int(f.readline())
  170. id=id+1
  171. f.close()
  172. except:
  173. id=0
  174. forId="{}.{}.{}.{}.{}".format(baseUUID,self.labelUUID['frameOfReference'],
  175. self.dataUUID[type],date,id)
  176. f=open(forFile,"w")
  177. f.write("{}".format(id))
  178. f.close()
  179. return forId
  180. def generateSeriesUUID(self,type,baseUUID):
  181. x=datetime.datetime.now()
  182. seriesInstanceUid=baseUUID+'.'+self.labelUUID['series']+'.'
  183. seriesInstanceUid+=self.dataUUID[type]+'.'+x.strftime("%Y%m%d")+'.'+getTimeCode(x)
  184. return seriesInstanceUid
  185. def setAttribute(self,shn,itemId,label,metadata):
  186. try:
  187. shn.SetItemAttribute(itemId,self.itemLabel['modality'],metadata['modality'])
  188. except KeyError:
  189. pass
  190. def addHierarchy(self,dataNode,metadata):
  191. #convert dataNode to fully fledged DICOM object in hierarchy
  192. #variation of addSeriesInSubjectHierarchy
  193. shn=slicer.vtkMRMLSubjectHierarchyNode.GetSubjectHierarchyNode(slicer.mrmlScene)
  194. sceneItemId=shn.GetSceneItemID()
  195. #add the series for the data node
  196. seriesItemId=shn.CreateItem(sceneItemId,dataNode)
  197. x=datetime.datetime.now()
  198. hour=x.strftime("%H")
  199. hour=re.sub('^0','',hour)
  200. ft=hour+x.strftime("%M%S")
  201. seriesInstanceUid=metadata['baseUUID']+'.'+self.labelUUID['series']+'.'
  202. seriesInstanceUid+=self.dataUUID['volume']+'.'+x.strftime("%Y%m%d")+'.'+ft
  203. shn.SetItemUID(seriesItemId,slicer.vtkMRMLSubjectHierarchyConstants.GetDICOMUIDName(),
  204. seriesInstanceUid)
  205. self.setAttribute(shn,seriesItemId,'modality',metadata)
  206. self.setAttribute(shn,seriesItemId,'seriesNumber',metadata)
  207. #add PatientId
  208. try:
  209. patientId=metadata['patientId']
  210. except KeyError:
  211. patientId='Unknown'
  212. try:
  213. studyInstanceUid=metadata['studyInstanceUid']
  214. except KeyError:
  215. studyInstanceUid=self.generateStudyUUID('volume',metadata['baseUUID'])
  216. slicer.vtkSlicerSubjectHierarchyModuleLogic.InsertDicomSeriesInHierarchy(shn,patientId,
  217. studyInstanceUid,seriesInstanceUid)
  218. patientItemId=shn.GetItemByUID(slicer.vtkMRMLSubjectHierarchyConstants.GetDICOMUIDName(),patientId)
  219. self.setAttribute(shn,patientItemId,'patientName',metadata)
  220. self.setAttribute(shn,patientItemId,'patientId',metadata)
  221. self.setAttribute(shn,patientItemId,'patientSex',metadata)
  222. self.setAttribute(shn,patientItemId,'patientBirthDate',metadata)
  223. self.setAttribute(shn,patientItemId,'patientComments',metadata)
  224. patientItemName=patientId
  225. shn.SetItemName(patientItemId,patientItemName)
  226. studyItemId=shn.GetItemByUID(slicer.vtkMRMLSubjectHierarchyConstants.GetDICOMUIDName(),studyInstanceUid)
  227. self.setAttribute(shn,studyItemId,'studyDescription',metadata)
  228. self.setAttribute(shn,studyItemId,'studyInstanceUid',metadata)
  229. self.setAttribute(shn,studyItemId,'studyId',metadata)
  230. self.setAttribute(shn,studyItemId,'studyDate',metadata)
  231. self.setAttribute(shn,studyItemId,'studyTime',metadata)
  232. studyItemName=studyInstanceUid
  233. shn.SetItemName(studyItemId,studyItemName)
  234. def setTagValue(self,cliparameters,metadata,field):
  235. try:
  236. cliparameters[field]=metadata[field]
  237. except KeyError:
  238. pass
  239. def setDirectoryValue(self,cliparameters,cliKey,metadata,key):
  240. try:
  241. cliparameters[cliKey]=metadata[key]
  242. except KeyError:
  243. pass
  244. def generateDicom(self, volumeNode, metadata, fdir):
  245. cliparameters={}
  246. tagList=['patientName','patientComments','studyDate','studyTime','studyDescription',
  247. 'modality','manufacturer','model','seriesDescription',
  248. 'seriesNumber','seriesDate','seriesTime','contentDate','contentTime']
  249. for tag in tagList:
  250. self.setTagValue(cliparameters,metadata,tag)
  251. valuePairs={'patientID':'patientId',
  252. 'studyID':'studyId',
  253. 'seriesInstanceUID':'seriesInstanceUid',
  254. 'studyInstanceUID':'studyInstanceUid',
  255. 'frameOfReferenceInstanceUID':'frameOfReferenceInstanceUid'}
  256. for key in valuePairs:
  257. self.setDirectoryValue(cliparameters,key,metadata,valuePairs[key])
  258. cliparameters['dicomDirectory']=fdir
  259. cliparameters['dicomPrefix']='IMG'
  260. cliparameters['inputVolume']=volumeNode.GetID()
  261. print("[CLI]SeriesInstanceUID: {}").format(cliparameters['seriesInstanceUID'])
  262. print("[MeD]SeriesInstanceUID: {}").format(metadata['seriesInstanceUid'])
  263. try:
  264. dicomWrite=slicer.modules.createdicomseries
  265. except AttributeError:
  266. print("Missing dicom exporter")
  267. return
  268. cliNode=slicer.cli.run(dicomWrite,None,cliparameters,wait_for_completion=True)
  269. status=cliNode.GetStatusString()
  270. print("Status: {}").format(status)
  271. if status.find("error")>-1:
  272. print("Error: {}").format(cliNode.GetErrorText())
  273. return False
  274. return True
  275. def exportNodeAsDICOM(self,net, project,volumeNode,metadata):
  276. #buffed up exportable is now ready to be stored
  277. fdir=net.GetLocalCacheDirectory()
  278. #project=project.replace('/','\\')
  279. fdir=os.path.join(fdir,project)
  280. fdir=os.path.join(fdir,'%40files')
  281. fdir=os.path.join(fdir,metadata['patientId'])
  282. #fdirReg=os.path.join(fdir,'Registration')
  283. if not os.path.isdir(fdir):
  284. os.makedirs(fdir)
  285. relDir=net.GetRelativePathFromLocalPath(fdir)
  286. remoteDir=net.GetLabkeyPathFromRelativePath(relDir)
  287. if not net.isRemoteDir(remoteDir):
  288. net.mkdir(remoteDir)
  289. dirName=volumeNode.GetName();
  290. dirName=dirName.replace(" ","_")
  291. fdir=os.path.join(fdir,dirName)
  292. if not os.path.isdir(fdir):
  293. os.mkdir(fdir)
  294. relDir=net.GetRelativePathFromLocalPath(fdir)
  295. remoteDir=net.GetLabkeyPathFromRelativePath(relDir)
  296. if not net.isRemoteDir(remoteDir):
  297. net.mkdir(remoteDir)
  298. if not self.generateDicom(volumeNode,metadata,fdir):
  299. return
  300. for f in os.listdir(fdir):
  301. localPath=os.path.join(fdir,f)
  302. print("localPath: {}").format(localPath)
  303. relativePath=net.GetRelativePathFromLocalPath(localPath)
  304. print("relativePath: {}").format(relativePath)
  305. remotePath=net.GetLabkeyPathFromRelativePath(relativePath)
  306. print("remotePath: {}").format(relativePath)
  307. net.copyLocalFileToRemote(localPath,remotePath)
  308. def getTimeCode(x):
  309. hour=x.strftime("%H")
  310. hour=re.sub('^0','',hour)
  311. return hour+x.strftime("%M%S")