Mercurial > hg > orthanc
comparison OrthancServer/Resources/Samples/Python/MicroCTDicomization.py @ 5240:c9e2c6d1cd62
added two Python samples: DicomizeImage.py and MicroCTDicomization.py
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Wed, 05 Apr 2023 17:54:21 +0200 |
parents | |
children | 48b8dae6dc77 |
comparison
equal
deleted
inserted
replaced
5235:5a1e81eef654 | 5240:c9e2c6d1cd62 |
---|---|
1 #!/usr/bin/python3 | |
2 # -*- coding: utf-8 -*- | |
3 | |
4 # Orthanc - A Lightweight, RESTful DICOM Store | |
5 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics | |
6 # Department, University Hospital of Liege, Belgium | |
7 # Copyright (C) 2017-2023 Osimis S.A., Belgium | |
8 # Copyright (C) 2021-2023 Sebastien Jodogne, ICTEAM UCLouvain, Belgium | |
9 # | |
10 # This program is free software: you can redistribute it and/or | |
11 # modify it under the terms of the GNU General Public License as | |
12 # published by the Free Software Foundation, either version 3 of the | |
13 # License, or (at your option) any later version. | |
14 # | |
15 # This program is distributed in the hope that it will be useful, but | |
16 # WITHOUT ANY WARRANTY; without even the implied warranty of | |
17 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
18 # General Public License for more details. | |
19 # | |
20 # You should have received a copy of the GNU General Public License | |
21 # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
22 | |
23 | |
24 # | |
25 # This sample Python script illustrates how to DICOM-ize a micro-CT | |
26 # acquisition, then to upload it to Orthanc. | |
27 # | |
28 # This sample assumes that the slices of the micro-CT are encoded as | |
29 # TIFF files, that are all stored inside the same ZIP archive. Make | |
30 # sure to adapt the parameters of the DICOM-ization below. | |
31 # | |
32 # The following command-line will install the required libraries: | |
33 # | |
34 # $ sudo pip3 install libtiff numpy pydicom requests | |
35 # | |
36 | |
37 import datetime | |
38 import io | |
39 import os | |
40 import tempfile | |
41 import zipfile | |
42 | |
43 import libtiff | |
44 import numpy | |
45 import pydicom | |
46 import pydicom._storage_sopclass_uids | |
47 import pydicom.datadict | |
48 import pydicom.tag | |
49 import requests | |
50 import requests.auth | |
51 | |
52 | |
53 ######################################## | |
54 ## Parameters for the DICOM-ization ## | |
55 ######################################## | |
56 | |
57 ZIP = os.path.join(os.getenv('HOME'), 'Downloads', 'SpyII_mb.zip') | |
58 | |
59 URL = 'http://localhost:8042/' | |
60 USERNAME = 'orthanc' | |
61 PASSWORD = 'orthanc' | |
62 | |
63 VOXEL_WIDTH = 1 | |
64 VOXEL_HEIGHT = 1 | |
65 VOXEL_DEPTH = 1 | |
66 | |
67 TAGS = { | |
68 'PatientID' : 'Test', | |
69 'PatientName' : 'Hello^World', | |
70 'StudyDate' : datetime.datetime.now().strftime('%Y%m%d'), | |
71 'StudyTime' : datetime.datetime.now().strftime('%H%M%S'), | |
72 | |
73 'AccessionNumber' : None, | |
74 'AcquisitionNumber' : None, | |
75 'KVP' : None, | |
76 'Laterality' : None, | |
77 'Manufacturer' : None, | |
78 'PatientBirthDate' : '', | |
79 'PatientPosition' : None, | |
80 'PatientSex' : 'O', | |
81 'PositionReferenceIndicator' : None, | |
82 'ReferringPhysicianName' : None, | |
83 'SeriesNumber' : 1, | |
84 'StudyID' : 'Test', | |
85 } | |
86 | |
87 | |
88 | |
89 ######################################## | |
90 ## Application of the DICOM-ization ## | |
91 ######################################## | |
92 | |
93 # Add the DICOM unique identifiers | |
94 for tag in [ 'StudyInstanceUID', | |
95 'SeriesInstanceUID', | |
96 'FrameOfReferenceUID' ]: | |
97 if not tag in TAGS: | |
98 TAGS[tag] = pydicom.uid.generate_uid() | |
99 | |
100 | |
101 def CreateDicomDataset(tif, sliceIndex): | |
102 image = tif.read_image().astype(numpy.uint16) | |
103 | |
104 meta = pydicom.Dataset() | |
105 meta.MediaStorageSOPClassUID = pydicom._storage_sopclass_uids.CTImageStorage | |
106 meta.MediaStorageSOPInstanceUID = pydicom.uid.generate_uid() | |
107 meta.TransferSyntaxUID = pydicom.uid.ImplicitVRLittleEndian | |
108 | |
109 dataset = pydicom.Dataset() | |
110 dataset.file_meta = meta | |
111 | |
112 dataset.is_little_endian = True | |
113 dataset.is_implicit_VR = True | |
114 dataset.SOPClassUID = meta.MediaStorageSOPClassUID | |
115 dataset.SOPInstanceUID = meta.MediaStorageSOPInstanceUID | |
116 dataset.Modality = 'CT' | |
117 | |
118 for (key, value) in TAGS.items(): | |
119 tag = pydicom.tag.Tag(key) | |
120 vr = pydicom.datadict.dictionary_VR(tag) | |
121 dataset.add_new(tag, vr, value) | |
122 | |
123 assert(image.dtype == numpy.uint16) | |
124 dataset.BitsStored = 16 | |
125 dataset.BitsAllocated = 16 | |
126 dataset.SamplesPerPixel = 1 | |
127 dataset.HighBit = 15 | |
128 | |
129 dataset.Rows = image.shape[0] | |
130 dataset.Columns = image.shape[1] | |
131 dataset.InstanceNumber = (sliceIndex + 1) | |
132 dataset.ImagePositionPatient = r'0\0\%f' % (-float(sliceIndex) * VOXEL_DEPTH) | |
133 dataset.ImageOrientationPatient = r'1\0\0\0\-1\0' | |
134 dataset.SliceThickness = VOXEL_DEPTH | |
135 dataset.ImageType = r'ORIGINAL\PRIMARY\AXIAL' | |
136 dataset.RescaleIntercept = '0' | |
137 dataset.RescaleSlope = '1' | |
138 dataset.PixelSpacing = r'%f\%f' % (VOXEL_HEIGHT, VOXEL_WIDTH) | |
139 dataset.PhotometricInterpretation = 'MONOCHROME2' | |
140 dataset.PixelRepresentation = 1 | |
141 | |
142 minValue = numpy.min(image) | |
143 maxValue = numpy.max(image) | |
144 dataset.WindowWidth = maxValue - minValue | |
145 dataset.WindowCenter = (minValue + maxValue) / 2.0 | |
146 | |
147 pydicom.dataset.validate_file_meta(dataset.file_meta, enforce_standard=True) | |
148 dataset.PixelData = image.tobytes() | |
149 | |
150 return dataset | |
151 | |
152 | |
153 # Create a temporary file, as libtiff is not able to read from BytesIO() | |
154 with tempfile.NamedTemporaryFile() as tmp: | |
155 sliceIndex = 0 | |
156 | |
157 # Loop over the files in the ZIP archive, after having sorted them | |
158 with zipfile.ZipFile(ZIP, 'r') as z: | |
159 for path in sorted(z.namelist()): | |
160 | |
161 # Ignore folders in the ZIP archive | |
162 info = z.getinfo(path) | |
163 if info.is_dir(): | |
164 continue | |
165 | |
166 # Extract the current file from the ZIP archive, into the temporary file | |
167 print('DICOM-izing: %s' % path) | |
168 data = z.read(path) | |
169 | |
170 with open(tmp.name, 'wb') as f: | |
171 f.write(data) | |
172 | |
173 # Try and decode the TIFF file | |
174 try: | |
175 tif = libtiff.TIFF.open(tmp.name) | |
176 except: | |
177 # Not a TIFF file, ignore | |
178 continue | |
179 | |
180 # Create a DICOM dataset from the TIFF | |
181 dataset = CreateDicomDataset(tif, sliceIndex) | |
182 b = io.BytesIO() | |
183 dataset.save_as(b, False) | |
184 | |
185 # Upload the DICOM dataset to Orthanc | |
186 r = requests.post('%s/instances' % URL, b.getvalue(), | |
187 auth = requests.auth.HTTPBasicAuth(USERNAME, PASSWORD)) | |
188 r.raise_for_status() | |
189 | |
190 sliceIndex += 1 |