├── 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 |
--------------------------------------------------------------------------------