# HG changeset patch # User Sebastien Jodogne # Date 1680862853 -7200 # Node ID b36f82260f417df7160c6de2c907e31574a4c0a9 # Parent 72dfa0ac84eb470f7159088b21878332172fbaec# Parent b2de3a2ad3b9757f3619bf144b849c0a4ff36511 integration mainline->db-protobuf diff -r 72dfa0ac84eb -r b36f82260f41 NEWS --- a/NEWS Thu Apr 06 16:55:55 2023 +0200 +++ b/NEWS Fri Apr 07 12:20:53 2023 +0200 @@ -22,6 +22,11 @@ * Added "OrthancPluginRegisterDatabaseBackendV4()" to communicate using Google Protocol Buffers between the Orthanc core and database plugins +Orthanc Explorer +---------------- + +* Added buttons to copy the URL of ZIP archives and DICOM files to the clipboard + Maintenance ----------- diff -r 72dfa0ac84eb -r b36f82260f41 OrthancServer/OrthancExplorer/explorer.html --- a/OrthancServer/OrthancExplorer/explorer.html Thu Apr 06 16:55:55 2023 +0200 +++ b/OrthancServer/OrthancExplorer/explorer.html Fri Apr 07 12:20:53 2023 +0200 @@ -267,6 +267,7 @@
  • Download ZIP
  • Download DICOMDIR
  • +
  • Copy link to ZIP
  • @@ -321,6 +322,7 @@
  • Download ZIP
  • Download DICOMDIR
  • +
  • Copy link to ZIP
  • @@ -377,6 +379,7 @@
  • Preview this series
  • Download ZIP
  • Download DICOMDIR
  • +
  • Copy link to ZIP
  • @@ -431,6 +434,7 @@ Before modification
  • Download the DICOM file
  • +
  • Copy link to the DICOM file
  • Download the JSON file
  • Preview the instance
  • diff -r 72dfa0ac84eb -r b36f82260f41 OrthancServer/OrthancExplorer/explorer.js --- a/OrthancServer/OrthancExplorer/explorer.js Thu Apr 06 16:55:55 2023 +0200 +++ b/OrthancServer/OrthancExplorer/explorer.js Fri Apr 07 12:20:53 2023 +0200 @@ -1482,6 +1482,36 @@ window.location.href = '../series/' + $.mobile.pageData.uuid + '/media'; }); + +$('#patient-archive-link').live('click', function(e) { + e.preventDefault(); + var url = new URL('../patients/' + $.mobile.pageData.uuid + '/archive', window.location.href); + navigator.clipboard.writeText(url.href); + $(e.target).closest('li').buttonMarkup({ icon: 'check' }); +}); + +$('#study-archive-link').live('click', function(e) { + e.preventDefault(); + var url = new URL('../studies/' + $.mobile.pageData.uuid + '/archive', window.location.href); + navigator.clipboard.writeText(url.href); + $(e.target).closest('li').buttonMarkup({ icon: 'check' }); +}); + +$('#series-archive-link').live('click', function(e) { + e.preventDefault(); + var url = new URL('../series/' + $.mobile.pageData.uuid + '/archive', window.location.href); + navigator.clipboard.writeText(url.href); + $(e.target).closest('li').buttonMarkup({ icon: 'check' }); +}); + +$('#instance-download-link').live('click', function(e) { + e.preventDefault(); + var url = new URL('../instances/' + $.mobile.pageData.uuid + '/file', window.location.href); + navigator.clipboard.writeText(url.href); + $(e.target).closest('li').buttonMarkup({ icon: 'check' }); +}); + + $('.patient-attachment').live('click', function(e) { e.preventDefault(); //stop the browser from following window.location.href = '../patients/' + $.mobile.pageData.uuid + '/attachments/' + e.target.id + '/data'; diff -r 72dfa0ac84eb -r b36f82260f41 OrthancServer/Resources/Samples/Python/DicomizeImage.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/Resources/Samples/Python/DicomizeImage.py Fri Apr 07 12:20:53 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 . + + +# +# 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() diff -r 72dfa0ac84eb -r b36f82260f41 OrthancServer/Resources/Samples/Python/MicroCTDicomization.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/Resources/Samples/Python/MicroCTDicomization.py Fri Apr 07 12:20:53 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 . + + +# +# 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