changeset 382:50cb2a69655f

added support for JPEG-LS transfer syntax in the Web viewer plugin
author Sebastien Jodogne <s.jodogne@gmail.com>
date Mon, 07 Apr 2025 18:08:06 +0200 (6 weeks ago)
parents 6ff4f1bd0861
children 63936f094eaa
files Framework/Enumerations.h Framework/ImageToolbox.cpp Framework/ImageToolbox.h Framework/Inputs/DicomPyramidInstance.cpp Framework/Inputs/DicomPyramidLevel.cpp NEWS ViewerPlugin/RawTile.cpp
diffstat 7 files changed, 104 insertions(+), 7 deletions(-) [+]
line wrap: on
line diff
--- a/Framework/Enumerations.h	Mon Apr 07 17:02:47 2025 +0200
+++ b/Framework/Enumerations.h	Mon Apr 07 18:08:06 2025 +0200
@@ -42,7 +42,8 @@
     ImageCompression_Png = 4,
     ImageCompression_Jpeg = 5,
     ImageCompression_Jpeg2000 = 6,
-    ImageCompression_Tiff = 7
+    ImageCompression_Tiff = 7,
+    ImageCompression_UseOrthancPreview = 8
   };
 
   enum OpticalPath
--- a/Framework/ImageToolbox.cpp	Mon Apr 07 17:02:47 2025 +0200
+++ b/Framework/ImageToolbox.cpp	Mon Apr 07 18:08:06 2025 +0200
@@ -356,5 +356,54 @@
           throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
       }
     }
+
+
+    bool HasPngSignature(const std::string& buffer)
+    {
+      if (buffer.size() < 8)
+      {
+        return false;
+      }
+      else
+      {
+        // https://en.wikipedia.org/wiki/PNG#File_header
+        // https://en.wikipedia.org/wiki/List_of_file_signatures
+        const uint8_t* p = reinterpret_cast<const uint8_t*>(buffer.data());
+        return (p[0] == 0x89 &&
+                p[1] == 0x50 &&
+                p[2] == 0x4e &&
+                p[3] == 0x47 &&
+                p[4] == 0x0d &&
+                p[5] == 0x0a &&
+                p[6] == 0x1a &&
+                p[7] == 0x0a);
+      }
+    }
+
+
+    bool HasJpegSignature(const std::string& buffer)
+    {
+      if (buffer.size() < 18)
+      {
+        return false;
+      }
+      else
+      {
+        // https://en.wikipedia.org/wiki/List_of_file_signatures
+        const uint8_t* p = reinterpret_cast<const uint8_t*>(buffer.data());
+        if (p[0] != 0xff ||
+            p[1] != 0xd8 ||
+            p[2] != 0xff)
+        {
+          return false;
+        }
+
+        // This is only a rough guess!
+        return (p[3] == 0xdb ||
+                p[3] == 0xe0 ||
+                p[3] == 0xee ||
+                p[3] == 0xe1);
+      }
+    }
   }
 }
--- a/Framework/ImageToolbox.h	Mon Apr 07 17:02:47 2025 +0200
+++ b/Framework/ImageToolbox.h	Mon Apr 07 18:08:06 2025 +0200
@@ -77,5 +77,9 @@
     void ConvertJpegYCbCrToRgb(Orthanc::ImageAccessor& image /* inplace */);
 
     ImageCompression Convert(Orthanc::MimeType type);
+
+    bool HasPngSignature(const std::string& buffer);
+
+    bool HasJpegSignature(const std::string& buffer);
   }
 }
--- a/Framework/Inputs/DicomPyramidInstance.cpp	Mon Apr 07 17:02:47 2025 +0200
+++ b/Framework/Inputs/DicomPyramidInstance.cpp	Mon Apr 07 18:08:06 2025 +0200
@@ -82,6 +82,10 @@
     {
       return ImageCompression_Jpeg2000;
     }
+    else if (s == "1.2.840.10008.1.2.4.80")
+    {
+      return ImageCompression_UseOrthancPreview;
+    }
     else
     {
       LOG(ERROR) << "Unsupported transfer syntax: " << s;
--- a/Framework/Inputs/DicomPyramidLevel.cpp	Mon Apr 07 17:02:47 2025 +0200
+++ b/Framework/Inputs/DicomPyramidLevel.cpp	Mon Apr 07 18:08:06 2025 +0200
@@ -134,15 +134,39 @@
       assert(tile.instance_ != NULL);
       DicomPyramidInstance& instance = *tile.instance_;
 
-      std::string uri = ("/instances/" + instance.GetInstanceId() + 
-                         "/frames/" + boost::lexical_cast<std::string>(tile.frame_) + "/raw");
-
-      orthanc.RestApiGet(raw, uri);
-
       compression = instance.GetImageCompression(orthanc);
       format = instance.GetPixelFormat();
 
-      return true;
+      if (compression == ImageCompression_UseOrthancPreview)
+      {
+        // If the WSI viewer plugin has no built-in support for this transfer syntax,
+        // use the decoding primitives offered by the Orthanc core
+        std::string uri = ("/instances/" + instance.GetInstanceId() +
+                           "/frames/" + boost::lexical_cast<std::string>(tile.frame_) + "/preview");
+        orthanc.RestApiGet(raw, uri);
+
+        if (ImageToolbox::HasPngSignature(raw))  // In theory, Orthanc should always generate PNG by default
+        {
+          compression = ImageCompression_Png;
+          return true;
+        }
+        else if (ImageToolbox::HasJpegSignature(raw))
+        {
+          compression = ImageCompression_Jpeg;
+          return true;
+        }
+        else
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, "Cannot decode a preview image generated by the Orthanc core");
+        }
+      }
+      else
+      {
+        std::string uri = ("/instances/" + instance.GetInstanceId() +
+                           "/frames/" + boost::lexical_cast<std::string>(tile.frame_) + "/raw");
+        orthanc.RestApiGet(raw, uri);
+        return true;
+      }
     }
     else
     {
--- a/NEWS	Mon Apr 07 17:02:47 2025 +0200
+++ b/NEWS	Mon Apr 07 18:08:06 2025 +0200
@@ -3,6 +3,7 @@
 
 * Support windowing when rendering grayscale images using on-the-fly deep zoom
 * Added tolerance to imaged volume width/height by looking only at the finest level
+* Added support for JPEG-LS transfer syntax in the Web viewer plugin
 
 
 Version 3.1 (2025-03-17)
--- a/ViewerPlugin/RawTile.cpp	Mon Apr 07 17:02:47 2025 +0200
+++ b/ViewerPlugin/RawTile.cpp	Mon Apr 07 18:08:06 2025 +0200
@@ -31,6 +31,7 @@
 #include <Compatibility.h>  // For std::unique_ptr
 #include <Images/ImageProcessing.h>
 #include <Images/JpegReader.h>
+#include <Images/PngReader.h>
 #include <Images/PngWriter.h>
 #include <MultiThreading/Semaphore.h>
 #include <OrthancException.h>
@@ -78,6 +79,19 @@
         return decoded.release();
       }
 
+      case ImageCompression_Png:
+      {
+        /**
+         * This is used if the DICOM instance has a transfer syntax
+         * that is not natively supported by the WSI viewer plugin, in
+         * which case the tile comes from the "/preview" route of the
+         * REST API of Orthanc.
+         **/
+        std::unique_ptr<Orthanc::PngReader> decoded(new Orthanc::PngReader);
+        decoded->ReadFromMemory(tile_);
+        return decoded.release();
+      }
+
       default:
         throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
     }