├── AHItoDICOMInterface.egg-info ├── PKG-INFO ├── SOURCES.txt ├── dependency_links.txt ├── requires.txt └── top_level.txt ├── AHItoDICOMInterface ├── AHIClientFactory.py ├── AHIDataDICOMizer.py ├── AHIFrameFetcher.py ├── AHItoDICOM.py └── __init__.py ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── NOTICE ├── README.md ├── build └── lib │ └── AHItoDICOMInterface │ ├── AHIClientFactory.py │ ├── AHIDataDICOMizer.py │ ├── AHIFrameFetcher.py │ ├── AHItoDICOM.py │ └── __init__.py ├── dist ├── AHItoDICOMInterface-0.1.3.4-py3-none-any.whl └── AHItoDICOMInterface-0.1.3.4.tar.gz ├── example ├── jupyter-sagemaker-example.ipynb ├── main.py └── metads.json └── setup.py /AHItoDICOMInterface.egg-info/PKG-INFO: -------------------------------------------------------------------------------- 1 | Metadata-Version: 2.1 2 | Name: AHItoDICOMInterface 3 | Version: 0.1.3.4 4 | Summary: A package to simply export DICOM data from AWS HealthImaging in your application memory or the file system. 5 | Home-page: https://github.com/aws-samples/healthlake-imaging-to-dicom-python-module 6 | Author: JP Leger 7 | Author-email: jpleger@amazon.com 8 | License: MIT-0 9 | Classifier: Development Status :: 4 - Beta 10 | Classifier: Intended Audience :: Science/Research 11 | Classifier: License :: OSI Approved :: MIT No Attribution License (MIT-0) 12 | Classifier: Operating System :: POSIX :: Linux 13 | Classifier: Operating System :: Microsoft :: Windows 14 | Classifier: Programming Language :: Python :: 3.10 15 | Classifier: Programming Language :: Python :: 3.11 16 | License-File: LICENSE 17 | License-File: NOTICE 18 | Requires-Dist: boto3 19 | Requires-Dist: botocore 20 | Requires-Dist: pydicom 21 | Requires-Dist: pylibjpeg-openjpeg>=1.3.0 22 | Requires-Dist: numpy 23 | Requires-Dist: pillow 24 | 25 | More details about the project and features can be found on the project's GitHub page. 26 | -------------------------------------------------------------------------------- /AHItoDICOMInterface.egg-info/SOURCES.txt: -------------------------------------------------------------------------------- 1 | LICENSE 2 | NOTICE 3 | README.md 4 | setup.py 5 | AHItoDICOMInterface/AHIClientFactory.py 6 | AHItoDICOMInterface/AHIDataDICOMizer.py 7 | AHItoDICOMInterface/AHIFrameFetcher.py 8 | AHItoDICOMInterface/AHItoDICOM.py 9 | AHItoDICOMInterface/__init__.py 10 | AHItoDICOMInterface.egg-info/PKG-INFO 11 | AHItoDICOMInterface.egg-info/SOURCES.txt 12 | AHItoDICOMInterface.egg-info/dependency_links.txt 13 | AHItoDICOMInterface.egg-info/requires.txt 14 | AHItoDICOMInterface.egg-info/top_level.txt -------------------------------------------------------------------------------- /AHItoDICOMInterface.egg-info/dependency_links.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /AHItoDICOMInterface.egg-info/requires.txt: -------------------------------------------------------------------------------- 1 | boto3 2 | botocore 3 | pydicom 4 | pylibjpeg-openjpeg>=1.3.0 5 | numpy 6 | pillow 7 | -------------------------------------------------------------------------------- /AHItoDICOMInterface.egg-info/top_level.txt: -------------------------------------------------------------------------------- 1 | AHItoDICOMInterface 2 | -------------------------------------------------------------------------------- /AHItoDICOMInterface/AHIClientFactory.py: -------------------------------------------------------------------------------- 1 | """ 2 | AHItoDICOM Module : This class contains the logic to create the AHI boto3 client. 3 | 4 | SPDX-License-Identifier: Apache-2.0 5 | """ 6 | import boto3 7 | import botocore 8 | import tempfile 9 | import logging 10 | 11 | 12 | 13 | class AHIClientFactory(object): 14 | 15 | 16 | def __init__(self) -> None: 17 | pass 18 | 19 | def __new__(self , aws_access_key : str = None , aws_secret_key : str = None , aws_accendpoint_url : str = None): 20 | try: 21 | session = boto3.Session() 22 | # session._loader.search_paths.extend([tempfile.gettempdir()]) 23 | AHIclient = boto3.client('medical-imaging', aws_access_key_id = aws_access_key , aws_secret_access_key = aws_secret_key , endpoint_url=aws_accendpoint_url , config=botocore.config.Config(max_pool_connections=200) ) 24 | return AHIclient 25 | except Exception as AHIErr: 26 | logging.error(f"[AHIClientFactory] - {AHIErr}") 27 | return None -------------------------------------------------------------------------------- /AHItoDICOMInterface/AHIDataDICOMizer.py: -------------------------------------------------------------------------------- 1 | """ 2 | AHItoDICOM Module : This class contains the logic to encapsulate the data and the pixels into a DICOM object. 3 | 4 | SPDX-License-Identifier: Apache-2.0 5 | """ 6 | from time import sleep 7 | from multiprocessing import Process , Queue , Value , Manager 8 | from ctypes import c_char_p 9 | import pydicom 10 | import logging 11 | from pydicom.sequence import Sequence 12 | from pydicom import Dataset , DataElement , multival 13 | from pydicom.dataset import FileDataset, FileMetaDataset 14 | from pydicom.uid import UID 15 | import base64 16 | 17 | 18 | class AHIDataDICOMizer(): 19 | 20 | ds = Dataset() 21 | InstanceId = None 22 | thread_running = None 23 | AHI_metadata = None 24 | process = None 25 | status = None 26 | logger = None 27 | 28 | 29 | def __init__(self, InstanceId, AHI_metadata) -> None: 30 | self.logger = logging.getLogger(__name__) 31 | self.InstanceId = InstanceId 32 | self.DICOMizeJobs = Queue() 33 | self.DICOMizeJobsCompleted = Queue() 34 | self.AHI_metadata = AHI_metadata 35 | manager = Manager() 36 | self.thread_running = manager.Value('i', 1) 37 | self.status = manager.Value(c_char_p, "idle") 38 | self.process = Process(target = self.ProcessJobs , args=(self.DICOMizeJobs, self.DICOMizeJobsCompleted, self.status , self.thread_running , self.InstanceId)) 39 | self.process.start() 40 | 41 | 42 | 43 | 44 | def AddDICOMizeJob(self,FetchJob): 45 | self.DICOMizeJobs.put(FetchJob) 46 | self.logger.debug("[{__name__}][AddDICOMizeJob]["+self.InstanceId+"] - DICOMize Job added "+str(FetchJob)+".") 47 | 48 | def ProcessJobs(self , DICOMizeJobs , DICOMizeJobsCompleted , status , thread_running , InstanceId): 49 | while(bool(thread_running.value)): 50 | if not DICOMizeJobs.empty(): 51 | status.value ="busy" 52 | try: 53 | ImageFrame = DICOMizeJobs.get(block=False) 54 | vrlist = [] 55 | file_meta = FileMetaDataset() 56 | self.ds = FileDataset(None, {}, file_meta=file_meta, preamble=b"\0" * 128) 57 | self.getDICOMVRs(self.AHI_metadata["Study"]["Series"][ImageFrame["SeriesUID"]]["Instances"][ImageFrame["SOPInstanceUID"]]["DICOMVRs"] , vrlist) 58 | PatientLevel = self.AHI_metadata["Patient"]["DICOM"] 59 | self.getTags(PatientLevel, self.ds , vrlist) 60 | StudyLevel = self.AHI_metadata["Study"]["DICOM"] 61 | self.getTags(StudyLevel, self.ds , vrlist) 62 | SeriesLevel=self.AHI_metadata["Study"]["Series"][ImageFrame["SeriesUID"]]["DICOM"] 63 | self.getTags(SeriesLevel, self.ds , vrlist) 64 | InstanceLevel=self.AHI_metadata["Study"]["Series"][ImageFrame["SeriesUID"]]["Instances"][ImageFrame["SOPInstanceUID"]]["DICOM"] 65 | self.getTags(InstanceLevel , self.ds , vrlist) 66 | self.ds.file_meta.TransferSyntaxUID = pydicom.uid.ExplicitVRLittleEndian 67 | self.ds.is_little_endian = True 68 | self.ds.is_implicit_VR = False 69 | file_meta.MediaStorageSOPInstanceUID = UID(ImageFrame["SOPInstanceUID"]) 70 | pixels = ImageFrame["PixelData"] 71 | if (pixels is not None): 72 | self.ds.PixelData = pixels 73 | vrlist.clear() 74 | DICOMizeJobsCompleted.put(self.ds) 75 | except Exception as DICOMizeError: 76 | print("ERROR") 77 | DICOMizeJobsCompleted.put(None) 78 | self.logger.error(f"[{__name__}][{str(self.InstanceId)}] - {DICOMizeError}") 79 | else: 80 | status.value = 'idle' 81 | sleep(0.1) 82 | self.logger.debug(f" DICOMizer Process {InstanceId} : {status.value}") 83 | status.value ="stopped" 84 | self.logger.debug(f" DICOMizer Process {InstanceId} : {status.value}") 85 | 86 | def getFramesDICOMized(self): 87 | if not self.DICOMizeJobsCompleted.empty(): 88 | obj = self.DICOMizeJobsCompleted.get() 89 | return obj 90 | else: 91 | return None 92 | 93 | def getDataset(self): 94 | return self.ds 95 | 96 | 97 | def getDICOMVRs(self,taglevel, vrlist): 98 | for theKey in taglevel: 99 | vrlist.append( [ theKey , taglevel[theKey] ]) 100 | self.logger.debug(f"[{__name__}][getDICOMVRs] - List of private tags VRs: {vrlist}\r\n") 101 | 102 | 103 | 104 | def getTags(self,tagLevel, ds , vrlist): 105 | for theKey in tagLevel: 106 | try: 107 | try: 108 | tagvr = pydicom.datadict.dictionary_VR(theKey) 109 | except: #In case the vr is not in the pydicom dictionnary, it might be a private tag , listed in the vrlist 110 | tagvr = None 111 | for vr in vrlist: 112 | if theKey == vr[0]: 113 | tagvr = vr[1] 114 | datavalue=tagLevel[theKey] 115 | #print(f"{theKey} : {datavalue}") 116 | if(tagvr == 'SQ'): 117 | #self.logger.debug(f"{theKey} : {tagLevel[theKey]} , {vrlist}") 118 | seqs = [] 119 | for underSeq in tagLevel[theKey]: 120 | seqds = Dataset() 121 | self.getTags(underSeq, seqds, vrlist) 122 | seqs.append(seqds) 123 | datavalue = Sequence(seqs) 124 | if(tagvr == 'US or SS'): 125 | datavalue=tagLevel[theKey] 126 | if isinstance(datavalue, int): #this could be a multi value element. 127 | if (int(datavalue) > 32767): 128 | tagvr = 'US' 129 | else: 130 | tagvr = 'SS' 131 | else: 132 | tagvr = 'US' 133 | if( tagvr in [ 'OB' , 'OD' , 'OF', 'OL', 'OW', 'UN' , 'OB or OW' ] ): 134 | base64_str = tagLevel[theKey] 135 | base64_bytes = base64_str.encode('utf-8') 136 | datavalue = base64.decodebytes(base64_bytes) 137 | data_element = DataElement(theKey , tagvr , datavalue ) 138 | if data_element.tag.group != 2: 139 | try: 140 | ds.add(data_element) 141 | except: 142 | continue 143 | except Exception as err: 144 | self.logger.warning(f"[{__name__}][getTags] - {err}") 145 | continue 146 | 147 | def Dispose(self): 148 | self.thread_running.value = 0 149 | self.process.kill() -------------------------------------------------------------------------------- /AHItoDICOMInterface/AHIFrameFetcher.py: -------------------------------------------------------------------------------- 1 | """ 2 | AHItoDICOM Module : This class contains the logic to query the Image pixel raster. 3 | 4 | SPDX-License-Identifier: Apache-2.0 5 | """ 6 | from multiprocessing import Process , Queue 7 | import logging 8 | from openjpeg import decode 9 | import io 10 | from .AHIClientFactory import * 11 | import time 12 | from multiprocessing.pool import ThreadPool 13 | 14 | 15 | class AHIFrameFetcher: 16 | 17 | 18 | status = 'idle' 19 | FetchJobs = None 20 | FetchJobsCompleted = None 21 | FetchJobsInError = None 22 | InstanceId= None 23 | client = None 24 | thread_running = True 25 | process = None 26 | aws_access_key = None 27 | aws_secret_key = None 28 | AHI_endpoint = None 29 | logger = None 30 | 31 | def __init__(self, InstanceId , aws_access_key , aws_secret_key , AHI_endpoint = None , ahi_client = None): 32 | self.logger = logging.getLogger(__name__) 33 | self.InstanceId = InstanceId 34 | self.FetchJobs = Queue() 35 | self.FetchJobsCompleted = Queue() 36 | self.FetchJobsInError = Queue() 37 | self.aws_secret_key = aws_access_key 38 | self.aws_secret_key = aws_secret_key 39 | self.AHI_endpoint = AHI_endpoint 40 | self.ahi_client = ahi_client 41 | self.process = Process(target = self.ProcessJobs , args=(self.FetchJobs,self.FetchJobsCompleted, self.FetchJobsInError , self.aws_access_key , self.aws_secret_key , self.AHI_endpoint , self.ahi_client)) 42 | self.process.start() 43 | 44 | def AddFetchJob(self,FetchJob): 45 | self.FetchJobs.put(FetchJob) 46 | self.logger.debug("[{__name__}]["+self.InstanceId+"] - Fetch Job added "+str(FetchJob)+".") 47 | 48 | def ProcessJobs(self,FetchJobs : Queue, FetchJobsCompleted : Queue , FetchJobsInError : Queue , aws_access_key : str = None , aws_secret_key : str = None , AHI_endpoint : str = None , ahi_client = None): 49 | if ahi_client is None: 50 | ahi_client = AHIClientFactory( aws_access_key= aws_access_key , aws_secret_key=aws_secret_key , aws_accendpoint_url=AHI_endpoint ) 51 | while(self.thread_running): 52 | if not FetchJobs.empty(): 53 | try: 54 | entry = FetchJobs.get(block=False) 55 | if(len(entry["frameIds"]) > 2): 56 | self.logger.debug("Multiframes fetch via threadPool") 57 | map_ite = [] 58 | i = 1 59 | for frameId in entry["frameIds"]: 60 | function_args = (entry["datastoreId"], entry["imagesetId"], frameId , i , ahi_client ) 61 | map_ite.append(function_args) 62 | i = i + 1 63 | with ThreadPool(100) as pool: 64 | framesToOrder = [] 65 | results = pool.map_async(GetFramePixels, map_ite , chunksize=5 ) 66 | results.wait() 67 | for result in results.get(): 68 | frame_number , pixels = result 69 | framesToOrder.append({ "frame_number" : frame_number , "pixels" : pixels}) 70 | framesToOrder.sort(key=lambda x: x["frame_number"]) 71 | entry["PixelData"] = b'' 72 | for frame in framesToOrder: 73 | result = frame["pixels"] 74 | entry["PixelData"] = entry["PixelData"] + result 75 | else: 76 | self.logger.debug(f"single frame fetch for {entry['datastoreId']}/{entry['imagesetId']}/{entry['frameIds'][0]}") 77 | frame_number , entry["PixelData"] = GetFramePixels( (entry["datastoreId"], entry["imagesetId"], entry["frameIds"][0] , 1 , ahi_client)) 78 | FetchJobsCompleted.put(entry) 79 | except Exception as e: 80 | self.logger.error("[{__name__}]["+self.InstanceId+"] - Error while processing job "+str(entry)+" : "+str(e)) 81 | FetchJobsInError.put(entry) 82 | else: 83 | time.sleep(0.1) 84 | 85 | def getFramesFetched(self): 86 | if not self.FetchJobsCompleted.empty() : 87 | obj = self.FetchJobsCompleted.get(block=False) 88 | return obj 89 | else: 90 | return None 91 | 92 | def Dispose(self): 93 | self.thread_running = False 94 | self.process.kill() 95 | 96 | 97 | def GetFramePixels( val ): 98 | datastoreId = val[0] 99 | imagesetId = val[1] 100 | imageFrameId = val[2] 101 | frame_number = val[3] 102 | client = val[4] 103 | 104 | try: 105 | res = client.get_image_frame( 106 | datastoreId=datastoreId, 107 | imageSetId=imagesetId, 108 | imageFrameInformation= {'imageFrameId' : imageFrameId}) 109 | b = io.BytesIO() 110 | b.write(res['imageFrameBlob'].read()) 111 | b.seek(0) 112 | d = decode(b).tobytes() 113 | return frame_number , d 114 | except Exception as e: 115 | logging.error("[{__name__}] - Frame could not be decoded.") 116 | logging.error(e) 117 | return None -------------------------------------------------------------------------------- /AHItoDICOMInterface/AHItoDICOM.py: -------------------------------------------------------------------------------- 1 | """ 2 | AHItoDICOM Module : This class contains the logic to query the Image pixel raster. 3 | 4 | SPDX-License-Identifier: Apache-2.0 5 | """ 6 | 7 | from .AHIDataDICOMizer import * 8 | from .AHIFrameFetcher import * 9 | from .AHIClientFactory import * 10 | import json 11 | import logging 12 | import collections 13 | from threading import Thread 14 | from time import sleep 15 | from PIL import Image 16 | import gzip 17 | import tempfile 18 | import os 19 | import shutil 20 | import multiprocessing as mp 21 | 22 | 23 | 24 | 25 | class AHItoDICOM: 26 | 27 | AHIclient = None 28 | frameFetcherThreadList = [] 29 | frameDICOMizerThreadList = [] 30 | fetcherProcessCount = None 31 | DICOMizerProcessCount = None 32 | ImageFrames = None 33 | frameToDICOMize = None 34 | FrameDICOMizerPoolManager = None 35 | DICOMizedFrames = None 36 | CountToDICOMize = 0 37 | still_processing = False 38 | aws_access_key = None 39 | aws_secret_key = None 40 | AHI_endpoint = None 41 | logger = None 42 | 43 | def __init__(self, aws_access_key : str = None, aws_secret_key : str = None , AHI_endpoint : str = None , fetcher_process_count : int = None , dicomizer_process_count : int = None ) -> None: 44 | """ 45 | Helper class constructor. 46 | 47 | :param aws_access_key: Optional IAM user access key. 48 | :param aws_secret_key: Optional IAM user secret key. 49 | :param AHI_endpoint: Optional AHI endpoint URL. Only useful to AWS employees. 50 | :param fetcher_process_count: Optional number of processes to use for fetching frames. Will default to CPU count x 8 51 | :param dicomizer_process_count: Optional number of processes to use for DICOMizing frames.Will default to CPU count. 52 | """ 53 | self.logger = logging.getLogger(__name__) 54 | self.ImageFrames = collections.deque() 55 | self.frameToDICOMize = collections.deque() 56 | self.DICOMizedFrames = collections.deque() 57 | self.aws_access_key = aws_access_key 58 | self.aws_secret_key = aws_secret_key 59 | self.AHI_endpoint = AHI_endpoint 60 | if fetcher_process_count is None: 61 | self.fetcherProcessCount = int(os.cpu_count()) * 8 62 | else: 63 | self.fetcherProcessCount = fetcher_process_count 64 | if dicomizer_process_count is None: 65 | self.DICOMizerProcessCount = int(os.cpu_count()) 66 | else: 67 | self.DICOMizerProcessCount = dicomizer_process_count 68 | 69 | self.logger.debug(f"[{__name__}] - Fetcher process count : {self.fetcherProcessCount} , DICOMizer process count : {self.DICOMizerProcessCount}") 70 | #mp.set_start_method('fork') 71 | 72 | def DICOMizeByStudyInstanceUID(self, datastore_id : str = None , study_instance_uid : str = None , header_only : bool = False): 73 | """ 74 | DICOMizeByStudyInstanceUID(datastore_id : str = None , study_instance_uid : str = None). 75 | 76 | :param datastore_id: The datastoreId containtaining the DICOM Study. 77 | :param study_instance_uid: The StudyInstanceUID (0020,000d) of the Study to be DICOMized from AHI. 78 | :return: A list of pydicom DICOM objects. 79 | """ 80 | search_criteria = { 81 | 'filters': [ 82 | { 83 | 'values': [ 84 | { 85 | 'DICOMStudyInstanceUID': study_instance_uid 86 | } 87 | ], 88 | 'operator': 'EQUAL' 89 | } 90 | ] 91 | } 92 | client = AHIClientFactory(self.aws_access_key , self.aws_secret_key , self.AHI_endpoint ) 93 | search_result = client.search_image_sets(datastoreId=datastore_id, searchCriteria = search_criteria) ### in theory we should check if a continuation token is returned and loop until we have all the results... 94 | instances = [] 95 | for imageset in search_result["imageSetsMetadataSummaries"]: 96 | current_imageset = imageset["imageSetId"] 97 | self.logger.debug(f"[{__name__}] - Exporting {current_imageset} instances in memory.") 98 | instances += self.DICOMizeImageSet(datastore_id=datastore_id , image_set_id=current_imageset , header_only=header_only) 99 | 100 | return instances 101 | 102 | def DICOMizeImageSet(self, datastore_id : str = None , imageset_id : str = None, image_set_id : str = None , header_only = False): 103 | """ 104 | DICOMizeImageSet(datastore_id : str = None , imageset_id : str = None). 105 | 106 | :param datastore_id: The datastoreId containing the DICOM Study. 107 | :param imageset_id: The ImageSetID of the data to be DICOMized from AHI. 108 | :return: A list of pydicom DICOM objects. 109 | """ 110 | 111 | #this is to prevent breaking changes in imageset_id paramater name. 112 | if image_set_id is not None and imageset_id is None: 113 | imageset_id = image_set_id 114 | 115 | self.ImageFrames = collections.deque() 116 | self.frameToDICOMize = collections.deque() 117 | self.DICOMizedFrames = collections.deque() 118 | client = AHIClientFactory(self.aws_access_key , self.aws_secret_key , self.AHI_endpoint ) 119 | self.still_processing = True 120 | self.FrameDICOMizerPoolManager = Thread(target = self.AssignDICOMizeJob) 121 | AHI_metadata = self.getMetadata(datastore_id, imageset_id, client) 122 | if AHI_metadata is None: 123 | self.logger.error(f"[{__name__}] - No metadata found for datastore_id : {datastore_id} , imageset_id : {imageset_id}") 124 | return None 125 | #threads init for Frame fetching and DICOM encapsulation 126 | self._initFetchAndDICOMizeProcesses(AHI_metadata=AHI_metadata ) 127 | series = self.getSeriesList(AHI_metadata , imageset_id)[0] 128 | self.ImageFrames.extendleft(self.getImageFrames(datastore_id, imageset_id , AHI_metadata , series["SeriesInstanceUID"])) 129 | instanceCount = len(self.ImageFrames) 130 | self.logger.debug(f"[{__name__}] - Importing {instanceCount} instances in memory.") 131 | self.CountToDICOMize = instanceCount 132 | self.FrameDICOMizerPoolManager.start() 133 | 134 | #Assigning jobs to the Frame fetching thread pool. 135 | if ( header_only == False): 136 | threadId = 0 137 | while(len(self.ImageFrames)> 0): 138 | self.frameFetcherThreadList[threadId].AddFetchJob(self.ImageFrames.popleft()) 139 | threadId+=1 140 | if threadId == self.fetcherProcessCount : 141 | threadId = 0 142 | instanceFetchedCount = 0 143 | while(instanceFetchedCount < (instanceCount)): 144 | self.logger.debug(f"Done {instanceFetchedCount}/{instanceCount}") 145 | for x in range(self.fetcherProcessCount): 146 | entry=self.frameFetcherThreadList[x].getFramesFetched() 147 | if entry is not None: 148 | instanceFetchedCount+=1 149 | self.frameToDICOMize.append(entry) 150 | sleep(0.01) 151 | self.logger.debug("All frames Fetched and submitted to the DICOMizer queue") 152 | 153 | else: 154 | while(len(self.ImageFrames)> 0): 155 | self.frameToDICOMize.append(self.ImageFrames.popleft()) 156 | for x in range(self.fetcherProcessCount): 157 | self.logger.debug(f"[{__name__}] - Disposing frame fetcher thread # {x}") 158 | self.frameFetcherThreadList[x].Dispose() 159 | self.logger.debug(f"[{__name__}] - frame fetcher thread # {x} disposed.") 160 | 161 | while(self.still_processing == True): 162 | self.logger.debug(f"[{__name__}] - Still processing DICOMizing...") 163 | sleep(0.1) 164 | 165 | returnlist = list(self.DICOMizedFrames) 166 | returnlist.sort( key= self.getInstanceNumberInDICOM) 167 | return returnlist 168 | 169 | 170 | 171 | 172 | def AssignDICOMizeJob(self): 173 | #this function rounds robin accross all the dicomizer threads, until all the images are actually dicomized. 174 | self.logger.debug(f"[AssignDICOMizeJob] - DICOMizer Thread Assigner started.") 175 | keep_running = True 176 | 177 | 178 | while( keep_running): 179 | while( len(self.frameToDICOMize) > 0): 180 | threadId = 0 181 | self.frameDICOMizerThreadList[threadId].AddDICOMizeJob(self.frameToDICOMize.popleft()) 182 | threadId+=1 183 | if(threadId == self.DICOMizerProcessCount): 184 | threadId = 0 185 | 186 | for x in range(self.DICOMizerProcessCount): 187 | while( not self.frameDICOMizerThreadList[x].DICOMizeJobsCompleted.empty()): 188 | self.DICOMizedFrames.append(self.frameDICOMizerThreadList[x].getFramesDICOMized()) 189 | dc = len(self.DICOMizedFrames) 190 | #print(dc) 191 | 192 | if(len(self.DICOMizedFrames) == self.CountToDICOMize): 193 | keep_running = False 194 | self.logger.debug(f"[{__name__}] - DICOMized count : {dc}") 195 | for x in range(self.DICOMizerProcessCount): 196 | self.logger.debug(f"[{__name__}] - Disposing DICOMizer thread # {x}") 197 | self.frameDICOMizerThreadList[x].Dispose() 198 | self.logger.debug(f"[{__name__}] - DICOMizer thread # {x} Disposed.") 199 | self.still_processing = False 200 | else: 201 | sleep(0.05) 202 | 203 | self.logger.debug(f"[AssignDICOMizeJob] - DICOMizer Thread Assigner finished.") 204 | 205 | def getImageFrames(self, datastoreId, imagesetId , AHI_metadata , seriesUid) -> collections.deque: 206 | instancesList = [] 207 | for instances in AHI_metadata["Study"]["Series"][seriesUid]["Instances"]: 208 | if len(AHI_metadata["Study"]["Series"][seriesUid]["Instances"][instances]["ImageFrames"]) < 1: 209 | self.logger.info("Skipping the following instance because it do not contain ImageFrames: " + instances) 210 | continue 211 | try: 212 | frameIds = [] 213 | for imageFrame in AHI_metadata["Study"]["Series"][seriesUid]["Instances"][instances]["ImageFrames"]: 214 | frameIds.append(imageFrame["ID"]) 215 | InstanceNumber = AHI_metadata["Study"]["Series"][seriesUid]["Instances"][instances]["DICOM"]["InstanceNumber"] 216 | instancesList.append( { "datastoreId" : datastoreId, "imagesetId" : imagesetId , "frameIds" : frameIds , "SeriesUID" : seriesUid , "SOPInstanceUID" : instances, "InstanceNumber" : InstanceNumber , "PixelData" : None}) 217 | except Exception as AHIErr: 218 | self.logger.error(f"[{__name__}] - {AHIErr}") 219 | instancesList.sort(key=self.getInstanceNumber) 220 | return collections.deque(instancesList) 221 | 222 | def getSeriesList(self, AHI_metadata , image_set_id : str): 223 | ###07/25/2023 - awsjpleger : this function is from a time when there could be multiple series withing a single ImageSetId. Still works with new AHI metadata, but should be refactored. 224 | seriesList = [] 225 | for series in AHI_metadata["Study"]["Series"]: 226 | SeriesNumber = AHI_metadata["Study"]["Series"][series]["DICOM"]["SeriesNumber"] 227 | Modality = AHI_metadata["Study"]["Series"][series]["DICOM"]["Modality"] 228 | try: # This is a non-mandatory tag 229 | SeriesDescription = AHI_metadata["Study"]["Series"][series]["DICOM"]["SeriesDescription"] 230 | except: 231 | SeriesDescription = "" 232 | SeriesInstanceUID = series 233 | try: 234 | instanceCount = len(AHI_metadata["Study"]["Series"][series]["Instances"]) 235 | except: 236 | instanceCount = 0 237 | seriesList.append({ "ImageSetId" : image_set_id, "SeriesNumber" : SeriesNumber , "Modality" : Modality , "SeriesDescription" : SeriesDescription , "SeriesInstanceUID" : SeriesInstanceUID , "InstanceCount" : instanceCount}) 238 | return seriesList 239 | 240 | def getMetadata(self, datastore_id, imageset_id , client = None): 241 | """ 242 | getMetadata(datastore_id : str = None , image_set_id : str , client : str = None). 243 | 244 | :param datastore_id: The datastoreId containtaining the DICOM Study. 245 | :param image_set_id: The ImageSetID of the data to be DICOMized from AHI. 246 | :param client: Optional boto3 medical-imaging client. The functions creates its own client by default. 247 | :return: a JSON structure corresponding to the ImageSet Metadata. 248 | """ 249 | try: 250 | if client is None: 251 | client = AHIClientFactory(self.aws_access_key , self.aws_secret_key , self.AHI_endpoint ) 252 | AHI_study_metadata = client.get_image_set_metadata(datastoreId=datastore_id , imageSetId=imageset_id) 253 | json_study_metadata = gzip.decompress(AHI_study_metadata["imageSetMetadataBlob"].read()) 254 | json_study_metadata = json.loads(json_study_metadata) 255 | return json_study_metadata 256 | except Exception as AHIErr : 257 | self.logger.error(f"[{__name__}] - {AHIErr}") 258 | return None 259 | 260 | def getImageSetToSeriesUIDMap(self, datastore_id : str, study_instance_uid : str ): 261 | """ 262 | getImageSetToSeriesUIDMap(datastore_id : str = None , study_instance_uid : str). 263 | 264 | :param datastore_id: The datastoreId containtaining the DICOM Study. 265 | :param study_instance_uid: The StudyInstanceUID (0020,000d) of the Study to be DICOMized from AHI. 266 | :return: An array of Series descriptors associated to their ImageSetIDs for all the ImageSets related to the DICOM Study. 267 | """ 268 | search_criteria = { 269 | 'filters': [ 270 | { 271 | 'values': [ 272 | { 273 | 'DICOMStudyInstanceUID': study_instance_uid 274 | } 275 | ], 276 | 'operator': 'EQUAL' 277 | } 278 | ] 279 | } 280 | client = AHIClientFactory(self.aws_access_key , self.aws_secret_key , self.AHI_endpoint ) 281 | search_result = client.search_image_sets(datastoreId=datastore_id, searchCriteria = search_criteria) ### in theory we should check if a continuation token is returned and loop until we have all the results... 282 | series_map = [] 283 | for imageset in search_result["imageSetsMetadataSummaries"]: 284 | current_imageset = imageset["imageSetId"] 285 | series_map.append(self.getSeriesList(self.getMetadata(datastore_id, current_imageset ) , current_imageset)[0]) 286 | return series_map 287 | 288 | 289 | def getInstanceNumber(self, elem): 290 | return int(elem["InstanceNumber"]) 291 | 292 | def getInstanceNumberInDICOM(self, elem): 293 | return int(elem["InstanceNumber"].value) 294 | 295 | def saveAsPngPIL(self, ds: Dataset , destination : str): 296 | """ 297 | saveAsPngPIL(ds : pydicom.Dataset , destination : str). 298 | Saves a PNG representation of the DICOM object to the specified destination. 299 | 300 | :param ds: The pydicom Dataset representing the DICOM object. 301 | :param destination: the file path where the file needs to be dumped to. the file path must include the file name and extension. 302 | """ 303 | try: 304 | folder_path = os.path.dirname(destination) 305 | os.makedirs( folder_path , exist_ok=True) 306 | import numpy as np 307 | shape = ds.pixel_array.shape 308 | image_2d = ds.pixel_array.astype(float) 309 | image_2d_scaled = (np.maximum(image_2d,0) / image_2d.max()) * 255.0 310 | image_2d_scaled = np.uint8(image_2d_scaled) 311 | if 'PhotometricInterpretation' in ds and ds.PhotometricInterpretation == "MONOCHROME1": 312 | image_2d_scaled = np.max(image_2d_scaled) - image_2d_scaled 313 | img = Image.fromarray(image_2d_scaled) 314 | img.save(destination, 'png') 315 | except Exception as err: 316 | self.logger.error(f"[{__name__}][saveAsPngPIL] - {err}") 317 | return False 318 | return True 319 | 320 | # def getSeries(self, datastore_id : str = None , image_set_id : str = None): 321 | # AHI_metadata = self.getMetadata(datastore_id, image_set_id, self.AHIclient) 322 | # seriesList = self.getSeriesList(AHI_metadata=AHI_metadata) 323 | # return seriesList 324 | 325 | def _initFetchAndDICOMizeProcesses(self, AHI_metadata): 326 | self.frameDICOMizerThreadList = [] 327 | self.frameDICOMizerThreadList = [] 328 | self.frameFetcherThreadList.clear() 329 | self.frameDICOMizerThreadList.clear() 330 | for x in range(self.fetcherProcessCount): 331 | self.logger.debug("[DICOMize] - Spawning AHIFrameFetcher thread # "+str(x)) 332 | self.frameFetcherThreadList.append(AHIFrameFetcher(str(x), self.aws_access_key , self.aws_access_key , self.AHI_endpoint )) 333 | for x in range(self.DICOMizerProcessCount): 334 | self.logger.debug("[DICOMize] - Spawning AHIDICOMizer thread # "+str(x)) 335 | self.frameDICOMizerThreadList.append(AHIDataDICOMizer(str(x) , AHI_metadata )) 336 | 337 | def saveAsDICOM(self, ds : pydicom.Dataset , destination : str = './out' ) -> bool: 338 | """ 339 | saveAsDICOM(ds : pydicom.Dataset , destination : str). 340 | Saves a DICOM Part10 file for the DICOM object to the specified destination. 341 | 342 | :param ds: The pydicom Dataset representing the DICOM object. 343 | :param destination: the folder path where to save the DICOM file to. The file name will be the SOPInstanceUID of the DICOM object suffixed by '.dcm'. 344 | """ 345 | try: 346 | os.makedirs( destination , exist_ok=True) 347 | filename = os.path.join( destination , ds["SOPInstanceUID"].value) 348 | ds.save_as(f"{filename}.dcm", write_like_original=False) 349 | except Exception as err: 350 | self.logger.error(f"[{__name__}][saveAsDICOM] - {err}") 351 | return False 352 | return True 353 | -------------------------------------------------------------------------------- /AHItoDICOMInterface/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | AHItoDICOMInterface. 3 | 4 | A package to simply export DICOM dataset in memory or on the file system.. 5 | """ 6 | 7 | __version__ = "0.1.3.4" 8 | __author__ = 'JP Leger' 9 | __credits__ = '' -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *main* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 41 | 42 | 43 | ## Finding contributions to work on 44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start. 45 | 46 | 47 | ## Code of Conduct 48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 50 | opensource-codeofconduct@amazon.com with any additional questions or comments. 51 | 52 | 53 | ## Security issue notifications 54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 55 | 56 | 57 | ## Licensing 58 | 59 | See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 60 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AWS HealthImaging DICOM Exporter module 2 | 3 | This project is a multi-processed python 3.8+ module facilitating the load of DICOM datasets stored in AWS HealthImaging into the memory or exported to the file system . 4 | 5 | ## Getting started 6 | 7 | This module can be installed with the python pip utility. 8 | 9 | 1. Clone this repository: 10 | ```terminal 11 | git clone https://github.com/aws-samples/healthlake-imaging-to-dicom-python-module.git 12 | ``` 13 | 2. Locate your terminal in the cloned folder. 14 | 3. Execute the below command to install the modudle via pip : 15 | ```terminal 16 | pip install . 17 | ``` 18 | 19 | ## How to use this module 20 | 21 | To use this module you need to import the AHItoDICOM class and instantiate the AHItoDICOM helper: 22 | 23 | ```python 24 | from AHItoDICOMInterface.AHItoDICOM import AHItoDICOM 25 | 26 | helper = AHItoDICOM( AHI_endpoint= AHIEndpoint , fetcher_process_count=fetcher_count , dicomizer_process_count=dicomizer_count) 27 | ``` 28 | 29 | Once the helper is instanciated, you can call th DICOMize() function to export DICOM data from AHI into the memory, as pydicom dataset array. 30 | 31 | ```python 32 | instances = helper.DICOMizeImageSet(datastore_id=datastoreId , image_set_id=imageSetId) 33 | ``` 34 | 35 | ## Available functions 36 | 37 | |Function|Description| 38 | |--------|-----------| 39 | AHItoDICOM(
aws_access_key : str = None,
aws_secret_key : str = None ,
AHI_endpoint : str = None,
fetcher_process_count : int = None,
dicomizer_process_count : int = None )| Use to instantiate the helper. All paraneters are non-mandatory.

