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