exportDicom.py 13 KB

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