changeset 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 5a1e81eef654
children b2de3a2ad3b9
files OrthancServer/Resources/Samples/Python/DicomizeImage.py OrthancServer/Resources/Samples/Python/MicroCTDicomization.py
diffstat 2 files changed, 306 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Resources/Samples/Python/DicomizeImage.py	Wed Apr 05 17:54:21 2023 +0200
@@ -0,0 +1,116 @@
+#!/usr/bin/python3
+# -*- coding: utf-8 -*-
+
+# Orthanc - A Lightweight, RESTful DICOM Store
+# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+# Department, University Hospital of Liege, Belgium
+# Copyright (C) 2017-2023 Osimis S.A., Belgium
+# Copyright (C) 2021-2023 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+#
+# This program is free software: you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+#
+# This sample Python script illustrates how to DICOM-ize a JPEG image,
+# a PNG image, or a PDF file using the route "/tools/create-dicom" of
+# the REST API of Orthanc. Make sure to adapt the parameters of the
+# DICOM-ization below.
+#
+# The following command-line will install the required library:
+#
+#  $ sudo pip3 install requests
+#
+
+import base64
+import imghdr
+import json
+import os
+import requests
+
+
+########################################
+##  Parameters for the DICOM-ization  ##
+########################################
+
+PATH = os.path.join(os.getenv('HOME'), 'Downloads', 'Spy 11B.jpg')
+
+URL = 'http://localhost:8042/'
+USERNAME = 'orthanc'
+PASSWORD = 'orthanc'
+
+TAGS = {
+    'ConversionType' : 'SI',  # Scanned Image
+    'InstanceNumber' : '1',
+    'Laterality' : '',
+    'Modality' : 'OT',
+    'PatientOrientation' : '',
+    'SOPClassUID' : '1.2.840.10008.5.1.4.1.1.7',  # Secondary Capture Image Storage
+    'SeriesNumber' : '1',
+    }
+
+if True:
+    # Case 1: Attach the new DICOM image as a new series in an
+    # existing study. In this case, "PARENT_STUDY" indicates the
+    # Orthanc identifier of the parent study:
+    # https://book.orthanc-server.com/faq/orthanc-ids.html
+    PARENT_STUDY = '66c8e41e-ac3a9029-0b85e42a-8195ee0a-92c2e62e'
+
+else:
+    # Case 2: Create a new study
+    PARENT_STUDY = None
+    STUDY_TAGS = {
+        'PatientID' : 'Test',
+        'PatientName' : 'Hello^World',
+        'PatientSex' : 'O',
+        
+        'PatientBirthDate' : None,
+        'StudyID' : 'Test',
+        'ReferringPhysicianName' : None,
+        'AccessionNumber' : None,
+    }
+
+    TAGS.update(STUDY_TAGS)
+
+
+
+########################################
+##  Application of the DICOM-ization  ##
+########################################
+
+if imghdr.what(PATH) == 'jpeg':
+    mime = 'image/jpeg'
+elif imghdr.what(PATH) == 'png':
+    mime = 'image/png'
+elif os.path.splitext(PATH) [1] == '.pdf':
+    mime = 'application/pdf'
+else:
+    raise Exception('The input image is neither JPEG, nor PNG, nor PDF')
+
+with open(PATH, 'rb') as f:
+    content = f.read()
+
+data = 'data:%s;base64,%s' % (mime, base64.b64encode(content).decode('ascii'))
+
+arguments = {
+    'Content': data,
+    'Tags': TAGS,
+}
+
+if PARENT_STUDY != None:
+    arguments['Parent'] = PARENT_STUDY
+
+r = requests.post('%s/tools/create-dicom' % URL,
+                  json.dumps(arguments),
+                  auth = requests.auth.HTTPBasicAuth(USERNAME, PASSWORD))
+r.raise_for_status()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Resources/Samples/Python/MicroCTDicomization.py	Wed Apr 05 17:54:21 2023 +0200
@@ -0,0 +1,190 @@
+#!/usr/bin/python3
+# -*- coding: utf-8 -*-
+
+# Orthanc - A Lightweight, RESTful DICOM Store
+# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+# Department, University Hospital of Liege, Belgium
+# Copyright (C) 2017-2023 Osimis S.A., Belgium
+# Copyright (C) 2021-2023 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+#
+# This program is free software: you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+#
+# This sample Python script illustrates how to DICOM-ize a micro-CT
+# acquisition, then to upload it to Orthanc.
+#
+# This sample assumes that the slices of the micro-CT are encoded as
+# TIFF files, that are all stored inside the same ZIP archive. Make
+# sure to adapt the parameters of the DICOM-ization below.
+#
+# The following command-line will install the required libraries:
+#
+#  $ sudo pip3 install libtiff numpy pydicom requests
+#
+
+import datetime
+import io
+import os
+import tempfile
+import zipfile
+
+import libtiff
+import numpy
+import pydicom
+import pydicom._storage_sopclass_uids
+import pydicom.datadict
+import pydicom.tag
+import requests
+import requests.auth
+
+
+########################################
+##  Parameters for the DICOM-ization  ##
+########################################
+
+ZIP = os.path.join(os.getenv('HOME'), 'Downloads', 'SpyII_mb.zip')
+
+URL = 'http://localhost:8042/'
+USERNAME = 'orthanc'
+PASSWORD = 'orthanc'
+
+VOXEL_WIDTH = 1
+VOXEL_HEIGHT = 1
+VOXEL_DEPTH = 1
+
+TAGS = {
+    'PatientID' : 'Test',
+    'PatientName' : 'Hello^World',
+    'StudyDate' : datetime.datetime.now().strftime('%Y%m%d'),
+    'StudyTime' : datetime.datetime.now().strftime('%H%M%S'),
+
+    'AccessionNumber' : None,
+    'AcquisitionNumber' : None,
+    'KVP' : None,
+    'Laterality' : None,
+    'Manufacturer' : None,
+    'PatientBirthDate' : '',
+    'PatientPosition' : None,
+    'PatientSex' : 'O',
+    'PositionReferenceIndicator' : None,
+    'ReferringPhysicianName' : None,
+    'SeriesNumber' : 1,
+    'StudyID' : 'Test',
+    }
+
+
+
+########################################
+##  Application of the DICOM-ization  ##
+########################################
+
+# Add the DICOM unique identifiers
+for tag in [ 'StudyInstanceUID',
+             'SeriesInstanceUID',
+             'FrameOfReferenceUID' ]:
+    if not tag in TAGS:
+        TAGS[tag] = pydicom.uid.generate_uid()
+
+
+def CreateDicomDataset(tif, sliceIndex):
+    image = tif.read_image().astype(numpy.uint16)
+
+    meta = pydicom.Dataset()
+    meta.MediaStorageSOPClassUID = pydicom._storage_sopclass_uids.CTImageStorage
+    meta.MediaStorageSOPInstanceUID = pydicom.uid.generate_uid()
+    meta.TransferSyntaxUID = pydicom.uid.ImplicitVRLittleEndian
+
+    dataset = pydicom.Dataset()
+    dataset.file_meta = meta
+
+    dataset.is_little_endian = True
+    dataset.is_implicit_VR = True
+    dataset.SOPClassUID = meta.MediaStorageSOPClassUID
+    dataset.SOPInstanceUID = meta.MediaStorageSOPInstanceUID
+    dataset.Modality = 'CT'
+
+    for (key, value) in TAGS.items():
+        tag = pydicom.tag.Tag(key)
+        vr = pydicom.datadict.dictionary_VR(tag)
+        dataset.add_new(tag, vr, value)
+
+    assert(image.dtype == numpy.uint16)
+    dataset.BitsStored = 16
+    dataset.BitsAllocated = 16
+    dataset.SamplesPerPixel = 1
+    dataset.HighBit = 15
+
+    dataset.Rows = image.shape[0]
+    dataset.Columns = image.shape[1]
+    dataset.InstanceNumber = (sliceIndex + 1)
+    dataset.ImagePositionPatient = r'0\0\%f' % (-float(sliceIndex) * VOXEL_DEPTH)
+    dataset.ImageOrientationPatient = r'1\0\0\0\-1\0'
+    dataset.SliceThickness = VOXEL_DEPTH
+    dataset.ImageType = r'ORIGINAL\PRIMARY\AXIAL'
+    dataset.RescaleIntercept = '0'
+    dataset.RescaleSlope = '1'
+    dataset.PixelSpacing = r'%f\%f' % (VOXEL_HEIGHT, VOXEL_WIDTH)
+    dataset.PhotometricInterpretation = 'MONOCHROME2'
+    dataset.PixelRepresentation = 1
+
+    minValue = numpy.min(image)
+    maxValue = numpy.max(image)
+    dataset.WindowWidth = maxValue - minValue
+    dataset.WindowCenter = (minValue + maxValue) / 2.0
+
+    pydicom.dataset.validate_file_meta(dataset.file_meta, enforce_standard=True)
+    dataset.PixelData = image.tobytes()
+
+    return dataset
+
+
+# Create a temporary file, as libtiff is not able to read from BytesIO()
+with tempfile.NamedTemporaryFile() as tmp:
+    sliceIndex = 0
+
+    # Loop over the files in the ZIP archive, after having sorted them
+    with zipfile.ZipFile(ZIP, 'r') as z:
+        for path in sorted(z.namelist()):
+
+            # Ignore folders in the ZIP archive
+            info = z.getinfo(path)
+            if info.is_dir():
+                continue
+
+            # Extract the current file from the ZIP archive, into the temporary file
+            print('DICOM-izing: %s' % path)
+            data = z.read(path)
+
+            with open(tmp.name, 'wb') as f:
+                f.write(data)
+
+            # Try and decode the TIFF file
+            try:
+                tif = libtiff.TIFF.open(tmp.name)
+            except:
+                # Not a TIFF file, ignore
+                continue
+
+            # Create a DICOM dataset from the TIFF
+            dataset = CreateDicomDataset(tif, sliceIndex)
+            b = io.BytesIO()
+            dataset.save_as(b, False)
+
+            # Upload the DICOM dataset to Orthanc
+            r = requests.post('%s/instances' % URL, b.getvalue(),
+                              auth = requests.auth.HTTPBasicAuth(USERNAME, PASSWORD))
+            r.raise_for_status()
+
+            sliceIndex += 1