aws_access_key & aws_secret_key and : Can be used if there is no default credentials configured in the aws client, or if the code runs in an environment not supporting IAM profile.
AHI_endpoint : Only useful to AWS employees. Other users should let this value set to None.
fetcher_process_count : This parameter defines the number of fetcher processes to instanciate to fetch and uncompress the frames. By default the module will create 4 x the number of cores.
dicomizer_process_count : This parameter defines the number of DICOMizer processes to instanciate to create the pydicom datasets. By default the module will create 1 x the number of cores.| 40 | |DICOMizeImageSet(datastore_id: str, image_set_id: str)| Use to request the pydicom datasets to be loaded in memory.

datastore_id : The AHI datastore where the ImageSet is stored.
image_set_id : The AHI ImageSet Id of the image collection requested.
| 41 | |DICOMizeByStudyInstanceUID(datastore_id: str, study_instance_uid: str)| Use to request the pydicom datasets to be loaded in memory.

datastore_id : The AHI datastore where the ImageSet is stored.
study_instance_uid : The DICOM study instance uid of the Study to export.
| 42 | |getImageSetToSeriesUIDMap(datastore_id: str, study_instance_uid: str)| Returns an array of thes series descriptors for the given study, associated with theit ImageSetIds. Can be useful to decide which series to later load in memory.

