exportDicom.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389
  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 generateFrameOfReferenceUUID(self,type,baseUUID):
  147. x=datetime.datetime.now()
  148. date=x.strftime("%Y%m%d")
  149. forFile=os.path.join(self.basePath,'frameCount'+date+'.txt')
  150. try:
  151. f=open(studyFile,"r")
  152. id=int(f.readline())
  153. id=id+1
  154. f.close()
  155. except:
  156. id=0
  157. forId="{}.{}.{}.{}.{}".format(baseUUID,self.labelUUID['frameOfReference'],
  158. self.dataUUID[type],date,id)
  159. f=open(forFile,"w")
  160. f.write("{}".format(id))
  161. f.close()
  162. return forId
  163. def generateSeriesUUID(self,type,baseUUID):
  164. x=datetime.datetime.now()
  165. hour=x.strftime("%H")
  166. hour=re.sub('^0','',hour)
  167. ft=hour+x.strftime("%M%S")
  168. seriesInstanceUid=baseUUID+'.'+self.labelUUID['series']+'.'
  169. seriesInstanceUid+=self.dataUUID[type]+'.'+x.strftime("%Y%m%d")+'.'+ft
  170. return seriesInstanceUid
  171. def setAttribute(self,shn,itemId,label,metadata):
  172. try:
  173. shn.SetItemAttribute(itemId,self.itemLabel['modality'],metadata['modality'])
  174. except KeyError:
  175. pass
  176. def addHierarchy(self,dataNode,metadata):
  177. #convert dataNode to fully fledged DICOM object in hierarchy
  178. #variation of addSeriesInSubjectHierarchy
  179. shn=slicer.vtkMRMLSubjectHierarchyNode.GetSubjectHierarchyNode(slicer.mrmlScene)
  180. sceneItemId=shn.GetSceneItemID()
  181. #add the series for the data node
  182. seriesItemId=shn.CreateItem(sceneItemId,dataNode)
  183. x=datetime.datetime.now()
  184. hour=x.strftime("%H")
  185. hour=re.sub('^0','',hour)
  186. ft=hour+x.strftime("%M%S")
  187. seriesInstanceUid=metadata['baseUUID']+'.'+self.labelUUID['series']+'.'
  188. seriesInstanceUid+=self.dataUUID['volume']+'.'+x.strftime("%Y%m%d")+'.'+ft
  189. shn.SetItemUID(seriesItemId,slicer.vtkMRMLSubjectHierarchyConstants.GetDICOMUIDName(),
  190. seriesInstanceUid)
  191. self.setAttribute(shn,seriesItemId,'modality',metadata)
  192. self.setAttribute(shn,seriesItemId,'seriesNumber',metadata)
  193. #add PatientId
  194. try:
  195. patientId=metadata['patientId']
  196. except KeyError:
  197. patientId='Unknown'
  198. try:
  199. studyInstanceUid=metadata['studyInstanceUid']
  200. except KeyError:
  201. studyInstanceUid=self.generateStudyUUID('volume',metadata['baseUUID'])
  202. slicer.vtkSlicerSubjectHierarchyModuleLogic.InsertDicomSeriesInHierarchy(shn,patientId,
  203. studyInstanceUid,seriesInstanceUid)
  204. patientItemId=shn.GetItemByUID(slicer.vtkMRMLSubjectHierarchyConstants.GetDICOMUIDName(),patientId)
  205. self.setAttribute(shn,patientItemId,'patientName',metadata)
  206. self.setAttribute(shn,patientItemId,'patientId',metadata)
  207. self.setAttribute(shn,patientItemId,'patientSex',metadata)
  208. self.setAttribute(shn,patientItemId,'patientBirthDate',metadata)
  209. self.setAttribute(shn,patientItemId,'patientComments',metadata)
  210. patientItemName=patientId
  211. shn.SetItemName(patientItemId,patientItemName)
  212. studyItemId=shn.GetItemByUID(slicer.vtkMRMLSubjectHierarchyConstants.GetDICOMUIDName(),studyInstanceUid)
  213. self.setAttribute(shn,studyItemId,'studyDescription',metadata)
  214. self.setAttribute(shn,studyItemId,'studyInstanceUid',metadata)
  215. self.setAttribute(shn,studyItemId,'studyId',metadata)
  216. self.setAttribute(shn,studyItemId,'studyDate',metadata)
  217. self.setAttribute(shn,studyItemId,'studyTime',metadata)
  218. studyItemName=studyInstanceUid
  219. shn.SetItemName(studyItemId,studyItemName)
  220. def setTagValue(self,cliparameters,metadata,field):
  221. try:
  222. cliparameters[field]=metadata[field]
  223. except KeyError:
  224. pass
  225. def setDirectoryValue(self,cliparameters,cliKey,metadata,key):
  226. try:
  227. cliparameters[cliKey]=metadata[key]
  228. except KeyError:
  229. pass
  230. def generateDicom(self, volumeNode, metadata, fdir):
  231. cliparameters={}
  232. tagList=['patientName','patientComments','studyDate','studyTime','studyDescription',
  233. 'modality','manufacturer','model','seriesDescription',
  234. 'seriesNumber','seriesDate','seriesTime','contentDate','contentTime']
  235. for tag in tagList:
  236. self.setTagValue(cliparameters,metadata,tag)
  237. valuePairs={'patientID':'patientId',
  238. 'studyID':'studyId',
  239. 'seriesInstanceUID':'seriesInstanceUid',
  240. 'studyInstanceUID':'studyInstanceUid',
  241. 'frameOfReferenceInstanceUID':'frameOfReferenceInstanceUid'}
  242. for key in valuePairs:
  243. self.setDirectoryValue(cliparameters,key,metadata,valuePairs[key])
  244. cliparameters['dicomDirectory']=fdir
  245. cliparameters['dicomPrefix']='IMG'
  246. cliparameters['inputVolume']=volumeNode.GetID()
  247. print("[CLI]SeriesInstanceUID: {}").format(cliparameters['seriesInstanceUID'])
  248. print("[MeD]SeriesInstanceUID: {}").format(metadata['seriesInstanceUid'])
  249. try:
  250. dicomWrite=slicer.modules.createdicomseries
  251. except AttributeError:
  252. print("Missing dicom exporter")
  253. return
  254. cliNode=slicer.cli.run(dicomWrite,None,cliparameters,wait_for_completion=True)
  255. status=cliNode.GetStatusString()
  256. print("Status: {}").format(status)
  257. if status.find("error")>-1:
  258. print("Error: {}").format(cliNode.GetErrorText())
  259. return False
  260. return True
  261. def exportNodeAsDICOM(self,net, project,volumeNode,metadata):
  262. #buffed up exportable is now ready to be stored
  263. fdir=net.GetLocalCacheDirectory()
  264. #project=project.replace('/','\\')
  265. fdir=os.path.join(fdir,project)
  266. fdir=os.path.join(fdir,'%40files')
  267. fdir=os.path.join(fdir,metadata['patientId'])
  268. #fdirReg=os.path.join(fdir,'Registration')
  269. if not os.path.isdir(fdir):
  270. os.makedirs(fdir)
  271. relDir=net.GetRelativePathFromLocalPath(fdir)
  272. remoteDir=net.GetLabkeyPathFromRelativePath(relDir)
  273. if not net.isRemoteDir(remoteDir):
  274. net.mkdir(remoteDir)
  275. dirName=volumeNode.GetName();
  276. dirName=dirName.replace(" ","_")
  277. fdir=os.path.join(fdir,dirName)
  278. if not os.path.isdir(fdir):
  279. os.mkdir(fdir)
  280. relDir=net.GetRelativePathFromLocalPath(fdir)
  281. remoteDir=net.GetLabkeyPathFromRelativePath(relDir)
  282. if not net.isRemoteDir(remoteDir):
  283. net.mkdir(remoteDir)
  284. if not self.generateDicom(volumeNode,metadata,fdir):
  285. return
  286. for f in os.listdir(fdir):
  287. localPath=os.path.join(fdir,f)
  288. print("localPath: {}").format(localPath)
  289. relativePath=net.GetRelativePathFromLocalPath(localPath)
  290. print("relativePath: {}").format(relativePath)
  291. remotePath=net.GetLabkeyPathFromRelativePath(relativePath)
  292. print("remotePath: {}").format(relativePath)
  293. net.copyLocalFileToRemote(localPath,remotePath)