changeset 1046:63edb430f259

mosaic
author Sebastien Jodogne <s.jodogne@gmail.com>
date Tue, 26 Mar 2024 09:50:04 +0100
parents bb92aeb7dbde
children 0d8089ca8183
files Sphinx/source/plugins/python.rst Sphinx/source/plugins/python/mosaic.png Sphinx/source/plugins/python/mosaic.py
diffstat 3 files changed, 64 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- a/Sphinx/source/plugins/python.rst	Mon Mar 25 18:33:57 2024 +0100
+++ b/Sphinx/source/plugins/python.rst	Tue Mar 26 09:50:04 2024 +0100
@@ -397,6 +397,8 @@
                     :language: python
                                
 
+.. _python-pil-thumbnail:
+
 Rendering a thumbnail using PIL/Pillow
 ......................................
 
@@ -899,6 +901,25 @@
 the :ref:`Orthanc Explorer 2 <orthanc-explorer-2>` interface.
 
 
+.. _python_mosaic:
+
+Generating a mosaic for a DICOM series
+......................................
+
+Thanks to the fact that Python plugins have access to :ref:`PIL/Pillow
+<python-pil-thumbnail>`, it is quite easy to generate a mosaic from a
+DICOM series:
+
+.. image:: python/mosaic.png
+           :align: center
+           :width: 512
+
+Here is the source code:
+
+.. literalinclude:: python/mosaic.py
+                    :language: python
+
+
 Performance and concurrency
 ---------------------------
 
Binary file Sphinx/source/plugins/python/mosaic.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Sphinx/source/plugins/python/mosaic.py	Tue Mar 26 09:50:04 2024 +0100
@@ -0,0 +1,43 @@
+import PIL.Image
+import io
+import json
+import math
+import orthanc
+
+def generate_mosaic(output, uri, **request):
+    # Sort the slices of the series, using the REST API of Orthanc
+    seriesId = request['groups'][0]
+    slices = json.loads(orthanc.RestApiGet('/series/%s/ordered-slices' % seriesId)) ['Slices']
+
+    # Retrieve the first slice of the mosaic
+    firstSliceBytes = orthanc.RestApiGet(slices[0] + '/preview')
+    firstSliceDecoded = PIL.Image.open(io.BytesIO(firstSliceBytes))
+
+    # Compute the size of the mosaic
+    sliceWidth, sliceHeight = firstSliceDecoded.size
+    side = math.ceil(math.sqrt(len(slices)))
+
+    # Create a PIL image to store the mosaic
+    image = PIL.Image.new(mode = 'L', size = (side * sliceWidth, side * sliceHeight))
+
+    # Loop over the instances of the series to populate the mosaic
+    x = 0
+    y = 0
+    for i in range(len(slices)):
+        sliceBytes = orthanc.RestApiGet(slices[i] + '/preview')
+        sliceDecoded = PIL.Image.open(io.BytesIO(sliceBytes))
+
+        image.paste(sliceDecoded, (x * sliceWidth, y * sliceHeight))
+
+        x += 1
+        if x == side:
+            x = 0
+            y += 1
+
+    # Answer with the mosaic encoded as a PNG image
+    with io.BytesIO() as png:
+        image.save(png, format = 'PNG')
+        png.seek(0)
+        output.AnswerBuffer(png.read(), 'image/png')
+
+orthanc.RegisterRestCallback('/series/(.*)/mosaic', generate_mosaic)