datastore_id : The AHI datastore where the ImageSet is stored.
study_instance_uid : The study instance UID of the DICOM study.

Returns an array of series descriptors like his :
[{'SeriesNumber': '1', 'Modality': 'CT', 'SeriesDescription': 'CT series for liver tumor from nii 014', 'SeriesInstanceUID': '1.2.826.0.1.3680043.2.1125.1.34918616334750294149839565085991567'}]| 43 | |saveAsDICOM(ds: Dataset,
destination : str)| Saves the DICOM in memory object on the filesystem destination.

ds : The pydicom dataset representing the instance. Mostly one instance of the array returned by DICOMize().
destination : The file path where to store the DIOCM P10 file.| 44 | |saveAsPngPIL(ds: Dataset,
destination : str)| Saves a representation of the pixel raster of one instance on the filesystem as PNG.

ds : The pydicom dataset representing the instance. Mostly one instance of the array returned by DICOMize().
destination : The file path where to store the PNG file.| 45 | 46 | ## Code Example 47 | 48 | The file `example/main.py` demonstrates how to use the various functions described above. To use it modifiy the `datastoreId` the `imageSetId` and the `studyInstanceUID` variables in the main function. You can also experiment by changing the `fetcher_count` and `dicomizer_count` parameters for better performance. Below is an example how the example can be started with an environment where the AWS CLI was configure with an IAM user and the region us-east-2 selected as default : 49 | 50 | ``` 51 | $ python3 main.py 52 | python main.py 53 | Getting ImageSet JSON metadata object. 54 | 5 55 | Listing ImageSets and Series info by StudyInstanceUID 56 | [{'ImageSetId': '0aaf9a3b6405bd6d393876806034b1c0', 'SeriesNumber': '3', 'Modality': 'CT', 'SeriesDescription': 'KneeHR 1.0 B60s', 'SeriesInstanceUID': '1.3.6.1.4.1.19291.2.1.2.1140133144321975855136128320349', 'InstanceCount': 74}, {'ImageSetId': '81bfc6aa3416912056e95188ab74870b', 'SeriesNumber': '2', 'Modality': 'CT', 'SeriesDescription': 'KneeHR 3.0 B60s', 'SeriesInstanceUID': '1.3.6.1.4.1.19291.2.1.2.1140133144321975855136128221126', 'InstanceCount': 222}] 57 | DICOMizing by StudyInstanceUID 58 | DICOMizebyStudyInstanceUID 59 | 0aaf9a3b6405bd6d393876806034b1c0 60 | 81bfc6aa3416912056e95188ab74870b 61 | DICOMizing by ImageSetID 62 | 222 DICOMized in 3.3336379528045654. 63 | Exporting images of the ImageSet in png format. 64 | Exporting images of the ImageSet in DICOM P10 format. 65 | ``` 66 | After the example code has returned the file system now contains folders named with the `StudyInstanceUID` of the imageSet exported within the `out` folder. This fodler prefixed with `dcm_` holds the DICOM P10 files for the imageSet. The folder prefixed with `png_` holds PNG image representations of the imageSet. 67 | 68 | ## Using this module in Amazon SageMaker 69 | 70 | This package can be used in Amazon SageMaker by adding the following code to the SageMaker notebook instance 2 first cells: 71 | 72 | ### Cell 1 73 | 74 | ```python 75 | #Install the python packages 76 | %%sh 77 | pip install --upgrade pip --quiet 78 | pip install boto3 botocore awscliv2 AHItoDICOMInterface --upgrade --quiet 79 | 80 | ``` 81 | 82 | ### Cell 2 83 | 84 | ```python 85 | #Restart the Kernel to take the new versions of awscliv2 in account. 86 | import IPython 87 | IPython.Application.instance().kernel.do_shutdown(True) #automatically restarts kernel 88 | ``` 89 | An example of a SageMaker Jupyter notebook using this module is available in the `example` folder of this repository : [jupyter-sagemaker-example.ipynb](./example/jupyter-sagemaker-example.ipynb) 90 | -------------------------------------------------------------------------------- /build/lib/AHItoDICOMInterface/AHIClientFactory.py: -------------------------------------------------------------------------------- 1 | """ 2 | AHItoDICOM Module : This class contains the logic to create the AHI boto3 client. 3 | 4 | SPDX-License-Identifier: Apache-2.0 5 | """ 6 | import boto3 7 | import botocore 8 | import tempfile 9 | import logging 10 | 11 | 12 | 13 | class AHIClientFactory(object): 14 | 15 | 16 | def __init__(self) -> None: 17 | pass 18 | 19 | def __new__(self , aws_access_key : str = None , aws_secret_key : str = None , aws_accendpoint_url : str = None): 20 | try: 21 | session = boto3.Session() 22 | # session._loader.search_paths.extend([tempfile.gettempdir()]) 23 | AHIclient = boto3.client('medical-imaging', aws_access_key_id = aws_access_key , aws_secret_access_key = aws_secret_key , endpoint_url=aws_accendpoint_url , config=botocore.config.Config(max_pool_connections=200) ) 24 | return AHIclient 25 | except Exception as AHIErr: 26 | logging.error(f"[AHIClientFactory] - {AHIErr}") 27 | return None -------------------------------------------------------------------------------- /build/lib/AHItoDICOMInterface/AHIDataDICOMizer.py: -------------------------------------------------------------------------------- 1 | """ 2 | AHItoDICOM Module : This class contains the logic to encapsulate the data and the pixels into a DICOM object. 3 | 4 | SPDX-License-Identifier: Apache-2.0 5 | """ 6 | from time import sleep 7 | from multiprocessing import Process , Queue , Value , Manager 8 | from ctypes import c_char_p 9 | import pydicom 10 | import logging 11 | from pydicom.sequence import Sequence 12 | from pydicom import Dataset , DataElement , multival 13 | from pydicom.dataset import FileDataset, FileMetaDataset 14 | from pydicom.uid import UID 15 | import base64 16 | 17 | 18 | class AHIDataDICOMizer(): 19 | 20 | ds = Dataset() 21 | InstanceId = None 22 | thread_running = None 23 | AHI_metadata = None 24 | process = None 25 | status = None 26 | logger = None 27 | 28 | 29 | def __init__(self, InstanceId, AHI_metadata) -> None: 30 | self.logger = logging.getLogger(__name__) 31 | self.InstanceId = InstanceId 32 | self.DICOMizeJobs = Queue() 33 | self.DICOMizeJobsCompleted = Queue() 34 | self.AHI_metadata = AHI_metadata 35 | manager = Manager() 36 | self.thread_running = manager.Value('i', 1) 37 | self.status = manager.Value(c_char_p, "idle") 38 | self.process = Process(target = self.ProcessJobs , args=(self.DICOMizeJobs, self.DICOMizeJobsCompleted, self.status , self.thread_running , self.InstanceId)) 39 | self.process.start() 40 | 41 | 42 | 43 | 44 | def AddDICOMizeJob(self,FetchJob): 45 | self.DICOMizeJobs.put(FetchJob) 46 | self.logger.debug("[{__name__}][AddDICOMizeJob]["+self.InstanceId+"] - DICOMize Job added "+str(FetchJob)+".") 47 | 48 | def ProcessJobs(self , DICOMizeJobs , DICOMizeJobsCompleted , status , thread_running , InstanceId): 49 | while(bool(thread_running.value)): 50 | if not DICOMizeJobs.empty(): 51 | status.value ="busy" 52 | try: 53 | ImageFrame = DICOMizeJobs.get(block=False) 54 | vrlist = [] 55 | file_meta = FileMetaDataset() 56 | self.ds = FileDataset(None, {}, file_meta=file_meta, preamble=b"\0" * 128) 57 | self.getDICOMVRs(self.AHI_metadata["Study"]["Series"][ImageFrame["SeriesUID"]]["Instances"][ImageFrame["SOPInstanceUID"]]["DICOMVRs"] , vrlist) 58 | PatientLevel = self.AHI_metadata["Patient"]["DICOM"] 59 | self.getTags(PatientLevel, self.ds , vrlist) 60 | StudyLevel = self.AHI_metadata["Study"]["DICOM"] 61 | self.getTags(StudyLevel, self.ds , vrlist) 62 | SeriesLevel=self.AHI_metadata["Study"]["Series"][ImageFrame["SeriesUID"]]["DICOM"] 63 | self.getTags(SeriesLevel, self.ds , vrlist) 64 | InstanceLevel=self.AHI_metadata["Study"]["Series"][ImageFrame["SeriesUID"]]["Instances"][ImageFrame["SOPInstanceUID"]]["DICOM"] 65 | self.getTags(InstanceLevel , self.ds , vrlist) 66 | self.ds.file_meta.TransferSyntaxUID = pydicom.uid.ExplicitVRLittleEndian 67 | self.ds.is_little_endian = True 68 | self.ds.is_implicit_VR = False 69 | file_meta.MediaStorageSOPInstanceUID = UID(ImageFrame["SOPInstanceUID"]) 70 | pixels = ImageFrame["PixelData"] 71 | if (pixels is not None): 72 | self.ds.PixelData = pixels 73 | vrlist.clear() 74 | DICOMizeJobsCompleted.put(self.ds) 75 | except Exception as DICOMizeError: 76 | print("ERROR") 77 | DICOMizeJobsCompleted.put(None) 78 | self.logger.error(f"[{__name__}][{str(self.InstanceId)}] - {DICOMizeError}") 79 | else: 80 | status.value = 'idle' 81 | sleep(0.1) 82 | self.logger.debug(f" DICOMizer Process {InstanceId} : {status.value}") 83 | status.value ="stopped" 84 | self.logger.debug(f" DICOMizer Process {InstanceId} : {status.value}") 85 | 86 | def getFramesDICOMized(self): 87 | if not self.DICOMizeJobsCompleted.empty(): 88 | obj = self.DICOMizeJobsCompleted.get() 89 | return obj 90 | else: 91 | return None 92 | 93 | def getDataset(self): 94 | return self.ds 95 | 96 | 97 | def getDICOMVRs(self,taglevel, vrlist): 98 | for theKey in taglevel: 99 | vrlist.append( [ theKey , taglevel[theKey] ]) 100 | self.logger.debug(f"[{__name__}][getDICOMVRs] - List of private tags VRs: {vrlist}\r\n") 101 | 102 | 103 | 104 | def getTags(self,tagLevel, ds , vrlist): 105 | for theKey in tagLevel: 106 | try: 107 | try: 108 | tagvr = pydicom.datadict.dictionary_VR(theKey) 109 | except: #In case the vr is not in the pydicom dictionnary, it might be a private tag , listed in the vrlist 110 | tagvr = None 111 | for vr in vrlist: 112 | if theKey == vr[0]: 113 | tagvr = vr[1] 114 | datavalue=tagLevel[theKey] 115 | #print(f"{theKey} : {datavalue}") 116 | if(tagvr == 'SQ'): 117 | #self.logger.debug(f"{theKey} : {tagLevel[theKey]} , {vrlist}") 118 | seqs = [] 119 | for underSeq in tagLevel[theKey]: 120 | seqds = Dataset() 121 | self.getTags(underSeq, seqds, vrlist) 122 | seqs.append(seqds) 123 | datavalue = Sequence(seqs) 124 | if(tagvr == 'US or SS'): 125 | datavalue=tagLevel[theKey] 126 | if isinstance(datavalue, int): #this could be a multi value element. 127 | if (int(datavalue) > 32767): 128 | tagvr = 'US' 129 | else: 130 | tagvr = 'SS' 131 | else: 132 | tagvr = 'US' 133 | if( tagvr in [ 'OB' , 'OD' , 'OF', 'OL', 'OW', 'UN' , 'OB or OW' ] ): 134 | base64_str = tagLevel[theKey] 135 | base64_bytes = base64_str.encode('utf-8') 136 | datavalue = base64.decodebytes(base64_bytes) 137 | data_element = DataElement(theKey , tagvr , datavalue ) 138 | if data_element.tag.group != 2: 139 | try: 140 | ds.add(data_element) 141 | except: 142 | continue 143 | except Exception as err: 144 | self.logger.warning(f"[{__name__}][getTags] - {err}") 145 | continue 146 | 147 | def Dispose(self): 148 | self.thread_running.value = 0 149 | self.process.kill() -------------------------------------------------------------------------------- /build/lib/AHItoDICOMInterface/AHIFrameFetcher.py: -------------------------------------------------------------------------------- 1 | """ 2 | AHItoDICOM Module : This class contains the logic to query the Image pixel raster. 3 | 4 | SPDX-License-Identifier: Apache-2.0 5 | """ 6 | from multiprocessing import Process , Queue 7 | import logging 8 | from openjpeg import decode 9 | import io 10 | from .AHIClientFactory import * 11 | import time 12 | from multiprocessing.pool import ThreadPool 13 | 14 | 15 | class AHIFrameFetcher: 16 | 17 | 18 | status = 'idle' 19 | FetchJobs = None 20 | FetchJobsCompleted = None 21 | FetchJobsInError = None 22 | InstanceId= None 23 | client = None 24 | thread_running = True 25 | process = None 26 | aws_access_key = None 27 | aws_secret_key = None 28 | AHI_endpoint = None 29 | logger = None 30 | 31 | def __init__(self, InstanceId , aws_access_key , aws_secret_key , AHI_endpoint = None , ahi_client = None): 32 | self.logger = logging.getLogger(__name__) 33 | self.InstanceId = InstanceId 34 | self.FetchJobs = Queue() 35 | self.FetchJobsCompleted = Queue() 36 | self.FetchJobsInError = Queue() 37 | self.aws_secret_key = aws_access_key 38 | self.aws_secret_key = aws_secret_key 39 | self.AHI_endpoint = AHI_endpoint 40 | self.ahi_client = ahi_client 41 | self.process = Process(target = self.ProcessJobs , args=(self.FetchJobs,self.FetchJobsCompleted, self.FetchJobsInError , self.aws_access_key , self.aws_secret_key , self.AHI_endpoint , self.ahi_client)) 42 | self.process.start() 43 | 44 | def AddFetchJob(self,FetchJob): 45 | self.FetchJobs.put(FetchJob) 46 | self.logger.debug("[{__name__}]["+self.InstanceId+"] - Fetch Job added "+str(FetchJob)+".") 47 | 48 | def ProcessJobs(self,FetchJobs : Queue, FetchJobsCompleted : Queue , FetchJobsInError : Queue , aws_access_key : str = None , aws_secret_key : str = None , AHI_endpoint : str = None , ahi_client = None): 49 | if ahi_client is None: 50 | ahi_client = AHIClientFactory( aws_access_key= aws_access_key , aws_secret_key=aws_secret_key , aws_accendpoint_url=AHI_endpoint ) 51 | while(self.thread_running): 52 | if not FetchJobs.empty(): 53 | try: 54 | entry = FetchJobs.get(block=False) 55 | if(len(entry["frameIds"]) > 2): 56 | self.logger.debug("Multiframes fetch via threadPool") 57 | map_ite = [] 58 | i = 1 59 | for frameId in entry["frameIds"]: 60 | function_args = (entry["datastoreId"], entry["imagesetId"], frameId , i , ahi_client ) 61 | map_ite.append(function_args) 62 | i = i + 1 63 | with ThreadPool(100) as pool: 64 | framesToOrder = [] 65 | results = pool.map_async(GetFramePixels, map_ite , chunksize=5 ) 66 | results.wait() 67 | for result in results.get(): 68 | frame_number , pixels = result 69 | framesToOrder.append({ "frame_number" : frame_number , "pixels" : pixels}) 70 | framesToOrder.sort(key=lambda x: x["frame_number"]) 71 | print("Sorting done") 72 | entry["PixelData"] = b'' 73 | for frame in framesToOrder: 74 | result = frame["pixels"] 75 | entry["PixelData"] = entry["PixelData"] + result 76 | print("Sorting and image reconstruction done") 77 | else: 78 | self.logger.debug(f"single frame fetch for {entry['datastoreId']}/{entry['imagesetId']}/{entry['frameIds'][0]}") 79 | frame_number , entry["PixelData"] = GetFramePixels( (entry["datastoreId"], entry["imagesetId"], entry["frameIds"][0] , 1 , ahi_client)) 80 | FetchJobsCompleted.put(entry) 81 | except Exception as e: 82 | self.logger.error("[{__name__}]["+self.InstanceId+"] - Error while processing job "+str(entry)+" : "+str(e)) 83 | FetchJobsInError.put(entry) 84 | else: 85 | time.sleep(0.1) 86 | 87 | 88 | 89 | def getFramesFetched(self): 90 | if not self.FetchJobsCompleted.empty() : 91 | obj = self.FetchJobsCompleted.get(block=False) 92 | return obj 93 | else: 94 | return None 95 | 96 | 97 | 98 | 99 | def Dispose(self): 100 | self.thread_running = False 101 | self.process.kill() 102 | 103 | def koin( val): 104 | print(val) 105 | 106 | #def GetFramePixels( datastoreId, imagesetId, imageFrameId , frame_number , client ): 107 | def GetFramePixels( val ): 108 | datastoreId = val[0] 109 | imagesetId = val[1] 110 | imageFrameId = val[2] 111 | frame_number = val[3] 112 | client = val[4] 113 | 114 | try: 115 | res = client.get_image_frame( 116 | datastoreId=datastoreId, 117 | imageSetId=imagesetId, 118 | imageFrameInformation= {'imageFrameId' : imageFrameId}) 119 | b = io.BytesIO() 120 | b.write(res['imageFrameBlob'].read()) 121 | b.seek(0) 122 | d = decode(b).tobytes() 123 | return frame_number , d 124 | except Exception as e: 125 | logging.error("[{__name__}] - Frame could not be decoded.") 126 | logging.error(e) 127 | return None -------------------------------------------------------------------------------- /build/lib/AHItoDICOMInterface/AHItoDICOM.py: -------------------------------------------------------------------------------- 1 | """ 2 | AHItoDICOM Module : This class contains the logic to query the Image pixel raster. 3 | 4 | SPDX-License-Identifier: Apache-2.0 5 | """ 6 | 7 | from .AHIDataDICOMizer import * 8 | from .AHIFrameFetcher import * 9 | from .AHIClientFactory import * 10 | import json 11 | import logging 12 | import collections 13 | from threading import Thread 14 | from time import sleep 15 | from PIL import Image 16 | import gzip 17 | import tempfile 18 | import os 19 | import shutil 20 | import multiprocessing as mp 21 | 22 | 23 | 24 | 25 | class AHItoDICOM: 26 | 27 | AHIclient = None 28 | frameFetcherThreadList = [] 29 | frameDICOMizerThreadList = [] 30 | fetcherProcessCount = None 31 | DICOMizerProcessCount = None 32 | ImageFrames = None 33 | frameToDICOMize = None 34 | FrameDICOMizerPoolManager = None 35 | DICOMizedFrames = None 36 | CountToDICOMize = 0 37 | still_processing = False 38 | aws_access_key = None 39 | aws_secret_key = None 40 | AHI_endpoint = None 41 | logger = None 42 | 43 | def __init__(self, aws_access_key : str = None, aws_secret_key : str = None , AHI_endpoint : str = None , fetcher_process_count : int = None , dicomizer_process_count : int = None ) -> None: 44 | """ 45 | Helper class constructor. 46 | 47 | :param aws_access_key: Optional IAM user access key. 48 | :param aws_secret_key: Optional IAM user secret key. 49 | :param AHI_endpoint: Optional AHI endpoint URL. Only useful to AWS employees. 50 | :param fetcher_process_count: Optional number of processes to use for fetching frames. Will default to CPU count x 8 51 | :param dicomizer_process_count: Optional number of processes to use for DICOMizing frames.Will default to CPU count. 52 | """ 53 | self.logger = logging.getLogger(__name__) 54 | self.ImageFrames = collections.deque() 55 | self.frameToDICOMize = collections.deque() 56 | self.DICOMizedFrames = collections.deque() 57 | self.aws_access_key = aws_access_key 58 | self.aws_secret_key = aws_secret_key 59 | self.AHI_endpoint = AHI_endpoint 60 | if fetcher_process_count is None: 61 | self.fetcherProcessCount = int(os.cpu_count()) * 8 62 | else: 63 | self.fetcherProcessCount = fetcher_process_count 64 | if dicomizer_process_count is None: 65 | self.DICOMizerProcessCount = int(os.cpu_count()) 66 | else: 67 | self.DICOMizerProcessCount = dicomizer_process_count 68 | 69 | self.logger.debug(f"[{__name__}] - Fetcher process count : {self.fetcherProcessCount} , DICOMizer process count : {self.DICOMizerProcessCount}") 70 | #mp.set_start_method('fork') 71 | 72 | def DICOMizeByStudyInstanceUID(self, datastore_id : str = None , study_instance_uid : str = None , header_only : bool = False): 73 | """ 74 | DICOMizeByStudyInstanceUID(datastore_id : str = None , study_instance_uid : str = None). 75 | 76 | :param datastore_id: The datastoreId containtaining the DICOM Study. 77 | :param study_instance_uid: The StudyInstanceUID (0020,000d) of the Study to be DICOMized from AHI. 78 | :return: A list of pydicom DICOM objects. 79 | """ 80 | search_criteria = { 81 | 'filters': [ 82 | { 83 | 'values': [ 84 | { 85 | 'DICOMStudyInstanceUID': study_instance_uid 86 | } 87 | ], 88 | 'operator': 'EQUAL' 89 | } 90 | ] 91 | } 92 | client = AHIClientFactory(self.aws_access_key , self.aws_secret_key , self.AHI_endpoint ) 93 | search_result = client.search_image_sets(datastoreId=datastore_id, searchCriteria = search_criteria) ### in theory we should check if a continuation token is returned and loop until we have all the results... 94 | instances = [] 95 | for imageset in search_result["imageSetsMetadataSummaries"]: 96 | current_imageset = imageset["imageSetId"] 97 | self.logger.debug(f"[{__name__}] - Exporting {current_imageset} instances in memory.") 98 | instances += self.DICOMizeImageSet(datastore_id=datastore_id , image_set_id=current_imageset , header_only=header_only) 99 | 100 | return instances 101 | 102 | def DICOMizeImageSet(self, datastore_id : str = None , imageset_id : str = None, image_set_id : str = None , header_only = False): 103 | """ 104 | DICOMizeImageSet(datastore_id : str = None , imageset_id : str = None). 105 | 106 | :param datastore_id: The datastoreId containing the DICOM Study. 107 | :param imageset_id: The ImageSetID of the data to be DICOMized from AHI. 108 | :return: A list of pydicom DICOM objects. 109 | """ 110 | 111 | #this is to prevent breaking changes in imageset_id paramater name. 112 | if image_set_id is not None and imageset_id is None: 113 | imageset_id = image_set_id 114 | 115 | self.ImageFrames = collections.deque() 116 | self.frameToDICOMize = collections.deque() 117 | self.DICOMizedFrames = collections.deque() 118 | client = AHIClientFactory(self.aws_access_key , self.aws_secret_key , self.AHI_endpoint ) 119 | self.still_processing = True 120 | self.FrameDICOMizerPoolManager = Thread(target = self.AssignDICOMizeJob) 121 | AHI_metadata = self.getMetadata(datastore_id, imageset_id, client) 122 | if AHI_metadata is None: 123 | self.logger.error(f"[{__name__}] - No metadata found for datastore_id : {datastore_id} , imageset_id : {imageset_id}") 124 | return None 125 | #threads init for Frame fetching and DICOM encapsulation 126 | self._initFetchAndDICOMizeProcesses(AHI_metadata=AHI_metadata ) 127 | series = self.getSeriesList(AHI_metadata , imageset_id)[0] 128 | self.ImageFrames.extendleft(self.getImageFrames(datastore_id, imageset_id , AHI_metadata , series["SeriesInstanceUID"])) 129 | instanceCount = len(self.ImageFrames) 130 | self.logger.debug(f"[{__name__}] - Importing {instanceCount} instances in memory.") 131 | self.CountToDICOMize = instanceCount 132 | self.FrameDICOMizerPoolManager.start() 133 | 134 | #Assigning jobs to the Frame fetching thread pool. 135 | if ( header_only == False): 136 | threadId = 0 137 | while(len(self.ImageFrames)> 0): 138 | self.frameFetcherThreadList[threadId].AddFetchJob(self.ImageFrames.popleft()) 139 | threadId+=1 140 | if threadId == self.fetcherProcessCount : 141 | threadId = 0 142 | instanceFetchedCount = 0 143 | while(instanceFetchedCount < (instanceCount)): 144 | self.logger.debug(f"Done {instanceFetchedCount}/{instanceCount}") 145 | for x in range(self.fetcherProcessCount): 146 | entry=self.frameFetcherThreadList[x].getFramesFetched() 147 | if entry is not None: 148 | instanceFetchedCount+=1 149 | self.frameToDICOMize.append(entry) 150 | sleep(0.01) 151 | self.logger.debug("All frames Fetched and submitted to the DICOMizer queue") 152 | 153 | else: 154 | while(len(self.ImageFrames)> 0): 155 | self.frameToDICOMize.append(self.ImageFrames.popleft()) 156 | for x in range(self.fetcherProcessCount): 157 | self.logger.debug(f"[{__name__}] - Disposing frame fetcher thread # {x}") 158 | self.frameFetcherThreadList[x].Dispose() 159 | self.logger.debug(f"[{__name__}] - frame fetcher thread # {x} disposed.") 160 | 161 | while(self.still_processing == True): 162 | self.logger.debug(f"[{__name__}] - Still processing DICOMizing...") 163 | sleep(0.1) 164 | 165 | returnlist = list(self.DICOMizedFrames) 166 | returnlist.sort( key= self.getInstanceNumberInDICOM) 167 | return returnlist 168 | 169 | 170 | 171 | 172 | def AssignDICOMizeJob(self): 173 | #this function rounds robin accross all the dicomizer threads, until all the images are actually dicomized. 174 | self.logger.debug(f"[AssignDICOMizeJob] - DICOMizer Thread Assigner started.") 175 | keep_running = True 176 | 177 | 178 | while( keep_running): 179 | while( len(self.frameToDICOMize) > 0): 180 | threadId = 0 181 | self.frameDICOMizerThreadList[threadId].AddDICOMizeJob(self.frameToDICOMize.popleft()) 182 | threadId+=1 183 | if(threadId == self.DICOMizerProcessCount): 184 | threadId = 0 185 | 186 | for x in range(self.DICOMizerProcessCount): 187 | while( not self.frameDICOMizerThreadList[x].DICOMizeJobsCompleted.empty()): 188 | self.DICOMizedFrames.append(self.frameDICOMizerThreadList[x].getFramesDICOMized()) 189 | dc = len(self.DICOMizedFrames) 190 | #print(dc) 191 | 192 | if(len(self.DICOMizedFrames) == self.CountToDICOMize): 193 | keep_running = False 194 | self.logger.debug(f"[{__name__}] - DICOMized count : {dc}") 195 | for x in range(self.DICOMizerProcessCount): 196 | self.logger.debug(f"[{__name__}] - Disposing DICOMizer thread # {x}") 197 | self.frameDICOMizerThreadList[x].Dispose() 198 | self.logger.debug(f"[{__name__}] - DICOMizer thread # {x} Disposed.") 199 | self.still_processing = False 200 | else: 201 | sleep(0.05) 202 | 203 | self.logger.debug(f"[AssignDICOMizeJob] - DICOMizer Thread Assigner finished.") 204 | 205 | def getImageFrames(self, datastoreId, imagesetId , AHI_metadata , seriesUid) -> collections.deque: 206 | instancesList = [] 207 | for instances in AHI_metadata["Study"]["Series"][seriesUid]["Instances"]: 208 | if len(AHI_metadata["Study"]["Series"][seriesUid]["Instances"][instances]["ImageFrames"]) < 1: 209 | self.logger.info("Skipping the following instance because it do not contain ImageFrames: " + instances) 210 | continue 211 | try: 212 | frameIds = [] 213 | for imageFrame in AHI_metadata["Study"]["Series"][seriesUid]["Instances"][instances]["ImageFrames"]: 214 | frameIds.append(imageFrame["ID"]) 215 | InstanceNumber = AHI_metadata["Study"]["Series"][seriesUid]["Instances"][instances]["DICOM"]["InstanceNumber"] 216 | instancesList.append( { "datastoreId" : datastoreId, "imagesetId" : imagesetId , "frameIds" : frameIds , "SeriesUID" : seriesUid , "SOPInstanceUID" : instances, "InstanceNumber" : InstanceNumber , "PixelData" : None}) 217 | except Exception as AHIErr: 218 | self.logger.error(f"[{__name__}] - {AHIErr}") 219 | instancesList.sort(key=self.getInstanceNumber) 220 | return collections.deque(instancesList) 221 | 222 | def getSeriesList(self, AHI_metadata , image_set_id : str): 223 | ###07/25/2023 - awsjpleger : this function is from a time when there could be multiple series withing a single ImageSetId. Still works with new AHI metadata, but should be refactored. 224 | seriesList = [] 225 | for series in AHI_metadata["Study"]["Series"]: 226 | SeriesNumber = AHI_metadata["Study"]["Series"][series]["DICOM"]["SeriesNumber"] 227 | Modality = AHI_metadata["Study"]["Series"][series]["DICOM"]["Modality"] 228 | try: # This is a non-mandatory tag 229 | SeriesDescription = AHI_metadata["Study"]["Series"][series]["DICOM"]["SeriesDescription"] 230 | except: 231 | SeriesDescription = "" 232 | SeriesInstanceUID = series 233 | try: 234 | instanceCount = len(AHI_metadata["Study"]["Series"][series]["Instances"]) 235 | except: 236 | instanceCount = 0 237 | seriesList.append({ "ImageSetId" : image_set_id, "SeriesNumber" : SeriesNumber , "Modality" : Modality , "SeriesDescription" : SeriesDescription , "SeriesInstanceUID" : SeriesInstanceUID , "InstanceCount" : instanceCount}) 238 | return seriesList 239 | 240 | def getMetadata(self, datastore_id, imageset_id , client = None): 241 | """ 242 | getMetadata(datastore_id : str = None , image_set_id : str , client : str = None). 243 | 244 | :param datastore_id: The datastoreId containtaining the DICOM Study. 245 | :param image_set_id: The ImageSetID of the data to be DICOMized from AHI. 246 | :param client: Optional boto3 medical-imaging client. The functions creates its own client by default. 247 | :return: a JSON structure corresponding to the ImageSet Metadata. 248 | """ 249 | try: 250 | if client is None: 251 | client = AHIClientFactory(self.aws_access_key , self.aws_secret_key , self.AHI_endpoint ) 252 | AHI_study_metadata = client.get_image_set_metadata(datastoreId=datastore_id , imageSetId=imageset_id) 253 | json_study_metadata = gzip.decompress(AHI_study_metadata["imageSetMetadataBlob"].read()) 254 | json_study_metadata = json.loads(json_study_metadata) 255 | return json_study_metadata 256 | except Exception as AHIErr : 257 | self.logger.error(f"[{__name__}] - {AHIErr}") 258 | return None 259 | 260 | def getImageSetToSeriesUIDMap(self, datastore_id : str, study_instance_uid : str ): 261 | """ 262 | getImageSetToSeriesUIDMap(datastore_id : str = None , study_instance_uid : str). 263 | 264 | :param datastore_id: The datastoreId containtaining the DICOM Study. 265 | :param study_instance_uid: The StudyInstanceUID (0020,000d) of the Study to be DICOMized from AHI. 266 | :return: An array of Series descriptors associated to their ImageSetIDs for all the ImageSets related to the DICOM Study. 267 | """ 268 | search_criteria = { 269 | 'filters': [ 270 | { 271 | 'values': [ 272 | { 273 | 'DICOMStudyInstanceUID': study_instance_uid 274 | } 275 | ], 276 | 'operator': 'EQUAL' 277 | } 278 | ] 279 | } 280 | client = AHIClientFactory(self.aws_access_key , self.aws_secret_key , self.AHI_endpoint ) 281 | search_result = client.search_image_sets(datastoreId=datastore_id, searchCriteria = search_criteria) ### in theory we should check if a continuation token is returned and loop until we have all the results... 282 | series_map = [] 283 | for imageset in search_result["imageSetsMetadataSummaries"]: 284 | current_imageset = imageset["imageSetId"] 285 | series_map.append(self.getSeriesList(self.getMetadata(datastore_id, current_imageset ) , current_imageset)[0]) 286 | return series_map 287 | 288 | 289 | def getInstanceNumber(self, elem): 290 | return int(elem["InstanceNumber"]) 291 | 292 | def getInstanceNumberInDICOM(self, elem): 293 | return int(elem["InstanceNumber"].value) 294 | 295 | def saveAsPngPIL(self, ds: Dataset , destination : str): 296 | """ 297 | saveAsPngPIL(ds : pydicom.Dataset , destination : str). 298 | Saves a PNG representation of the DICOM object to the specified destination. 299 | 300 | :param ds: The pydicom Dataset representing the DICOM object. 301 | :param destination: the file path where the file needs to be dumped to. the file path must include the file name and extension. 302 | """ 303 | try: 304 | folder_path = os.path.dirname(destination) 305 | os.makedirs( folder_path , exist_ok=True) 306 | import numpy as np 307 | shape = ds.pixel_array.shape 308 | image_2d = ds.pixel_array.astype(float) 309 | image_2d_scaled = (np.maximum(image_2d,0) / image_2d.max()) * 255.0 310 | image_2d_scaled = np.uint8(image_2d_scaled) 311 | if 'PhotometricInterpretation' in ds and ds.PhotometricInterpretation == "MONOCHROME1": 312 | image_2d_scaled = np.max(image_2d_scaled) - image_2d_scaled 313 | img = Image.fromarray(image_2d_scaled) 314 | img.save(destination, 'png') 315 | except Exception as err: 316 | self.logger.error(f"[{__name__}][saveAsPngPIL] - {err}") 317 | return False 318 | return True 319 | 320 | # def getSeries(self, datastore_id : str = None , image_set_id : str = None): 321 | # AHI_metadata = self.getMetadata(datastore_id, image_set_id, self.AHIclient) 322 | # seriesList = self.getSeriesList(AHI_metadata=AHI_metadata) 323 | # return seriesList 324 | 325 | def _initFetchAndDICOMizeProcesses(self, AHI_metadata): 326 | self.frameDICOMizerThreadList = [] 327 | self.frameDICOMizerThreadList = [] 328 | self.frameFetcherThreadList.clear() 329 | self.frameDICOMizerThreadList.clear() 330 | for x in range(self.fetcherProcessCount): 331 | self.logger.debug("[DICOMize] - Spawning AHIFrameFetcher thread # "+str(x)) 332 | self.frameFetcherThreadList.append(AHIFrameFetcher(str(x), self.aws_access_key , self.aws_access_key , self.AHI_endpoint )) 333 | for x in range(self.DICOMizerProcessCount): 334 | self.logger.debug("[DICOMize] - Spawning AHIDICOMizer thread # "+str(x)) 335 | self.frameDICOMizerThreadList.append(AHIDataDICOMizer(str(x) , AHI_metadata )) 336 | 337 | def saveAsDICOM(self, ds : pydicom.Dataset , destination : str = './out' ) -> bool: 338 | """ 339 | saveAsDICOM(ds : pydicom.Dataset , destination : str). 340 | Saves a DICOM Part10 file for the DICOM object to the specified destination. 341 | 342 | :param ds: The pydicom Dataset representing the DICOM object. 343 | :param destination: the folder path where to save the DICOM file to. The file name will be the SOPInstanceUID of the DICOM object suffixed by '.dcm'. 344 | """ 345 | try: 346 | os.makedirs( destination , exist_ok=True) 347 | filename = os.path.join( destination , ds["SOPInstanceUID"].value) 348 | ds.save_as(f"{filename}.dcm", write_like_original=False) 349 | except Exception as err: 350 | self.logger.error(f"[{__name__}][saveAsDICOM] - {err}") 351 | return False 352 | return True 353 | -------------------------------------------------------------------------------- /build/lib/AHItoDICOMInterface/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | AHItoDICOMInterface. 3 | 4 | A package to simply export DICOM dataset in memory or on the file system.. 5 | """ 6 | 7 | __version__ = "0.1.3.3" 8 | __author__ = 'JP Leger' 9 | __credits__ = '' -------------------------------------------------------------------------------- /dist/AHItoDICOMInterface-0.1.3.4-py3-none-any.whl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/healthlake-imaging-to-dicom-python-module/4150f92ff766453a070538d5cf9997799906f504/dist/AHItoDICOMInterface-0.1.3.4-py3-none-any.whl -------------------------------------------------------------------------------- /dist/AHItoDICOMInterface-0.1.3.4.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/healthlake-imaging-to-dicom-python-module/4150f92ff766453a070538d5cf9997799906f504/dist/AHItoDICOMInterface-0.1.3.4.tar.gz -------------------------------------------------------------------------------- /example/jupyter-sagemaker-example.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 2, 6 | "id": "10a650f2-7fb8-433b-b7d0-405eeb08fac6", 7 | "metadata": { 8 | "tags": [] 9 | }, 10 | "outputs": [ 11 | { 12 | "name": "stderr", 13 | "output_type": "stream", 14 | "text": [ 15 | "\u001b[33mWARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv\u001b[0m\u001b[33m\n", 16 | "\u001b[0m\u001b[33mWARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv\u001b[0m\u001b[33m\n" 17 | ] 18 | }, 19 | { 20 | "name": "stdout", 21 | "output_type": "stream", 22 | "text": [ 23 | "Python 3.10.6\n" 24 | ] 25 | }, 26 | { 27 | "name": "stderr", 28 | "output_type": "stream", 29 | "text": [ 30 | "\u001b[0m" 31 | ] 32 | } 33 | ], 34 | "source": [ 35 | "#Install the python packages \n", 36 | "%%sh\n", 37 | "pip install --upgrade pip --quiet\n", 38 | "pip install boto3 botocore awscliv2 AHItoDICOMInterface --upgrade --quiet\n", 39 | "python --version \n", 40 | "\n" 41 | ] 42 | }, 43 | { 44 | "cell_type": "code", 45 | "execution_count": 3, 46 | "id": "ff3571ea-0859-4c9f-a214-6e79c4379c8f", 47 | "metadata": { 48 | "tags": [] 49 | }, 50 | "outputs": [ 51 | { 52 | "data": { 53 | "text/plain": [ 54 | "{'status': 'ok', 'restart': True}" 55 | ] 56 | }, 57 | "execution_count": 3, 58 | "metadata": {}, 59 | "output_type": "execute_result" 60 | } 61 | ], 62 | "source": [ 63 | "#Restart the Kernel to take the new versions of awscliv2 in account.\n", 64 | "import IPython\n", 65 | "IPython.Application.instance().kernel.do_shutdown(True) #automatically restarts kernel" 66 | ] 67 | }, 68 | { 69 | "cell_type": "code", 70 | "execution_count": 2, 71 | "id": "1133cab6-73e7-4d77-95f6-236c2883dfb5", 72 | "metadata": { 73 | "tags": [] 74 | }, 75 | "outputs": [ 76 | { 77 | "name": "stdout", 78 | "output_type": "stream", 79 | "text": [ 80 | "Listing ImageSets and Series info by StudyInstanceUID\n", 81 | "DICOMizing the ImageSet\n", 82 | "exporting the instance 100 of the Study as PNG image\n" 83 | ] 84 | }, 85 | { 86 | "data": { 87 | "text/plain": [ 88 | "True" 89 | ] 90 | }, 91 | "execution_count": 2, 92 | "metadata": {}, 93 | "output_type": "execute_result" 94 | } 95 | ], 96 | "source": [ 97 | "from AHItoDICOMInterface.AHItoDICOM import AHItoDICOM\n", 98 | "\n", 99 | "datastoreId = \"713e4f5237a84bec991d283fa9a0788a\" #Replace this value with your datastoreId.\n", 100 | "imageSetId = \"81bfc6aa3416912056e95188ab74870b\" #Replace this value with your imageSetId.\n", 101 | "studyInstanceUID = \"1.3.6.1.4.1.19291.2.1.1.11401331443219758551361281482\" #Replace this value with the studyInstanceUID of a study exisiting in the datastore.\n", 102 | "\n", 103 | "helper=AHItoDICOM()\n", 104 | "print(\"Listing ImageSets and Series info by StudyInstanceUID\")\n", 105 | "seriesdesc = helper.getImageSetToSeriesUIDMap(datastore_id=datastoreId , study_instance_uid=studyInstanceUID)\n", 106 | "\n", 107 | "print(\"DICOMizing the ImageSet\")\n", 108 | "DICOMInstances = helper.DICOMizeImageSet(datastoreId,imageSetId)\n", 109 | "print(\"exporting the instance 100 of the Study as PNG image\")\n", 110 | "helper.saveAsPngPIL(ds= DICOMInstances[99], destination=f\"./image.png\")\n", 111 | "\n" 112 | ] 113 | }, 114 | { 115 | "cell_type": "code", 116 | "execution_count": 4, 117 | "id": "e4b496f6-ece9-4a4f-83c5-c82c50e673b3", 118 | "metadata": { 119 | "tags": [] 120 | }, 121 | "outputs": [ 122 | { 123 | "data": { 124 | "application/vnd.jupyter.widget-view+json": { 125 | "model_id": "5429eca7611941929ecf65fa46b522cf", 126 | "version_major": 2, 127 | "version_minor": 0 128 | }, 129 | "text/plain": [ 130 | "HBox(children=(Output(),))" 131 | ] 132 | }, 133 | "metadata": {}, 134 | "output_type": "display_data" 135 | } 136 | ], 137 | "source": [ 138 | "#Format and display Series descriptions:\n", 139 | "\n", 140 | "import ipywidgets as widgets\n", 141 | "from IPython import display\n", 142 | "import pandas as pd\n", 143 | "import numpy as np\n", 144 | "\n", 145 | "# sample data\n", 146 | "df1 = pd.DataFrame(seriesdesc)\n", 147 | "\n", 148 | "# create output widgets\n", 149 | "widget1 = widgets.Output()\n", 150 | "with widget1:\n", 151 | " display.display(df1)\n", 152 | "hbox = widgets.HBox([widget1])\n", 153 | "\n", 154 | "# render hbox\n", 155 | "hbox" 156 | ] 157 | }, 158 | { 159 | "cell_type": "code", 160 | "execution_count": 6, 161 | "id": "d2dcdbaa-7a14-4867-a4d8-60b5d1c449df", 162 | "metadata": { 163 | "tags": [] 164 | }, 165 | "outputs": [ 166 | { 167 | "data": { 168 | "text/plain": [ 169 | "" 170 | ] 171 | }, 172 | "execution_count": 6, 173 | "metadata": {}, 174 | "output_type": "execute_result" 175 | }, 176 | { 177 | "data": { 178 | "image/png": "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PfLII3T06FH67d/+bbr44otpc3NTlvn0pz9Nl1xyCT3++OP09NNP06/92q/RDTfcQHEcn+e7Off0oQ99iL7+9a/TsWPH6MiRI/SRj3yELr/8ctra2pJlLuQx+va3v03f+c536MUXX6QXX3yRvvCFL5Dv+3Ts2DEiurDHRqf//u//pne96110/fXX0z333COPX8hjdN9999E111xDy8vL8m91dVWeP59js+uA65d/+Zfp05/+dO7Y+973PvrTP/3Td6hH7wzpwJWmKR04cIAefPBBeWw0GtH8/Dz9/d//PRERra+vk+/79PDDD8syb775JjmOQ//+7/9+3vp+vmh1dZUA0BNPPEFEzRiZaHFxkf7hH/6hGRuF+v0+XXXVVfT444/TrbfeKoHrQh+j++67j2644QbjufM9NrvKVBiGIZ566incfvvtueO33347fvjDH75DvfrZoFdeeQUrKyu5sQmCALfeeqscm6eeegpRFOXKHDx4ENdee+3P5fhtbGwAAJaWlgA0Y6RSkiR4+OGHsb29jcOHDzdjo9Af//Ef4yMf+UhhV/ZmjICXXnoJBw8exKFDh/DJT34SL7/8MoDzPza7KsnuqVOnkCQJ9u/fnzu+f/9+rKysvEO9+tkgcf+msXnttddkmVarhcXFxUKZn7fxIyJ85jOfwa/8yq/g2muvBdCMEQAcPXoUhw8fxmg0wszMDB599FFcffXVknFcyGMDAA8//DCefvpp/PjHPy6cu9Dnz0033YRvfOMbeM973oMTJ07gS1/6Em655RY899xz531sdhVwCWLaBmpEVDh2odJOxubncfzuuusuPPvss3jyyScL5y7kMXrve9+LI0eOYH19HY888gjuvPNOPPHEE/L8hTw2b7zxBu655x489thjaLfb1nIX6hh9+MMflt+vu+46HD58GFdeeSUeeugh3HzzzQDO39jsKlPh3r174bpuAZ1XV1cLSH+hkYjuKRubAwcOIAxDrK2tWcv8PNDdd9+Nb3/72/je976HSy+9VB5vxghotVp497vfjQ984AN44IEHcMMNN+Av//Ivm7EBN2Wtrq7ixhtvhOd58DwPTzzxBP7qr/4KnufJe7yQx0ilXq+H6667Di+99NJ5nz+7CrharRZuvPFGPP7447njjz/+OG655ZZ3qFc/G3To0CEcOHAgNzZhGOKJJ56QY3PjjTfC9/1cmeXlZRw7duznYvyICHfddRe++c1v4j//8z9x6NCh3PlmjIpERBiPx83YALjttttw9OhRHDlyRP594AMfwO/+7u/iyJEj+IVf+IULfoxUGo/HeOGFF3DxxRef//kzVSjHzwCJcPivfe1r9Pzzz9O9995LvV6PXn311Xe6a2879ft9euaZZ+iZZ54hAPSVr3yFnnnmGbkU4MEHH6T5+Xn65je/SUePHqVPfepTxnDUSy+9lL773e/S008/Tb/+67/+cxGqS0T0h3/4hzQ/P0/f//73cyG7g8FAlrmQx+jzn/88/eAHP6BXXnmFnn32WfrCF75AjuPQY489RkQX9tjYSI0qJLqwx+izn/0sff/736eXX36ZfvSjH9Edd9xBs7Ozkveez7HZdcBFRPQ3f/M3dMUVV1Cr1aL3v//9Mtz5552+973vEYDC35133klEPCT1vvvuowMHDlAQBPSrv/qrdPTo0Vwdw+GQ7rrrLlpaWqJOp0N33HEHvf766+/A3Zx7Mo0NAPr6178uy1zIY/R7v/d78r3Zt28f3XbbbRK0iC7ssbGRDlwX8hiJdVm+79PBgwfpE5/4BD333HPy/Pkcm2Zbk4YaaqihhnYV7SofV0MNNdRQQw01wNVQQw011NCuoga4GmqooYYa2lXUAFdDDTXUUEO7ihrgaqihhhpqaFdRA1wNNdRQQw3tKmqAq6GGGmqooV1FDXA11FBDDTW0q6gBroYaaqihhnYVNcDVUEMNNdTQrqIGuBpqqKGGGtpV1ABXQw011FBDu4r+P6FDttmMcytTAAAAAElFTkSuQmCC", 179 | "text/plain": [ 180 | "
" 181 | ] 182 | }, 183 | "metadata": {}, 184 | "output_type": "display_data" 185 | } 186 | ], 187 | "source": [ 188 | "#Display the exported image\n", 189 | "\n", 190 | "from PIL import Image\n", 191 | "import matplotlib.pyplot as plt\n", 192 | "\n", 193 | "img = Image.open('./image.png')\n", 194 | "plt.imshow(img)\n", 195 | "\n" 196 | ] 197 | } 198 | ], 199 | "metadata": { 200 | "availableInstances": [ 201 | { 202 | "_defaultOrder": 0, 203 | "_isFastLaunch": true, 204 | "category": "General purpose", 205 | "gpuNum": 0, 206 | "hideHardwareSpecs": false, 207 | "memoryGiB": 4, 208 | "name": "ml.t3.medium", 209 | "vcpuNum": 2 210 | }, 211 | { 212 | "_defaultOrder": 1, 213 | "_isFastLaunch": false, 214 | "category": "General purpose", 215 | "gpuNum": 0, 216 | "hideHardwareSpecs": false, 217 | "memoryGiB": 8, 218 | "name": "ml.t3.large", 219 | "vcpuNum": 2 220 | }, 221 | { 222 | "_defaultOrder": 2, 223 | "_isFastLaunch": false, 224 | "category": "General purpose", 225 | "gpuNum": 0, 226 | "hideHardwareSpecs": false, 227 | "memoryGiB": 16, 228 | "name": "ml.t3.xlarge", 229 | "vcpuNum": 4 230 | }, 231 | { 232 | "_defaultOrder": 3, 233 | "_isFastLaunch": false, 234 | "category": "General purpose", 235 | "gpuNum": 0, 236 | "hideHardwareSpecs": false, 237 | "memoryGiB": 32, 238 | "name": "ml.t3.2xlarge", 239 | "vcpuNum": 8 240 | }, 241 | { 242 | "_defaultOrder": 4, 243 | "_isFastLaunch": true, 244 | "category": "General purpose", 245 | "gpuNum": 0, 246 | "hideHardwareSpecs": false, 247 | "memoryGiB": 8, 248 | "name": "ml.m5.large", 249 | "vcpuNum": 2 250 | }, 251 | { 252 | "_defaultOrder": 5, 253 | "_isFastLaunch": false, 254 | "category": "General purpose", 255 | "gpuNum": 0, 256 | "hideHardwareSpecs": false, 257 | "memoryGiB": 16, 258 | "name": "ml.m5.xlarge", 259 | "vcpuNum": 4 260 | }, 261 | { 262 | "_defaultOrder": 6, 263 | "_isFastLaunch": false, 264 | "category": "General purpose", 265 | "gpuNum": 0, 266 | "hideHardwareSpecs": false, 267 | "memoryGiB": 32, 268 | "name": "ml.m5.2xlarge", 269 | "vcpuNum": 8 270 | }, 271 | { 272 | "_defaultOrder": 7, 273 | "_isFastLaunch": false, 274 | "category": "General purpose", 275 | "gpuNum": 0, 276 | "hideHardwareSpecs": false, 277 | "memoryGiB": 64, 278 | "name": "ml.m5.4xlarge", 279 | "vcpuNum": 16 280 | }, 281 | { 282 | "_defaultOrder": 8, 283 | "_isFastLaunch": false, 284 | "category": "General purpose", 285 | "gpuNum": 0, 286 | "hideHardwareSpecs": false, 287 | "memoryGiB": 128, 288 | "name": "ml.m5.8xlarge", 289 | "vcpuNum": 32 290 | }, 291 | { 292 | "_defaultOrder": 9, 293 | "_isFastLaunch": false, 294 | "category": "General purpose", 295 | "gpuNum": 0, 296 | "hideHardwareSpecs": false, 297 | "memoryGiB": 192, 298 | "name": "ml.m5.12xlarge", 299 | "vcpuNum": 48 300 | }, 301 | { 302 | "_defaultOrder": 10, 303 | "_isFastLaunch": false, 304 | "category": "General purpose", 305 | "gpuNum": 0, 306 | "hideHardwareSpecs": false, 307 | "memoryGiB": 256, 308 | "name": "ml.m5.16xlarge", 309 | "vcpuNum": 64 310 | }, 311 | { 312 | "_defaultOrder": 11, 313 | "_isFastLaunch": false, 314 | "category": "General purpose", 315 | "gpuNum": 0, 316 | "hideHardwareSpecs": false, 317 | "memoryGiB": 384, 318 | "name": "ml.m5.24xlarge", 319 | "vcpuNum": 96 320 | }, 321 | { 322 | "_defaultOrder": 12, 323 | "_isFastLaunch": false, 324 | "category": "General purpose", 325 | "gpuNum": 0, 326 | "hideHardwareSpecs": false, 327 | "memoryGiB": 8, 328 | "name": "ml.m5d.large", 329 | "vcpuNum": 2 330 | }, 331 | { 332 | "_defaultOrder": 13, 333 | "_isFastLaunch": false, 334 | "category": "General purpose", 335 | "gpuNum": 0, 336 | "hideHardwareSpecs": false, 337 | "memoryGiB": 16, 338 | "name": "ml.m5d.xlarge", 339 | "vcpuNum": 4 340 | }, 341 | { 342 | "_defaultOrder": 14, 343 | "_isFastLaunch": false, 344 | "category": "General purpose", 345 | "gpuNum": 0, 346 | "hideHardwareSpecs": false, 347 | "memoryGiB": 32, 348 | "name": "ml.m5d.2xlarge", 349 | "vcpuNum": 8 350 | }, 351 | { 352 | "_defaultOrder": 15, 353 | "_isFastLaunch": false, 354 | "category": "General purpose", 355 | "gpuNum": 0, 356 | "hideHardwareSpecs": false, 357 | "memoryGiB": 64, 358 | "name": "ml.m5d.4xlarge", 359 | "vcpuNum": 16 360 | }, 361 | { 362 | "_defaultOrder": 16, 363 | "_isFastLaunch": false, 364 | "category": "General purpose", 365 | "gpuNum": 0, 366 | "hideHardwareSpecs": false, 367 | "memoryGiB": 128, 368 | "name": "ml.m5d.8xlarge", 369 | "vcpuNum": 32 370 | }, 371 | { 372 | "_defaultOrder": 17, 373 | "_isFastLaunch": false, 374 | "category": "General purpose", 375 | "gpuNum": 0, 376 | "hideHardwareSpecs": false, 377 | "memoryGiB": 192, 378 | "name": "ml.m5d.12xlarge", 379 | "vcpuNum": 48 380 | }, 381 | { 382 | "_defaultOrder": 18, 383 | "_isFastLaunch": false, 384 | "category": "General purpose", 385 | "gpuNum": 0, 386 | "hideHardwareSpecs": false, 387 | "memoryGiB": 256, 388 | "name": "ml.m5d.16xlarge", 389 | "vcpuNum": 64 390 | }, 391 | { 392 | "_defaultOrder": 19, 393 | "_isFastLaunch": false, 394 | "category": "General purpose", 395 | "gpuNum": 0, 396 | "hideHardwareSpecs": false, 397 | "memoryGiB": 384, 398 | "name": "ml.m5d.24xlarge", 399 | "vcpuNum": 96 400 | }, 401 | { 402 | "_defaultOrder": 20, 403 | "_isFastLaunch": false, 404 | "category": "General purpose", 405 | "gpuNum": 0, 406 | "hideHardwareSpecs": true, 407 | "memoryGiB": 0, 408 | "name": "ml.geospatial.interactive", 409 | "supportedImageNames": [ 410 | "sagemaker-geospatial-v1-0" 411 | ], 412 | "vcpuNum": 0 413 | }, 414 | { 415 | "_defaultOrder": 21, 416 | "_isFastLaunch": true, 417 | "category": "Compute optimized", 418 | "gpuNum": 0, 419 | "hideHardwareSpecs": false, 420 | "memoryGiB": 4, 421 | "name": "ml.c5.large", 422 | "vcpuNum": 2 423 | }, 424 | { 425 | "_defaultOrder": 22, 426 | "_isFastLaunch": false, 427 | "category": "Compute optimized", 428 | "gpuNum": 0, 429 | "hideHardwareSpecs": false, 430 | "memoryGiB": 8, 431 | "name": "ml.c5.xlarge", 432 | "vcpuNum": 4 433 | }, 434 | { 435 | "_defaultOrder": 23, 436 | "_isFastLaunch": false, 437 | "category": "Compute optimized", 438 | "gpuNum": 0, 439 | "hideHardwareSpecs": false, 440 | "memoryGiB": 16, 441 | "name": "ml.c5.2xlarge", 442 | "vcpuNum": 8 443 | }, 444 | { 445 | "_defaultOrder": 24, 446 | "_isFastLaunch": false, 447 | "category": "Compute optimized", 448 | "gpuNum": 0, 449 | "hideHardwareSpecs": false, 450 | "memoryGiB": 32, 451 | "name": "ml.c5.4xlarge", 452 | "vcpuNum": 16 453 | }, 454 | { 455 | "_defaultOrder": 25, 456 | "_isFastLaunch": false, 457 | "category": "Compute optimized", 458 | "gpuNum": 0, 459 | "hideHardwareSpecs": false, 460 | "memoryGiB": 72, 461 | "name": "ml.c5.9xlarge", 462 | "vcpuNum": 36 463 | }, 464 | { 465 | "_defaultOrder": 26, 466 | "_isFastLaunch": false, 467 | "category": "Compute optimized", 468 | "gpuNum": 0, 469 | "hideHardwareSpecs": false, 470 | "memoryGiB": 96, 471 | "name": "ml.c5.12xlarge", 472 | "vcpuNum": 48 473 | }, 474 | { 475 | "_defaultOrder": 27, 476 | "_isFastLaunch": false, 477 | "category": "Compute optimized", 478 | "gpuNum": 0, 479 | "hideHardwareSpecs": false, 480 | "memoryGiB": 144, 481 | "name": "ml.c5.18xlarge", 482 | "vcpuNum": 72 483 | }, 484 | { 485 | "_defaultOrder": 28, 486 | "_isFastLaunch": false, 487 | "category": "Compute optimized", 488 | "gpuNum": 0, 489 | "hideHardwareSpecs": false, 490 | "memoryGiB": 192, 491 | "name": "ml.c5.24xlarge", 492 | "vcpuNum": 96 493 | }, 494 | { 495 | "_defaultOrder": 29, 496 | "_isFastLaunch": true, 497 | "category": "Accelerated computing", 498 | "gpuNum": 1, 499 | "hideHardwareSpecs": false, 500 | "memoryGiB": 16, 501 | "name": "ml.g4dn.xlarge", 502 | "vcpuNum": 4 503 | }, 504 | { 505 | "_defaultOrder": 30, 506 | "_isFastLaunch": false, 507 | "category": "Accelerated computing", 508 | "gpuNum": 1, 509 | "hideHardwareSpecs": false, 510 | "memoryGiB": 32, 511 | "name": "ml.g4dn.2xlarge", 512 | "vcpuNum": 8 513 | }, 514 | { 515 | "_defaultOrder": 31, 516 | "_isFastLaunch": false, 517 | "category": "Accelerated computing", 518 | "gpuNum": 1, 519 | "hideHardwareSpecs": false, 520 | "memoryGiB": 64, 521 | "name": "ml.g4dn.4xlarge", 522 | "vcpuNum": 16 523 | }, 524 | { 525 | "_defaultOrder": 32, 526 | "_isFastLaunch": false, 527 | "category": "Accelerated computing", 528 | "gpuNum": 1, 529 | "hideHardwareSpecs": false, 530 | "memoryGiB": 128, 531 | "name": "ml.g4dn.8xlarge", 532 | "vcpuNum": 32 533 | }, 534 | { 535 | "_defaultOrder": 33, 536 | "_isFastLaunch": false, 537 | "category": "Accelerated computing", 538 | "gpuNum": 4, 539 | "hideHardwareSpecs": false, 540 | "memoryGiB": 192, 541 | "name": "ml.g4dn.12xlarge", 542 | "vcpuNum": 48 543 | }, 544 | { 545 | "_defaultOrder": 34, 546 | "_isFastLaunch": false, 547 | "category": "Accelerated computing", 548 | "gpuNum": 1, 549 | "hideHardwareSpecs": false, 550 | "memoryGiB": 256, 551 | "name": "ml.g4dn.16xlarge", 552 | "vcpuNum": 64 553 | }, 554 | { 555 | "_defaultOrder": 35, 556 | "_isFastLaunch": false, 557 | "category": "Accelerated computing", 558 | "gpuNum": 1, 559 | "hideHardwareSpecs": false, 560 | "memoryGiB": 61, 561 | "name": "ml.p3.2xlarge", 562 | "vcpuNum": 8 563 | }, 564 | { 565 | "_defaultOrder": 36, 566 | "_isFastLaunch": false, 567 | "category": "Accelerated computing", 568 | "gpuNum": 4, 569 | "hideHardwareSpecs": false, 570 | "memoryGiB": 244, 571 | "name": "ml.p3.8xlarge", 572 | "vcpuNum": 32 573 | }, 574 | { 575 | "_defaultOrder": 37, 576 | "_isFastLaunch": false, 577 | "category": "Accelerated computing", 578 | "gpuNum": 8, 579 | "hideHardwareSpecs": false, 580 | "memoryGiB": 488, 581 | "name": "ml.p3.16xlarge", 582 | "vcpuNum": 64 583 | }, 584 | { 585 | "_defaultOrder": 38, 586 | "_isFastLaunch": false, 587 | "category": "Accelerated computing", 588 | "gpuNum": 8, 589 | "hideHardwareSpecs": false, 590 | "memoryGiB": 768, 591 | "name": "ml.p3dn.24xlarge", 592 | "vcpuNum": 96 593 | }, 594 | { 595 | "_defaultOrder": 39, 596 | "_isFastLaunch": false, 597 | "category": "Memory Optimized", 598 | "gpuNum": 0, 599 | "hideHardwareSpecs": false, 600 | "memoryGiB": 16, 601 | "name": "ml.r5.large", 602 | "vcpuNum": 2 603 | }, 604 | { 605 | "_defaultOrder": 40, 606 | "_isFastLaunch": false, 607 | "category": "Memory Optimized", 608 | "gpuNum": 0, 609 | "hideHardwareSpecs": false, 610 | "memoryGiB": 32, 611 | "name": "ml.r5.xlarge", 612 | "vcpuNum": 4 613 | }, 614 | { 615 | "_defaultOrder": 41, 616 | "_isFastLaunch": false, 617 | "category": "Memory Optimized", 618 | "gpuNum": 0, 619 | "hideHardwareSpecs": false, 620 | "memoryGiB": 64, 621 | "name": "ml.r5.2xlarge", 622 | "vcpuNum": 8 623 | }, 624 | { 625 | "_defaultOrder": 42, 626 | "_isFastLaunch": false, 627 | "category": "Memory Optimized", 628 | "gpuNum": 0, 629 | "hideHardwareSpecs": false, 630 | "memoryGiB": 128, 631 | "name": "ml.r5.4xlarge", 632 | "vcpuNum": 16 633 | }, 634 | { 635 | "_defaultOrder": 43, 636 | "_isFastLaunch": false, 637 | "category": "Memory Optimized", 638 | "gpuNum": 0, 639 | "hideHardwareSpecs": false, 640 | "memoryGiB": 256, 641 | "name": "ml.r5.8xlarge", 642 | "vcpuNum": 32 643 | }, 644 | { 645 | "_defaultOrder": 44, 646 | "_isFastLaunch": false, 647 | "category": "Memory Optimized", 648 | "gpuNum": 0, 649 | "hideHardwareSpecs": false, 650 | "memoryGiB": 384, 651 | "name": "ml.r5.12xlarge", 652 | "vcpuNum": 48 653 | }, 654 | { 655 | "_defaultOrder": 45, 656 | "_isFastLaunch": false, 657 | "category": "Memory Optimized", 658 | "gpuNum": 0, 659 | "hideHardwareSpecs": false, 660 | "memoryGiB": 512, 661 | "name": "ml.r5.16xlarge", 662 | "vcpuNum": 64 663 | }, 664 | { 665 | "_defaultOrder": 46, 666 | "_isFastLaunch": false, 667 | "category": "Memory Optimized", 668 | "gpuNum": 0, 669 | "hideHardwareSpecs": false, 670 | "memoryGiB": 768, 671 | "name": "ml.r5.24xlarge", 672 | "vcpuNum": 96 673 | }, 674 | { 675 | "_defaultOrder": 47, 676 | "_isFastLaunch": false, 677 | "category": "Accelerated computing", 678 | "gpuNum": 1, 679 | "hideHardwareSpecs": false, 680 | "memoryGiB": 16, 681 | "name": "ml.g5.xlarge", 682 | "vcpuNum": 4 683 | }, 684 | { 685 | "_defaultOrder": 48, 686 | "_isFastLaunch": false, 687 | "category": "Accelerated computing", 688 | "gpuNum": 1, 689 | "hideHardwareSpecs": false, 690 | "memoryGiB": 32, 691 | "name": "ml.g5.2xlarge", 692 | "vcpuNum": 8 693 | }, 694 | { 695 | "_defaultOrder": 49, 696 | "_isFastLaunch": false, 697 | "category": "Accelerated computing", 698 | "gpuNum": 1, 699 | "hideHardwareSpecs": false, 700 | "memoryGiB": 64, 701 | "name": "ml.g5.4xlarge", 702 | "vcpuNum": 16 703 | }, 704 | { 705 | "_defaultOrder": 50, 706 | "_isFastLaunch": false, 707 | "category": "Accelerated computing", 708 | "gpuNum": 1, 709 | "hideHardwareSpecs": false, 710 | "memoryGiB": 128, 711 | "name": "ml.g5.8xlarge", 712 | "vcpuNum": 32 713 | }, 714 | { 715 | "_defaultOrder": 51, 716 | "_isFastLaunch": false, 717 | "category": "Accelerated computing", 718 | "gpuNum": 1, 719 | "hideHardwareSpecs": false, 720 | "memoryGiB": 256, 721 | "name": "ml.g5.16xlarge", 722 | "vcpuNum": 64 723 | }, 724 | { 725 | "_defaultOrder": 52, 726 | "_isFastLaunch": false, 727 | "category": "Accelerated computing", 728 | "gpuNum": 4, 729 | "hideHardwareSpecs": false, 730 | "memoryGiB": 192, 731 | "name": "ml.g5.12xlarge", 732 | "vcpuNum": 48 733 | }, 734 | { 735 | "_defaultOrder": 53, 736 | "_isFastLaunch": false, 737 | "category": "Accelerated computing", 738 | "gpuNum": 4, 739 | "hideHardwareSpecs": false, 740 | "memoryGiB": 384, 741 | "name": "ml.g5.24xlarge", 742 | "vcpuNum": 96 743 | }, 744 | { 745 | "_defaultOrder": 54, 746 | "_isFastLaunch": false, 747 | "category": "Accelerated computing", 748 | "gpuNum": 8, 749 | "hideHardwareSpecs": false, 750 | "memoryGiB": 768, 751 | "name": "ml.g5.48xlarge", 752 | "vcpuNum": 192 753 | }, 754 | { 755 | "_defaultOrder": 55, 756 | "_isFastLaunch": false, 757 | "category": "Accelerated computing", 758 | "gpuNum": 8, 759 | "hideHardwareSpecs": false, 760 | "memoryGiB": 1152, 761 | "name": "ml.p4d.24xlarge", 762 | "vcpuNum": 96 763 | }, 764 | { 765 | "_defaultOrder": 56, 766 | "_isFastLaunch": false, 767 | "category": "Accelerated computing", 768 | "gpuNum": 8, 769 | "hideHardwareSpecs": false, 770 | "memoryGiB": 1152, 771 | "name": "ml.p4de.24xlarge", 772 | "vcpuNum": 96 773 | } 774 | ], 775 | "instance_type": "ml.t3.medium", 776 | "kernelspec": { 777 | "display_name": "Python 3 (Data Science 3.0)", 778 | "language": "python", 779 | "name": "python3__SAGEMAKER_INTERNAL__arn:aws:sagemaker:us-east-1:081325390199:image/sagemaker-data-science-310-v1" 780 | }, 781 | "language_info": { 782 | "codemirror_mode": { 783 | "name": "ipython", 784 | "version": 3 785 | }, 786 | "file_extension": ".py", 787 | "mimetype": "text/x-python", 788 | "name": "python", 789 | "nbconvert_exporter": "python", 790 | "pygments_lexer": "ipython3", 791 | "version": "3.10.6" 792 | } 793 | }, 794 | "nbformat": 4, 795 | "nbformat_minor": 5 796 | } 797 | -------------------------------------------------------------------------------- /example/main.py: -------------------------------------------------------------------------------- 1 | """ 2 | main.py : This program is an example of how to use the AHItoDICOM module. 3 | 4 | SPDX-License-Identifier: Apache-2.0 5 | """ 6 | 7 | import json 8 | from AHItoDICOMInterface.AHItoDICOM import AHItoDICOM 9 | import time 10 | import os 11 | import logging 12 | 13 | def main(): 14 | # logging.basicConfig(level=logging.CRITICAL) 15 | # logging.getLogger('boto3').setLevel(logging.CRITICAL) 16 | # logging.getLogger('botocore').setLevel(logging.CRITICAL) 17 | # logging.getLogger('nose').setLevel(logging.CRITICAL) 18 | # logging.getLogger('urllib3').setLevel(logging.CRITICAL) 19 | logging.getLogger('AHItoDICOMInterface').setLevel(logging.DEBUG) 20 | logging.getLogger('AHItoDICOMInterface.AHIFrameFetcher').setLevel(logging.DEBUG) 21 | datastoreId = "8a23b5b729c44f0d8b15cfd06253c5cd" #Replace this value with your datastoreId. 22 | imageSetId = "c5fdaa101da9f693f795f433d503049d" #Replace this value with your imageSetId. 23 | studyInstanceUID = "1.3.6.1.4.1.19291.2.1.1.11401331443219758551361281482" #Replace this value with the studyInstanceUID of a study exisiting in the datastore. 24 | AHIEndpoint = None # Can be set to None if the default AHI endpoint is used. 25 | 26 | # Default values for Frame Fetcher and DICOMizer processes count. 27 | # Frame Fetcher : Number of Parallelize processes to fetchand decompress the HTJ2K frames from AHI. If Set to None the default value will be 4 x number of cores. 28 | # DICOMizer : Number of Parallel processes to the build the DICOM dataset form the metadata and the frames fetched. If Set to None the default value will be 1 x number of cores. 29 | fetcher_count = None 30 | dicomizer_count = None 31 | 32 | 33 | 34 | # Initialize the AHItoDICOM conversion helper. 35 | print("Getting ImageSet JSON metadata object.") 36 | helper = AHItoDICOM( AHI_endpoint= AHIEndpoint , fetcher_process_count=fetcher_count , dicomizer_process_count=dicomizer_count) 37 | 38 | # Demonstrates how to get the metadata of an ImageSet from AHI, returned as a JSON object. 39 | ImageSet_metdata = helper.getMetadata(datastore_id=datastoreId , imageset_id=imageSetId) 40 | #print(ImageSet_metdata) 41 | f = open("metads.json", "w") 42 | f.write(json.dumps(ImageSet_metdata)) 43 | f.close() 44 | 45 | #Demonstrates how to get the series descriptions and ImageSetIDs by Study Instance UID 46 | print("Listing ImageSets and Series info by StudyInstanceUID") 47 | # print(helper.getImageSetToSeriesUIDMap(datastore_id=datastoreId , study_instance_uid=studyInstanceUID)) 48 | 49 | 50 | #Demonstrates how to export the DICOM study by Study Instance UID 51 | # print("DICOMizing by StudyInstanceUID") 52 | # instances = helper.DICOMizeByStudyInstanceUID(datastore_id=datastoreId , study_instance_uid=studyInstanceUID) 53 | 54 | # Demonstrates how to load an ImageSet from AHI in memory. All the instances of the ImageSet are returned in a list of pydicom dataset. 55 | print("DICOMizing by ImageSetID") 56 | start_time = time.time() 57 | instances = helper.DICOMizeImageSet(datastore_id=datastoreId , imageset_id=imageSetId , header_only=True) 58 | end_time = time.time() 59 | dat = {} 60 | print(f"{len(instances)} DICOMized in {end_time-start_time}.") 61 | 62 | 63 | # Demonstrates how to convert DICOM images to PNG representations. 64 | # print("Exporting images of the ImageSet in png format.") 65 | # instances = helper.DICOMizeImageSet(datastore_id=datastoreId , image_set_id=imageSetId) 66 | # StudyUID = instances[0]["StudyInstanceUID"].value 67 | # for ins in instances: 68 | # insId = ins["SOPInstanceUID"].value 69 | # helper.saveAsPngPIL(ds= ins, destination=f"./out/png_{StudyUID}/{insId}.png") 70 | 71 | # Demonstrates how to save DICOM files on the filesystem. 72 | print("Exporting images of the ImageSet in DICOM P10 format.") 73 | instances = helper.DICOMizeImageSet(datastore_id=datastoreId , imageset_id=imageSetId) 74 | for ins in instances: 75 | StudyUID = ins["StudyInstanceUID"].value 76 | helper.saveAsDICOM(ds= ins, destination=f"./out/dcm_{StudyUID}") 77 | 78 | 79 | if __name__ == "__main__": 80 | main() 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup( 4 | name='AHItoDICOMInterface', 5 | version='0.1.3.4', 6 | description='A package to simply export DICOM data from AWS HealthImaging in your application memory or the file system.', 7 | url='https://github.com/aws-samples/healthlake-imaging-to-dicom-python-module', 8 | long_description='More details about the project and features can be found on the project\'s GitHub page.', 9 | author='JP Leger', 10 | author_email='jpleger@amazon.com', 11 | license='MIT-0', 12 | packages=['AHItoDICOMInterface'], 13 | install_requires=[ 'boto3', 14 | 'botocore', 15 | 'pydicom', 16 | 'pylibjpeg-openjpeg>=1.3.0', 17 | 'numpy', 18 | 'pillow ', 19 | ], 20 | 21 | classifiers=[ 22 | 'Development Status :: 4 - Beta', 23 | 'Intended Audience :: Science/Research', 24 | 'License :: OSI Approved :: MIT No Attribution License (MIT-0)', 25 | 'Operating System :: POSIX :: Linux', 26 | 'Operating System :: Microsoft :: Windows', 27 | 'Programming Language :: Python :: 3.10', 28 | 'Programming Language :: Python :: 3.11' 29 | ], 30 | 31 | ) 32 | --------------------------------------------------------------------------------