def getTempDirectoryBase(self): tempDir = qt.QDir(slicer.app.temporaryPath) fileInfo = qt.QFileInfo(qt.QDir(tempDir), "SegmentMesher") dirPath = fileInfo.absoluteFilePath() qt.QDir().mkpath(dirPath) return dirPath
def createMeshFromSegmentationCleaver(self, inputSegmentation, outputMeshNode, additionalParameters="--scale 0.2 --multiplier 2 --grading 5"): self.abortRequested = False tempDir = self.createTempDirectory() self.addLog('Mesh generation using Cleaver is started in working directory: '+tempDir) inputParamsCleaver = [] # Write inputs qt.QDir().mkpath(tempDir) labelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLLabelMapVolumeNode') slicer.modules.segmentations.logic().ExportAllSegmentsToLabelmapNode(inputSegmentation, labelmapVolumeNode) inputLabelmapVolumeFilePath = os.path.join(tempDir, "inputLabelmap.nrrd") slicer.util.saveNode(labelmapVolumeNode, inputLabelmapVolumeFilePath, {"useCompression": False}) inputParamsCleaver.extend(["--input_files", inputLabelmapVolumeFilePath]) inputParamsCleaver.append("--segmentation") # Keep IJK to RAS matrix, we'll need it later unscaledIjkToRasMatrix = vtk.vtkMatrix4x4() labelmapVolumeNode.GetIJKToRASDirectionMatrix(unscaledIjkToRasMatrix) origin = labelmapVolumeNode.GetOrigin() for i in range(3): unscaledIjkToRasMatrix.SetElement(i,3, origin[i]) # Keep color node, we'll need it later colorTableNode = labelmapVolumeNode.GetDisplayNode().GetColorNode() # Background color is transparent by default which is not ideal for 3D display colorTableNode.SetColor(0,0.6,0.6,0.6,1.0) slicer.mrmlScene.RemoveNode(labelmapVolumeNode) slicer.mrmlScene.RemoveNode(colorTableNode) # Set up output format inputParamsCleaver.extend(["--output_path", tempDir+"/"]) inputParamsCleaver.extend(["--output_format", "vtkUSG"]) # VTK unstructed grid inputParamsCleaver.append("--fix_tet_windup") # prevent inside-out tets inputParamsCleaver.append("--strip_exterior") # remove temporary elements that are added to make the volume cubic inputParamsCleaver.append("--verbose") # Quality inputParamsCleaver.extend(slicer.util.toVTKString(additionalParameters).split(' ')) # Run Cleaver ep = self.startMesher(inputParamsCleaver, self.getCleaverPath()) self.logProcessOutput(ep, self.cleaverFilename) # Read results if not self.abortRequested: outputVolumetricMeshPath = os.path.join(tempDir, "output.vtk") outputReader = vtk.vtkUnstructuredGridReader() outputReader.SetFileName(outputVolumetricMeshPath) outputReader.ReadAllScalarsOn() outputReader.ReadAllVectorsOn() outputReader.ReadAllNormalsOn() outputReader.ReadAllTensorsOn() outputReader.ReadAllColorScalarsOn() outputReader.ReadAllTCoordsOn() outputReader.ReadAllFieldsOn() outputReader.Update() # Cleaver returns the mesh in voxel coordinates, need to transform to RAS space transformer = vtk.vtkTransformFilter() transformer.SetInputData(outputReader.GetOutput()) ijkToRasTransform = vtk.vtkTransform() ijkToRasTransform.SetMatrix(unscaledIjkToRasMatrix) transformer.SetTransform(ijkToRasTransform) outputMeshNode.SetUnstructuredGridConnection(transformer.GetOutputPort()) outputMeshDisplayNode = outputMeshNode.GetDisplayNode() if not outputMeshDisplayNode: # Initial setup of display node outputMeshNode.CreateDefaultDisplayNodes() outputMeshDisplayNode = outputMeshNode.GetDisplayNode() outputMeshDisplayNode.SetEdgeVisibility(True) outputMeshDisplayNode.SetClipping(True) colorTableNode = slicer.mrmlScene.AddNode(colorTableNode) outputMeshDisplayNode.SetAndObserveColorNodeID(colorTableNode.GetID()) outputMeshDisplayNode.ScalarVisibilityOn() outputMeshDisplayNode.SetActiveScalarName('labels') outputMeshDisplayNode.SetActiveAttributeLocation(vtk.vtkAssignAttribute.CELL_DATA); outputMeshDisplayNode.SetSliceIntersectionVisibility(True) outputMeshDisplayNode.SetSliceIntersectionOpacity(0.5) else: currentColorNode = outputMeshDisplayNode.GetColorNode() if currentColorNode.GetType() == currentColorNode.User and currentColorNode.IsA("vtkMRMLColorTableNode"): # current color table node can be overwritten currentColorNode.Copy(colorTableNode) else: colorTableNode = slicer.mrmlScene.AddNode(colorTableNode) outputMeshDisplayNode.SetAndObserveColorNodeID(colorTableNode.GetID()) # Clean up if self.deleteTemporaryFiles: import shutil shutil.rmtree(tempDir) self.addLog("Model generation is completed")
def test_Part1DICOM(self, enableScreenshotsFlag=0, screenshotScaleFactor=1): """ Test the DICOM part of the test using the head atlas """ self.enableScreenshots = enableScreenshotsFlag self.screenshotScaleFactor = screenshotScaleFactor import os self.delayDisplay("Starting the DICOM test") # # first, get the data - a zip file of dicom data # import urllib downloads = (('http://slicer.kitware.com/midas3/download?items=124183', 'dataset1_Thorax_Abdomen.zip'), ) self.delayDisplay("Downloading") for url, name in downloads: filePath = slicer.app.temporaryPath + '/' + name if not os.path.exists(filePath) or os.stat(filePath).st_size == 0: self.delayDisplay('Requesting download %s from %s...\n' % (name, url)) urllib.urlretrieve(url, filePath) self.delayDisplay('Finished with download\n') self.delayDisplay("Unzipping") dicomFilesDirectory = slicer.app.temporaryPath + '/dicomFiles' qt.QDir().mkpath(dicomFilesDirectory) slicer.app.applicationLogic().Unzip(filePath, dicomFilesDirectory) try: self.delayDisplay("Switching to temp database directory") originalDatabaseDirectory = DICOMUtils.openTemporaryDatabase( 'tempDICOMDatabase') self.delayDisplay('Importing DICOM') mainWindow = slicer.util.mainWindow() mainWindow.moduleSelector().selectModule('DICOM') indexer = ctk.ctkDICOMIndexer() indexer.addDirectory(slicer.dicomDatabase, dicomFilesDirectory, None) indexer.waitForImportFinished() dicomWidget.detailsPopup.open() # load the data by series UID dicomWidget.detailsPopup.offerLoadables( '1.3.12.2.1107.5.1.4.50025.30000005060811542834300000776', 'Series') dicomWidget.detailsPopup.examineForLoading() self.delayDisplay('Loading Selection') dicomWidget.detailsPopup.loadCheckedLoadables() self.takeScreenshot('LoadingADICOMVolume-Loaded', 'Loaded DICOM Volume', -1) layoutManager = slicer.app.layoutManager() redWidget = layoutManager.sliceWidget('Red') slicer.util.clickAndDrag(redWidget, start=(10, 10), end=(10, 40)) slicer.util.clickAndDrag(redWidget, start=(10, 10), end=(40, 10)) self.takeScreenshot('LoadingADICOMVolume-WL', 'Changed level and window', -1) redWidget.sliceController().setSliceLink(True) redWidget.sliceController().setSliceVisible(True) self.takeScreenshot('LoadingADICOMVolume-LinkView', 'Linked and visible', -1) slicer.util.clickAndDrag(redWidget, button='Right', start=(10, 10), end=(10, 40)) self.takeScreenshot('LoadingADICOMVolume-Zoom', 'Zoom', -1) threeDView = layoutManager.threeDWidget(0).threeDView() slicer.util.clickAndDrag(threeDView) self.takeScreenshot('LoadingADICOMVolume-Rotate', 'Rotate', -1) threeDView.resetFocalPoint() self.takeScreenshot('LoadingADICOMVolume-Center', 'Center the view', -1) layoutManager.setLayout(slicer.vtkMRMLLayoutNode. SlicerLayoutConventionalWidescreenView) self.takeScreenshot('LoadingADICOMVolume-ConventionalWidescreen', 'Conventional Widescreen Layout', -1) slicer.util.mainWindow().moduleSelector().selectModule( 'VolumeRendering') self.takeScreenshot('VolumeRendering-Module', 'Volume Rendering', -1) volumeRenderingWidgetRep = slicer.modules.volumerendering.widgetRepresentation( ) abdomenVolume = slicer.mrmlScene.GetFirstNodeByName( '6: CT_Thorax_Abdomen') volumeRenderingWidgetRep.setMRMLVolumeNode(abdomenVolume) self.takeScreenshot('VolumeRendering-SelectVolume', 'Select the volume 6: CT_Thorax_Abdomen', -1) presetsScene = slicer.modules.volumerendering.logic( ).GetPresetsScene() ctCardiac3 = presetsScene.GetFirstNodeByName('CT-Cardiac3') volumeRenderingWidgetRep.applyPreset(ctCardiac3) self.takeScreenshot('VolumeRendering-SelectPreset', 'Select the Preset CT-Cardiac-3') self.delayDisplay('Skipping: Select VTK CPU Ray Casting') volumeRenderingNode = slicer.mrmlScene.GetFirstNodeByName( 'VolumeRendering') volumeRenderingNode.SetVisibility(1) self.takeScreenshot('VolumeRendering-ViewRendering', 'View Volume Rendering', -1) self.delayDisplay('Skipping Move the Shift slider') redWidget.sliceController().setSliceVisible(False) self.takeScreenshot('VolumeRendering-SlicesOff', 'Turn off visibility of slices in 3D', -1) threeDView = layoutManager.threeDWidget(0).threeDView() slicer.util.clickAndDrag(threeDView) self.takeScreenshot('VolumeRendering-RotateVolumeRendering', 'Rotate volume rendered image', -1) volumeRenderingNode.SetVisibility(0) self.takeScreenshot('VolumeRendering-TurnOffVolumeRendering', 'Turn off volume rendered image', -1) volumeRenderingNode.SetCroppingEnabled(1) annotationROI = slicer.mrmlScene.GetFirstNodeByName( 'AnnotationROI') annotationROI.SetDisplayVisibility(1) self.takeScreenshot('VolumeRendering-DisplayROI', 'Enable cropping and display ROI', -1) redWidget.sliceController().setSliceVisible(True) self.takeScreenshot('VolumeRendering-SlicesOn', 'Turn on visibility of slices in 3D', -1) annotationROI.SetXYZ(-79.61, 154.16, -232.591) annotationROI.SetRadiusXYZ(43.4, 65.19, 70.5) self.takeScreenshot('VolumeRendering-SizedROI', 'Position the ROI over a kidney', -1) volumeRenderingNode.SetVisibility(1) self.takeScreenshot('VolumeRendering-ROIRendering', 'ROI volume rendered', -1) annotationROI.SetXYZ(15, 146, -186) annotationROI.SetRadiusXYZ(138, 57, 61) self.takeScreenshot('VolumeRendering-BothKidneys', 'Rendered both kidneys', -1) self.delayDisplay('Test passed!') except Exception, e: import traceback traceback.print_exc() self.delayDisplay('Test caused exception!\n' + str(e))
def export(self, exportables): for exportable in exportables: # Get volume node to export shNode = slicer.vtkMRMLSubjectHierarchyNode.GetSubjectHierarchyNode( slicer.mrmlScene) if shNode is None: error = "Invalid subject hierarchy" logging.error(error) return error volumeNode = shNode.GetItemDataNode( exportable.subjectHierarchyItemID) if volumeNode is None or not volumeNode.IsA( 'vtkMRMLScalarVolumeNode'): error = "Series '" + shNode.GetItemName( exportable.subjectHierarchyItemID) + "' cannot be exported" logging.error(error) return error # Get output directory and create a subdirectory. This is necessary # to avoid overwriting the files in case of multiple exportables, as # naming of the DICOM files is static directoryName = 'ScalarVolume_' + str( exportable.subjectHierarchyItemID) directoryDir = qt.QDir(exportable.directory) directoryDir.mkpath(directoryName) directoryDir.cd(directoryName) directory = directoryDir.absolutePath() logging.info("Export scalar volume '" + volumeNode.GetName() + "' to directory " + directory) # Get study and patient items studyItemID = shNode.GetItemParent( exportable.subjectHierarchyItemID) if not studyItemID: error = "Unable to get study for series '" + volumeNode.GetName( ) + "'" logging.error(error) return error patientItemID = shNode.GetItemParent(studyItemID) if not patientItemID: error = "Unable to get patient for series '" + volumeNode.GetName( ) + "'" logging.error(error) return error # Assemble tags dictionary for volume export tags = {} tags['Patient Name'] = exportable.tag( slicer.vtkMRMLSubjectHierarchyConstants. GetDICOMPatientNameTagName()) tags['Patient ID'] = exportable.tag( slicer.vtkMRMLSubjectHierarchyConstants. GetDICOMPatientIDTagName()) tags['Patient Birth Date'] = exportable.tag( slicer.vtkMRMLSubjectHierarchyConstants. GetDICOMPatientBirthDateTagName()) tags['Patient Sex'] = exportable.tag( slicer.vtkMRMLSubjectHierarchyConstants. GetDICOMPatientSexTagName()) tags['Patient Comments'] = exportable.tag( slicer.vtkMRMLSubjectHierarchyConstants. GetDICOMPatientCommentsTagName()) tags['Study ID'] = exportable.tag( slicer.vtkMRMLSubjectHierarchyConstants.GetDICOMStudyIDTagName( )) tags['Study Date'] = exportable.tag( slicer.vtkMRMLSubjectHierarchyConstants. GetDICOMStudyDateTagName()) tags['Study Time'] = exportable.tag( slicer.vtkMRMLSubjectHierarchyConstants. GetDICOMStudyTimeTagName()) tags['Study Description'] = exportable.tag( slicer.vtkMRMLSubjectHierarchyConstants. GetDICOMStudyDescriptionTagName()) tags['Modality'] = exportable.tag('Modality') tags['Manufacturer'] = exportable.tag('Manufacturer') tags['Model'] = exportable.tag('Model') tags['Series Description'] = exportable.tag('SeriesDescription') tags['Series Number'] = exportable.tag('SeriesNumber') tags['Series Date'] = exportable.tag('SeriesDate') tags['Series Time'] = exportable.tag('SeriesTime') tags['Content Date'] = exportable.tag('ContentDate') tags['Content Time'] = exportable.tag('ContentTime') tags['Study Instance UID'] = exportable.tag('StudyInstanceUID') tags['Series Instance UID'] = exportable.tag('SeriesInstanceUID') tags['Frame of Reference UID'] = exportable.tag( 'FrameOfReferenceUID') # Generate any missing but required UIDs if not tags['Study Instance UID']: import pydicom as dicom tags['Study Instance UID'] = dicom.uid.generate_uid() if not tags['Series Instance UID']: import pydicom as dicom tags['Series Instance UID'] = dicom.uid.generate_uid() if not tags['Frame of Reference UID']: import pydicom as dicom tags['Frame of Reference UID'] = dicom.uid.generate_uid() # Use the default Study ID if none is specified if not tags['Study ID']: tags['Study ID'] = self.defaultStudyID # Validate tags if tags['Modality'] == "": error = "Empty modality for series '" + volumeNode.GetName( ) + "'" logging.error(error) return error seriesInstanceUID = tags['Series Instance UID'] if seriesInstanceUID: # Make sure we don't use a series instance UID that already exists (it would mix in more slices into an existing series, # which is very unlikely that users would want). db = slicer.dicomDatabase studyInstanceUID = db.studyForSeries(seriesInstanceUID) if studyInstanceUID: # This seriesInstanceUID is already found in the database if len(seriesInstanceUID) > 25: seriesInstanceUID = seriesInstanceUID[:20] + "..." error = f"A series already exists in the database by SeriesInstanceUID {seriesInstanceUID}." logging.error(error) return error #TODO: more tag checks # Perform export exporter = DICOMExportScalarVolume(tags['Study ID'], volumeNode, tags, directory) if not exporter.export(): return "Creating DICOM files from scalar volume failed. See the application log for details." # Success return ""
def registerVolumes(self, fixedVolumeNode, movingVolumeNode, parameterFilenames=None, outputVolumeNode=None, outputTransformNode=None, fixedVolumeMaskNode=None, movingVolumeMaskNode=None): self.abortRequested = False tempDir = self.createTempDirectory() self.addLog('Volume registration is started in working directory: ' + tempDir) # Write inputs inputDir = os.path.join(tempDir, 'input') qt.QDir().mkpath(inputDir) inputParamsElastix = [] # Add input volumes inputVolumes = [] inputVolumes.append([fixedVolumeNode, 'fixed.mha', '-f']) inputVolumes.append([movingVolumeNode, 'moving.mha', '-m']) inputVolumes.append([fixedVolumeMaskNode, 'fixedMask.mha', '-fMask']) inputVolumes.append([movingVolumeMaskNode, 'movingMask.mha', '-mMask']) for [volumeNode, filename, paramName] in inputVolumes: if not volumeNode: continue filePath = os.path.join(inputDir, filename) slicer.util.saveNode(volumeNode, filePath, {"useCompression": False}) inputParamsElastix.append(paramName) inputParamsElastix.append(filePath) # Specify output location resultTransformDir = os.path.join(tempDir, 'result-transform') qt.QDir().mkpath(resultTransformDir) inputParamsElastix += ['-out', resultTransformDir] # Specify parameter files if parameterFilenames == None: parameterFilenames = self.getRegistrationPresets( )[0][RegistrationPresets_ParameterFilenames] for parameterFilename in parameterFilenames: inputParamsElastix.append('-p') parameterFilePath = os.path.abspath( os.path.join(self.registrationParameterFilesDir, parameterFilename)) inputParamsElastix.append(parameterFilePath) # Run registration ep = self.startElastix(inputParamsElastix) self.logProcessOutput(ep) # Resample if not self.abortRequested: resultResampleDir = os.path.join(tempDir, 'result-resample') qt.QDir().mkpath(resultResampleDir) inputParamsTransformix = [ '-in', os.path.join(inputDir, 'moving.mha'), '-out', resultResampleDir ] if outputTransformNode: inputParamsTransformix += ['-def', 'all'] if outputVolumeNode: inputParamsTransformix += [ '-tp', resultTransformDir + '/TransformParameters.' + str(len(parameterFilenames) - 1) + '.txt' ] tp = self.startTransformix(inputParamsTransformix) self.logProcessOutput(tp) # Write results if not self.abortRequested: if outputVolumeNode: outputVolumePath = os.path.join(resultResampleDir, "result.mhd") [success, loadedOutputVolumeNode ] = slicer.util.loadVolume(outputVolumePath, returnNode=True) if success: outputVolumeNode.SetAndObserveImageData( loadedOutputVolumeNode.GetImageData()) ijkToRas = vtk.vtkMatrix4x4() loadedOutputVolumeNode.GetIJKToRASMatrix(ijkToRas) outputVolumeNode.SetIJKToRASMatrix(ijkToRas) slicer.mrmlScene.RemoveNode(loadedOutputVolumeNode) if outputTransformNode: outputTransformPath = os.path.join(resultResampleDir, "deformationField.mhd") [success, loadedOutputTransformNode ] = slicer.util.loadTransform(outputTransformPath, returnNode=True) if success: if loadedOutputTransformNode.GetReadAsTransformToParent(): outputTransformNode.SetAndObserveTransformToParent( loadedOutputTransformNode.GetTransformToParent()) else: outputTransformNode.SetAndObserveTransformFromParent( loadedOutputTransformNode.GetTransformFromParent()) slicer.mrmlScene.RemoveNode(loadedOutputTransformNode) # Clean up if self.deleteTemporaryFiles: import shutil shutil.rmtree(tempDir) self.addLog("Registration is completed")
def export(self, exportables): for exportable in exportables: # Get volume node to export shNode = slicer.vtkMRMLSubjectHierarchyNode.GetSubjectHierarchyNode( slicer.mrmlScene) if shNode is None: error = "Invalid subject hierarchy" logging.error(error) return error volumeNode = shNode.GetItemDataNode( exportable.subjectHierarchyItemID) if volumeNode is None or not volumeNode.IsA( 'vtkMRMLScalarVolumeNode'): error = "Series '" + shNode.GetItemName( exportable.subjectHierarchyItemID) + "' cannot be exported" logging.error(error) return error # Get output directory and create a subdirectory. This is necessary # to avoid overwriting the files in case of multiple exportables, as # naming of the DICOM files is static directoryName = 'ScalarVolume_' + str( exportable.subjectHierarchyItemID) directoryDir = qt.QDir(exportable.directory) directoryDir.mkdir(directoryName) directoryDir.cd(directoryName) directory = directoryDir.absolutePath() logging.info("Export scalar volume '" + volumeNode.GetName() + "' to directory " + directory) # Get study and patient items studyItemID = shNode.GetItemParent( exportable.subjectHierarchyItemID) if not studyItemID: error = "Unable to get study for series '" + volumeNode.GetName( ) + "'" logging.error(error) return error patientItemID = shNode.GetItemParent(studyItemID) if not patientItemID: error = "Unable to get patient for series '" + volumeNode.GetName( ) + "'" logging.error(error) return error # Assemble tags dictionary for volume export tags = {} tags['Patient Name'] = exportable.tag( slicer.vtkMRMLSubjectHierarchyConstants. GetDICOMPatientNameTagName()) tags['Patient ID'] = exportable.tag( slicer.vtkMRMLSubjectHierarchyConstants. GetDICOMPatientIDTagName()) tags['Patient Birth Date'] = exportable.tag( slicer.vtkMRMLSubjectHierarchyConstants. GetDICOMPatientBirthDateTagName()) tags['Patient Sex'] = exportable.tag( slicer.vtkMRMLSubjectHierarchyConstants. GetDICOMPatientSexTagName()) tags['Patient Comments'] = exportable.tag( slicer.vtkMRMLSubjectHierarchyConstants. GetDICOMPatientCommentsTagName()) tags['Study ID'] = self.defaultStudyID tags['Study Date'] = exportable.tag( slicer.vtkMRMLSubjectHierarchyConstants. GetDICOMStudyDateTagName()) tags['Study Time'] = exportable.tag( slicer.vtkMRMLSubjectHierarchyConstants. GetDICOMStudyTimeTagName()) tags['Study Description'] = exportable.tag( slicer.vtkMRMLSubjectHierarchyConstants. GetDICOMStudyDescriptionTagName()) tags['Modality'] = exportable.tag('Modality') tags['Manufacturer'] = exportable.tag('Manufacturer') tags['Model'] = exportable.tag('Model') tags['Series Description'] = exportable.tag('SeriesDescription') tags['Series Number'] = exportable.tag('SeriesNumber') tags['Series Date'] = exportable.tag('SeriesDate') tags['Series Time'] = exportable.tag('SeriesTime') tags['Content Date'] = exportable.tag('ContentDate') tags['Content Time'] = exportable.tag('ContentTime') tags['Study Instance UID'] = exportable.tag('StudyInstanceUID') tags['Series Instance UID'] = exportable.tag('SeriesInstanceUID') tags['Frame of Reference Instance UID'] = exportable.tag( 'FrameOfReferenceInstanceUID') # Validate tags if tags['Modality'] == "": error = "Empty modality for series '" + volumeNode.GetName( ) + "'" logging.error(error) return error #TODO: more tag checks # Perform export exporter = DICOMExportScalarVolume(tags['Study ID'], volumeNode, tags, directory) exporter.export() # Success return ""
def loadModules(self, path, depth=1): # Get list of modules in specified path modules = ModuleInfo.findModules(path, depth) # Determine which modules in above are not already loaded factory = slicer.app.moduleManager().factoryManager() loadedModules = factory.instantiatedModuleNames() candidates = [m for m in modules if m.key not in loadedModules] # Prompt to load additional module(s) if len(candidates): dlg = LoadModulesDialog(self.parent.window()) dlg.setModules(candidates) if dlg.exec_() == qt.QDialog.Accepted: modulesToLoad = dlg.selectedModules # Add module(s) to permanent search paths, if requested if dlg.addToSearchPaths: settings = slicer.app.revisionUserSettings() rawSearchPaths = list( _settingsList(settings, "Modules/AdditionalPaths")) searchPaths = [qt.QDir(path) for path in rawSearchPaths] modified = False for module in modulesToLoad: rawPath = os.path.dirname(module.path) path = qt.QDir(rawPath) if not path in searchPaths: searchPaths.append(path) rawSearchPaths.append(rawPath) modified = True if modified: settings.setValue("Modules/AdditionalPaths", rawSearchPaths) # Enable developer mode (shows Reload&Test section, etc.), if requested if dlg.enableDeveloperMode: qt.QSettings().setValue('Developer/DeveloperMode', 'true') # Register requested module(s) failed = [] for module in modulesToLoad: factory.registerModule(qt.QFileInfo(module.path)) if not factory.isRegistered(module.key): failed.append(module) if len(failed): if len(failed) > 1: text = "The following modules could not be registered:" else: text = "The '%s' module could not be registered:" % failed[ 0].key failedFormat = "<ul><li>%(key)s<br/>(%(path)s)</li></ul>" detailedInformation = "".join( [failedFormat % m.__dict__ for m in failed]) slicer.util.errorDisplay( text, parent=self.parent.window(), windowTitle="Module loading failed", standardButtons=qt.QMessageBox.Close, informativeText=detailedInformation) return # Instantiate and load requested module(s) if not factory.loadModules( [module.key for module in modulesToLoad]): text = ("The module factory manager reported an error. " "One or more of the requested module(s) and/or " "dependencies thereof may not have been loaded.") slicer.util.errorDisplay( text, parent=self.parent.window(), windowTitle="Error loading module(s)", standardButtons=qt.QMessageBox.Close)
def downloadFromSource(self,source,attemptCount=0): """Given an instance of SampleDataSource, downloads the associated data and load them into Slicer if it applies. The function always returns a list. Based on the fileType(s), nodeName(s) and loadFile(s) associated with the source, different values may be appended to the returned list: - if nodeName is specified, appends loaded nodes but if ``loadFile`` is False appends downloaded filepath - if fileType is ``SceneFile``, appends downloaded filepath - if fileType is ``ZipFile``, appends directory of extracted archive but if ``loadFile`` is False appends downloaded filepath If no ``nodeNames`` and no ``fileTypes`` are specified or if ``loadFiles`` are all False, returns the list of all downloaded filepaths. """ nodes = [] filePaths = [] for uri,fileName,nodeName,loadFile,loadFileType in zip(source.uris,source.fileNames,source.nodeNames,source.loadFiles,source.loadFileType): current_source = SampleDataSource(uris=uri, fileNames=fileName, nodeNames=nodeName, loadFiles=loadFile, loadFileType=loadFileType, loadFileProperties=source.loadFileProperties) filePath = self.downloadFileIntoCache(uri, fileName) filePaths.append(filePath) if loadFileType == 'ZipFile': if loadFile == False: nodes.append(filePath) continue outputDir = slicer.mrmlScene.GetCacheManager().GetRemoteCacheDirectory() + "/" + os.path.splitext(os.path.basename(filePath))[0] qt.QDir().mkpath(outputDir) success = slicer.util.extractArchive(filePath, outputDir) if not success and attemptCount < 5: attemptCount += 1 self.logMessage('<b>Load failed! Trying to download again (%d of 5 attempts)...</b>' % (attemptCount), logging.ERROR) file = qt.QFile(filePath) if not file.remove(): self.logMessage('<b>Load failed! Unable to delete and try again loading %s!</b>' % filePath, logging.ERROR) nodes.append(None) break outputDir = self.downloadFromSource(current_source,attemptCount)[0] nodes.append(outputDir) elif loadFileType == 'SceneFile': if not loadFile: nodes.append(filePath) continue success = self.loadScene(filePath, source.loadFileProperties) if not success and attemptCount < 5: attemptCount += 1 self.logMessage('<b>Load failed! Trying to download again (%d of 5 attempts)...</b>' % (attemptCount), logging.ERROR) file = qt.QFile(filePath) if not file.remove(): self.logMessage('<b>Load failed! Unable to delete and try again loading %s!</b>' % filePath, logging.ERROR) nodes.append(None) break filePath = self.downloadFromSource(current_source,attemptCount)[0] nodes.append(filePath) elif nodeName: if loadFile == False: nodes.append(filePath) continue loadedNode = self.loadNode(filePath, nodeName, loadFileType, source.loadFileProperties) if loadedNode is None and attemptCount < 5: attemptCount += 1 self.logMessage('<b>Load failed! Trying to download again (%d of 5 attempts)...</b>' % (attemptCount), logging.ERROR) file = qt.QFile(filePath) if not file.remove(): self.logMessage('<b>Load failed! Unable to delete and try again loading %s!</b>' % filePath, logging.ERROR) loadedNode.append(None) break loadedNode = self.downloadFromSource(current_source,attemptCount)[0] nodes.append(loadedNode) if nodes: return nodes else: return filePaths
def test_Part1DICOM(self): """ Test the DICOM part of the test using the head atlas """ import os self.delayDisplay("Starting the DICOM test") # # first, get the data - a zip file of dicom data # import urllib downloads = (('http://slicer.kitware.com/midas3/download?items=18822', 'Dcmtk-db.zip'), ) self.delayDisplay("Downloading") for url, name in downloads: filePath = slicer.app.temporaryPath + '/' + name if not os.path.exists(filePath) or os.stat(filePath).st_size == 0: self.delayDisplay('Requesting download %s from %s...\n' % (name, url)) urllib.urlretrieve(url, filePath) self.delayDisplay('Finished with download\n') self.delayDisplay("Unzipping") dicomFilesDirectory = slicer.app.temporaryPath + '/dicomFiles' qt.QDir().mkpath(dicomFilesDirectory) slicer.app.applicationLogic().Unzip(filePath, dicomFilesDirectory) try: self.delayDisplay("Switching to temp database directory") originalDatabaseDirectory = DICOMUtils.openTemporaryDatabase( 'tempDICOMDatbase') self.delayDisplay('Start Local DICOM Q/R SCP') import subprocess import os configFilePath = dicomFilesDirectory + '/Dcmtk-db/dcmqrscp.cfg' processCurrentPath = dicomFilesDirectory + '/Dcmtk-db/' dcmqrscpExeOptions = ( '/bin', '/../CTK-build/CMakeExternals/Install/bin', '/../DCMTK-install/bin', '/../DCMTK-build/bin', ) dcmqrscpExePath = None dcmqrscpExeName = '/dcmqrscp' if slicer.app.os == 'win': dcmqrscpExeName = dcmqrscpExeName + '.exe' for path in dcmqrscpExeOptions: testPath = slicer.app.slicerHome + path + dcmqrscpExeName if os.path.exists(testPath): dcmqrscpExePath = testPath break if not dcmqrscpExePath: raise (UserWarning("Could not find dcmqrscp executable")) args = (dcmqrscpExePath, '-c', configFilePath) popen = subprocess.Popen(args, stdout=subprocess.PIPE, cwd=processCurrentPath) self.delayDisplay('Retrieve DICOM') mainWindow = slicer.util.mainWindow() mainWindow.moduleSelector().selectModule('DICOM') dicomRetrieve = ctk.ctkDICOMRetrieve() dicomRetrieve.setKeepAssociationOpen(True) dicomRetrieve.setDatabase(slicer.dicomDatabase) dicomRetrieve.setCallingAETitle('SlicerAE') dicomRetrieve.setCalledAETitle('DCMTK') dicomRetrieve.setPort(12345) dicomRetrieve.setHost('localhost') dicomRetrieve.getStudy( '1.2.124.113932.1.170.223.162.178.20050502.160340.12640015') popen.kill() dicomWidget.detailsPopup.open() # click on the first row of the tree index = dicomWidget.tree.indexAt(qt.QPoint(0, 0)) dicomWidget.onTreeClicked(index) self.delayDisplay('Loading Selection') dicomWidget.detailsPopup.loadCheckedLoadables() self.delayDisplay('Change Level') layoutManager = slicer.app.layoutManager() redWidget = layoutManager.sliceWidget('Red') slicer.util.clickAndDrag(redWidget, start=(10, 10), end=(10, 40)) self.delayDisplay('Change Window') slicer.util.clickAndDrag(redWidget, start=(10, 10), end=(40, 10)) self.delayDisplay('Change Layout') layoutManager = slicer.app.layoutManager() layoutManager.setLayout( slicer.vtkMRMLLayoutNode.SlicerLayoutOneUpRedSliceView) self.delayDisplay('Zoom') slicer.util.clickAndDrag(redWidget, button='Right', start=(10, 10), end=(10, 40)) self.delayDisplay('Pan') slicer.util.clickAndDrag(redWidget, button='Middle', start=(10, 10), end=(40, 40)) self.delayDisplay('Center') redWidget.sliceController().fitSliceToBackground() self.delayDisplay('Lightbox') redWidget.sliceController().setLightboxTo6x6() self.delayDisplay('Conventional Layout') layoutManager.setLayout( slicer.vtkMRMLLayoutNode.SlicerLayoutConventionalView) self.delayDisplay('No Lightbox') redWidget.sliceController().setLightboxTo1x1() self.delayDisplay('Four Up Layout') layoutManager.setLayout( slicer.vtkMRMLLayoutNode.SlicerLayoutFourUpView) self.delayDisplay('Shift Mouse') slicer.util.clickAndDrag(redWidget, button='None', start=(100, 100), end=(140, 140), modifiers=['Shift']) self.delayDisplay('Conventional, Link, Slice Model') layoutManager.setLayout( slicer.vtkMRMLLayoutNode.SlicerLayoutConventionalView) redWidget.sliceController().setSliceLink(True) redWidget.sliceController().setSliceVisible(True) self.delayDisplay('Rotate') threeDView = layoutManager.threeDWidget(0).threeDView() slicer.util.clickAndDrag(threeDView) self.delayDisplay('Zoom') threeDView = layoutManager.threeDWidget(0).threeDView() slicer.util.clickAndDrag(threeDView, button='Right') self.delayDisplay('Test passed!') except Exception, e: import traceback traceback.print_exc() self.delayDisplay('Test caused exception!\n' + str(e))
def test_Part3PETCT(self, enableScreenshotsFlag=0, screenshotScaleFactor=1): """ Test using the PETCT module """ self.enableScreenshots = enableScreenshotsFlag self.screenshotScaleFactor = screenshotScaleFactor self.delayDisplay("Starting the test") # # first, get some data # import urllib downloads = (('http://slicer.kitware.com/midas3/download?items=124185', 'dataset3_PETCT.zip'), ) self.delayDisplay("Downloading") for url, name in downloads: filePath = slicer.app.temporaryPath + '/' + name if not os.path.exists(filePath) or os.stat(filePath).st_size == 0: print('Requesting download %s from %s...\n' % (name, url)) urllib.urlretrieve(url, filePath) self.delayDisplay('Finished with download\n') self.delayDisplay("Unzipping to %s" % (slicer.app.temporaryPath)) zipFilePath = slicer.app.temporaryPath + '/' + 'dataset3_PETCT.zip' extractPath = slicer.app.temporaryPath + '/' + 'dataset3_PETCT' qt.QDir().mkpath(extractPath) self.delayDisplay("Using extract path %s" % (extractPath)) applicationLogic = slicer.app.applicationLogic() applicationLogic.Unzip(zipFilePath, extractPath) self.delayDisplay("Loading PET_CT_pre-treatment.mrb") preTreatmentPath = extractPath + '/PET_CT_pre-treatment.mrb' slicer.util.loadScene(preTreatmentPath) self.takeScreenshot('PETCT-LoadedPre', 'Loaded pre-treatement scene', -1) try: logic = RSNAQuantTutorialLogic() mainWindow = slicer.util.mainWindow() layoutManager = slicer.app.layoutManager() threeDView = layoutManager.threeDWidget(0).threeDView() redWidget = layoutManager.sliceWidget('Red') redController = redWidget.sliceController() greenWidget = layoutManager.sliceWidget('Green') greenController = greenWidget.sliceController() yellowWidget = layoutManager.sliceWidget('Yellow') yellowController = yellowWidget.sliceController() viewNode = threeDView.mrmlViewNode() cameras = slicer.util.getNodes('vtkMRMLCameraNode*') for cameraNode in cameras.values(): if cameraNode.GetActiveTag() == viewNode.GetID(): break threeDView.resetFocalPoint() slicer.util.clickAndDrag(threeDView, button='Right') redWidget.sliceController().setSliceVisible(True) yellowWidget.sliceController().setSliceVisible(True) self.takeScreenshot('PETCT-ConfigureView', 'Configure View', -1) mainWindow.moduleSelector().selectModule('Volumes') compositNode = redWidget.mrmlSliceCompositeNode() compositNode.SetForegroundOpacity(0.2) self.takeScreenshot('PETCT-ShowVolumes', 'Show Volumes with lesion', -1) compositNode.SetForegroundOpacity(0.5) self.takeScreenshot('PETCT-CTOpacity', 'CT1 volume opacity to 0.5', -1) yellowWidget.sliceController().setSliceVisible(False) greenWidget.sliceController().setSliceVisible(True) self.takeScreenshot('PETCT-ShowSlices', 'Show axial and sagittal slices', -1) self.delayDisplay('SUV Computation') if not hasattr(slicer.modules, 'petstandarduptakevaluecomputation'): self.delayDisplay( "PET SUV Computation not available, skipping the test.") return slicer.util.selectModule('PETStandardUptakeValueComputation') parameters = { "PETDICOMPath": extractPath + '/' + 'PET1', "PETVolume": slicer.util.getNode('PET1'), "VOIVolume": slicer.util.getNode('PET1-label'), } suvComputation = slicer.modules.petstandarduptakevaluecomputation self.CLINode1 = None self.CLINode1 = slicer.cli.run(suvComputation, self.CLINode1, parameters, delete_temporary_files=False) waitCount = 0 while self.CLINode1.GetStatusString( ) != 'Completed' and waitCount < 100: self.delayDisplay("Running SUV Computation... %d" % waitCount) waitCount += 1 # close the scene slicer.mrmlScene.Clear(0) self.delayDisplay("Loading PET_CT_post-treatment.mrb") postTreatmentPath = extractPath + '/PET_CT_post-treatment.mrb' slicer.util.loadScene(postTreatmentPath) self.takeScreenshot('PETCT-LoadedPost', 'Loaded post-treatement scene', -1) compositNode.SetForegroundOpacity(0.5) self.takeScreenshot('PETCT-CT2Opacity', 'CT2 volume opacity to 0.5', -1) redController.setSliceOffsetValue(-165.01) self.takeScreenshot('PETCT-LarynxUptake', 'Mild uptake in the larynx and pharynx', -1) redController.setSliceOffsetValue(-106.15) self.takeScreenshot('PETCT-TumorUptake', 'No uptake in the area of the primary tumor', -1) self.delayDisplay('Test passed!') except Exception, e: import traceback traceback.print_exc() self.delayDisplay('Test caused exception!\n' + str(e))
def onStartStopDicomPeer(self, flag): if flag: import os self.startStopDicomPeerButton.setEnabled(False) dicomFilesDirectory = slicer.app.temporaryPath configFilePath = dicomFilesDirectory + '/Dcmtk-db/dcmqrscp.cfg' processCurrentPath = dicomFilesDirectory + '/Dcmtk-db/' if slicer.util.confirmYesNoDisplay( 'Do you want to choose local DCMTK database folder?'): print 'Yes' dicomFilesDirectory = qt.QFileDialog.getExistingDirectory( None, 'Select DCMTK database folder') configFilePath = dicomFilesDirectory + '/dcmqrscp.cfg' processCurrentPath = dicomFilesDirectory else: downloads = ( ('http://slicer.kitware.com/midas3/download?items=18822', 'Dcmtk-db.zip'), ) print 'Downloading' import urllib for url, name in downloads: filePath = slicer.app.temporaryPath + '/' + name if not os.path.exists(filePath) or os.stat( filePath).st_size == 0: print 'Requesting download %s from %s...\n' % (name, url) urllib.urlretrieve(url, filePath) print 'Finished with download' print 'Unzipping' qt.QDir().mkpath(dicomFilesDirectory) slicer.app.applicationLogic().Unzip(filePath, dicomFilesDirectory) import subprocess dcmqrscpExeOptions = ( '/bin', '/../CTK-build/CMakeExternals/Install/bin', '/../DCMTK-install/bin', '/../DCMTK-build/bin', ) dcmqrscpExePath = None dcmqrscpExeName = '/dcmqrscp' if slicer.app.os == 'win': dcmqrscpExeName = dcmqrscpExeName + '.exe' for path in dcmqrscpExeOptions: testPath = slicer.app.slicerHome + path + dcmqrscpExeName if os.path.exists(testPath): dcmqrscpExePath = testPath break if not dcmqrscpExePath: raise (UserWarning("Could not find dcmqrscp executable")) args = (dcmqrscpExePath, '-c', configFilePath) print 'Start DICOM peer' self.popen = subprocess.Popen(args, stdout=subprocess.PIPE, cwd=processCurrentPath) self.startStopDicomPeerButton.setEnabled(True) else: print 'Stop DICOM peer' self.popen.kill()
def test_AlternateReaders(self): """ Test the DICOM loading of sample testing data """ testPass = True import os, json self.delayDisplay("Starting the DICOM test", 100) referenceData = json.JSONDecoder().decode('''[ { "url": "http://slicer.kitware.com/midas3/download/item/292839/Mouse-MR-example-where-GDCM_fails.zip", "fileName": "Mouse-MR-example-where-GDCM_fails.zip", "name": "Mouse-MR-example-where-GDCM_fails", "seriesUID": "1.3.6.1.4.1.9590.100.1.2.366426457713813178933224342280246227461", "expectedFailures": ["GDCM", "Archetype"], "voxelValueQuantity": "(110852, DCM, \\"MR signal intensity\\")", "voxelValueUnits": "(1, UCUM, \\"no units\\")" }, { "url": "http://slicer.kitware.com/midas3/download/item/294857/deidentifiedMRHead-dcm-one-series.zip", "fileName": "deidentifiedMRHead-dcm-one-series.zip", "name": "deidentifiedMRHead-dcm-one-series", "seriesUID": "1.3.6.1.4.1.5962.99.1.3814087073.479799962.1489872804257.270.0", "expectedFailures": [], "voxelValueQuantity": "(110852, DCM, \\"MR signal intensity\\")", "voxelValueUnits": "(1, UCUM, \\"no units\\")" } ]''') # another dataset that could be added in the future - currently fails for all readers # due to invalid format - see https://issues.slicer.org/view.php?id=3569 #{ "url": "http://slicer.kitware.com/midas3/download/item/293587/RIDER_bug.zip", #"fileName": "RIDER_bug.zip", #"name": "RIDER_bug", #"seriesUID": "1.3.6.1.4.1.9328.50.7.261772317324041365541450388603508531852", #"expectedFailures": [] #} loadingResult = {} # # first, get the data - a zip file of dicom data # self.delayDisplay("Downloading", 100) for dataset in referenceData: try: filePath = slicer.app.temporaryPath + '/' + dataset['fileName'] if not os.path.exists(filePath) or os.stat( filePath).st_size == 0: self.delayDisplay( 'Requesting download %s from %s...\n' % (dataset['fileName'], dataset['url']), 100) urllib.urlretrieve(dataset['url'], filePath) self.delayDisplay('Finished with download\n', 100) self.delayDisplay("Unzipping", 100) dicomFilesDirectory = slicer.app.temporaryPath + dataset['name'] qt.QDir().mkpath(dicomFilesDirectory) slicer.app.applicationLogic().Unzip(filePath, dicomFilesDirectory) # # insert the data into th database # self.delayDisplay("Switching to temp database directory", 100) originalDatabaseDirectory = DICOMUtils.openTemporaryDatabase( 'tempDICOMDatabase') self.delayDisplay('Importing DICOM', 100) mainWindow = slicer.util.mainWindow() mainWindow.moduleSelector().selectModule('DICOM') indexer = ctk.ctkDICOMIndexer() indexer.addDirectory(slicer.dicomDatabase, dicomFilesDirectory, None) indexer.waitForImportFinished() # # select the series # detailsPopup = slicer.modules.DICOMWidget.detailsPopup detailsPopup.open() # load the data by series UID detailsPopup.offerLoadables(dataset['seriesUID'], 'Series') detailsPopup.examineForLoading() loadable = detailsPopup.getAllSelectedLoadables().keys()[0] # # try loading using each of the selected readers, fail # on enexpected load issue # scalarVolumePlugin = slicer.modules.dicomPlugins[ 'DICOMScalarVolumePlugin']() readerApproaches = scalarVolumePlugin.readerApproaches() basename = loadable.name volumesByApproach = {} for readerApproach in readerApproaches: self.delayDisplay( 'Loading Selection with approach: %s' % readerApproach, 100) loadable.name = basename + "-" + readerApproach volumeNode = scalarVolumePlugin.load( loadable, readerApproach) if not volumeNode and readerApproach not in dataset[ 'expectedFailures']: raise Exception( "Expected to be able to read with %s, but couldn't" % readerApproach) if volumeNode and readerApproach in dataset[ 'expectedFailures']: raise Exception( "Expected to NOT be able to read with %s, but could!" % readerApproach) if volumeNode: volumesByApproach[readerApproach] = volumeNode self.delayDisplay('Test quantity and unit') if 'voxelValueQuantity' in dataset.keys(): self.assertEqual( volumeNode.GetVoxelValueQuantity( ).GetAsPrintableString(), dataset['voxelValueQuantity']) if 'voxelValueUnits' in dataset.keys(): self.assertEqual( volumeNode.GetVoxelValueUnits( ).GetAsPrintableString(), dataset['voxelValueUnits']) # # for each approach that loaded as expected, compare the volumes # to ensure they match in terms of pixel data and metadata # failedComparisons = {} approachesThatLoaded = volumesByApproach.keys() print('approachesThatLoaded %s' % approachesThatLoaded) for approachIndex in range(len(approachesThatLoaded)): firstApproach = approachesThatLoaded[approachIndex] firstVolume = volumesByApproach[firstApproach] for secondApproachIndex in range( approachIndex + 1, len(approachesThatLoaded)): secondApproach = approachesThatLoaded[ secondApproachIndex] secondVolume = volumesByApproach[secondApproach] print('comparing %s,%s' % (firstApproach, secondApproach)) comparison = slicer.modules.dicomPlugins[ 'DICOMScalarVolumePlugin'].compareVolumeNodes( firstVolume, secondVolume) if comparison != "": print('failed: %s', comparison) failedComparisons[firstApproach, secondApproach] = comparison if len(failedComparisons.keys()) > 0: raise Exception("Loaded volumes don't match: %s" % failedComparisons) self.delayDisplay('%s Test passed!' % dataset['name'], 200) except Exception, e: import traceback traceback.print_exc() self.delayDisplay( '%s Test caused exception!\n' % dataset['name'] + str(e), 2000) testPass = False
def test_MissingSlices(self): """ Test behavior of the readers when slices are missing To edit and run this test from the python console, paste this below: reloadScriptedModule('DICOMReaders'); import DICOMReaders; tester = DICOMReaders.DICOMReadersTest(); tester.setUp(); tester.test_MissingSlices() """ testPass = True import os, json self.delayDisplay("Starting the DICOM test", 100) datasetURL = "http://slicer.kitware.com/midas3/download/item/294857/deidentifiedMRHead-dcm-one-series.zip" fileName = "deidentifiedMRHead-dcm-one-series.zip" filePath = os.path.join(slicer.app.temporaryPath, fileName) seriesUID = "1.3.6.1.4.1.5962.99.1.3814087073.479799962.1489872804257.270.0" seriesRASBounds = [ -87.29489517211913, 81.70450973510744, -121.57139587402344, 134.42860412597656, -138.71430206298828, 117.28569793701172 ] seriesDirectory = "Series 004 [MR - SAG RF FAST VOL FLIP 20]" lastSliceCorners = [[[81.05451202, 133.92860413, 116.78569794], [81.05451202, -122.07139587, 116.78569794]], [[81.05451202, 133.92860413, -139.21429443], [81.05451202, -122.07139587, -139.21429443]]] filesToRemove = [ "1.3.6.1.4.1.5962.99.1.3814087073.479799962.1489872804257.361.0.dcm", "1.3.6.1.4.1.5962.99.1.3814087073.479799962.1489872804257.362.0.dcm", "1.3.6.1.4.1.5962.99.1.3814087073.479799962.1489872804257.363.0.dcm", "1.3.6.1.4.1.5962.99.1.3814087073.479799962.1489872804257.364.0.dcm", "1.3.6.1.4.1.5962.99.1.3814087073.479799962.1489872804257.365.0.dcm", "1.3.6.1.4.1.5962.99.1.3814087073.479799962.1489872804257.366.0.dcm", "1.3.6.1.4.1.5962.99.1.3814087073.479799962.1489872804257.367.0.dcm", "1.3.6.1.4.1.5962.99.1.3814087073.479799962.1489872804257.368.0.dcm", "1.3.6.1.4.1.5962.99.1.3814087073.479799962.1489872804257.369.0.dcm", "1.3.6.1.4.1.5962.99.1.3814087073.479799962.1489872804257.370.0.dcm", "1.3.6.1.4.1.5962.99.1.3814087073.479799962.1489872804257.371.0.dcm", "1.3.6.1.4.1.5962.99.1.3814087073.479799962.1489872804257.372.0.dcm", "1.3.6.1.4.1.5962.99.1.3814087073.479799962.1489872804257.373.0.dcm", "1.3.6.1.4.1.5962.99.1.3814087073.479799962.1489872804257.374.0.dcm", "1.3.6.1.4.1.5962.99.1.3814087073.479799962.1489872804257.375.0.dcm", "1.3.6.1.4.1.5962.99.1.3814087073.479799962.1489872804257.376.0.dcm", ] try: if not os.path.exists(filePath) or os.stat(filePath).st_size == 0: self.delayDisplay( 'Requesting download %s from %s...\n' % (fileName, datasetURL), 100) urllib.urlretrieve(datasetURL, filePath) self.delayDisplay('Finished with download\n', 100) self.delayDisplay("Unzipping", 100) dicomFilesDirectory = slicer.app.temporaryPath + 'MRhead' qt.QDir().mkpath(dicomFilesDirectory) slicer.app.applicationLogic().Unzip(filePath, dicomFilesDirectory) print('Removing %d files from the middle of the series' % len(filesToRemove)) for file in filesToRemove: filePath = os.path.join(dicomFilesDirectory, seriesDirectory, file) os.remove(filePath) # # insert the data into the database # self.delayDisplay("Switching to temp database directory", 100) originalDatabaseDirectory = DICOMUtils.openTemporaryDatabase( 'tempDICOMDatabase') self.delayDisplay('Importing DICOM', 100) mainWindow = slicer.util.mainWindow() mainWindow.moduleSelector().selectModule('DICOM') indexer = ctk.ctkDICOMIndexer() indexer.addDirectory(slicer.dicomDatabase, dicomFilesDirectory, None) indexer.waitForImportFinished() # # select the series # detailsPopup = slicer.modules.DICOMWidget.detailsPopup detailsPopup.open() # load the data by series UID detailsPopup.offerLoadables(seriesUID, 'Series') detailsPopup.examineForLoading() loadable = detailsPopup.getAllSelectedLoadables().keys()[0] if len(loadable.warning) == 0: raise Exception( "Expected warning about geometry issues due to missing slices!" ) # # load and correct for acquisition then check the geometry # scalarVolumePlugin = slicer.modules.dicomPlugins[ 'DICOMScalarVolumePlugin']() volumeNode = scalarVolumePlugin.load(loadable) if not numpy.allclose( scalarVolumePlugin.acquisitionModeling.fixedCorners[-1], lastSliceCorners): raise Exception( "Acquisition transform didn't fix slice corners!") self.delayDisplay('test_MissingSlices passed!', 200) except Exception, e: import traceback traceback.print_exc() self.delayDisplay( 'Missing Slices Test caused exception!\n' + str(e), 2000) testPass = False
def test_SEGExporterSelfTest1(self): """ Test DICOM import, segmentation, export """ self.messageDelay = 50 import os self.delayDisplay("Starting the DICOM SEG Export test") # # first, get the data - a zip file of dicom data # import urllib downloads = (( 'http://slicer.kitware.com/midas3/download/item/220834/PieperMRHead.zip', 'PieperMRHead.zip'), ) self.delayDisplay("Downloading") for url, name in downloads: filePath = slicer.app.temporaryPath + '/' + name if not os.path.exists(filePath) or os.stat(filePath).st_size == 0: self.delayDisplay('Requesting download %s from %s...\n' % (name, url)) urllib.urlretrieve(url, filePath) self.delayDisplay('Finished with download\n') self.delayDisplay("Unzipping") dicomFilesDirectory = slicer.app.temporaryPath + '/dicomFiles' qt.QDir().mkpath(dicomFilesDirectory) slicer.app.applicationLogic().Unzip(filePath, dicomFilesDirectory) try: self.delayDisplay("Switching to temp database directory") tempDatabaseDirectory = slicer.app.temporaryPath + '/tempDICOMDatabase' import shutil shutil.rmtree(tempDatabaseDirectory) qt.QDir().mkpath(tempDatabaseDirectory) if slicer.dicomDatabase: originalDatabaseDirectory = os.path.split( slicer.dicomDatabase.databaseFilename)[0] else: originalDatabaseDirectory = None settings = qt.QSettings() settings.setValue('DatabaseDirectory', tempDatabaseDirectory) dicomWidget = slicer.modules.dicom.widgetRepresentation().self() dicomWidget.onDatabaseDirectoryChanged(tempDatabaseDirectory) self.delayDisplay('Importing DICOM') mainWindow = slicer.util.mainWindow() mainWindow.moduleSelector().selectModule('DICOM') indexer = ctk.ctkDICOMIndexer() indexer.addDirectory(slicer.dicomDatabase, dicomFilesDirectory, None) indexer.waitForImportFinished() dicomWidget.detailsPopup.open() # load the data by series UID mrHeadSeriesUID = "2.16.840.1.113662.4.4168496325.1025306170.548651188813145058" dicomWidget.detailsPopup.offerLoadables(mrHeadSeriesUID, 'Series') dicomWidget.detailsPopup.examineForLoading() self.delayDisplay('Loading Selection') dicomWidget.detailsPopup.loadCheckedLoadables() # # create a label map and set it for editing # masterNode = slicer.util.getNode('2: SAG*') volumesLogic = slicer.modules.volumes.logic() mergeNode = volumesLogic.CreateAndAddLabelVolume( slicer.mrmlScene, masterNode, masterNode.GetName() + '-label') mergeNode.GetDisplayNode().SetAndObserveColorNodeID( 'vtkMRMLColorTableNodeFileGenericAnatomyColors.txt') selectionNode = slicer.app.applicationLogic().GetSelectionNode() selectionNode.SetReferenceActiveVolumeID(masterNode.GetID()) selectionNode.SetReferenceActiveLabelVolumeID(mergeNode.GetID()) slicer.app.applicationLogic().PropagateVolumeSelection(0) # # go to the editor and do some drawing # slicer.util.selectModule('Editor') import EditorLib from EditorLib.EditUtil import EditUtil parameterNode = EditUtil.getParameterNode() parameterNode.SetParameter("LabelEffect,paintThreshold", "1") parameterNode.SetParameter("LabelEffect,paintThresholdMin", "70.0") parameterNode.SetParameter("LabelEffect,paintThresholdMax", "279.75") parameterNode.SetParameter("PaintEffect,radius", "40") parameterNode.SetParameter("PaintEffect,sphere", "1") self.delayDisplay("Paint some things") parameterNode = EditUtil.getParameterNode() lm = slicer.app.layoutManager() paintEffect = EditorLib.PaintEffectOptions() paintEffect.setMRMLDefaults() paintEffect.__del__() sliceWidget = lm.sliceWidget('Red') paintTool = EditorLib.PaintEffectTool(sliceWidget) EditUtil.setLabel(1) paintTool.paintAddPoint(100, 100) paintTool.paintApply() EditUtil.setLabel(2) paintTool.paintAddPoint(200, 200) paintTool.paintApply() paintTool.cleanup() paintTool = None # save these to compare with the one we read back originalSegmentationArray = slicer.util.array(mergeNode.GetID()) originalSegmentationNodeCopy = slicer.vtkMRMLLabelMapVolumeNode() originalSegmentationNodeCopy.CopyOrientation(mergeNode) # export the volumes into a SEG tempSEGDirectory = slicer.app.temporaryPath + '/tempDICOMSEG' qt.QDir().mkpath(tempSEGDirectory) segFilePath = os.path.join(tempSEGDirectory, "test.SEG.dcm") self.delayDisplay('spliting...', 200) EditUtil.splitPerStructureVolumes(masterNode, mergeNode) self.delayDisplay('exporting...', 200) EditUtil.exportAsDICOMSEG(masterNode) # close scene re-load the input data and SEG slicer.mrmlScene.Clear(0) indexer.addDirectory(slicer.dicomDatabase, tempSEGDirectory, None) indexer.waitForImportFinished() mrHeadStudyUID = "2.16.840.1.113662.4.4168496325.1025305873.7118351817185979330" dicomWidget.detailsPopup.offerLoadables(mrHeadStudyUID, 'Study') dicomWidget.detailsPopup.examineForLoading() self.delayDisplay('Loading Selection') dicomWidget.detailsPopup.loadCheckedLoadables() # confirm that segmentations are correctly reloaded headLabelName = '2: SAG/RF-FAST/VOL/FLIP 30-label' reloadedLabel = slicer.util.getNode(headLabelName) reloadedSegmentationArray = slicer.util.array( reloadedLabel.GetID()) import numpy self.assertTrue( numpy.alltrue( originalSegmentationArray == reloadedSegmentationArray)) geometryWarnings = volumesLogic.CompareVolumeGeometry( mergeNode, reloadedLabel) print(geometryWarnings) self.assertTrue(geometryWarnings == '') # re-export # close scene re-load the input data and SEG # confirm that segmentations are available again as per-structure volumes self.delayDisplay('Test passed!') except Exception, e: import traceback traceback.print_exc() self.delayDisplay('Test caused exception!\n' + str(e))
def export(self, exportables): for exportable in exportables: # Get node to export node = slicer.mrmlScene.GetNodeByID(exportable.nodeID) if node.GetAssociatedNode() is None or not node.GetAssociatedNode( ).IsA('vtkMRMLScalarVolumeNode'): error = "Series '" + node.GetNameWithoutPostfix( ) + "' cannot be exported!" logging.error(error) return error # Get output directory and create a subdirectory. This is necessary # to avoid overwriting the files in case of multiple exportables, as # naming of the DICOM files is static directoryDir = qt.QDir(exportable.directory) directoryDir.mkdir(exportable.nodeID) directoryDir.cd(exportable.nodeID) directory = directoryDir.absolutePath() logging.info("Export scalar volume '" + node.GetAssociatedNode().GetName() + "' to directory " + directory) # Get study and patient nodes studyNode = node.GetParentNode() if studyNode is None: error = "Unable to get study node for series '" + node.GetAssociatedNode( ).GetName() + "'" logging.error(error) return error patientNode = studyNode.GetParentNode() if patientNode is None: error = "Unable to get patient node for series '" + node.GetAssociatedNode( ).GetName() + "'" logging.error(error) return error # Assemble tags dictionary for volume export from vtkSlicerSubjectHierarchyModuleMRMLPython import vtkMRMLSubjectHierarchyConstants tags = {} tags['Patient Name'] = exportable.tag( vtkMRMLSubjectHierarchyConstants.GetDICOMPatientNameTagName()) tags['Patient ID'] = exportable.tag( vtkMRMLSubjectHierarchyConstants.GetDICOMPatientIDTagName()) tags['Patient Comments'] = exportable.tag( vtkMRMLSubjectHierarchyConstants. GetDICOMPatientCommentsTagName()) tags['Study ID'] = self.defaultStudyID tags['Study Date'] = exportable.tag( vtkMRMLSubjectHierarchyConstants.GetDICOMStudyDateTagName()) tags['Study Description'] = exportable.tag( vtkMRMLSubjectHierarchyConstants. GetDICOMStudyDescriptionTagName()) tags['Modality'] = exportable.tag('Modality') tags['Manufacturer'] = exportable.tag('Manufacturer') tags['Model'] = exportable.tag('Model') tags['Series Description'] = exportable.tag('SeriesDescription') tags['Series Number'] = exportable.tag('SeriesNumber') # Validate tags if tags['Modality'] == "": error = "Empty modality for series '" + node.GetAssociatedNode( ).GetName() + "'" logging.error(error) return error #TODO: more tag checks # Perform export exporter = DICOMExportScalarVolume(tags['Study ID'], node.GetAssociatedNode(), tags, directory) exporter.export() # Success return ""