import DICOMLib from slicer.ScriptedLoadableModule import * import slicerNetwork import qt,vtk,ctk,slicer import datetime import re import os import slicer.cli import json class exportDicom(slicer.ScriptedLoadableModule.ScriptedLoadableModule): def __init__(self,parent): slicer.ScriptedLoadableModule.ScriptedLoadableModule.__init__(self, parent) self.className="exportDicom" self.parent.title="exportDicom" self.parent.categories = ["LabKey"] self.parent.dependencies = [] self.parent.contributors = ["Andrej Studen (University of Ljubljana)"] # replace with "Firstname Lastname (Organization)" self.parent.helpText = """ This is an example of scripted loadable module bundled in an extension. It adds hierarchy datat for reliable dicom export of a node """ self.parent.helpText += self.getDefaultModuleDocumentationLink() self.parent.acknowledgementText = """ This extension developed within Medical Physics research programe of ARRS """ # replace with organization, grant and thanks. # # dataExplorerWidget class exportDicomWidget(ScriptedLoadableModuleWidget): """Uses ScriptedLoadableModuleWidget base class, available at: https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py """ def setup(self): ScriptedLoadableModuleWidget.setup(self) self.logic=exportDicomLogic(self) try: fhome=os.environ["HOME"] except: #in windows, the variable is called HOMEPATH fhome=os.environ['HOMEDRIVE']+os.environ['HOMEPATH'] cfgPath=os.path.join(fhome,".labkey") cfgNet=os.path.join(cfgPath,"Remote.json") self.project='DICOM/Export' baseUUIDFile=os.path.join(cfgPath,"baseUUID.json") baseUUIDCandidate='0.0.0.0.0' try: print("Opening {}".format(baseUUIDFile)) f=open(baseUUIDFile,'r') print("Parsing {}".format(baseUUIDFile)) baseUUIDCfg=json.load(f) print("Decoding {}".format(baseUUIDCfg)) baseUUIDCandidate=baseUUIDCfg['baseUUID'] print("baseUUID {}".format(baseUUIDCandidate)) self.project=baseUUIDCfg['project'] print("project {}".format(self.project)) except IOError: pass except JSONDecodeError: pass except KeyError: pass self.Net=slicerNetwork.labkeyURIHandler() self.Net.parseConfig(cfgNet) self.Net.initRemote() datasetCollapsibleButton = ctk.ctkCollapsibleButton() datasetCollapsibleButton.text = "Node data" self.layout.addWidget(datasetCollapsibleButton) # Layout within the dummy collapsible button datasetFormLayout = qt.QFormLayout(datasetCollapsibleButton) self.volumeNode=qt.QLineEdit("enter name of the volume Node") datasetFormLayout.addRow("volumeNode:",self.volumeNode) self.patientId=qt.QLineEdit("enter PatientID") datasetFormLayout.addRow("PatientId:",self.patientId) self.studyInstanceUid=qt.QLineEdit("enter study id") datasetFormLayout.addRow("StudyInstanceUid:",self.studyInstanceUid) self.studyDescription=qt.QLineEdit("enter study description") datasetFormLayout.addRow("StudyDescription:",self.studyDescription) self.addHierarchyButton=qt.QPushButton("Add hierarchy") self.addHierarchyButton.clicked.connect(self.onAddHierarchyButtonClicked) datasetFormLayout.addRow("Volume:",self.addHierarchyButton) self.baseUUIDText=qt.QLineEdit(baseUUIDCandidate) datasetFormLayout.addRow("Base UUID:",self.baseUUIDText) self.exportButton=qt.QPushButton("Export") self.exportButton.clicked.connect(self.onExportButtonClicked) datasetFormLayout.addRow("Export:",self.exportButton) def onAddHierarchyButtonClicked(self): metadata={'patientId':self.patientId.text, 'studyDescription':self.studyDescription.text, 'studyInstanceUid':self.studyInstanceUid.text, 'baseUUID':self.baseUUIDText.text} node=slicer.util.getFirstNodeByName(self.volumeNode.text) if node==None: return self.logic.addHierarchy(node,metadata) def onExportButtonClicked(self): metadata={'patientId':self.patientId.text, 'studyDescription':self.studyDescription.text, 'studyInstanceUid':self.studyInstanceUid.text, 'seriesInstanceUid':self.logic.generateSeriesUUID('volume',self.baseUUIDText.text), 'frameOfReferenceInstanceUid':self.logic.generateFrameOfReferenceUUID('volume',self.baseUUIDText.text)} node=slicer.util.getFirstNodeByName(self.volumeNode.text) if node==None: return self.logic.exportNodeAsDICOM(self.Net,self.project,node,metadata) class exportDicomLogic(slicer.ScriptedLoadableModule.ScriptedLoadableModuleLogic): def __init__(self,parent): slicer.ScriptedLoadableModule.ScriptedLoadableModuleLogic.__init__(self, parent) self.labelUUID={'series':'1','study':'2','instance':'3','frameOfReference':4} self.dataUUID={'volume':'1','segmentation':'2','transformation':'3'} try: fhome=os.environ["HOME"] except: #in windows, the variable is called HOMEPATH fhome=os.environ['HOMEDRIVE']+os.environ['HOMEPATH'] self.basePath=os.path.join(fhome,'.dicom') if not os.path.isdir(self.basePath): os.mkdir(self.basePath) self.itemLabel={ 'patientName': slicer.vtkMRMLSubjectHierarchyConstants.GetDICOMPatientNameAttributeName(), 'modality': slicer.vtkMRMLSubjectHierarchyConstants.GetDICOMSeriesModalityAttributeName(), 'patientId': slicer.vtkMRMLSubjectHierarchyConstants.GetDICOMPatientIDAttributeName(), 'patientSex': slicer.vtkMRMLSubjectHierarchyConstants.GetDICOMPatientSexAttributeName(), 'patientBirthDate': slicer.vtkMRMLSubjectHierarchyConstants.GetDICOMPatientBirthDateAttributeName(), 'patientComments': slicer.vtkMRMLSubjectHierarchyConstants.GetDICOMPatientCommentsAttributeName(), 'studyDescription': slicer.vtkMRMLSubjectHierarchyConstants.GetDICOMStudyDescriptionAttributeName(), 'studyInstanceUid': slicer.vtkMRMLSubjectHierarchyConstants.GetDICOMStudyInstanceUIDTagName(), 'studyDate': slicer.vtkMRMLSubjectHierarchyConstants.GetDICOMStudyDateAttributeName(), 'studyId': slicer.vtkMRMLSubjectHierarchyConstants.GetDICOMStudyIDTagName(), 'studyTime': slicer.vtkMRMLSubjectHierarchyConstants.GetDICOMStudyTimeAttributeName()} def generateStudyUUID(self,type,baseUUID): x=datetime.datetime.now() date=x.strftime("%Y%m%d") studyFile=os.path.join(self.basePath,'studyCount'+date+'.txt') try: f=open(studyFile,"r") id=int(f.readline()) id=id+1 f.close() except: id=0 studyId="{}.{}.{}.{}.{}".format(baseUUID,self.labelUUID['study'], self.dataUUID[type],date,id) f=open(studyFile,"w") f.write("{}".format(id)) f.close() return studyId def generateFrameOfReferenceUUID(self,type,baseUUID): x=datetime.datetime.now() date=x.strftime("%Y%m%d") forFile=os.path.join(self.basePath,'frameCount'+date+'.txt') try: f=open(studyFile,"r") id=int(f.readline()) id=id+1 f.close() except: id=0 forId="{}.{}.{}.{}.{}".format(baseUUID,self.labelUUID['frameOfReference'], self.dataUUID[type],date,id) f=open(forFile,"w") f.write("{}".format(id)) f.close() return forId def generateSeriesUUID(self,type,baseUUID): x=datetime.datetime.now() hour=x.strftime("%H") hour=re.sub('^0','',hour) ft=hour+x.strftime("%M%S") seriesInstanceUid=baseUUID+'.'+self.labelUUID['series']+'.' seriesInstanceUid+=self.dataUUID[type]+'.'+x.strftime("%Y%m%d")+'.'+ft return seriesInstanceUid def setAttribute(self,shn,itemId,label,metadata): try: shn.SetItemAttribute(itemId,self.itemLabel['modality'],metadata['modality']) except KeyError: pass def addHierarchy(self,dataNode,metadata): #convert dataNode to fully fledged DICOM object in hierarchy #variation of addSeriesInSubjectHierarchy shn=slicer.vtkMRMLSubjectHierarchyNode.GetSubjectHierarchyNode(slicer.mrmlScene) sceneItemId=shn.GetSceneItemID() #add the series for the data node seriesItemId=shn.CreateItem(sceneItemId,dataNode) x=datetime.datetime.now() hour=x.strftime("%H") hour=re.sub('^0','',hour) ft=hour+x.strftime("%M%S") seriesInstanceUid=metadata['baseUUID']+'.'+self.labelUUID['series']+'.' seriesInstanceUid+=self.dataUUID['volume']+'.'+x.strftime("%Y%m%d")+'.'+ft shn.SetItemUID(seriesItemId,slicer.vtkMRMLSubjectHierarchyConstants.GetDICOMUIDName(), seriesInstanceUid) self.setAttribute(shn,seriesItemId,'modality',metadata) self.setAttribute(shn,seriesItemId,'seriesNumber',metadata) #add PatientId try: patientId=metadata['patientId'] except KeyError: patientId='Unknown' try: studyInstanceUid=metadata['studyInstanceUid'] except KeyError: studyInstanceUid=self.generateStudyUUID('volume',metadata['baseUUID']) slicer.vtkSlicerSubjectHierarchyModuleLogic.InsertDicomSeriesInHierarchy(shn,patientId, studyInstanceUid,seriesInstanceUid) patientItemId=shn.GetItemByUID(slicer.vtkMRMLSubjectHierarchyConstants.GetDICOMUIDName(),patientId) self.setAttribute(shn,patientItemId,'patientName',metadata) self.setAttribute(shn,patientItemId,'patientId',metadata) self.setAttribute(shn,patientItemId,'patientSex',metadata) self.setAttribute(shn,patientItemId,'patientBirthDate',metadata) self.setAttribute(shn,patientItemId,'patientComments',metadata) patientItemName=patientId shn.SetItemName(patientItemId,patientItemName) studyItemId=shn.GetItemByUID(slicer.vtkMRMLSubjectHierarchyConstants.GetDICOMUIDName(),studyInstanceUid) self.setAttribute(shn,studyItemId,'studyDescription',metadata) self.setAttribute(shn,studyItemId,'studyInstanceUid',metadata) self.setAttribute(shn,studyItemId,'studyId',metadata) self.setAttribute(shn,studyItemId,'studyDate',metadata) self.setAttribute(shn,studyItemId,'studyTime',metadata) studyItemName=studyInstanceUid shn.SetItemName(studyItemId,studyItemName) def setTagValue(self,cliparameters,metadata,field): try: cliparameters[field]=metadata[field] except KeyError: pass def setDirectoryValue(self,cliparameters,cliKey,metadata,key): try: cliparameters[cliKey]=metadata[key] except KeyError: pass def generateDicom(self, volumeNode, metadata, fdir): cliparameters={} tagList=['patientName','patientComments','studyDate','studyTime','studyDescription', 'modality','manufacturer','model','seriesDescription', 'seriesNumber','seriesDate','seriesTime','contentDate','contentTime'] for tag in tagList: self.setTagValue(cliparameters,metadata,tag) valuePairs={'patientID':'patientId', 'studyID':'studyId', 'seriesInstanceUID':'seriesInstanceUid', 'studyInstanceUID':'studyInstanceUid', 'frameOfReferenceInstanceUID':'frameOfReferenceInstanceUid'} for key in valuePairs: self.setDirectoryValue(cliparameters,key,metadata,valuePairs[key]) cliparameters['dicomDirectory']=fdir cliparameters['dicomPrefix']='IMG' cliparameters['inputVolume']=volumeNode.GetID() print("[CLI]SeriesInstanceUID: {}").format(cliparameters['seriesInstanceUID']) print("[MeD]SeriesInstanceUID: {}").format(metadata['seriesInstanceUid']) try: dicomWrite=slicer.modules.createdicomseries except AttributeError: print("Missing dicom exporter") return cliNode=slicer.cli.run(dicomWrite,None,cliparameters,wait_for_completion=True) status=cliNode.GetStatusString() print("Status: {}").format(status) if status.find("error")>-1: print("Error: {}").format(cliNode.GetErrorText()) return False return True def exportNodeAsDICOM(self,net, project,volumeNode,metadata): #buffed up exportable is now ready to be stored fdir=net.GetLocalCacheDirectory() #project=project.replace('/','\\') fdir=os.path.join(fdir,project) fdir=os.path.join(fdir,'%40files') fdir=os.path.join(fdir,metadata['patientId']) #fdirReg=os.path.join(fdir,'Registration') if not os.path.isdir(fdir): os.makedirs(fdir) relDir=net.GetRelativePathFromLocalPath(fdir) remoteDir=net.GetLabkeyPathFromRelativePath(relDir) if not net.isRemoteDir(remoteDir): net.mkdir(remoteDir) dirName=volumeNode.GetName(); dirName=dirName.replace(" ","_") fdir=os.path.join(fdir,dirName) if not os.path.isdir(fdir): os.mkdir(fdir) relDir=net.GetRelativePathFromLocalPath(fdir) remoteDir=net.GetLabkeyPathFromRelativePath(relDir) if not net.isRemoteDir(remoteDir): net.mkdir(remoteDir) if not self.generateDicom(volumeNode,metadata,fdir): return for f in os.listdir(fdir): localPath=os.path.join(fdir,f) print("localPath: {}").format(localPath) relativePath=net.GetRelativePathFromLocalPath(localPath) print("relativePath: {}").format(relativePath) remotePath=net.GetLabkeyPathFromRelativePath(relativePath) print("remotePath: {}").format(relativePath) net.copyLocalFileToRemote(localPath,remotePath)