def importFromDICOMWeb(dicomWebEndpoint, studyInstanceUID, seriesInstanceUID=None, accessToken=None): """ Downloads and imports DICOM series from a DICOMweb instance. Example usage: from DICOMLib import DICOMUtils loadedUIDs = DICOMUtils.importFromDICOMWeb(dicomWebEndpoint="https://yourdicomweburl/dicomWebEndpoint", studyInstanceUID="2.16.840.1.113669.632.20.1211.10000509338") accessToken="YOUR_ACCESS_TOKEN") :param dicomWebEndpoint: Endpoint URL for retrieving the study/series from DICOMweb :param studyInstanceUID: UID for the study to be downloaded :param seriesInstanceUID: UID for the series to be downloaded. If not specified, all series will be downloaded from the study :param accessToken: Optional access token for the query :return: List of imported study UIDs """ from dicomweb_client.api import DICOMwebClient import random if accessToken is None: client = DICOMwebClient(url = dicomWebEndpoint) else: client = DICOMwebClient( url = dicomWebEndpoint, headers = { "Authorization": "Bearer {}".format(accessToken) }, ) seriesList = client.search_for_series(study_instance_uid=studyInstanceUID) seriesInstanceUIDs = [] if not seriesInstanceUID is None: seriesInstanceUIDs = [seriesInstanceUID] else: for series in seriesList: currentSeriesInstanceUID = series['0020000E']['Value'][0] seriesInstanceUIDs.append(currentSeriesInstanceUID) fileNumber = 0 for currentSeriesInstanceUID in seriesInstanceUIDs: instances = client.retrieve_series( study_instance_uid=studyInstanceUID, series_instance_uid=currentSeriesInstanceUID) outputDirectoryBase = slicer.dicomDatabase.databaseDirectory + "/DICOMweb" if not os.access(outputDirectoryBase, os.F_OK): os.makedirs(outputDirectoryBase) outputDirectoryBase += "/" + qt.QDateTime.currentDateTime().toString("yyyyMMdd-hhmmss") outputDirectory = qt.QTemporaryDir(outputDirectoryBase) # Add unique substring to directory outputDirectory.setAutoRemove(False) outputDirectoryPath = outputDirectory.path() for instance in instances: filename = outputDirectoryPath + "/" + str(fileNumber) + ".dcm" instance.save_as(filename) fileNumber += 1 slicer.app.processEvents() importDicom(outputDirectoryPath) return seriesInstanceUIDs
def _search_for_series(args): '''Searches for Series and writes metadata to standard output.''' params = _parse_search_parameters(args) client = DICOMwebClient(args.url, username=args.username, password=args.password, ca_bundle=args.ca_bundle, cert=args.cert) series = client.search_for_series(args.study_instance_uid, **params) _print_metadata(series, args.prettify, args.dicomize)
def importFromDICOMWeb(dicomWebEndpoint, studyInstanceUID, seriesInstanceUID=None, accessToken=None): """ Downloads and imports DICOM series from a DICOMweb instance. Progress is displayed and if errors occur then they are displayed in a popup window in the end. If all the instances in a series are already imported then the series will not be retrieved and imported again. :param dicomWebEndpoint: Endpoint URL for retrieving the study/series from DICOMweb :param studyInstanceUID: UID for the study to be downloaded :param seriesInstanceUID: UID for the series to be downloaded. If not specified, all series will be downloaded from the study :param accessToken: Optional access token for the query :return: List of imported study UIDs Example: calling from PythonSlicer console .. code-block:: python from DICOMLib import DICOMUtils loadedUIDs = DICOMUtils.importFromDICOMWeb(dicomWebEndpoint="https://yourdicomweburl/dicomWebEndpoint", studyInstanceUID="2.16.840.1.113669.632.20.1211.10000509338") accessToken="YOUR_ACCESS_TOKEN") """ from dicomweb_client.api import DICOMwebClient seriesImported = [] errors = [] clientLogger = logging.getLogger('dicomweb_client') originalClientLogLevel = clientLogger.level progressDialog = slicer.util.createProgressDialog( parent=slicer.util.mainWindow(), value=0, maximum=100) try: progressDialog.labelText = f'Retrieving series list...' slicer.app.processEvents() if accessToken is None: client = DICOMwebClient(url=dicomWebEndpoint) else: client = DICOMwebClient( url=dicomWebEndpoint, headers={"Authorization": f"Bearer {accessToken}"}, ) seriesList = client.search_for_series( study_instance_uid=studyInstanceUID) seriesInstanceUIDs = [] if seriesInstanceUID is not None: seriesInstanceUIDs = [seriesInstanceUID] else: for series in seriesList: currentSeriesInstanceUID = series['0020000E']['Value'][0] seriesInstanceUIDs.append(currentSeriesInstanceUID) # Turn off detailed logging, because it would slow down the file transfer clientLogger.setLevel(logging.WARNING) fileNumber = 0 cancelled = False for seriesIndex, currentSeriesInstanceUID in enumerate( seriesInstanceUIDs): progressDialog.labelText = f'Retrieving series {seriesIndex+1} of {len(seriesInstanceUIDs)}...' slicer.app.processEvents() try: seriesInfo = client.retrieve_series_metadata( study_instance_uid=studyInstanceUID, series_instance_uid=currentSeriesInstanceUID) numberOfInstances = len(seriesInfo) # Skip retrieve and import of this series if it is already imported alreadyImportedInstances = slicer.dicomDatabase.instancesForSeries( currentSeriesInstanceUID) seriesAlreadyImported = True for serieInfo in seriesInfo: sopInstanceUID = serieInfo['00080018']['Value'][0] if sopInstanceUID not in alreadyImportedInstances: seriesAlreadyImported = False break if seriesAlreadyImported: seriesImported.append(currentSeriesInstanceUID) continue instances = client.iter_series( study_instance_uid=studyInstanceUID, series_instance_uid=currentSeriesInstanceUID) slicer.app.processEvents() cancelled = progressDialog.wasCanceled if cancelled: break outputDirectoryBase = slicer.dicomDatabase.databaseDirectory + "/DICOMweb" if not os.access(outputDirectoryBase, os.F_OK): os.makedirs(outputDirectoryBase) outputDirectoryBase += "/" + qt.QDateTime.currentDateTime( ).toString("yyyyMMdd-hhmmss") outputDirectory = qt.QTemporaryDir( outputDirectoryBase) # Add unique substring to directory outputDirectory.setAutoRemove(False) outputDirectoryPath = outputDirectory.path() for instanceIndex, instance in enumerate(instances): progressDialog.setValue( int(100 * instanceIndex / numberOfInstances)) slicer.app.processEvents() cancelled = progressDialog.wasCanceled if cancelled: break filename = outputDirectoryPath + "/" + str( fileNumber) + ".dcm" instance.save_as(filename) fileNumber += 1 if cancelled: # cancel was requested in instance retrieve loop, # stop the entire import process break importDicom(outputDirectoryPath) seriesImported.append(currentSeriesInstanceUID) except Exception as e: import traceback errors.append( f"Error importing series {currentSeriesInstanceUID}: {str(e)} ({traceback.format_exc()})" ) except Exception as e: import traceback errors.append(f"{str(e)} ({traceback.format_exc()})") finally: progressDialog.close() clientLogger.setLevel(originalClientLogLevel) if errors: slicer.util.errorDisplay( f"Errors occurred during DICOMweb import of {len(errors)} series.", detailedText="\n\n".join(errors)) elif cancelled and (len(seriesImported) < len(seriesInstanceUIDs)): slicer.util.infoDisplay( f"DICOMweb import has been interrupted after completing {len(seriesImported)} out of {len(seriesInstanceUIDs)} series." ) return seriesImported
def importFromDICOMWeb(dicomWebEndpoint, studyInstanceUID, seriesInstanceUID=None, accessToken=None): """ Downloads and imports DICOM series from a DICOMweb instance. Example usage: from DICOMLib import DICOMUtils loadedUIDs = DICOMUtils.importFromDICOMWeb(dicomWebEndpoint="https://yourdicomweburl/dicomWebEndpoint", studyInstanceUID="2.16.840.1.113669.632.20.1211.10000509338") accessToken="YOUR_ACCESS_TOKEN") :param dicomWebEndpoint: Endpoint URL for retrieving the study/series from DICOMweb :param studyInstanceUID: UID for the study to be downloaded :param seriesInstanceUID: UID for the series to be downloaded. If not specified, all series will be downloaded from the study :param accessToken: Optional access token for the query :return: List of imported study UIDs """ from dicomweb_client.api import DICOMwebClient import random progressDialog = slicer.util.createProgressDialog( parent=slicer.util.mainWindow(), value=0, maximum=100) progressDialog.labelText = f'Retrieving series list...' slicer.app.processEvents() if accessToken is None: client = DICOMwebClient(url=dicomWebEndpoint, callback=progressCallback) else: client = DICOMwebClient( url=dicomWebEndpoint, headers={"Authorization": f"Bearer {accessToken}"}, ) seriesList = client.search_for_series(study_instance_uid=studyInstanceUID) seriesInstanceUIDs = [] if not seriesInstanceUID is None: seriesInstanceUIDs = [seriesInstanceUID] else: for series in seriesList: currentSeriesInstanceUID = series['0020000E']['Value'][0] seriesInstanceUIDs.append(currentSeriesInstanceUID) fileNumber = 0 cancelled = False for seriesIndex, currentSeriesInstanceUID in enumerate(seriesInstanceUIDs): progressDialog.labelText = f'Retrieving series {seriesIndex+1} of {len(seriesInstanceUIDs)}...' slicer.app.processEvents() instances = client.retrieve_series( study_instance_uid=studyInstanceUID, series_instance_uid=currentSeriesInstanceUID) progressDialog.setValue( int(100 * seriesIndex / len(seriesInstanceUIDs))) slicer.app.processEvents() cancelled = progressDialog.wasCanceled if cancelled: break outputDirectoryBase = slicer.dicomDatabase.databaseDirectory + "/DICOMweb" if not os.access(outputDirectoryBase, os.F_OK): os.makedirs(outputDirectoryBase) outputDirectoryBase += "/" + qt.QDateTime.currentDateTime().toString( "yyyyMMdd-hhmmss") outputDirectory = qt.QTemporaryDir( outputDirectoryBase) # Add unique substring to directory outputDirectory.setAutoRemove(False) outputDirectoryPath = outputDirectory.path() for instanceIndex, instance in enumerate(instances): filename = outputDirectoryPath + "/" + str(fileNumber) + ".dcm" instance.save_as(filename) fileNumber += 1 importDicom(outputDirectoryPath) if cancelled: break progressDialog.close() return seriesInstanceUIDs
def get_files(self, ds, **kwargs): print("Starting module LocalGetRefSeriesOperator") if self.search_policy != 'reference_uid': print( "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++" ) print("") print("Parameter 'modality' has to be set for search_policy: {} !". format(self.search_policy)) print("Abort.") print("") print( "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++" ) exit(1) client = DICOMwebClient(url=self.pacs_dcmweb, qido_url_prefix="rs", wado_url_prefix="rs", stow_url_prefix="rs") run_dir = os.path.join(WORKFLOW_DIR, kwargs['dag_run'].run_id) batch_folder = [ f for f in glob.glob(os.path.join(run_dir, BATCH_NAME, '*')) ] download_series_list = [] for batch_element_dir in batch_folder: print("batch_element_dir: {}".format(batch_element_dir)) dcm_files = sorted( glob.glob(os.path.join(batch_element_dir, "initial-input", "*.dcm*"), recursive=True)) if len(dcm_files) > 0: incoming_dcm = pydicom.dcmread(dcm_files[0]) if self.search_policy == 'reference_uid': if (0x0008, 0x1115) in incoming_dcm: for ref_series in incoming_dcm[0x0008, 0x1115]: if (0x0020, 0x000E) not in ref_series: print( "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++" ) print("") print( "Could not extract SeriesUID from referenced DICOM series." ) print("Abort.") print("") print( "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++" ) exit(1) reference_series_uid = ref_series[0x0020, 0x000E].value pacs_series = client.search_for_series( search_filters={ '0020000E': reference_series_uid }) if len(pacs_series) != 1: print( "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++" ) print("") print( "Could not find referenced SeriesUID in the PACS: {} !" .format(reference_series_uid)) print("Abort.") print("") print( "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++" ) exit(1) download_series_list.append({ "reference_study_uid": pacs_series[0]['0020000D']['Value'][0], "reference_series_uid": reference_series_uid, "target_dir": os.path.join(batch_element_dir, self.operator_out_dir) }) else: print( "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++" ) print("") print( "Could not find referenced dcm-series within the metadata!" ) print("Abort.") print("") print( "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++" ) exit(1) if self.search_policy == 'study_uid': pacs_series = client.search_for_series( search_filters={ '0020000D': incoming_dcm.StudyInstanceUID, '00080060': self.modality.upper() }) if len(pacs_series) != 1: print( "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++" ) print("") print( "Could not identify the searched modality: {} from the studyUID: {} !" .format(self.modality, incoming_dcm.StudyInstanceUID)) print( "Number of possible DICOMs in the PACS: {}".format( len(pacs_series))) print("Abort.") print("") print( "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++" ) exit(1) download_series_list.append({ "reference_study_uid": pacs_series[0]['0020000D']['Value'][0], "reference_series_uid": pacs_series[0]['0020000E']['Value'][0], "target_dir": os.path.join(batch_element_dir, self.operator_out_dir) }) if self.search_policy == 'patient_uid': if not (0x0010, 0x0020) in incoming_dcm: print( "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++" ) print("") print( "Could not extract PatientUID from referenced DICOM series." ) print("Abort.") print("") print( "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++" ) exit(1) patient_uid = incoming_dcm[0x0010, 0x0020].value pacs_series = client.search_for_series( search_filters={ '00100020': patient_uid, '00080060': self.modality.upper() }) if len(pacs_series) != 1: print( "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++" ) print("") print( "Could not identify the searched modality: {} from the PatientUID: {} !" .format(self.modality, patient_uid)) print( "Number of possible DICOMs in the PACS: {}".format( len(pacs_series))) print("Abort.") print("") print( "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++" ) exit(1) download_series_list.append({ "reference_study_uid": pacs_series[0]['0020000D']['Value'][0], "reference_series_uid": pacs_series[0]['0020000E']['Value'][0], "target_dir": os.path.join(batch_element_dir, self.operator_out_dir) }) if len(download_series_list) == 0: print( "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++" ) print("") print("No series to download could be found!") print("Abort.") print("") print( "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++" ) exit(1) for series in download_series_list: print("Downloading series: {}".format( series["reference_series_uid"])) HelperDcmWeb.downloadSeries( studyUID=series["reference_study_uid"], seriesUID=series["reference_series_uid"], target_dir=series['target_dir'])
def get_files(self, ds, **kwargs): print("Starting module LocalGetRefSeriesOperator") if self.search_policy != 'reference_uid' and self.modality is None: print("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++") print("") print("Parameter 'modality' has to be set for search_policy: {} !".format(self.search_policy)) print("Abort.") print("") print("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++") exit(1) client = DICOMwebClient(url=self.pacs_dcmweb, qido_url_prefix="rs", wado_url_prefix="rs", stow_url_prefix="rs") run_dir = os.path.join(WORKFLOW_DIR, kwargs['dag_run'].run_id) batch_folder = [f for f in glob.glob(os.path.join(run_dir, BATCH_NAME, '*'))] download_series_list = [] for batch_element_dir in batch_folder: dcm_files = sorted(glob.glob(os.path.join(batch_element_dir, self.operator_in_dir, "*.dcm*"), recursive=True)) if len(dcm_files) > 0: incoming_dcm = pydicom.dcmread(dcm_files[0]) if self.search_policy == 'reference_uid': if (0x0008, 0x1115) in incoming_dcm: for ref_series in incoming_dcm[0x0008, 0x1115]: if (0x0020, 0x000E) not in ref_series: print("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++") print("") print("Could not extract SeriesUID from referenced DICOM series.") print("Abort.") print("") print("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++") exit(1) reference_series_uid = ref_series[0x0020, 0x000E].value pacs_series = client.search_for_series(search_filters={'0020000E': reference_series_uid}) print(f"Found series: {len(pacs_series)} for reference_series_uid: {reference_series_uid}") if len(pacs_series) != 1: print("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++") print("") print(f"Could not find referenced SeriesUID in the PACS: {reference_series_uid} !") print("Abort.") print("") print("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++") exit(1) download_series_list.append( { "reference_study_uid": pacs_series[0]['0020000D']['Value'][0], "reference_series_uid": reference_series_uid, "target_dir": os.path.join(batch_element_dir, self.operator_out_dir) } ) else: print("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++") print("") print("Could not find referenced dcm-series within the metadata!") print("Abort.") print("") print("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++") exit(1) if self.search_policy == 'study_uid': pacs_series = client.search_for_series(search_filters={'0020000D': incoming_dcm.StudyInstanceUID, '00080060': self.modality.upper()}) print(f"Found series: {len(pacs_series)} for modality {self.modality.upper()} in study: {incoming_dcm.StudyInstanceUID}") if len(pacs_series) == 0 or (self.expected_file_count != "all" and len(pacs_series) != self.expected_file_count): print("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++") print("") print(f"Could not identify the searched modality: {self.modality} from the studyUID: {incoming_dcm.StudyInstanceUID} !") print(f"Expected {self.expected_file_count} series of modality {self.modality} - found {len(pacs_series)} series") print("Abort.") print("") print("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++") exit(1) for series in pacs_series: download_series_list.append( { "reference_study_uid": series['0020000D']['Value'][0], "reference_series_uid": series['0020000E']['Value'][0], "target_dir": os.path.join(batch_element_dir, self.operator_out_dir) } ) if self.search_policy == 'patient_uid': if not (0x0010, 0x0020) in incoming_dcm: print("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++") print("") print("Could not extract PatientUID from referenced DICOM series.") print("Abort.") print("") print("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++") exit(1) patient_uid = incoming_dcm[0x0010, 0x0020].value pacs_series = client.search_for_series(search_filters={'00100020': patient_uid, '00080060': self.modality.upper()}) print(f"Found series: {len(pacs_series)} for modality {self.modality.upper()} in patient_uid: {patient_uid}") if len(pacs_series) == 0 or (self.expected_file_count != "all" and len(pacs_series) != self.expected_file_count): print("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++") print("") print(f"Could not identify the searched modality: {self.modality} from the studyUID: {incoming_dcm.StudyInstanceUID} !") print(f"Expected {self.expected_file_count} series of modality {self.modality} - found {len(pacs_series)} series") print("Abort.") print("") print("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++") exit(1) for series in pacs_series: download_series_list.append( { "reference_study_uid": series['0020000D']['Value'][0], "reference_series_uid": series['0020000E']['Value'][0], "target_dir": os.path.join(batch_element_dir, self.operator_out_dir) } ) if len(download_series_list) == 0: print("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++") print("") print("No series to download could be found!") print("Abort.") print("") print("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++") exit(1) results = ThreadPool(self.parallel_downloads).imap_unordered(self.download_series, download_series_list) for result in results: print(result)