def tearDown(self): """ Close temporary DICOM database and remove temporary data """ import shutil if self.originalDicomDatabase: DICOMUtils.closeTemporaryDatabase(self.originalDicomDatabase, True) shutil.rmtree(self.tempDicomDatabase) # closeTemporaryDatabase cleanup doesn't work. We need to do it manually self.originalDicomDatabase = None
def section_LoadDicomData(self): try: # Download and unzip test CT DICOM data import urllib downloads = ( ('http://slicer.kitware.com/midas3/download/item/137843/TestDicomCT.zip', self.dicomZipFilePath), ) downloaded = 0 for url,filePath in downloads: if not os.path.exists(filePath) or os.stat(filePath).st_size == 0: if downloaded == 0: self.delayDisplay('Downloading input data to folder\n' + self.dicomZipFilePath + '.\n\n It may take a few minutes...',self.delayMs) print('Requesting download from %s...' % (url)) urllib.urlretrieve(url, filePath) downloaded += 1 else: self.delayDisplay('Input data has been found in folder ' + self.dicomZipFilePath, self.delayMs) if downloaded > 0: self.delayDisplay('Downloading input data finished',self.delayMs) numOfFilesInDicomDataDir = len([name for name in os.listdir(self.dicomDataDir) if os.path.isfile(self.dicomDataDir + '/' + name)]) if (numOfFilesInDicomDataDir != self.expectedNumOfFilesInDicomDataDir): slicer.app.applicationLogic().Unzip(self.dicomZipFilePath, self.dicomDataDir) self.delayDisplay("Unzipping done",self.delayMs) numOfFilesInDicomDataDirTest = len([name for name in os.listdir(self.dicomDataDir) if os.path.isfile(self.dicomDataDir + '/' + name)]) self.assertEqual( numOfFilesInDicomDataDirTest, self.expectedNumOfFilesInDicomDataDir ) # Open test database and empty it with DICOMUtils.TemporaryDICOMDatabase(self.dicomDatabaseDir, True) as db: self.assertTrue( db.isOpen ) self.assertEqual( slicer.dicomDatabase, db) # Import test data in database indexer = ctk.ctkDICOMIndexer() self.assertIsNotNone( indexer ) indexer.addDirectory( slicer.dicomDatabase, self.dicomDataDir ) self.assertEqual( len(slicer.dicomDatabase.patients()), 1 ) self.assertIsNotNone( slicer.dicomDatabase.patients()[0] ) # Load test data numOfScalarVolumeNodesBeforeLoad = len( slicer.util.getNodes('vtkMRMLScalarVolumeNode*') ) self.assertEqual( len( slicer.util.getNodes('vtkMRMLSubjectHierarchyNode*') ), 1 ) patient = slicer.dicomDatabase.patients()[0] DICOMUtils.loadPatientByUID(patient) self.assertEqual( len( slicer.util.getNodes('vtkMRMLScalarVolumeNode*') ), numOfScalarVolumeNodesBeforeLoad + 1 ) self.assertEqual( len( slicer.util.getNodes('vtkMRMLSubjectHierarchyNode*') ), 1 ) except Exception, e: import traceback traceback.print_exc() self.delayDisplay('Test caused exception!\n' + str(e),self.delayMs*2)
def setUp(self): """ Do whatever is needed to reset the state - typically a scene clear will be enough. """ self.UID = '1.3.6.1.4.1.14519.5.2.1.2744.7002.886851941687931416391879144903' self.PatientName = 'QIN-HEADNECK-01-0139' self.tempDicomDatabase = os.path.join(slicer.app.temporaryPath,'PETTest') slicer.mrmlScene.Clear(0) self.originalDicomDatabase = DICOMUtils.openTemporaryDatabase(self.tempDicomDatabase)
def section_LoadDicomDataWitchBatchProcessing(self): try: # Open Data module so that a subject hierarchy scene model is active # (which caused problems with batch processing) slicer.util.selectModule('Data') # Open test database and empty it with DICOMUtils.TemporaryDICOMDatabase( self.dicomDatabaseDir) as db: self.assertTrue(db.isOpen) self.assertEqual(slicer.dicomDatabase, db) slicer.mrmlScene.StartState( slicer.vtkMRMLScene.BatchProcessState) # Download, unzip, import, and load data. Verify loaded nodes. loadedNodes = {'vtkMRMLScalarVolumeNode': 1} with DICOMUtils.LoadDICOMFilesToDatabase( \ self.dicomZipFileUrl, self.dicomZipFilePath, \ self.dicomDataDir, self.expectedNumOfFilesInDicomDataDir, \ {}, loadedNodes, checksum=self.dicomZipChecksum) as success: self.assertTrue(success) slicer.mrmlScene.EndState( slicer.vtkMRMLScene.BatchProcessState) self.assertEqual( len(slicer.util.getNodes('vtkMRMLSubjectHierarchyNode*')), 1) shNode = slicer.mrmlScene.GetSubjectHierarchyNode() self.assertIsNotNone(shNode) loadedDicomVolumeItemID = shNode.GetItemByName( self.loadedDicomVolumeName) loadedDicomStudyItemID = shNode.GetItemByName( self.loadedDicomStudyName) self.assertEqual(shNode.GetItemParent(loadedDicomVolumeItemID), loadedDicomStudyItemID) except Exception as e: import traceback traceback.print_exc() self.delayDisplay('Test caused exception!\n' + str(e), self.delayMs * 2)
def _loadWithPlugin(self, UID, pluginName): dicomWidget = slicer.modules.dicom.widgetRepresentation().self() dicomPluginCheckbox = dicomWidget.detailsPopup.pluginSelector.checkBoxByPlugin dicomPluginStates = {(key,value.checked) for key,value in dicomPluginCheckbox.iteritems()} for cb in dicomPluginCheckbox.itervalues(): cb.checked=False dicomPluginCheckbox[pluginName].checked = True success=DICOMUtils.loadSeriesByUID([UID]) for key,value in dicomPluginStates: dicomPluginCheckbox[key].checked=value
def test_PETDicomExtensionSelfTest_Main(self): """ test PET SUV Plugin and DICOM RWVM creation """ self.delayDisplay('Checking for PET DICOM plugins') dicomWidget = slicer.modules.dicom.widgetRepresentation().self() dicomPluginCheckbox = dicomWidget.detailsPopup.pluginSelector.checkBoxByPlugin self.assertIn('DICOMPETSUVPlugin', dicomPluginCheckbox) self.assertIn('DICOMRWVMPlugin', dicomPluginCheckbox) self.delayDisplay('Adding PET DICOM dataset to DICOM database (including download if necessary)') self._downloadTestData() self.delayDisplay('Loading data with DICOMPETSUVPlugin') self._loadWithPlugin(self.UID, 'DICOMPETSUVPlugin') imageNode = slicer.mrmlScene.GetFirstNodeByClass('vtkMRMLScalarVolumeNode') self.assertIsNotNone(imageNode) self.delayDisplay('Testing properties of loaded SUV normalized data') self._testDataProperties(imageNode) self.delayDisplay('Testing DICOM database for created RWVM file') patientUID = DICOMUtils.getDatabasePatientUIDByPatientName(self.PatientName) studies = slicer.dicomDatabase.studiesForPatient(patientUID) series = slicer.dicomDatabase.seriesForStudy(studies[0]) RWVMSeries = None for serie in series: description = slicer.dicomDatabase.descriptionForSeries(serie) if description=='PET SUV Factors': RWVMSeries = serie self.assertIsNotNone(RWVMSeries) files = slicer.dicomDatabase.filesForSeries(RWVMSeries) self.assertTrue(len(files)>0) RWVMFile = files[0] print(RWVMFile) self.delayDisplay('Testing RealWorldValueSlope stored in RWVM file') rwvm=dicom.read_file(RWVMFile) self.assertIn('ReferencedImageRealWorldValueMappingSequence', rwvm) rirwvms = rwvm.ReferencedImageRealWorldValueMappingSequence[0] self.assertIn('RealWorldValueMappingSequence', rirwvms) rwvms = rirwvms.RealWorldValueMappingSequence[0] self.assertIn('RealWorldValueSlope', rwvms) slope = rwvms.RealWorldValueSlope self.assertTrue(abs(slope-0.000401664)<0.00001) self.delayDisplay('Loading data with DICOMRWVMPlugin') slicer.mrmlScene.Clear(0) self._loadWithPlugin(RWVMSeries, 'DICOMRWVMPlugin') imageNode = slicer.mrmlScene.GetFirstNodeByClass('vtkMRMLScalarVolumeNode') self.assertIsNotNone(imageNode) self.delayDisplay('Testing properties of loaded SUV normalized data') self._testDataProperties(imageNode) self.delayDisplay('Test passed!')
def _test_PETLiverUptakeMeasurementQR2_WithQRExtension(self): """ verify SR measurements assuming QuantiativeReporting Extension is installed and enabled """ from DICOMLib import DICOMUtils self.delayDisplay('Loading DICOM SEG and SR') # note: introduces dependency on QuantiativeReporting and DICOMPETExtension # with QuantiativeReporting installed, this should load the DICOM SR and SEG together with the PET scan # disable all dicom plugins except those necessary dicomWidget = slicer.modules.dicom.widgetRepresentation().self() dicomPluginCheckbox = dicomWidget.detailsPopup.pluginSelector.checkBoxByPlugin dicomPluginStates = {(key,value.checked) for key,value in dicomPluginCheckbox.iteritems()} for cb in dicomPluginCheckbox.itervalues(): cb.checked=False dicomPluginCheckbox['DICOMRWVMPlugin'].checked = True dicomPluginCheckbox['DICOMPETSUVPlugin'].checked = True dicomPluginCheckbox['DICOMSegmentationPlugin'].checked = True dicomPluginCheckbox['DICOMTID1500Plugin'].checked = True DICOMUtils.loadPatientByName(self.patienName) for key,value in dicomPluginStates: dicomPluginCheckbox[key].checked=value # with QuantiativeReporting installed, this should load the DICOM SR and SEG together with the PET scan DICOMUtils.loadPatientByName(self.patienName) tableNode = slicer.mrmlScene.GetFirstNodeByClass("vtkMRMLTableNode") self.assertIsNotNone(tableNode, "Loading SR into mrmlScene failed. No vtkMRMLTableNodes were found within the scene.") self.assertEqual(tableNode.GetNumberOfRows(),1) self.assertTrue(tableNode.GetNumberOfColumns()>3) self.assertEqual(tableNode.GetColumnName(0),'Segment') self.assertEqual(tableNode.GetColumnName(1),'Mean [{SUVbw}g/ml]') self.assertEqual(tableNode.GetColumnName(2),'Standard Deviation [{SUVbw}g/ml]') self.assertEqual(tableNode.GetColumnName(3),'Median [{SUVbw}g/ml]') self.assertEqual(tableNode.GetCellText(0,0),'Liver reference region') self.assertTrue(abs(float(tableNode.GetCellText(0,1))-2.36253)<0.01) self.assertTrue(abs(float(tableNode.GetCellText(0,2))-0.402997)<0.01) self.assertTrue(abs(float(tableNode.GetCellText(0,3))-2.335)<0.01) self.delayDisplay('Test passed!') for key,value in dicomPluginStates: dicomPluginCheckbox[key].checked=value
def section_LoadDicomData(self): try: # Open test database and empty it with DICOMUtils.TemporaryDICOMDatabase(self.dicomDatabaseDir) as db: self.assertTrue( db.isOpen ) self.assertEqual( slicer.dicomDatabase, db) # Download, unzip, import, and load data. Verify loaded nodes. loadedNodes = {'vtkMRMLScalarVolumeNode':1} with DICOMUtils.LoadDICOMFilesToDatabase( \ self.dicomZipFileUrl, self.dicomZipFilePath, \ self.dicomDataDir, self.expectedNumOfFilesInDicomDataDir, \ {}, loadedNodes, checksum=self.dicomZipChecksum) as success: self.assertTrue(success) self.assertEqual( len( slicer.util.getNodes('vtkMRMLSubjectHierarchyNode*') ), 1 ) except Exception as e: import traceback traceback.print_exc() self.delayDisplay('Test caused exception!\n' + str(e),self.delayMs*2)
def accessDICOMwebStudy(self, request, requestBody): """ Access DICOMweb server to download requested study, add it to Slicer's dicom database, and load it into the scene. :param requestBody: is a json string :param requestBody['dicomWEBPrefix']: is the start of the url :param requestBody['dicomWEBStore']: is the middle of the url :param requestBody['studyUID']: is the end of the url :param requestBody['accessToken']: is the authorization bearer token for the DICOMweb server """ p = urllib.parse.urlparse(request.decode()) q = urllib.parse.parse_qs(p.query) request = json.loads(requestBody), b'application/json' dicomWebEndpoint = request['dicomWEBPrefix'] + '/' + request[ 'dicomWEBStore'] print(f"Loading from {dicomWebEndpoint}") from DICOMLib import DICOMUtils loadedUIDs = DICOMUtils.importFromDICOMWeb( dicomWebEndpoint=request['dicomWEBPrefix'] + '/' + request['dicomWEBStore'], studyInstanceUID=request['studyUID'], accessToken=request['accessToken']) files = [] for studyUID in loadedUIDs: for seriesUID in slicer.dicomDatabase.seriesForStudy(studyUID): for instance in slicer.dicomDatabase.instancesForSeries( seriesUID): files.append( slicer.dicomDatabase.fileForInstance(instance)) loadables = DICOMUtils.getLoadablesFromFileLists([files]) loadedNodes = DICOMUtils.loadLoadables(loadLoadables) print(f"Loaded {loadedUIDs}, and {loadedNodes}") return b'{"result": "ok"}'
def test_BatchStructureSetConversion_FullTest1(self): # Create logic self.logic = BatchStructureSetConversionLogic() # Check for modules self.assertTrue(slicer.modules.dicomrtimportexport) self.assertTrue(slicer.modules.segmentations) self.TestSection_0_SetupPathsAndNames() # Open test database and empty it with DICOMUtils.TemporaryDICOMDatabase(self.dicomDatabaseDir) as self.db: self.TestSection_1_LoadDicomData() self.TestSection_2_ConvertStructureSetToLabelmap() self.TestSection_3_SaveLabelmaps() logging.info('Test finished')
def generateSlicenamesTextfile(ctDicomSeriesUID, slicenamesFilename, outputFolder): """ Generate slicenames.txt file, with list of ct dicom slices, in increasing slice order (IS direction) """ filePaths = slicer.dicomDatabase.filesForSeries(ctDicomSeriesUID) if len(filePaths) == 0: logging.error('Failed to find files in DICOM database for UID ' + str(ctDicomSeriesUID)) return False unsortedFileList = slicer.dicomDatabase.filesForSeries(ctDicomSeriesUID) sortedFileList, distances, warnings = DICOMUtils.getSortedImageFiles(unsortedFileList) outFile = open(os.path.join(outputFolder, slicenamesFilename), "wb") counter = 1 numDicomFiles = len(sortedFileList) for sliceFileName in sortedFileList: outFile.write(sliceFileName) if counter != numDicomFiles: outFile.write("\n") counter += 1 outFile.close() return True
def _loadTestData_WithPETDICOMExtension(self): """ load SUV normalized PET scan from DICOM fileassuming Slicer-PETDICOM extension is installed and enabled """ from DICOMLib import DICOMUtils import urllib data = { 'PETVolume': { 'UID': '1.3.6.1.4.1.14519.5.2.1.2744.7002.886851941687931416391879144903', 'url': 'http://slicer.kitware.com/midas3/download/item/257234/QIN-HEADNECK-01-0139-PET.zip', 'zipFile': 'QIN-HEADNECK-01-0139-PET.zip', 'SUVNormalizationFactor': 0.00040166400000000007 } } destinationDirectory = self.tempDataDir for key, value in data.iteritems(): # download data if necessary UID = value['UID'] if not len(slicer.dicomDatabase.filesForSeries(UID)): url = value['url'] zipFile = value['zipFile'] filePath = os.path.join(destinationDirectory, zipFile) if not os.path.exists(os.path.dirname(filePath)): os.makedirs(os.path.dirname(filePath)) logging.debug('Saving download %s to %s ' % (url, filePath)) if not os.path.exists(filePath) or os.stat(filePath).st_size == 0: slicer.util.delayDisplay('Requesting download of %s...\n' % url, 1000) urllib.urlretrieve(url, filePath) if os.path.exists(filePath) and os.path.splitext(filePath)[1]=='.zip': success = slicer.app.applicationLogic().Unzip(filePath, destinationDirectory) if not success: logging.error("Archive %s was NOT unzipped successfully." % filePath) indexer = ctk.ctkDICOMIndexer() indexer.addDirectory(slicer.dicomDatabase, destinationDirectory, None) indexer.waitForImportFinished() # load dataset success=DICOMUtils.loadSeriesByUID([data['PETVolume']['UID']]) if not success: logging.error("Unable to load dicom data %s\n." % data['PETVolume']['UID']) return slicer.mrmlScene.GetFirstNodeByClass('vtkMRMLScalarVolumeNode')
def generateSlicenamesTextfile(ctDicomSeriesUID, slicenamesFilename, outputFolder): """ Generate slicenames.txt file, with list of ct dicom slices, in increasing slice order (IS direction) """ filePaths = slicer.dicomDatabase.filesForSeries(ctDicomSeriesUID) if len(filePaths) == 0: logging.error('Failed to find files in DICOM database for UID ' + str(ctDicomSeriesUID)) return False unsortedFileList = slicer.dicomDatabase.filesForSeries(ctDicomSeriesUID) sortedFileList, distances, warnings = DICOMUtils.getSortedImageFiles( unsortedFileList) outFile = open(os.path.join(outputFolder, slicenamesFilename), "wb") counter = 1 numDicomFiles = len(sortedFileList) for sliceFileName in sortedFileList: outFile.write(sliceFileName) if counter != numDicomFiles: outFile.write("\n") counter += 1 outFile.close() return True
def LoadFirstPatientIntoSlicer(self): # Choose first patient from the patient list patient = slicer.dicomDatabase.patients()[0] DICOMUtils.loadPatientByUID(patient)
def main(argv): try: # Parse command-line arguments parser = argparse.ArgumentParser( description="Batch Structure Set Conversion") parser.add_argument( "-i", "--input-folder", dest="input_folder", metavar="PATH", default="-", required=True, help= "Folder of input DICOM study (or database path to use existing)") parser.add_argument("-x", "--exist-db", dest="exist_db", default=False, required=False, action='store_true', help="Process an existing database") parser.add_argument( "-p", "--patient", "--patients", dest="patients", default=[], required=False, nargs='+', help="Process particular patients in the existing database") parser.add_argument("-m", "--export-images", dest="export_images", default=False, required=False, action='store_true', help="Export image data with labelmaps") parser.add_argument("-o", "--output-folder", dest="output_folder", metavar="PATH", default=".", help="Folder for output labelmaps") parser.add_argument( "--stop-on-error", dest="stop_on_error", default=False, required=False, action='store_true', help="Halt the script if processing a patient fails") parser.add_argument( "--do-not-exit", dest="do_not_exit", default=False, required=False, action='store_true', help="Don't exit Slicer after performing conversion(s)") args = parser.parse_args(argv) logging.info("Running with arguments:") logging.info(args) # Check required arguments if args.input_folder == "-": logging.error("Please specify input DICOM study folder!") sys.exit(-1) if args.output_folder == ".": logging.error( "Current directory is selected as output folder (default). To change it, please specify --output-folder" ) sys.exit(-1) if len(args.patients) > 0 and not args.exist_db: logging.error("Can only specify patients in an existing database") sys.exit(-1) # Convert to python path style input_folder = args.input_folder.replace('\\', '/') output_folder = args.output_folder.replace('\\', '/') exist_db = args.exist_db export_images = args.export_images # Perform batch conversion logic = BatchStructureSetConversionLogic() def save_rtslices(output_dir): logging.info("Convert loaded structure set to labelmap volumes") labelmaps = logic.ConvertStructureSetToLabelmap() logging.info("Save labelmaps to directory " + output_dir) logic.SaveLabelmaps(labelmaps, output_dir) if export_images: logic.SaveImages(output_dir) logging.info("Save screen shots to directory " + output_dir) logic.SaveSegmentScreenShots(output_dir) logging.info("DONE saving to %s" % output_dir) def create_results_page(output_folder, converted_patients, skipped_patients, failed_patients): """Make an html page that summarizes the results with thumbnails for the screenshots""" fp = open(os.path.join(output_folder, "index.html"), "w") fp.write(''' <style> /* based on: https://www.w3schools.com/howto/howto_css_thumbnail.asp */ img { border: 1px solid #ddd; /* Gray border */ border-radius: 4px; /* Rounded border */ padding: 5px; /* Some padding */ width: 550px; /* Set a small width */ } /* Add a hover effect (blue shadow) */ img:hover { box-shadow: 0 0 2px 1px rgba(0, 140, 186, 0.5); } </style> <body> ''') fp.write("<p>converted: %s</p>" % converted_patients) fp.write("<p>skipped: %s</p>" % skipped_patients) fp.write("<p>failed: %s</p>" % failed_patients) for patient in converted_patients: fp.write(''' <a target="_blank" href="%s/GTV-1.png"> <img src="%s/GTV-1.png" alt="%s"> </a> <p>Patient: %s</p> ''' % ( patient, patient, patient, patient, )) fp.write("</body>") fp.close() if exist_db: logging.info("BatchStructureSet Running in Existing Database Mode") database_start_time = time.time() logic.LoadDatabase(input_folder) all_patients = slicer.dicomDatabase.patients() if len(args.patients) > 0: all_patients = args.patients logging.info("Must Process Patients %s" % len(all_patients)) converted_patients = [] skipped_patients = [] failed_patients = [] for patient in all_patients: patient_start_time = time.time() try: slicer.mrmlScene.Clear(0) # clear the scene if len(slicer.dicomDatabase.studiesForPatient( patient)) == 0: logging.warning( "Skipping patient with no studies: %s" % patient) skipped_patients.append(patient) continue DICOMUtils.loadPatientByUID(patient) output_dir = os.path.join(output_folder, patient) if not os.access(output_dir, os.F_OK): os.mkdir(output_dir) save_rtslices(output_dir) converted_patients.append(patient) except Exception, e: import traceback traceback.print_exc() logging.error("Failed to convert patient: %s", patient) failed_patients.append(patient) if args.stop_on_error: logging.warning( "Stopped processing after failure on patient %s" % patient) sys.exit(-1) logging.info("Finished patient %s in %d seconds" % (patient, time.time() - patient_start_time)) create_results_page(output_folder, converted_patients, skipped_patients, failed_patients) logging.info("\n------------") logging.info("Results") logging.info("------------\n") logging.info("Finished database in %d seconds" % (time.time() - database_start_time)) logging.info("Converted %d patients" % len(converted_patients)) logging.info("Skipped %d patients" % len(skipped_patients)) logging.error("Conversion failed on %d patients" % len(failed_patients)) logging.error("Failed patients:") logging.error(failed_patients) else:
def main(argv): try: # Parse command-line arguments parser = argparse.ArgumentParser(description="Batch Structure Set Conversion") parser.add_argument("-i", "--input-folder", dest="input_folder", metavar="PATH", default="-", required=True, help="Folder of input DICOM study (or database path to use existing)") parser.add_argument("-x", "--exist-db", dest="exist_db", default=False, required=False, action='store_true', help="Process an existing database") parser.add_argument("-m", "--export-images", dest="export_images", default=False, required=False, action='store_true', help="Export image data with labelmaps") parser.add_argument("-o", "--output-folder", dest="output_folder", metavar="PATH", default=".", help="Folder for output labelmaps") args = parser.parse_args(argv) # Check required arguments if args.input_folder == "-": logging.warning('Please specify input DICOM study folder!') if args.output_folder == ".": logging.info('Current directory is selected as output folder (default). To change it, please specify --output-folder') # Convert to python path style input_folder = args.input_folder.replace('\\', '/') output_folder = args.output_folder.replace('\\', '/') exist_db = args.exist_db export_images = args.export_images # Perform batch conversion logic = BatchStructureSetConversionLogic() def save_rtslices(output_dir): # package the saving code into a subfunction logging.info("Convert loaded structure set to labelmap volumes") labelmaps = logic.ConvertStructureSetToLabelmap() logging.info("Save labelmaps to directory " + output_dir) logic.SaveLabelmaps(labelmaps, output_dir) if export_images: logic.SaveImages(output_dir) logging.info("DONE") if exist_db: logging.info('BatchStructureSet running in existing database mode') DICOMUtils.openDatabase(input_folder) all_patients = slicer.dicomDatabase.patients() logging.info('Must Process Patients %s' % len(all_patients)) for patient in all_patients: slicer.mrmlScene.Clear(0) # clear the scene DICOMUtils.loadPatientByUID(patient) output_dir = os.path.join(output_folder,patient) if not os.access(output_dir, os.F_OK): os.mkdir(output_dir) save_rtslices(output_dir) else: logging.info("Import DICOM data from " + input_folder) DICOMUtils.openTemporaryDatabase() DICOMUtils.importDicom(input_folder) logging.info("Load first patient into Slicer") logic.LoadFirstPatientIntoSlicer() save_rtslices(output_folder) except Exception as e: print(e) sys.exit(0)
def examineFiles(self, files): """ Returns a list of DICOMLoadable instances corresponding to ways of interpreting the files parameter. """ seriesUID = slicer.dicomDatabase.fileValue(files[0], self.tags['seriesUID']) seriesName = self.defaultSeriesNodeName(seriesUID) # default loadable includes all files for series allFilesLoadable = DICOMLoadable() allFilesLoadable.files = files allFilesLoadable.name = self.cleanNodeName(seriesName) allFilesLoadable.tooltip = "%d files, first file: %s" % (len( allFilesLoadable.files), allFilesLoadable.files[0]) allFilesLoadable.selected = True # add it to the list of loadables later, if pixel data is available in at least one file # make subseries volumes based on tag differences subseriesTags = [ "seriesInstanceUID", "acquisitionNumber", # GE volume viewer and Siemens Axiom CBCT systems put an overview (localizer) slice and all the reconstructed slices # in one series, using two different image types. Splitting based on image type allows loading of these volumes # (loading the series without localizer). "imageType", "imageOrientationPatient", "diffusionGradientOrientation", ] if self.allowLoadingByTime(): subseriesTags.append("contentTime") subseriesTags.append("triggerTime") # Values for these tags will only be enumerated (value itself will not be part of the loadable name) # because the vale itself is usually too long and complicated to be displayed to users subseriesTagsToEnumerateValues = [ "seriesInstanceUID", "imageOrientationPatient", "diffusionGradientOrientation", ] # # first, look for subseries within this series # - build a list of files for each unique value # of each tag # subseriesFiles = {} subseriesValues = {} for file in allFilesLoadable.files: # check for subseries values for tag in subseriesTags: value = slicer.dicomDatabase.fileValue(file, self.tags[tag]) value = value.replace( ",", "_") # remove commas so it can be used as an index if tag not in subseriesValues: subseriesValues[tag] = [] if not subseriesValues[tag].__contains__(value): subseriesValues[tag].append(value) if (tag, value) not in subseriesFiles: subseriesFiles[tag, value] = [] subseriesFiles[tag, value].append(file) loadables = [] # Pixel data is available, so add the default loadable to the output loadables.append(allFilesLoadable) # # second, for any tags that have more than one value, create a new # virtual series # subseriesCount = 0 # List of loadables that look like subseries that contain the full series except a single frame probableLocalizerFreeLoadables = [] for tag in subseriesTags: if len(subseriesValues[tag]) > 1: subseriesCount += 1 for valueIndex, value in enumerate(subseriesValues[tag]): # default loadable includes all files for series loadable = DICOMLoadable() loadable.files = subseriesFiles[tag, value] # value can be a long string (and it will be used for generating node name) # therefore use just an index instead if tag in subseriesTagsToEnumerateValues: loadable.name = seriesName + " - %s %d" % ( tag, valueIndex + 1) else: loadable.name = seriesName + " - %s %s" % (tag, value) loadable.name = self.cleanNodeName(loadable.name) loadable.tooltip = "%d files, grouped by %s = %s. First file: %s. %s = %s" % ( len(loadable.files), tag, value, loadable.files[0], tag, value) loadable.selected = False loadables.append(loadable) if len(subseriesValues[tag]) == 2: otherValue = subseriesValues[tag][1 - valueIndex] if len(subseriesFiles[tag, value]) > 1 and len( subseriesFiles[tag, otherValue]) == 1: # this looks like a subseries without a localizer image probableLocalizerFreeLoadables.append(loadable) # remove any files from loadables that don't have pixel data (no point sending them to ITK for reading) # also remove DICOM SEG, since it is not handled by ITK readers newLoadables = [] for loadable in loadables: newFiles = [] excludedLoadable = False for file in loadable.files: if slicer.dicomDatabase.fileValueExists( file, self.tags['pixelData']): newFiles.append(file) if slicer.dicomDatabase.fileValue( file, self.tags['sopClassUID'] ) == '1.2.840.10008.5.1.4.1.1.66.4': excludedLoadable = True if 'DICOMSegmentationPlugin' not in slicer.modules.dicomPlugins: logging.warning( 'Please install Quantitative Reporting extension to enable loading of DICOM Segmentation objects' ) elif slicer.dicomDatabase.fileValue( file, self.tags['sopClassUID'] ) == '1.2.840.10008.5.1.4.1.1.481.3': excludedLoadable = True if 'DicomRtImportExportPlugin' not in slicer.modules.dicomPlugins: logging.warning( 'Please install SlicerRT extension to enable loading of DICOM RT Structure Set objects' ) if len(newFiles) > 0 and not excludedLoadable: loadable.files = newFiles loadable.grayscale = ( 'MONOCHROME' in slicer.dicomDatabase.fileValue( newFiles[0], self.tags['photometricInterpretation'])) newLoadables.append(loadable) elif excludedLoadable: continue else: # here all files in have no pixel data, so they might be # secondary capture images which will read, so let's pass # them through with a warning and low confidence loadable.warning += "There is no pixel data attribute for the DICOM objects, but they might be readable as secondary capture images. " loadable.confidence = 0.2 loadable.grayscale = ( 'MONOCHROME' in slicer.dicomDatabase.fileValue( loadable.files[0], self.tags['photometricInterpretation'])) newLoadables.append(loadable) loadables = newLoadables # # now for each series and subseries, sort the images # by position and check for consistency # then adjust confidence values based on warnings # for loadable in loadables: loadable.files, distances, loadable.warning = DICOMUtils.getSortedImageFiles( loadable.files, self.epsilon) loadablesBetterThanAllFiles = [] if allFilesLoadable.warning != "": for probableLocalizerFreeLoadable in probableLocalizerFreeLoadables: if probableLocalizerFreeLoadable.warning == "": # localizer-free loadables are better then all files, if they don't have warning loadablesBetterThanAllFiles.append( probableLocalizerFreeLoadable) if not loadablesBetterThanAllFiles and subseriesCount == 1: # there was a sorting warning and # only one kind of subseries, so it's probably correct # to have lower confidence in the default all-files version. for loadable in loadables: if loadable != allFilesLoadable and loadable.warning == "": loadablesBetterThanAllFiles.append(loadable) # if there are loadables that are clearly better then all files, then use those (otherwise use all files loadable) preferredLoadables = loadablesBetterThanAllFiles if loadablesBetterThanAllFiles else [ allFilesLoadable ] # reduce confidence and deselect all non-preferred loadables for loadable in loadables: if loadable in preferredLoadables: loadable.selected = True else: loadable.selected = False if loadable.confidence > .45: loadable.confidence = .45 return loadables
def test_PETLiverUptakeMeasurementQR1(self): """ test standard segmentation and report generation """ try: self.assertIsNotNone( slicer.modules.petliveruptakemeasurement ) with DICOMUtils.TemporaryDICOMDatabase(self.tempDicomDatabaseDir) as db: self.assertTrue(db.isOpen) self.assertEqual(slicer.dicomDatabase, db) self.delayDisplay('Loading PET DICOM dataset (including download if necessary)') petNode = self.loadTestData() self.delayDisplay('Running segmentation') m = slicer.util.mainWindow() m.moduleSelector().selectModule('PETLiverUptakeMeasurementQR') qrWidget = slicer.modules.PETLiverUptakeMeasurementQRWidget qrWidget.inputSelector.setCurrentNode(petNode) segmentationNode = qrWidget.segmentationSelector.addNode() qrWidget.segmentButton.click() self.assertTrue(abs(float(qrWidget.meanValueLineEdit.text)-2.36253)<0.01) self.assertTrue(abs(float(qrWidget.stdValueLineEdit.text)-0.402997)<0.01) self.assertTrue(abs(float(qrWidget.medianValueLineEdit.text)-2.335)<0.01) self.delayDisplay('Completing and writing DICOM report') qrWidget.readerValueLineEdit.text = 'autotest' self.assertTrue(qrWidget.saveReport(completed=True)) self.delayDisplay('Checking for DICOM SEG and SR') import dicom patientUID = DICOMUtils.getDatabasePatientUIDByPatientName(self.patienName) studies = slicer.dicomDatabase.studiesForPatient(patientUID) series = slicer.dicomDatabase.seriesForStudy(studies[0]) SRSeries = None SEGSeries = None for serie in series: description = slicer.dicomDatabase.descriptionForSeries(serie) if description=='Automatic Liver Reference Region Segmentation': SEGSeries = serie if description=='Liver Reference Region Measurement Report': SRSeries = serie self.assertIsNotNone(SRSeries) self.assertIsNotNone(SEGSeries) SRFile = slicer.dicomDatabase.filesForSeries(SRSeries)[0] self.delayDisplay('Loading DICOM SR and verifying stored measurements') sr = dicom.read_file(SRFile) dicomMean = self._getMeasuredValue(sr,'Mean') self.assertIsNotNone(dicomMean) self.assertEqual(dicomMean.MeasuredValueSequence[0].NumericValue, 2.36253) dicomStandardDeviation = self._getMeasuredValue(sr,'Standard Deviation') self.assertIsNotNone(dicomStandardDeviation) self.assertEqual(dicomStandardDeviation.MeasuredValueSequence[0].NumericValue, 0.402997) dicomMedian = self._getMeasuredValue(sr,'Median') self.assertIsNotNone(dicomMedian) self.assertEqual(dicomMedian.MeasuredValueSequence[0].NumericValue, 2.335) # clean up data from DICOM database db.removePatient(patientUID) self.delayDisplay('Test passed!') except Exception, e: import traceback traceback.print_exc() self.delayDisplay('Test caused exception!\n' + str(e),self.delayMs*2)
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
class RSNAVisTutorialTest(unittest.TestCase): """ This is the test case for your scripted module. """ def delayDisplay(self,message,msec=1000): """This utility method displays a small dialog and waits. This does two things: 1) it lets the event loop catch up to the state of the test so that rendering and widget updates have all taken place before the test continues and 2) it shows the user/developer/tester the state of the test so that we'll know when it breaks. """ print(message) self.info = qt.QDialog() self.infoLayout = qt.QVBoxLayout() self.info.setLayout(self.infoLayout) self.label = qt.QLabel(message,self.info) self.infoLayout.addWidget(self.label) qt.QTimer.singleShot(msec, self.info.close) self.info.exec_() def takeScreenshot(self,name,description,type=-1): # show the message even if not taking a screen shot self.delayDisplay(description) if self.enableScreenshots == 0: return lm = slicer.app.layoutManager() # switch on the type to get the requested window widget = 0 if type == slicer.qMRMLScreenShotDialog.FullLayout: # full layout widget = lm.viewport() elif type == slicer.qMRMLScreenShotDialog.ThreeD: # just the 3D window widget = lm.threeDWidget(0).threeDView() elif type == slicer.qMRMLScreenShotDialog.Red: # red slice window widget = lm.sliceWidget("Red") elif type == slicer.qMRMLScreenShotDialog.Yellow: # yellow slice window widget = lm.sliceWidget("Yellow") elif type == slicer.qMRMLScreenShotDialog.Green: # green slice window widget = lm.sliceWidget("Green") else: # default to using the full window widget = slicer.util.mainWindow() # reset the type so that the node is set correctly type = slicer.qMRMLScreenShotDialog.FullLayout # grab and convert to vtk image data qpixMap = qt.QPixmap().grabWidget(widget) qimage = qpixMap.toImage() imageData = vtk.vtkImageData() slicer.qMRMLUtils().qImageToVtkImageData(qimage,imageData) annotationLogic = slicer.modules.annotations.logic() annotationLogic.CreateSnapShot(name, description, type, self.screenshotScaleFactor, imageData) def setUp(self): """ Do whatever is needed to reset the state - typically a scene clear will be enough. """ self.delayDisplay("Closing the scene") layoutManager = slicer.app.layoutManager() layoutManager.setLayout(slicer.vtkMRMLLayoutNode.SlicerLayoutConventionalView) slicer.mrmlScene.Clear(0) def runTest(self): """Run as few or as many tests as needed here. """ self.setUp() self.test_Part1DICOM() self.setUp() self.test_Part2Head() self.setUp() self.test_Part3Liver() self.setUp() self.test_Part4Lung() 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 = slicer.modules.DICOMWidget 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)) self.delayDisplay("Restoring original database directory") DICOMUtils.closeTemporaryDatabase(originalDatabaseDirectory)
def examineFiles(self,files): """ Returns a list of DICOMLoadable instances corresponding to ways of interpreting the files parameter. """ seriesUID = slicer.dicomDatabase.fileValue(files[0],self.tags['seriesUID']) seriesName = self.defaultSeriesNodeName(seriesUID) # default loadable includes all files for series loadable = DICOMLoadable() loadable.files = files loadable.name = seriesName loadable.tooltip = "%d files, first file: %s" % (len(loadable.files), loadable.files[0]) loadable.selected = True # add it to the list of loadables later, if pixel data is available in at least one file # make subseries volumes based on tag differences subseriesTags = [ "seriesInstanceUID", "imageOrientationPatient", "diffusionGradientOrientation", ] if self.allowLoadingByTime(): subseriesTags.append("contentTime") subseriesTags.append("triggerTime") # # first, look for subseries within this series # - build a list of files for each unique value # of each tag # subseriesFiles = {} subseriesValues = {} for file in loadable.files: # check for subseries values for tag in subseriesTags: value = slicer.dicomDatabase.fileValue(file,self.tags[tag]) value = value.replace(",","_") # remove commas so it can be used as an index if tag not in subseriesValues: subseriesValues[tag] = [] if not subseriesValues[tag].__contains__(value): subseriesValues[tag].append(value) if (tag,value) not in subseriesFiles: subseriesFiles[tag,value] = [] subseriesFiles[tag,value].append(file) loadables = [] # Pixel data is available, so add the default loadable to the output loadables.append(loadable) # # second, for any tags that have more than one value, create a new # virtual series # for tag in subseriesTags: if len(subseriesValues[tag]) > 1: for valueIndex, value in enumerate(subseriesValues[tag]): # default loadable includes all files for series loadable = DICOMLoadable() loadable.files = subseriesFiles[tag,value] # value can be a long string (and it will be used for generating node name) # therefore use just an index instead loadable.name = seriesName + " - %s %d" % (tag,valueIndex+1) loadable.tooltip = "%d files, grouped by %s = %s. First file: %s" % (len(loadable.files), tag, value, loadable.files[0]) loadable.selected = False loadables.append(loadable) # remove any files from loadables that don't have pixel data (no point sending them to ITK for reading) # also remove DICOM SEG, since it is not handled by ITK readers newLoadables = [] for loadable in loadables: newFiles = [] excludedLoadable = False for file in loadable.files: if slicer.dicomDatabase.fileValue(file,self.tags['pixelData'])!='': newFiles.append(file) if slicer.dicomDatabase.fileValue(file,self.tags['sopClassUID'])=='1.2.840.10008.5.1.4.1.1.66.4': excludedLoadable = True logging.error('Please install Quantitative Reporting extension to enable loading of DICOM Segmentation objects') elif slicer.dicomDatabase.fileValue(file,self.tags['sopClassUID'])=='1.2.840.10008.5.1.4.1.1.481.3': excludedLoadable = True logging.error('Please install SlicerRT extension to enable loading of DICOM RT Structure Set objects') if len(newFiles) > 0 and not excludedLoadable: loadable.files = newFiles newLoadables.append(loadable) elif excludedLoadable: continue else: # here all files in have no pixel data, so they might be # secondary capture images which will read, so let's pass # them through with a warning and low confidence loadable.warning += "There is no pixel data attribute for the DICOM objects, but they might be readable as secondary capture images. " loadable.confidence = 0.2 newLoadables.append(loadable) loadables = newLoadables # # now for each series and subseries, sort the images # by position and check for consistency # for loadable in loadables: loadable.files, distances, loadable.warning = DICOMUtils.getSortedImageFiles(loadable.files, self.epsilon) return loadables
def test_Part1DICOM(self, enableScreenshotsFlag=0, screenshotScaleFactor=1): """ Test the DICOM part of the test using the head atlas """ logic = RSNAVisTutorialLogic() logic.enableScreenshots = enableScreenshotsFlag logic.screenshotScaleFactor = screenshotScaleFactor import os self.delayDisplay("Starting the DICOM test") # # first, get the data - a zip file of dicom data # import SampleData dicomFilesDirectory = SampleData.downloadFromURL( fileNames='dataset1_Thorax_Abdomen.zip', uris=TESTING_DATA_URL + 'SHA256/17a4199aad03a373dab27dc17e5bfcf84fc194d0a30975b4073e5b595d43a56a', checksums= 'SHA256:17a4199aad03a373dab27dc17e5bfcf84fc194d0a30975b4073e5b595d43a56a' )[0] try: self.delayDisplay("Switching to temp database directory") originalDatabaseDirectory = DICOMUtils.openTemporaryDatabase( 'tempDICOMDatabase') slicer.util.selectModule('DICOM') browserWidget = slicer.modules.DICOMWidget.browserWidget dicomBrowser = browserWidget.dicomBrowser dicomBrowser.importDirectory(dicomFilesDirectory, dicomBrowser.ImportDirectoryAddLink) dicomBrowser.waitForImportFinished() # load the data by series UID dicomBrowser.dicomTableManager().patientsTable().selectFirst() browserWidget.examineForLoading() self.delayDisplay('Loading Selection') browserWidget.loadCheckedLoadables() logic.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)) logic.takeScreenshot('LoadingADICOMVolume-WL', 'Changed level and window', -1) redWidget.sliceController().setSliceLink(True) redWidget.sliceController().setSliceVisible(True) logic.takeScreenshot('LoadingADICOMVolume-LinkView', 'Linked and visible', -1) slicer.util.clickAndDrag(redWidget, button='Right', start=(10, 10), end=(10, 40)) logic.takeScreenshot('LoadingADICOMVolume-Zoom', 'Zoom', -1) threeDView = layoutManager.threeDWidget(0).threeDView() slicer.util.clickAndDrag(threeDView) logic.takeScreenshot('LoadingADICOMVolume-Rotate', 'Rotate', -1) threeDView.resetFocalPoint() logic.takeScreenshot('LoadingADICOMVolume-Center', 'Center the view', -1) layoutManager.setLayout(slicer.vtkMRMLLayoutNode. SlicerLayoutConventionalWidescreenView) logic.takeScreenshot('LoadingADICOMVolume-ConventionalWidescreen', 'Conventional Widescreen Layout', -1) slicer.util.mainWindow().moduleSelector().selectModule( 'VolumeRendering') logic.takeScreenshot('VolumeRendering-Module', 'Volume Rendering', -1) volumeRenderingWidgetRep = slicer.modules.volumerendering.widgetRepresentation( ) abdomenVolume = slicer.mrmlScene.GetFirstNodeByName( '6: CT_Thorax_Abdomen') volumeRenderingWidgetRep.setMRMLVolumeNode(abdomenVolume) logic.takeScreenshot('VolumeRendering-SelectVolume', 'Select the volume 6: CT_Thorax_Abdomen', -1) presetsScene = slicer.modules.volumerendering.logic( ).GetPresetsScene() ctCardiac3 = presetsScene.GetFirstNodeByName('CT-Cardiac3') volumeRenderingWidgetRep.mrmlVolumePropertyNode().Copy(ctCardiac3) logic.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) logic.takeScreenshot('VolumeRendering-ViewRendering', 'View Volume Rendering', -1) self.delayDisplay('Skipping Move the Shift slider') redWidget.sliceController().setSliceVisible(False) logic.takeScreenshot('VolumeRendering-SlicesOff', 'Turn off visibility of slices in 3D', -1) threeDView = layoutManager.threeDWidget(0).threeDView() slicer.util.clickAndDrag(threeDView) logic.takeScreenshot('VolumeRendering-RotateVolumeRendering', 'Rotate volume rendered image', -1) volumeRenderingNode.SetVisibility(0) logic.takeScreenshot('VolumeRendering-TurnOffVolumeRendering', 'Turn off volume rendered image', -1) volumeRenderingNode.SetCroppingEnabled(1) annotationROI = slicer.mrmlScene.GetFirstNodeByName( 'AnnotationROI') annotationROI.SetDisplayVisibility(1) logic.takeScreenshot('VolumeRendering-DisplayROI', 'Enable cropping and display ROI', -1) redWidget.sliceController().setSliceVisible(True) logic.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) logic.takeScreenshot('VolumeRendering-SizedROI', 'Position the ROI over a kidney', -1) volumeRenderingNode.SetVisibility(1) logic.takeScreenshot('VolumeRendering-ROIRendering', 'ROI volume rendered', -1) annotationROI.SetXYZ(15, 146, -186) annotationROI.SetRadiusXYZ(138, 57, 61) logic.takeScreenshot('VolumeRendering-BothKidneys', 'Rendered both kidneys', -1) self.delayDisplay('Test passed!') except Exception as e: import traceback traceback.print_exc() self.delayDisplay('Test caused exception!\n' + str(e)) self.delayDisplay("Restoring original database directory") DICOMUtils.closeTemporaryDatabase(originalDatabaseDirectory)
class RSNAVisTutorialTest(ScriptedLoadableModuleTest): """ This is the test case for your scripted module. Uses ScriptedLoadableModuleTest base class, available at: https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py """ def setUp(self): """ Do whatever is needed to reset the state - typically a scene clear will be enough. """ self.delayDisplay("Closing the scene") layoutManager = slicer.app.layoutManager() layoutManager.setLayout(slicer.vtkMRMLLayoutNode.SlicerLayoutConventionalView) slicer.mrmlScene.Clear(0) def runTest(self): """Run as few or as many tests as needed here. """ self.setUp() self.test_Part1DICOM() self.setUp() self.test_Part2Head() self.setUp() self.test_Part3Liver() self.setUp() self.test_Part4Lung() def test_Part1DICOM(self,enableScreenshotsFlag=0,screenshotScaleFactor=1): """ Test the DICOM part of the test using the head atlas """ logic = RSNAVisTutorialLogic() logic.enableScreenshots = enableScreenshotsFlag logic.screenshotScaleFactor = screenshotScaleFactor import os self.delayDisplay("Starting the DICOM test") # # first, get the data - a zip file of dicom data # import SampleData dicomFilesDirectory = SampleData.downloadFromURL( fileNames='dataset1_Thorax_Abdomen.zip', uris='http://slicer.kitware.com/midas3/download?items=124183')[0] 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 = slicer.modules.DICOMWidget 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() logic.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)) logic.takeScreenshot('LoadingADICOMVolume-WL','Changed level and window',-1) redWidget.sliceController().setSliceLink(True) redWidget.sliceController().setSliceVisible(True); logic.takeScreenshot('LoadingADICOMVolume-LinkView','Linked and visible',-1) slicer.util.clickAndDrag(redWidget,button='Right',start=(10,10),end=(10,40)) logic.takeScreenshot('LoadingADICOMVolume-Zoom','Zoom',-1) threeDView = layoutManager.threeDWidget(0).threeDView() slicer.util.clickAndDrag(threeDView) logic.takeScreenshot('LoadingADICOMVolume-Rotate','Rotate',-1) threeDView.resetFocalPoint() logic.takeScreenshot('LoadingADICOMVolume-Center','Center the view',-1) layoutManager.setLayout(slicer.vtkMRMLLayoutNode.SlicerLayoutConventionalWidescreenView) logic.takeScreenshot('LoadingADICOMVolume-ConventionalWidescreen','Conventional Widescreen Layout',-1) slicer.util.mainWindow().moduleSelector().selectModule('VolumeRendering') logic.takeScreenshot('VolumeRendering-Module','Volume Rendering',-1) volumeRenderingWidgetRep = slicer.modules.volumerendering.widgetRepresentation() abdomenVolume = slicer.mrmlScene.GetFirstNodeByName('6: CT_Thorax_Abdomen') volumeRenderingWidgetRep.setMRMLVolumeNode(abdomenVolume) logic.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) logic.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) logic.takeScreenshot('VolumeRendering-ViewRendering','View Volume Rendering',-1) self.delayDisplay('Skipping Move the Shift slider') redWidget.sliceController().setSliceVisible(False); logic.takeScreenshot('VolumeRendering-SlicesOff','Turn off visibility of slices in 3D',-1) threeDView = layoutManager.threeDWidget(0).threeDView() slicer.util.clickAndDrag(threeDView) logic.takeScreenshot('VolumeRendering-RotateVolumeRendering','Rotate volume rendered image',-1) volumeRenderingNode.SetVisibility(0) logic.takeScreenshot('VolumeRendering-TurnOffVolumeRendering','Turn off volume rendered image',-1) volumeRenderingNode.SetCroppingEnabled(1) annotationROI = slicer.mrmlScene.GetFirstNodeByName('AnnotationROI') annotationROI.SetDisplayVisibility(1) logic.takeScreenshot('VolumeRendering-DisplayROI','Enable cropping and display ROI',-1) redWidget.sliceController().setSliceVisible(True) logic.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) logic.takeScreenshot('VolumeRendering-SizedROI','Position the ROI over a kidney',-1) volumeRenderingNode.SetVisibility(1) logic.takeScreenshot('VolumeRendering-ROIRendering','ROI volume rendered',-1) annotationROI.SetXYZ(15,146,-186) annotationROI.SetRadiusXYZ(138,57,61) logic.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)) self.delayDisplay("Restoring original database directory") DICOMUtils.closeTemporaryDatabase(originalDatabaseDirectory)
def main(argv): try: # Parse command-line arguments parser = argparse.ArgumentParser(description="Batch Structure Set Conversion") parser.add_argument("-i", "--input-folder", dest="input_folder", metavar="PATH", default="-", required=True, help="Folder of input DICOM study (or database path to use existing)") parser.add_argument("-x", "--exist-db", dest="exist_db", default=False, required=False, action='store_true', help="Process an existing database") parser.add_argument("-m", "--export-images", dest="export_images", default=False, required=False, action='store_true', help="Export image data with labelmaps") parser.add_argument("-o", "--output-folder", dest="output_folder", metavar="PATH", default=".", help="Folder for output labelmaps") args = parser.parse_args(argv) # Check required arguments if args.input_folder == "-": logging.warning('Please specify input DICOM study folder!') if args.output_folder == ".": logging.info('Current directory is selected as output folder (default). To change it, please specify --output-folder') # Convert to python path style input_folder = args.input_folder.replace('\\', '/') output_folder = args.output_folder.replace('\\', '/') exist_db = args.exist_db export_images = args.export_images # Perform batch conversion logic = BatchStructureSetConversionLogic() def save_rtslices(output_dir): # package the saving code into a subfunction logging.info("Convert loaded structure set to labelmap volumes") labelmaps = logic.ConvertStructureSetToLabelmap() logging.info("Save labelmaps to directory " + output_dir) logic.SaveLabelmaps(labelmaps, output_dir) if export_images: logic.SaveImages(output_dir) logging.info("DONE") if exist_db: logging.info('BatchStructureSet running in existing database mode') DICOMUtils.openDatabase(input_folder) all_patients = slicer.dicomDatabase.patients() logging.info('Must Process Patients %s' % len(all_patients)) for patient in all_patients: slicer.mrmlScene.Clear(0) # clear the scene DICOMUtils.loadPatientByUID(patient) output_dir = os.path.join(output_folder,patient) if not os.access(output_dir, os.F_OK): os.mkdir(output_dir) save_rtslices(output_dir) else: logging.info("Import DICOM data from " + input_folder) DICOMUtils.openTemporaryDatabase() DICOMUtils.importDicom(input_folder) logging.info("Load first patient into Slicer") logic.LoadFirstPatientIntoSlicer() save_rtslices(output_folder) except Exception, e: print(e)
def test_PETLiverUptakeMeasurementQR2(self): """ test segmentation options """ try: self.assertIsNotNone( slicer.modules.petliveruptakemeasurement ) with DICOMUtils.TemporaryDICOMDatabase(self.tempDicomDatabaseDir) as db: self.assertTrue(db.isOpen) self.assertEqual(slicer.dicomDatabase, db) self.delayDisplay('Loading PET DICOM dataset (including download if necessary)') petNode = self.loadTestData() qrWidget = slicer.modules.PETLiverUptakeMeasurementQRWidget qrWidget.inputSelector.setCurrentNode(petNode) segmentationNode = qrWidget.segmentationSelector.addNode() self.delayDisplay('Running segmentation with standard settings') qrWidget.segmentButton.click() self.assertTrue(abs(float(qrWidget.meanValueLineEdit.text)-2.36253)<0.01) self.delayDisplay('Specifying annotation ROI') roi=slicer.vtkMRMLAnnotationROINode() roi.SetXYZ([-34,243,-1168]) roi.SetRadiusXYZ([85,102,82]) roi.SetName('ROI') slicer.mrmlScene.AddNode(roi) qrWidget.regionSelector.setCurrentNode(roi) qrWidget.segmentButton.click() self.assertTrue(abs(float(qrWidget.meanValueLineEdit.text)-2.91891)<0.01) self.delayDisplay('Changing erosion range') originalErosion = qrWidget.erosionSlider.value qrWidget.erosionSlider.value = 0 qrWidget.segmentButton.click() self.assertTrue(abs(float(qrWidget.meanValueLineEdit.text)-2.71982)<0.01) self.delayDisplay('Changing thresholds') originalMinimValue = qrWidget.thresholdRangeSlider.minimumValue originalMaximumValue = qrWidget.thresholdRangeSlider.maximumValue qrWidget.thresholdRangeSlider.minimumValue = 2 qrWidget.thresholdRangeSlider.maximumValue = 20 qrWidget.segmentButton.click() self.assertTrue(abs(float(qrWidget.meanValueLineEdit.text)-3.72669)<0.01) self.delayDisplay('Completing and writing DICOM report') qrWidget.readerValueLineEdit.text = 'semiautotest' self.assertTrue(qrWidget.saveReport(completed=True)) self.delayDisplay('Testing that report was saved as semiautomatic result') import dicom patientUID = DICOMUtils.getDatabasePatientUIDByPatientName('QIN-HEADNECK-01-0139') studies = slicer.dicomDatabase.studiesForPatient(patientUID) series = slicer.dicomDatabase.seriesForStudy(studies[0]) SEGSeries = None for serie in series: description = slicer.dicomDatabase.descriptionForSeries(serie) if description=='Semiautomatic Liver Reference Region Segmentation': SEGSeries = serie self.assertIsNotNone(SEGSeries) # reset values qrWidget.regionSelector.removeCurrentNode() qrWidget.erosionSlider.value = originalErosion qrWidget.thresholdRangeSlider.minimumValue = originalMinimValue qrWidget.thresholdRangeSlider.maximumValue = originalMaximumValue # clean up data from DICOM database db.removePatient(patientUID) self.delayDisplay('Test passed!') except Exception, e: import traceback traceback.print_exc() self.delayDisplay('Test caused exception!\n' + str(e),self.delayMs*2)
def main(argv): try: logging.info("Start batch RTSTRUCT conversion") # Parse command-line arguments parser = argparse.ArgumentParser( description="Batch Structure Set Conversion") parser.add_argument( "-i", "--input-folder", dest="input_folder", metavar="PATH", default="-", required=True, help= "Folder of input DICOM study (or database path to use existing)") parser.add_argument( "-r", "--ref-dicom-folder", dest="ref_dicom_folder", metavar="PATH", default="", required=False, help= "Folder containing reference anatomy DICOM image series, if stored outside the input study" ) parser.add_argument( "-u", "--use-ref-image", dest="use_ref_image", default=False, required=False, action='store_true', help= "Use anatomy image as reference when converting structure set to labelmap" ) parser.add_argument("-x", "--exist-db", dest="exist_db", default=False, required=False, action='store_true', help="Process an existing database") parser.add_argument("-m", "--export-images", dest="export_images", default=False, required=False, action='store_true', help="Export image data with labelmaps") parser.add_argument("-o", "--output-folder", dest="output_folder", metavar="PATH", default=".", help="Folder for output labelmaps") parser.add_argument("-s", "--export-surfaces", dest="export_surfaces", default=False, required=False, action='store_true', help="Export surface mesh representation") parser.add_argument( "-c", "--show-python-console", dest="show_python_console", default=False, required=False, action='store_true', help= "If this flag is specified then messages are displayed in an interactive Python console and the application does not quit when the script is finished." ) args = parser.parse_args(argv) if args.show_python_console: slicer.util.pythonShell().show() slicer.exit_when_finished = False # Check if SlicerRT is installed try: slicer.modules.dicomrtimportexport except AttributeError: logging.error("Please install SlicerRT extension") return 1 # Check required arguments if args.input_folder == "-": logging.warning('Please specify input DICOM study folder!') if args.output_folder == ".": logging.info( 'Current directory is selected as output folder (default). To change it, please specify --output-folder' ) # Convert to python path style input_folder = args.input_folder.replace('\\', '/') ref_dicom_folder = args.ref_dicom_folder.replace('\\', '/') output_folder = args.output_folder.replace('\\', '/') use_ref_image = args.use_ref_image exist_db = args.exist_db export_images = args.export_images export_surfaces = args.export_surfaces # Perform batch conversion logic = BatchStructureSetConversionLogic() def save_rtslices(output_dir, use_ref_image, ref_image_node_id=None): # package the saving code into a subfunction logging.info("Convert loaded structure set to labelmap volumes") labelmaps = logic.ConvertStructureSetToLabelmap( use_ref_image, ref_image_node_id) logging.info("Save labelmaps to directory " + output_dir) logic.SaveLabelmaps(labelmaps, output_dir) if export_surfaces: logic.SaveModels(output_dir) if export_images: logic.SaveImages(output_dir) logging.info("DONE") if exist_db: logging.info('BatchStructureSet running in existing database mode') DICOMUtils.openDatabase(input_folder) all_patients = slicer.dicomDatabase.patients() logging.info('Processing %d patients...' % len(all_patients)) for patient in all_patients: try: slicer.mrmlScene.Clear(0) # clear the scene DICOMUtils.loadPatientByUID(patient) output_dir = os.path.join(output_folder, patient) if not os.access(output_dir, os.F_OK): os.mkdir(output_dir) save_rtslices(output_dir, use_ref_image) except OSError as e: # Failed to load data from this patient, continue with the next one print(e) else: logging.info('BatchStructureSet running in file mode') ref_volume_file_path = None if os.path.isdir(ref_dicom_folder): # If reference DICOM folder is given and valid, then import reference patient and save its ID logging.info("Import reference anatomy DICOM data from " + ref_dicom_folder) DICOMUtils.openTemporaryDatabase() DICOMUtils.importDicom(ref_dicom_folder) # Save first volume to be used as reference logic.LoadFirstPatientIntoSlicer() scalarVolumeNodes = list( slicer.util.getNodes('vtkMRMLScalarVolume*').values()) if len(scalarVolumeNodes) > 0: refVolNode = scalarVolumeNodes[0] refVolStorageNode = refVolNode.CreateDefaultStorageNode() ref_volume_file_path = os.path.join( output_folder, 'refVolume.nrrd') refVolStorageNode.SetFileName(ref_volume_file_path) refVolStorageNode.WriteData(refVolNode) logging.info("Import DICOM data from " + input_folder) DICOMUtils.openTemporaryDatabase() DICOMUtils.importDicom(input_folder) all_patients = slicer.dicomDatabase.patients() logging.info('Processing %d patients...' % len(all_patients)) for patient in all_patients: try: slicer.mrmlScene.Clear(0) # clear the scene DICOMUtils.loadPatientByUID(patient) output_dir = os.path.join(output_folder, patient) if not os.access(output_dir, os.F_OK): os.mkdir(output_dir) ref_volume_node_id = None if ref_volume_file_path: try: refVolNode = slicer.util.loadVolume( ref_volume_file_path) ref_volume_node_id = refVolNode.GetID() except: pass save_rtslices(output_dir, use_ref_image, ref_volume_node_id) except OSError as e: # Failed to load data from this patient, continue with the next one print(e) except Exception as e: import traceback traceback.print_exc() print(e) return 1 return 0
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 = slicer.modules.DICOMWidget 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 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_Part1DICOM(self,enableScreenshotsFlag=0,screenshotScaleFactor=1): """ Test the DICOM part of the test using the head atlas """ logic = RSNAVisTutorialLogic() logic.enableScreenshots = enableScreenshotsFlag logic.screenshotScaleFactor = screenshotScaleFactor import os self.delayDisplay("Starting the DICOM test") # # first, get the data - a zip file of dicom data # import SampleData dicomFilesDirectory = SampleData.downloadFromURL( fileNames='dataset1_Thorax_Abdomen.zip', uris='http://slicer.kitware.com/midas3/download?items=124183')[0] 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 = slicer.modules.DICOMWidget 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() logic.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)) logic.takeScreenshot('LoadingADICOMVolume-WL','Changed level and window',-1) redWidget.sliceController().setSliceLink(True) redWidget.sliceController().setSliceVisible(True); logic.takeScreenshot('LoadingADICOMVolume-LinkView','Linked and visible',-1) slicer.util.clickAndDrag(redWidget,button='Right',start=(10,10),end=(10,40)) logic.takeScreenshot('LoadingADICOMVolume-Zoom','Zoom',-1) threeDView = layoutManager.threeDWidget(0).threeDView() slicer.util.clickAndDrag(threeDView) logic.takeScreenshot('LoadingADICOMVolume-Rotate','Rotate',-1) threeDView.resetFocalPoint() logic.takeScreenshot('LoadingADICOMVolume-Center','Center the view',-1) layoutManager.setLayout(slicer.vtkMRMLLayoutNode.SlicerLayoutConventionalWidescreenView) logic.takeScreenshot('LoadingADICOMVolume-ConventionalWidescreen','Conventional Widescreen Layout',-1) slicer.util.mainWindow().moduleSelector().selectModule('VolumeRendering') logic.takeScreenshot('VolumeRendering-Module','Volume Rendering',-1) volumeRenderingWidgetRep = slicer.modules.volumerendering.widgetRepresentation() abdomenVolume = slicer.mrmlScene.GetFirstNodeByName('6: CT_Thorax_Abdomen') volumeRenderingWidgetRep.setMRMLVolumeNode(abdomenVolume) logic.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) logic.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) logic.takeScreenshot('VolumeRendering-ViewRendering','View Volume Rendering',-1) self.delayDisplay('Skipping Move the Shift slider') redWidget.sliceController().setSliceVisible(False); logic.takeScreenshot('VolumeRendering-SlicesOff','Turn off visibility of slices in 3D',-1) threeDView = layoutManager.threeDWidget(0).threeDView() slicer.util.clickAndDrag(threeDView) logic.takeScreenshot('VolumeRendering-RotateVolumeRendering','Rotate volume rendered image',-1) volumeRenderingNode.SetVisibility(0) logic.takeScreenshot('VolumeRendering-TurnOffVolumeRendering','Turn off volume rendered image',-1) volumeRenderingNode.SetCroppingEnabled(1) annotationROI = slicer.mrmlScene.GetFirstNodeByName('AnnotationROI') annotationROI.SetDisplayVisibility(1) logic.takeScreenshot('VolumeRendering-DisplayROI','Enable cropping and display ROI',-1) redWidget.sliceController().setSliceVisible(True) logic.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) logic.takeScreenshot('VolumeRendering-SizedROI','Position the ROI over a kidney',-1) volumeRenderingNode.SetVisibility(1) logic.takeScreenshot('VolumeRendering-ROIRendering','ROI volume rendered',-1) annotationROI.SetXYZ(15,146,-186) annotationROI.SetRadiusXYZ(138,57,61) logic.takeScreenshot('VolumeRendering-BothKidneys','Rendered both kidneys',-1) self.delayDisplay('Test passed!') except Exception as e: import traceback traceback.print_exc() self.delayDisplay('Test caused exception!\n' + str(e)) self.delayDisplay("Restoring original database directory") DICOMUtils.closeTemporaryDatabase(originalDatabaseDirectory)
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 SampleData SampleData.downloadFromURL( fileNames='Dcmtk-db.zip', uris='http://slicer.kitware.com/midas3/download?items=18822')[0] 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_AlternateReaders(self): """ Test the DICOM loading of sample testing data """ testPass = True import os, json self.delayDisplay("Starting the DICOM test") referenceData = [{ "url": "http://slicer.kitware.com/midas3/download?items=292839", "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?items=294857", "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") for dataset in referenceData: try: import SampleData dicomFilesDirectory = SampleData.downloadFromURL( fileNames=dataset['fileName'], uris=dataset['url'])[0] self.delayDisplay('Finished with download') # # insert the data into th database # 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() # # 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) 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']) except Exception, e: import traceback traceback.print_exc() self.delayDisplay('%s Test caused exception!\n' % dataset['name'] + str(e)) testPass = False