def performPostModuleDiscoveryTasks(self): """Since dicom plugins are discovered while the application is initialized, they may be found after the DICOM module itself if initialized. This method is tied to a singleShot that will be called once the event loop is read to start. """ if slicer.mrmlScene.GetTagByClassName( "vtkMRMLScriptedModuleNode") != 'ScriptedModule': slicer.mrmlScene.RegisterNodeClass(vtkMRMLScriptedModuleNode()) # initialize the dicom infrastructure slicer.dicomDatabase = None settings = qt.QSettings() # the dicom database is a global object for slicer if settings.contains('DatabaseDirectory'): databaseDirectory = settings.value('DatabaseDirectory') if databaseDirectory: slicer.dicomDatabase = ctk.ctkDICOMDatabase() slicer.dicomDatabase.openDatabase( databaseDirectory + "/ctkDICOM.sql", "SLICER") if not slicer.dicomDatabase.isOpen: # can't open the database, so prompt the user later if they enter module slicer.dicomDatabase = None else: self.startListener() if slicer.dicomDatabase: slicer.app.setDICOMDatabase(slicer.dicomDatabase) # set the dicom pre-cache tags once all plugin classes have been initialized DICOMLib.setDatabasePrecacheTags() if not slicer.app.commandOptions().noMainWindow: # add to the main app file menu self.addMenu() # add the settings options self.settingsPanel = DICOMSettingsPanel() slicer.app.settingsDialog().addPanel("DICOM", self.settingsPanel) dataProbe = slicer.util.mainWindow().findChild( "QWidget", "DataProbeCollapsibleWidget") self.wasDataProbeVisible = dataProbe.isVisible() layoutManager = slicer.app.layoutManager() layoutManager.layoutChanged.connect(self.onLayoutChanged) layout = ("<layout type=\"horizontal\">" " <item>" " <dicombrowser></dicombrowser>" " </item>" "</layout>") layoutNode = slicer.app.layoutManager().layoutLogic( ).GetLayoutNode() layoutNode.AddLayoutDescription( slicer.vtkMRMLLayoutNode.SlicerLayoutDicomBrowserView, layout) self.currentViewArrangement = layoutNode.GetViewArrangement() self.previousViewArrangement = layoutNode.GetViewArrangement() self.detailsPopup = None self.viewWidget = None self.browserSettingsWidget = None self.setDetailsPopup(DICOMWidget.getSavedDICOMDetailsWidgetType()())
def performPostModuleDiscoveryTasks(self): """Since dicom plugins are discovered while the application is initialized, they may be found after the DICOM module itself if initialized. This method is tied to a singleShot that will be called once the event loop is read to start. """ if slicer.mrmlScene.GetTagByClassName( "vtkMRMLScriptedModuleNode" ) != 'ScriptedModule': slicer.mrmlScene.RegisterNodeClass(vtkMRMLScriptedModuleNode()) # initialize the dicom infrastructure slicer.dicomDatabase = None settings = qt.QSettings() # the dicom database is a global object for slicer if settings.contains('DatabaseDirectory'): databaseDirectory = settings.value('DatabaseDirectory') if databaseDirectory: slicer.dicomDatabase = ctk.ctkDICOMDatabase() slicer.dicomDatabase.openDatabase(databaseDirectory + "/ctkDICOM.sql", "SLICER") if not slicer.dicomDatabase.isOpen: # can't open the database, so prompt the user later if they enter module slicer.dicomDatabase = None else: self.startListener() if slicer.dicomDatabase: slicer.app.setDICOMDatabase(slicer.dicomDatabase) # set the dicom pre-cache tags once all plugin classes have been initialized DICOMLib.setDatabasePrecacheTags() if not slicer.app.commandOptions().noMainWindow: # add to the main app file menu self.addMenu() # add the settings options self.settingsPanel = DICOMSettingsPanel() slicer.app.settingsDialog().addPanel("DICOM", self.settingsPanel)
def examineForLoading(self): """For selected plugins, give user the option of what to load""" (self.loadablesByPlugin, loadEnabled) = self.getLoadablesFromFileLists(self.fileLists) DICOMLib.selectHighestConfidenceLoadables(self.loadablesByPlugin) self.loadableTable.setLoadables(self.loadablesByPlugin) self.updateButtonStates()
def onClearDatabase(self): patientIds = slicer.dicomDatabase.patients() if len(patientIds) == 0: slicer.util.infoDisplay("DICOM database is already empty.") elif not slicer.util.confirmYesNoDisplay( 'Are you sure you want to delete all data and files copied into the database (%d patients)?' % len(patientIds), windowTitle='Clear entire DICOM database'): return DICOMLib.clearDatabase(slicer.dicomDatabase)
def performPostModuleDiscoveryTasks(self): """Since dicom plugins are discovered while the application is initialized, they may be found after the DICOM module itself if initialized. This method is tied to a singleShot that will be called once the event loop is read to start. """ # set the dicom pre-cache tags once all plugin classes have been initialized DICOMLib.setDatabasePrecacheTags() # add to the main app file menu self.addMenu() # add the settings options self.settingsPanel = DICOMSettingsPanel() slicer.app.settingsDialog().addPanel("DICOM", self.settingsPanel)
def performPostModuleDiscoveryTasks(self): """Since dicom plugins are discovered while the application is initialized, they may be found after the DICOM module itself if initialized. This method is tied to a singleShot that will be called once the event loop is read to start. """ # set the dicom pre-cache tags once all plugin classes have been initialized DICOMLib.setDatabasePrecacheTags() if not slicer.app.commandOptions().noMainWindow: # add to the main app file menu self.addMenu() # add the settings options self.settingsPanel = DICOMSettingsPanel() slicer.app.settingsDialog().addPanel("DICOM", self.settingsPanel)
def load_dicom_archive(self, file_path): """ Load unzipped DICOMs into Slicer. Args: file_path (str): path to the cached dicom archive. https://discourse.slicer.org/t/fastest-way-to-load-dicom/9317/2 """ with tempfile.TemporaryDirectory() as dicomDataDir: dicom_zip = ZipFile(file_path) dicom_zip.extractall(path=dicomDataDir) DICOMLib.importDicom(dicomDataDir) dicomFiles = slicer.util.getFilesInDirectory(dicomDataDir) loadablesByPlugin, loadEnabled = DICOMLib.getLoadablesFromFileLists([dicomFiles]) loadedNodeIDs = DICOMLib.loadLoadables(loadablesByPlugin)
def onOk(self): self.sendingIsInProgress = True address = self.serverAddressLineEdit.text protocol = self.protocolSelectorCombobox.currentText self.settings.setValue('DICOM/Send/URL', address) self.settings.setValue('DICOM/Send/Protocol', protocol) self.progressBar.value = 0 self.progressBar.maximum = len(self.files) + 1 self.progressBar.show() self.cancelRequested = False okButton = self.bbox.button(self.bbox.Ok) try: #qt.QApplication.setOverrideCursor(qt.Qt.WaitCursor) okButton.enabled = False DICOMLib.DICOMSender(self.files, address, protocol, progressCallback=self.onProgress) logging.debug("DICOM sending of %s files succeeded" % len(self.files)) self.close() except Exception as result: import traceback slicer.util.errorDisplay("DICOM sending failed: %s" % str(result), parent=self.parent().window(), detailedText=traceback.format_exc()) #qt.QApplication.restoreOverrideCursor() okButton.enabled = True self.sendingIsInProgress = False
def onExportClicked(self): """Associate a slicer volume as a series in the selected dicom study""" uid = self.selection.data(self.dicomModelUIDRole) exportDialog = DICOMLib.DICOMExportDialog( uid, onExportFinished=self.onExportFinished) self.dicomApp.suspendModel() exportDialog.open()
def proceedWithReferencedLoadablesSelection(self): if not self.warnUserIfLoadableWarningsAndProceed(): return progressDialog = slicer.util.createProgressDialog(parent=self, value=0, maximum=100) def progressCallback(progressDialog, progressLabel, progressValue): progressDialog.labelText = '\nLoading %s' % progressLabel slicer.app.processEvents() progressDialog.setValue(progressValue) slicer.app.processEvents() cancelled = progressDialog.wasCanceled return cancelled qt.QApplication.setOverrideCursor(qt.Qt.WaitCursor) messages = [] loadedNodeIDs = DICOMLib.loadLoadables(self.loadablesByPlugin, messages, lambda progressLabel, progressValue, progressDialog=progressDialog: progressCallback(progressDialog, progressLabel, progressValue)) loadedFileParameters = {} loadedFileParameters['nodeIDs'] = loadedNodeIDs slicer.app.ioManager().emitNewFileLoaded(loadedFileParameters) qt.QApplication.restoreOverrideCursor() progressDialog.close() if messages: slicer.util.warningDisplay('\n'.join(messages), windowTitle='DICOM loading') self.onLoadingFinished()
def onToggleServer(self): if self.testingServer and self.testingServer.qrRunning(): self.testingServer.stop() self.toggleServer.text = "Start Testing Server" else: # # create&configure the testingServer if needed, start the server, and populate it # if not self.testingServer: # find the helper executables (only works on build trees # with standard naming conventions) self.exeDir = slicer.app.slicerHome if slicer.app.intDir: self.exeDir = self.exeDir + '/' + slicer.app.intDir self.exeDir = self.exeDir + '/../CTK-build/DCMTK-build' # TODO: deal with Debug/RelWithDebInfo on windows # set up temp dir tmpDir = slicer.app.userSettings().value('Modules/TemporaryDirectory') if not os.path.exists(tmpDir): os.mkdir(tmpDir) self.tmpDir = tmpDir + '/DICOM' if not os.path.exists(self.tmpDir): os.mkdir(self.tmpDir) self.testingServer = DICOMLib.DICOMTestingQRServer(exeDir=self.exeDir,tmpDir=self.tmpDir) # look for the sample data to load (only works on build trees # with standard naming conventions) self.dataDir = slicer.app.slicerHome + '/../../Slicer4/Testing/Data/Input/CTHeadAxialDicom' files = glob.glob(self.dataDir+'/*.dcm') # now start the server self.testingServer.start(verbose=self.verboseServer.checked,initialFiles=files)
def getLoadablesFromRWVMFile(self, file): rwvLoadable = DICOMLib.DICOMLoadable() rwvLoadable.files.append(file) rwvLoadable.patientName = self.__getSeriesInformation( rwvLoadable.files, self.tags['patientName']) rwvLoadable.patientID = self.__getSeriesInformation( rwvLoadable.files, self.tags['patientID']) rwvLoadable.studyDate = self.__getSeriesInformation( rwvLoadable.files, self.tags['studyDate']) dicomFile = dicom.read_file(file) rwvmSeq = dicomFile.ReferencedImageRealWorldValueMappingSequence[ 0].RealWorldValueMappingSequence unitsSeq = rwvmSeq[0].MeasurementUnitsCodeSequence rwvLoadable.name = rwvLoadable.patientName + ' ' + self.convertStudyDate( rwvLoadable.studyDate) + ' ' + unitsSeq[0].CodeMeaning rwvLoadable.unitName = unitsSeq[0].CodeMeaning (quantity, units) = self.getQuantityAndUnitsFromDICOM(dicomFile) rwvLoadable.quantity = quantity rwvLoadable.units = units rwvLoadable.tooltip = rwvLoadable.name rwvLoadable.selected = True rwvLoadable.confidence = 0.90 return [rwvLoadable]
def onOk(self): self.sendingIsInProgress = True address = self.serverAddressLineEdit.text aeTitle = self.serverAETitleEdit.text protocol = self.protocolSelectorCombobox.currentText self.settings.setValue('DICOM/Send/URL', address) self.settings.setValue('DICOM/Send/AETitle', aeTitle) self.settings.setValue('DICOM/Send/Protocol', protocol) self.progressBar.value = 0 self.progressBar.maximum = len(self.files) + 1 self.progressBar.show() self.cancelRequested = False okButton = self.bbox.button(self.bbox.Ok) with slicer.util.tryWithErrorDisplay("DICOM sending failed."): okButton.enabled = False DICOMLib.DICOMSender(self.files, address, protocol, aeTitle=aeTitle, progressCallback=self.onProgress) logging.debug("DICOM sending of %s files succeeded" % len(self.files)) self.close() okButton.enabled = True self.sendingIsInProgress = False
def __init__(self, parent): import string parent.title = "DICOM" parent.categories = ["", "Informatics"] # top level module parent.contributors = ["Steve Pieper (Isomics)"] parent.helpText = string.Template(""" The DICOM module integrates DICOM classes from CTK (based on DCMTK). See <a href=\"$a/Documentation/$b.$c/Modules/DICOM\">the documentaiton</a> for more information. """).substitute({ 'a': parent.slicerWikiUrl, 'b': slicer.app.majorVersion, 'c': slicer.app.minorVersion }) parent.acknowledgementText = """ This work is supported by NA-MIC, NAC, BIRN, NCIGT, and the Slicer Community. See <a href=http://www.slicer.org>http://www.slicer.org</a> for details. Module implemented by Steve Pieper. Based on work from CommonTK (http://www.commontk.org). """ parent.icon = qt.QIcon(':Icons/Medium/SlicerLoadDICOM.png') self.parent = parent if slicer.mrmlScene.GetTagByClassName( "vtkMRMLScriptedModuleNode") != 'ScriptedModule': slicer.mrmlScene.RegisterNodeClass(vtkMRMLScriptedModuleNode()) # initialize the dicom infrastructure slicer.dicomDatabase = None settings = qt.QSettings() # the dicom database is a global object for slicer if settings.contains('DatabaseDirectory'): databaseDirectory = settings.value('DatabaseDirectory') if databaseDirectory: slicer.dicomDatabase = ctk.ctkDICOMDatabase() slicer.dicomDatabase.openDatabase( databaseDirectory + "/ctkDICOM.sql", "SLICER") if not slicer.dicomDatabase.isOpen: # can't open the database, so prompt the user later if they enter module slicer.dicomDatabase = None else: # the dicom listener is also global, but only started on app start if # the user so chooses if settings.contains('DICOM/RunListenerAtStart'): if settings.value( 'DICOM/RunListenerAtStart') == 'true': if not hasattr(slicer, 'dicomListener'): try: slicer.dicomListener = DICOMLib.DICOMListener( slicer.dicomDatabase) slicer.dicomListener.start() except (UserWarning, OSError) as message: # TODO: how to put this into the error log? print( 'Problem trying to start DICOMListener:\n %s' % message) if slicer.dicomDatabase: slicer.app.setDICOMDatabase(slicer.dicomDatabase) # Trigger the menu to be added when application has started up if not slicer.app.commandOptions().noMainWindow: qt.QTimer.singleShot(0, self.addMenu) # set the dicom pre-cache tags once all plugin classes have been initialized qt.QTimer.singleShot(0, DICOM.setDatabasePrecacheTags)
def examineFilesMultiseries(self, files): """ This strategy is similar to examineFiles(), but does not separate the files by individual series before parsing multivolumes out. """ if self.detailedLogging: logging.debug('MultiVolumeImporterPlugin: examineMultiseries') loadables = [] mvNodes = self.initMultiVolumes(files, prescribedTags=[ 'SeriesTime', 'AcquisitionTime', 'FlipAngle', 'CardiacCycle' ]) if self.detailedLogging: logging.debug( 'MultiVolumeImporterPlugin: found {} multivolumes!'.format( len(mvNodes))) for mvNode in mvNodes: tagName = mvNode.GetAttribute( 'MultiVolume.FrameIdentifyingDICOMTagName') nFrames = mvNode.GetNumberOfFrames() orderedFiles = mvNode.GetAttribute( 'MultiVolume.FrameFileList').split(',') desc = slicer.dicomDatabase.fileValue( orderedFiles[0], self.tags['studyDescription']) # SeriesDescription num = slicer.dicomDatabase.fileValue(orderedFiles[0], self.tags['seriesNumber']) if num != "": name = num + ": " + desc else: name = desc if self.isFrameOriginConsistent(orderedFiles, mvNode) == False: continue loadable = DICOMLib.DICOMLoadable() loadable.files = orderedFiles loadable.tooltip = name + ' - ' + str( nFrames) + ' frames MultiVolume by ' + tagName loadable.name = name loadable.selected = True loadable.multivolume = mvNode if tagName == 'TemporalPositionIdentifier': loadable.confidence = 0.9 else: loadable.confidence = 1. loadables.append(loadable) return loadables
def __init__(self, parent): ScriptedLoadableModule.__init__(self, parent) import string self.parent.title = "DICOM" self.parent.categories = ["", "Informatics"] # top level module self.parent.contributors = ["Steve Pieper (Isomics)"] self.parent.helpText = """ The DICOM module integrates DICOM classes from CTK (based on DCMTK). """ self.parent.helpText += self.getDefaultModuleDocumentationLink() self.parent.acknowledgementText = """ This work is supported by NA-MIC, NAC, BIRN, NCIGT, and the Slicer Community. See <a href=http://www.slicer.org>http://www.slicer.org</a> for details. Module implemented by Steve Pieper. Based on work from CommonTK (http://www.commontk.org). """ self.parent.icon = qt.QIcon(':Icons/Medium/SlicerLoadDICOM.png') self.parent.dependencies = ["SubjectHierarchy"] if slicer.mrmlScene.GetTagByClassName( "vtkMRMLScriptedModuleNode") != 'ScriptedModule': slicer.mrmlScene.RegisterNodeClass(vtkMRMLScriptedModuleNode()) # initialize the dicom infrastructure slicer.dicomDatabase = None settings = qt.QSettings() # the dicom database is a global object for slicer if settings.contains('DatabaseDirectory'): databaseDirectory = settings.value('DatabaseDirectory') if databaseDirectory: slicer.dicomDatabase = ctk.ctkDICOMDatabase() slicer.dicomDatabase.openDatabase( databaseDirectory + "/ctkDICOM.sql", "SLICER") if not slicer.dicomDatabase.isOpen: # can't open the database, so prompt the user later if they enter module slicer.dicomDatabase = None else: # the dicom listener is also global, but only started on app start if # the user so chooses if settings.contains('DICOM/RunListenerAtStart'): if settings.value( 'DICOM/RunListenerAtStart') == 'true': if not hasattr(slicer, 'dicomListener'): try: slicer.dicomListener = DICOMLib.DICOMListener( slicer.dicomDatabase) slicer.dicomListener.start() except (UserWarning, OSError) as message: logging.error( 'Problem trying to start DICOMListener:\n %s' % message) if slicer.dicomDatabase: slicer.app.setDICOMDatabase(slicer.dicomDatabase) # Tasks to execute after the application has started up slicer.app.connect("startupCompleted()", self.performPostModuleDiscoveryTasks)
def getLoadablesFromFileLists(self, fileLists): """Take list of file lists, return loadables by plugin dictionary """ loadablesByPlugin = {} loadEnabled = False allFileCount = missingFileCount = 0 for fileList in fileLists: for filePath in fileList: allFileCount += 1 if not os.path.exists(filePath): missingFileCount += 1 if missingFileCount > 0: messages.appendslicer.util.warningDisplay( "Warning: %d of %d selected files listed in the database cannot be found on disk." % (missingFileCount, allFileCount), windowTitle="DICOM") if missingFileCount == allFileCount: # Nothing to load return loadablesByPlugin, loadEnabled progressDialog = slicer.util.createProgressDialog(parent=self, value=0, maximum=100) def progressCallback(progressDialog, progressLabel, progressValue): progressDialog.labelText = '\nChecking %s' % progressLabel slicer.app.processEvents() progressDialog.setValue(progressValue) slicer.app.processEvents() cancelled = progressDialog.wasCanceled return cancelled messages = [] loadablesByPlugin, loadEnabled = DICOMLib.getLoadablesFromFileLists( fileLists, self.pluginSelector.selectedPlugins(), messages, lambda progressLabel, progressValue, progressDialog=progressDialog: progressCallback(progressDialog, progressLabel, progressValue), self.pluginInstances) progressDialog.close() if messages: slicer.util.warningDisplay( "Warning: %s\n\nSee python console for error message." % ' '.join(messages), windowTitle="DICOM", parent=self) return loadablesByPlugin, loadEnabled
def startListener(self): # the dicom listener is also global, but only started on app start if # the user so chooses settings = qt.QSettings() if settings.contains('DICOM/RunListenerAtStart'): if settings.value('DICOM/RunListenerAtStart') == 'true': if not hasattr(slicer, 'dicomListener'): try: slicer.dicomListener = DICOMLib.DICOMListener(slicer.dicomDatabase) slicer.dicomListener.start() except (UserWarning,OSError) as message: logging.error('Problem trying to start DICOMListener:\n %s' % message)
def performPostModuleDiscoveryTasks(self): """Since dicom plugins are discovered while the application is initialized, they may be found after the DICOM module itself if initialized. This method is tied to a singleShot that will be called once the event loop is read to start. """ if slicer.mrmlScene.GetTagByClassName( "vtkMRMLScriptedModuleNode") != 'ScriptedModule': slicer.mrmlScene.RegisterNodeClass(vtkMRMLScriptedModuleNode()) # initialize the dicom infrastructure slicer.dicomDatabase = None settings = qt.QSettings() # the dicom database is a global object for slicer if settings.contains('DatabaseDirectory'): databaseDirectory = settings.value('DatabaseDirectory') if databaseDirectory: slicer.dicomDatabase = ctk.ctkDICOMDatabase() slicer.dicomDatabase.openDatabase( databaseDirectory + "/ctkDICOM.sql", "SLICER") if not slicer.dicomDatabase.isOpen: # can't open the database, so prompt the user later if they enter module slicer.dicomDatabase = None else: self.startListener() if slicer.dicomDatabase: slicer.app.setDICOMDatabase(slicer.dicomDatabase) # set the dicom pre-cache tags once all plugin classes have been initialized DICOMLib.setDatabasePrecacheTags() if not slicer.app.commandOptions().noMainWindow: # add to the main app file menu self.addMenu() # add the settings options self.settingsPanel = DICOMSettingsPanel() slicer.app.settingsDialog().addPanel("DICOM", self.settingsPanel)
def onToggleListener(self): if hasattr(slicer, 'dicomListener'): slicer.dicomListener.stop() del slicer.dicomListener self.toggleListener.text = "Start Listener" else: try: slicer.dicomListener = DICOMLib.DICOMListener(database=slicer.dicomDatabase) slicer.dicomListener.start() self.onListenerStateChanged(slicer.dicomListener.process.state()) slicer.dicomListener.process.connect('stateChanged(QProcess::ProcessState)',self.onListenerStateChanged) slicer.dicomListener.fileToBeAddedCallback = self.onListenerToAddFile slicer.dicomListener.fileAddedCallback = self.onListenerAddedFile self.toggleListener.text = "Stop Listener" except UserWarning as message: self.messageBox(self,"Could not start listener:\n %s" % message,title='DICOM')
def onOk(self): address = self.dicomEntries['Destination Address'].text port = self.dicomEntries['Destination Port'].text settings = qt.QSettings() settings.setValue('DICOM.sendAddress', address) settings.setValue('DICOM.sendPort', port) self.progress = qt.QProgressDialog(slicer.util.mainWindow()) self.progress.minimumDuration = 0 self.progress.setMaximum(len(self.files)) self.progressValue = 0 try: DICOMLib.DICOMSender(self.files, address, port, progressCallback = self.onProgress) except Exception as result: qt.QMessageBox.warning(self.dialog, 'DICOM Send', 'Could not send data: %s' % result) self.progress.close() self.dialog.close()
def onToggleListener(self): self.toggleListener.checked = False if hasattr(slicer, 'dicomListener'): slicer.dicomListener.stop() del slicer.dicomListener else: try: dicomListener = DICOMLib.DICOMListener(database=slicer.dicomDatabase) dicomListener.start() if dicomListener.process: self.onListenerStateChanged(dicomListener.process.state()) dicomListener.process.connect('stateChanged(QProcess::ProcessState)',self.onListenerStateChanged) dicomListener.fileToBeAddedCallback = self.onListenerToAddFile dicomListener.fileAddedCallback = self.onListenerAddedFile slicer.dicomListener = dicomListener except UserWarning as message: slicer.util.warningDisplay("Could not start listener:\n %s" % message, windowTitle="DICOM")
def startListener(self): if not slicer.dicomDatabase.isOpen: logging.error("Failed to start DICOM listener. DICOM database is not open.") return False if not hasattr(slicer, 'dicomListener'): try: dicomListener = DICOMLib.DICOMListener(slicer.dicomDatabase) dicomListener.start() except (UserWarning, OSError) as message: logging.error('Problem trying to start DICOM listener:\n %s' % message) return False if not dicomListener.process: logging.error("Failed to start DICOM listener. Process start failed.") return False slicer.dicomListener = dicomListener logging.info("DICOM C-Store SCP service started at port "+str(slicer.dicomListener.port))
def examineFilesMultiseries(self, files): print('MultiVolumeImportPlugin:examineMultiseries') loadables = [] mvNodes = self.initMultiVolumes( files, prescribedTags=['SeriesTime', 'AcquisitionTime', 'FlipAngle']) print('DICOMMultiVolumePlugin found ' + str(len(mvNodes)) + ' multivolumes!') for mvNode in mvNodes: tagName = mvNode.GetAttribute( 'MultiVolume.FrameIdentifyingDICOMTagName') nFrames = mvNode.GetNumberOfFrames() orderedFiles = string.split( mvNode.GetAttribute('MultiVolume.FrameFileList'), ',') desc = slicer.dicomDatabase.fileValue( orderedFiles[0], self.tags['studyDescription']) # SeriesDescription num = slicer.dicomDatabase.fileValue(orderedFiles[0], self.tags['seriesNumber']) if num != "": name = num + ": " + desc else: name = desc if self.isFrameOriginConsistent(orderedFiles, mvNode) == False: continue loadable = DICOMLib.DICOMLoadable() loadable.files = orderedFiles loadable.tooltip = name + ' - ' + str( nFrames) + ' frames MultiVolume by ' + tagName loadable.name = name loadable.selected = True loadable.multivolume = mvNode loadable.confidence = 0.9 loadables.append(loadable) return loadables
def onOk(self): """Run the export process for either the scene or the selected volume""" if self.exportScene.checked: volumeNode = None else: volumeNode = self.volumeSelector.currentNode() if volumeNode or self.exportScene.checked: parameters = {} for label in self.dicomParameters.keys(): parameters[label] = self.dicomEntries[label].text try: exporter = DICOMLib.DICOMExporter(self.studyUID,volumeNode,parameters) exporter.export() except Exception as result: import traceback qt.QMessageBox.warning(self.dialog, 'DICOM Export', 'Could not export data: %s\n\n%s' % (result, traceback.format_exception(*sys.exc_info()))) if self.onExportFinished: self.onExportFinished() self.dialog.close()
def __init__(self, parent): parent.title = "DICOM" parent.category = "" # top level module parent.contributor = "Steve Pieper" parent.helpText = """ The DICOM module is a place to experiment a bit with dicom classes from CTK (based on DCMTK). It is a 'tent' because it is meant to be suitable for explorers, but may not be robust enough for civilized people. Warning: all data directories are temporary and data may be gone next time you look! """ parent.acknowledgementText = """ This work is supported by NA-MIC, NAC, BIRN, NCIGT, and the Slicer Community. See <a>http://www.slicer.org</a> for details. Module implemented by Steve Pieper. Based on work from CommonTK (http://www.commontk.org). """ self.parent = parent if slicer.mrmlScene.GetTagByClassName( "vtkMRMLScriptedModuleNode") != 'ScriptedModule': slicer.mrmlScene.RegisterNodeClass(vtkMRMLScriptedModuleNode()) # initialize the dicom infrastructure settings = qt.QSettings() # the dicom database is a global object for slicer databaseDirectory = settings.value('DatabaseDirectory') if databaseDirectory: slicer.dicomDatabase = ctk.ctkDICOMDatabase() slicer.dicomDatabase.openDatabase( databaseDirectory + "/ctkDICOM.sql", "SLICER") # the dicom listener is also global, but only started on app start if # the user so chooses if settings.contains('DICOM/RunListenerAtStart'): if bool(settings.value('DICOM/RunListenerAtStart')): if not hasattr(slicer, 'dicomListener'): try: slicer.dicomListener = DICOMLib.DICOMListener( slicer.dicomDatabase) except UserWarning as message: # TODO: how to put this into the error log? print( 'Problem trying to start DICOMListener:\n %s' % message) slicer.dicomListener.start()
def onSendClicked(self): """Perform a dicom store of slicer data to a peer""" # TODO: this should migrate to ctk for a more complete implementation # - just the basics for now uid = self.selection.data(self.dicomModelUIDRole) role = self.dicomModelTypes[self.selection.data(self.dicomModelTypeRole)] studies = [] if role == "Patient": studies = slicer.dicomDatabase.studiesForPatient(uid) if role == "Study": studies = [uid] series = [] if role == "Series": series = [uid] else: for study in studies: series += slicer.dicomDatabase.seriesForStudy(study) files = [] for serie in series: files += slicer.dicomDatabase.filesForSeries(serie) sendDialog = DICOMLib.DICOMSendDialog(files) sendDialog.open()
def convertData(self, plugin, loadable): node = plugin.load(loadable) dcmFile = loadable.files[0] seriesNumber = self.dicomDatabase.fileValue(dcmFile, "0020,0011") patientID = self.dicomDatabase.fileValue(dcmFile, "0010,0020") studyDate = self.dicomDatabase.fileValue(dcmFile, "0008,0020") studyTime = self.dicomDatabase.fileValue(dcmFile, "0008,0030")[0:4] if node: storageNode = node.CreateDefaultStorageNode() studyID = '{}_{}_{}'.format(patientID, studyDate, studyTime) dirName = os.path.join(self.outputDir, studyID, "RESOURCES", seriesNumber, "Reconstructions") xmlName = os.path.join(dirName, seriesNumber + '.xml') try: os.makedirs(dirName) except: pass DICOMLib.DICOMCommand("dcm2xml", [dcmFile, xmlName]).start() nrrdName = os.path.join(dirName, seriesNumber + ".nrrd") # print(nrrdName) storageNode.SetFileName(nrrdName) storageNode.WriteData(node) if self.copyDICOM: fileCount = 0 dirName = os.path.join(self.outputDir, studyID, "RESOURCES", seriesNumber, "DICOM") try: os.makedirs(dirName) except: pass for dcm in loadable.files: shutil.copy(dcm, os.path.join(dirName, "%06d.dcm" % fileCount)) fileCount = fileCount + 1 else: print('No node!')
def onOk(self): address = self.dicomEntries['Destination Address'].text port = self.dicomEntries['Destination Port'].text self.settings.setValue('DICOM.sendAddress', address) self.settings.setValue('DICOM.sendPort', port) self.progress = slicer.util.createProgressDialog(value=0, maximum=len( self.files)) self.progressValue = 0 try: DICOMLib.DICOMSender(self.files, address, port, progressCallback=self.onProgress) except Exception as result: slicer.util.warningDisplay('Could not send data: %s' % result, windowTitle='DICOM Send', parent=self) self.progress.close() self.progress = None self.progressValue = None self.close()
def createDICOMFileForScene(self): """ Export the scene data: - first to a directory using the utility in the mrmlScene - create a zip file using the application logic - create secondary capture based on the sample dataset - add the zip file as a private creator tag TODO: confirm that resulting file is valid - may need to change the CLI to include more parameters or do a new implementation ctk/DCMTK See: http://sourceforge.net/apps/mediawiki/gdcm/index.php?title=Writing_DICOM """ # set up temp directories and files self.dicomDirectory = tempfile.mkdtemp('', 'dicomExport', slicer.app.temporaryPath) self.sceneDirectory = os.path.join(self.dicomDirectory,'scene') os.mkdir(self.sceneDirectory) # known to be unique self.imageFile = os.path.join(self.dicomDirectory, "scene.jpg") self.zipFile = os.path.join(self.dicomDirectory, "scene.zip") self.dumpFile = os.path.join(self.dicomDirectory, "dicom.dump") self.sdbFile = os.path.join(self.dicomDirectory, "SlicerDataBundle.dcm") # Clean up paths on Windows (some commands and operations are not performed properly with mixed slash and backslash) self.dicomDirectory = self.dicomDirectory.replace('\\','/') self.sceneDirectory = self.sceneDirectory.replace('\\','/') # otherwise invalid zip file is created on Windows (with the same size strangely) self.imageFile = self.imageFile.replace('\\','/') self.zipFile = self.zipFile.replace('\\','/') self.dumpFile = self.dumpFile.replace('\\','/') self.sdbFile = self.sdbFile.replace('\\','/') # get the screen image self.progress('Saving Image...') image = ctk.ctkWidgetsUtils.grabWidget(slicer.util.mainWindow()) image.save(self.imageFile) imageReader = vtk.vtkJPEGReader() imageReader.SetFileName(self.imageFile) imageReader.Update() #add storage node for each storable node in the scene, add file name if file name doesn't exist # TODO: this could be moved to appLogic.SaveSceneToSlicerDataBundleDirectory lnodes = slicer.mrmlScene.GetNodesByClass("vtkMRMLLinearTransformNode") lnodes.UnRegister(slicer.mrmlScene) lnum = lnodes.GetNumberOfItems() for itemNum in range(lnum): print(itemNum) node = lnodes.GetItemAsObject(itemNum) snode = node.GetStorageNode() if snode is None: print("something is none") snode = node.CreateDefaultStorageNode() slicer.mrmlScene.AddNode(snode) node.SetAndObserveStorageNodeID(snode.GetID()) if snode.GetFileName() is None: snode.SetFileName(node.GetID()+".h5") # save the scene to the temp dir self.progress('Saving Scene...') appLogic = slicer.app.applicationLogic() appLogic.SaveSceneToSlicerDataBundleDirectory(self.sceneDirectory, imageReader.GetOutput()) # make the zip file self.progress('Making zip...') appLogic.Zip(self.zipFile, self.sceneDirectory) zipSize = os.path.getsize(self.zipFile) # now create the dicom file # - create the dump (capture stdout) # cmd = "dcmdump --print-all --write-pixel %s %s" % (self.dicomDirectory, self.referenceFile) self.progress('Making dicom reference file...') if not self.referenceFile: # set reference file the first file found in the DICOM database self.getFirstFileInDatabase() # if there is still no reference file, then there are no files in the database, cannot continue if not self.referenceFile: logging.error('No reference file! DICOM database is empty') return False args = ['--print-all', '--write-pixel', self.dicomDirectory, self.referenceFile] dump = DICOMLib.DICOMCommand('dcmdump', args).start() # append this to the dumped output and save the result as self.dicomDirectory/dcm.dump # with %s as self.zipFile and %d being its size in bytes zipSizeString = "%d" % zipSize # hack: encode the file zip file size as part of the creator string # because none of the normal types (UL, DS, LO) seem to survive # the dump2dcm step (possibly due to the Unknown nature of the private tag) creatorString = "3D Slicer %s" % zipSizeString candygram = """(cadb,0010) LO [%s] # %d, 1 PrivateCreator (cadb,1008) LO [%s] # 4, 1 Unknown Tag & Data (cadb,1010) OB =%s # %d, 1 Unknown Tag & Data """ % (creatorString, len(creatorString), zipSizeString, self.zipFile, zipSize) dump = str(dump) + candygram logging.debug('dumping to: %s/dump.dcm' % self.dicomDirectory, 'w') fp = open('%s/dump.dcm' % self.dicomDirectory, 'w') fp.write(dump) fp.close() self.progress('Encapsulating Scene in DICOM Dump...') args = [ '%s/dump.dcm' % self.dicomDirectory, '%s/template.dcm' % self.dicomDirectory, '--generate-new-uids', '--overwrite-uids', '--ignore-errors'] DICOMLib.DICOMCommand('dump2dcm', args).start() # now create the Secondary Capture data set # cmd = "img2dcm -k 'InstanceNumber=1' -k 'SeriesDescription=Slicer Data Bundle' -df %s/template.dcm %s %s" % (self.dicomDirectory, self.imageFile, self.sdbFile) args = [ '-k', 'InstanceNumber=1', '-k', 'StudyDescription=Slicer Scene Export', '-k', 'SeriesDescription=Slicer Data Bundle', '--dataset-from', '%s/template.dcm' % self.dicomDirectory, self.imageFile, self.sdbFile] self.progress('Creating DICOM Binary File...') DICOMLib.DICOMCommand('img2dcm', args).start() self.progress('Done') return True
def open(self): # main dialog self.dialog = qt.QDialog(slicer.util.mainWindow()) self.dialog.setWindowTitle('Export to DICOM Study') self.dialog.setWindowModality(1) layout = qt.QVBoxLayout() self.dialog.setLayout(layout) self.studyLabel = qt.QLabel('Attach Data to Study: %s' % self.studyUID) layout.addWidget(self.studyLabel) # scene or volume option self.selectFrame = qt.QFrame(self.dialog) layout.addWidget(self.selectFrame) self.selectLayout = qt.QGridLayout() self.selectFrame.setLayout(self.selectLayout) self.exportScene = qt.QRadioButton("Export Entire Scene", self.selectFrame) self.exportScene.setToolTip( "Create a Slicer Data Bundle in a DICOM Private Creator\n(Only compatible with Slicer)" ) self.exportVolume = qt.QRadioButton("Export Selected Volume", self.selectFrame) self.exportVolume.setToolTip( "Create a compatible DICOM series of slice images") self.exportVolume.checked = True self.selectLayout.addWidget(self.exportScene, 0, 0) self.selectLayout.addWidget(self.exportVolume, 1, 0) self.exportScene.connect('toggled(bool)', self.onExportRadio) self.exportVolume.connect('toggled(bool)', self.onExportRadio) # select volume self.volumeSelector = slicer.qMRMLNodeComboBox(self.dialog) self.volumeSelector.nodeTypes = ("vtkMRMLScalarVolumeNode", "") self.volumeSelector.selectNodeUponCreation = False self.volumeSelector.addEnabled = False self.volumeSelector.noneEnabled = False self.volumeSelector.removeEnabled = False self.volumeSelector.showHidden = False self.volumeSelector.showChildNodeTypes = False self.volumeSelector.setMRMLScene(slicer.mrmlScene) self.volumeSelector.setToolTip("Pick the label map to edit") self.selectLayout.addWidget(self.volumeSelector, 1, 1) # DICOM Parameters self.dicomFrame = qt.QFrame(self.dialog) self.dicomFormLayout = qt.QFormLayout() self.dicomFrame.setLayout(self.dicomFormLayout) self.dicomEntries = {} exporter = DICOMLib.DICOMExporter(self.studyUID) self.dicomParameters = exporter.parametersFromStudy() self.dicomParameters['Series Description'] = '3D Slicer Export' for label in self.dicomParameters.keys(): self.dicomEntries[label] = qt.QLineEdit() self.dicomEntries[label].text = self.dicomParameters[label] self.dicomFormLayout.addRow(label + ": ", self.dicomEntries[label]) layout.addWidget(self.dicomFrame) # button box bbox = qt.QDialogButtonBox(self.dialog) bbox.addButton(bbox.Ok) bbox.addButton(bbox.Cancel) bbox.connect('accepted()', self.onOk) bbox.connect('rejected()', self.onCancel) layout.addWidget(bbox) self.dialog.open()
def examine(self, fileLists): """ Returns a list of DICOMLoadable instances corresponding to ways of interpreting the fileLists parameter. Top-level examine() calls various individual strategies implemented in examineFiles*(). """ loadables = [] allfiles = [] for files in fileLists: loadables += self.examineFiles(files) # this strategy sorts the files into groups loadables += self.examineFilesIPPAcqTime(files) allfiles += files # here all files are lumped into one list for the situations when # individual frames should be parsed from series loadables += self.examineFilesMultiseries(allfiles) if len(allfiles) > len(files): # only examineFilesIPPAcqTime again if there are multiple file groups loadables += self.examineFilesIPPAcqTime(allfiles) # this strategy sorts the files into groups loadables += self.examineFilesIPPInstanceNumber(allfiles) # If Sequences module is available then duplicate all the loadables # for loading them as volume sequence. # A slightly higher confidence value is set for volume sequence loadables, # therefore by default data will be loaded as volume sequence. if hasattr(slicer.modules, 'sequences'): seqLoadables = [] for loadable in loadables: seqLoadable = DICOMLib.DICOMLoadable() seqLoadable.files = loadable.files seqLoadable.tooltip = loadable.tooltip.replace( ' frames MultiVolume by ', ' frames Volume Sequence by ') seqLoadable.name = loadable.name.replace( ' frames MultiVolume by ', ' frames Volume Sequence by ') seqLoadable.multivolume = loadable.multivolume seqLoadable.selected = loadable.selected seqLoadable.confidence = loadable.confidence seqLoadable.loadAsVolumeSequence = True seqLoadables.append(seqLoadable) # Among all selected loadables, the ones that are listed first will be selected by default, # therefore we need to prepend loadables if sequence format is preferred. # Determine from settings loading into sequence node should have higher confidence (selected by default). settings = qt.QSettings() sequenceFormatPreferred = (settings.value( "DICOM/PreferredMultiVolumeImportFormat", "default") == "sequence") if sequenceFormatPreferred: # prepend loadables[0:0] = seqLoadables else: # append loadables += seqLoadables return loadables