[med-svn] [Git][med-team/orthanc-wsi][upstream] New upstream version 3.2+dfsg

Sebastien Jodogne (@jodogne-guest) gitlab at salsa.debian.org
Tue Apr 8 16:38:26 BST 2025



Sebastien Jodogne pushed to branch upstream at Debian Med / orthanc-wsi


Commits:
7d17a3ba by jodogne-guest at 2025-04-08T17:21:28+02:00
New upstream version 3.2+dfsg
- - - - -


29 changed files:

- .hg_archival.txt
- Applications/ApplicationToolbox.cpp
- Applications/CMakeLists.txt
- Applications/DicomToTiff.cpp
- Applications/Dicomizer.cpp
- CITATION.cff
- Framework/Enumerations.cpp
- Framework/Enumerations.h
- Framework/ImageToolbox.cpp
- Framework/ImageToolbox.h
- Framework/Inputs/DicomPyramid.cpp
- Framework/Inputs/DicomPyramidInstance.cpp
- Framework/Inputs/DicomPyramidInstance.h
- Framework/Inputs/DicomPyramidLevel.cpp
- Framework/Outputs/DicomPyramidWriter.cpp
- Framework/Outputs/DicomPyramidWriter.h
- Framework/Outputs/HierarchicalTiffWriter.cpp
- Framework/Outputs/HierarchicalTiffWriter.h
- Framework/Outputs/MultiframeDicomWriter.cpp
- Framework/Outputs/PyramidWriterBase.cpp
- Framework/Outputs/PyramidWriterBase.h
- NEWS
- Resources/CMake/Version.cmake
- Resources/Orthanc/CMake/Compiler.cmake
- Resources/Orthanc/CMake/DownloadOrthancFramework.cmake
- ViewerPlugin/CMakeLists.txt
- ViewerPlugin/OrthancPyramidFrameFetcher.cpp
- ViewerPlugin/OrthancPyramidFrameFetcher.h
- ViewerPlugin/RawTile.cpp


Changes:

=====================================
.hg_archival.txt
=====================================
@@ -1,6 +1,6 @@
 repo: 4a7a53257c7df5a97aea39377b8c9a6e815c9763
-node: 61a748cd203c81fc7133725f96a1cc05a3f97610
-branch: OrthancWSI-3.1
+node: 6d9987f3061295004737628903bfcbca15968f4f
+branch: OrthancWSI-3.2
 latesttag: null
-latesttagdistance: 356
-changessincelatesttag: 365
+latesttagdistance: 372
+changessincelatesttag: 382


=====================================
Applications/ApplicationToolbox.cpp
=====================================
@@ -71,11 +71,19 @@ namespace OrthancWSI
       Orthanc::HttpClient::GlobalInitialize();
       Orthanc::FromDcmtkBridge::InitializeDictionary(false /* don't load private dictionary */);
       assert(DisplayPerformanceWarning());
+
+#if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1
+      Orthanc::FromDcmtkBridge::InitializeCodecs();
+#endif
     }
 
 
     void GlobalFinalize()
     {
+#if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1
+      Orthanc::FromDcmtkBridge::FinalizeCodecs();
+#endif
+
       OrthancWSI::OpenSlideLibrary::Finalize();
       Orthanc::HttpClient::GlobalFinalize();
       Orthanc::Toolbox::FinalizeOpenSsl();


=====================================
Applications/CMakeLists.txt
=====================================
@@ -19,7 +19,7 @@
 # along with this program. If not, see <http://www.gnu.org/licenses/>.
 
 
-cmake_minimum_required(VERSION 2.8)
+cmake_minimum_required(VERSION 2.8...4.0)
 project(OrthancWSIApplications)
 
 include(${CMAKE_SOURCE_DIR}/../Resources/CMake/Version.cmake)
@@ -51,7 +51,13 @@ include(${CMAKE_SOURCE_DIR}/../Resources/Orthanc/CMake/DownloadOrthancFramework.
 
 if (ORTHANC_FRAMEWORK_SOURCE STREQUAL "system")
   if (ORTHANC_FRAMEWORK_USE_SHARED)    
-    include(FindBoost)
+    # https://cmake.org/cmake/help/latest/policy/CMP0167.html
+    if (CMAKE_VERSION VERSION_GREATER "3.30")
+      find_package(Boost CONFIG)
+    else()
+      include(FindBoost)
+    endif()
+
     find_package(Boost COMPONENTS filesystem program_options regex system thread date_time)
     
     if (NOT Boost_FOUND)
@@ -75,7 +81,8 @@ else()
   SET(ENABLE_CRYPTO_OPTIONS ON)
   SET(ENABLE_DCMTK ON)
   SET(ENABLE_DCMTK_JPEG OFF)          # Disable DCMTK's support for JPEG, that clashes with libtiff
-  SET(ENABLE_DCMTK_JPEG_LOSSLESS OFF) # Disable DCMTK's support for JPEG-LS
+  SET(ENABLE_DCMTK_JPEG_LOSSLESS ON)  # Enable DCMTK's support for JPEG-LS (was disabled in WSI <= 3.1)
+  SET(ENABLE_DCMTK_TRANSCODING ON)    # Enable DCMTK's support for transcoding (was disabled in WSI <= 3.1)
   SET(ENABLE_DCMTK_NETWORKING OFF)    # Disable DCMTK's support for DICOM networking
   SET(ENABLE_JPEG ON)
   SET(ENABLE_LOCALE ON)               # Enable support for locales (notably in Boost)
@@ -89,9 +96,19 @@ else()
   include(${ORTHANC_FRAMEWORK_ROOT}/../Resources/CMake/OrthancFrameworkConfiguration.cmake)
 
   include(${ORTHANC_WSI_DIR}/Resources/CMake/BoostExtendedConfiguration.cmake)
+
+  if (STATIC_BUILD OR NOT USE_SYSTEM_DCMTK)
+    # This is a backport of changeset: https://orthanc.uclouvain.be/hg/orthanc/rev/97d69fb5958e
+    # TODO - Remove after next upgrade to Orthanc framework > 1.12.7
+    include_directories(
+      ${DCMTK_SOURCES_DIR}/dcmimgle/include
+      )
+  endif()
 endif()
 
 
+
+
 # Include components specific to WSI
 include(${ORTHANC_WSI_DIR}/Resources/CMake/OpenJpegConfiguration.cmake)
 include(${ORTHANC_WSI_DIR}/Resources/CMake/LibTiffConfiguration.cmake)


=====================================
Applications/DicomToTiff.cpp
=====================================
@@ -333,7 +333,7 @@ int main(int argc, char* argv[])
   }
   catch (Orthanc::OrthancException& e)
   {
-    LOG(ERROR) << "Terminating on exception: " << e.What();
+    LOG(ERROR) << "Terminating on exception: " << e.What() << ": " << e.GetDetails();;
 
     if (options.count(OPTION_REENCODE) == 0)
     {


=====================================
Applications/Dicomizer.cpp
=====================================
@@ -248,14 +248,22 @@ static void Recompress(OrthancWSI::IFileTarget& output,
       switch (parameters.GetTargetCompression())
       {
         case OrthancWSI::ImageCompression_Jpeg:
-        case OrthancWSI::ImageCompression_Jpeg2000:
           targetPhotometric = Orthanc::PhotometricInterpretation_YBRFull422;
           break;
 
+        case OrthancWSI::ImageCompression_Jpeg2000:
+          // Was set to Orthanc::PhotometricInterpretation_YBRFull422 in WSI <= 3.1
+          targetPhotometric = Orthanc::PhotometricInterpretation_RGB;
+          break;
+
         case OrthancWSI::ImageCompression_None:
           targetPhotometric = Orthanc::PhotometricInterpretation_RGB;
           break;
 
+        case OrthancWSI::ImageCompression_JpegLS:
+          targetPhotometric = Orthanc::PhotometricInterpretation_RGB;
+          break;
+
         default:
           throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
       }
@@ -670,7 +678,7 @@ static bool ParseParameters(int& exitStatus,
     (OPTION_TILE_HEIGHT, boost::program_options::value<int>(),
      "Height of the tiles in the target image")
     (OPTION_COMPRESSION, boost::program_options::value<std::string>(), 
-     "Compression of the target image (\"none\", \"jpeg\" or \"jpeg2000\")")
+     "Compression of the target image (\"none\", \"jpeg\", \"jpeg2000\", or \"jpeg-ls\")")
     (OPTION_JPEG_QUALITY, boost::program_options::value<int>(),
      "Set quality level for JPEG (0..100)")
     (OPTION_MAX_SIZE, boost::program_options::value<int>()->default_value(10),
@@ -956,6 +964,10 @@ static bool ParseParameters(int& exitStatus,
     {
       parameters.SetTargetCompression(OrthancWSI::ImageCompression_Jpeg2000);
     }
+    else if (s == "jpeg-ls")
+    {
+      parameters.SetTargetCompression(OrthancWSI::ImageCompression_JpegLS);
+    }
     else
     {
       LOG(ERROR) << "Unknown image compression for the target image: " << s;
@@ -1293,7 +1305,7 @@ int main(int argc, char* argv[])
   }
   catch (Orthanc::OrthancException& e)
   {
-    LOG(ERROR) << "Terminating on exception: " << e.What();
+    LOG(ERROR) << "Terminating on exception: " << e.What() << ": " << e.GetDetails();
     exitStatus = -1;
   }
 


=====================================
CITATION.cff
=====================================
@@ -37,5 +37,5 @@ authors:
 doi: "10.5220/0006155100810087"
 license: "AGPL-3.0-or-later"
 repository-code: "https://orthanc.uclouvain.be/hg/orthanc-wsi/"
-version: "3.1"
-date-released: 2025-03-17
+version: "3.2"
+date-released: 2025-04-08


=====================================
Framework/Enumerations.cpp
=====================================
@@ -63,6 +63,9 @@ namespace OrthancWSI
       case ImageCompression_Dicom:
         return "DICOM";
 
+      case ImageCompression_JpegLS:
+        return "JPEG-LS";
+
       default:
         throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
     }


=====================================
Framework/Enumerations.h
=====================================
@@ -42,7 +42,9 @@ namespace OrthancWSI
     ImageCompression_Png = 4,
     ImageCompression_Jpeg = 5,
     ImageCompression_Jpeg2000 = 6,
-    ImageCompression_Tiff = 7
+    ImageCompression_Tiff = 7,
+    ImageCompression_UseOrthancPreview = 8,
+    ImageCompression_JpegLS = 9
   };
 
   enum OpticalPath


=====================================
Framework/ImageToolbox.cpp
=====================================
@@ -169,6 +169,20 @@ namespace OrthancWSI
     }
 
 
+    void EncodeUncompressedTile(std::string& target,
+                                const Orthanc::ImageAccessor& source)
+    {
+      unsigned int pitch = GetBytesPerPixel(source.GetFormat()) * source.GetWidth();
+      target.resize(pitch * source.GetHeight());
+
+      const unsigned int height = source.GetHeight();
+      for (unsigned int i = 0; i < height; i++)
+      {
+        memcpy(&target[i * pitch], source.GetConstRow(i), pitch);
+      }
+    }
+
+
     void EncodeTile(std::string& target,
                     const Orthanc::ImageAccessor& source,
                     ImageCompression compression,
@@ -176,14 +190,7 @@ namespace OrthancWSI
     {
       if (compression == ImageCompression_None)
       {
-        unsigned int pitch = GetBytesPerPixel(source.GetFormat()) * source.GetWidth();
-        target.resize(pitch * source.GetHeight());
-
-        const unsigned int height = source.GetHeight();
-        for (unsigned int i = 0; i < height; i++)
-        {
-          memcpy(&target[i * pitch], source.GetConstRow(i), pitch);
-        }
+        EncodeUncompressedTile(target, source);
       }
       else
       {
@@ -356,5 +363,54 @@ namespace OrthancWSI
           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);
+      }
+    }
   }
 }


=====================================
Framework/ImageToolbox.h
=====================================
@@ -58,6 +58,9 @@ namespace OrthancWSI
                                           unsigned int width,
                                           unsigned int height);
     
+    void EncodeUncompressedTile(std::string& target,
+                                const Orthanc::ImageAccessor& source);
+
     void EncodeTile(std::string& target,
                     const Orthanc::ImageAccessor& source,
                     ImageCompression compression,
@@ -77,5 +80,9 @@ namespace OrthancWSI
     void ConvertJpegYCbCrToRgb(Orthanc::ImageAccessor& image /* inplace */);
 
     ImageCompression Convert(Orthanc::MimeType type);
+
+    bool HasPngSignature(const std::string& buffer);
+
+    bool HasJpegSignature(const std::string& buffer);
   }
 }


=====================================
Framework/Inputs/DicomPyramid.cpp
=====================================
@@ -192,6 +192,8 @@ namespace OrthancWSI
 
     for (size_t i = 0; i < instances_.size(); i++)
     {
+      assert(instances_[i] != NULL);
+
       if (i == 0 ||
           instances_[i - 1]->GetTotalWidth() != instances_[i]->GetTotalWidth())
       {
@@ -202,6 +204,8 @@ namespace OrthancWSI
         assert(levels_.back() != NULL);
         levels_.back()->AddInstance(*instances_[i]);
       }
+
+      instances_[i]->SetLevel(levels_.size() - 1);
     }
   }
 
@@ -283,7 +287,8 @@ namespace OrthancWSI
     {
       assert(instances_[i] != NULL);
 
-      if (instances_[i]->HasImagedVolumeSize())
+      if (instances_[i]->IsLevel(0) &&  // Only consider the finest level
+          instances_[i]->HasImagedVolumeSize())
       {
         if (!found)
         {


=====================================
Framework/Inputs/DicomPyramidInstance.cpp
=====================================
@@ -82,6 +82,17 @@ namespace OrthancWSI
     {
       return ImageCompression_Jpeg2000;
     }
+    else if (s == "1.2.840.10008.1.2.1.99" ||
+             s == "1.2.840.10008.1.2.2"    ||
+             s == "1.2.840.10008.1.2.4.51" ||
+             s == "1.2.840.10008.1.2.4.57" ||
+             s == "1.2.840.10008.1.2.4.70" ||
+             s == "1.2.840.10008.1.2.4.80" ||
+             s == "1.2.840.10008.1.2.4.81" ||
+             s == "1.2.840.10008.1.2.5")
+    {
+      return ImageCompression_UseOrthancPreview;
+    }
     else
     {
       LOG(ERROR) << "Unsupported transfer syntax: " << s;
@@ -305,7 +316,9 @@ namespace OrthancWSI
     backgroundBlue_(0),
     hasImagedVolumeSize_(false),
     imagedVolumeWidth_(0),
-    imagedVolumeHeight_(0)
+    imagedVolumeHeight_(0),
+    hasLevel_(false),
+    level_(0)
   {
     if (useCache)
     {
@@ -570,4 +583,25 @@ namespace OrthancWSI
       throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
     }
   }
+
+
+  void DicomPyramidInstance::SetLevel(unsigned int level)
+  {
+    if (hasLevel_)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      level_ = level;
+      hasLevel_ = true;
+    }
+  }
+
+
+  bool DicomPyramidInstance::IsLevel(unsigned int level) const
+  {
+    return (hasLevel_ &&
+            level_ == level);
+  }
 }


=====================================
Framework/Inputs/DicomPyramidInstance.h
=====================================
@@ -54,6 +54,8 @@ namespace OrthancWSI
     bool                                hasImagedVolumeSize_;
     double                              imagedVolumeWidth_;
     double                              imagedVolumeHeight_;
+    bool                                hasLevel_;
+    unsigned int                        level_;
 
     void Load(OrthancStone::IOrthancConnection&  orthanc,
               const std::string& instanceId);
@@ -137,5 +139,9 @@ namespace OrthancWSI
     double GetImagedVolumeWidth() const;
 
     double GetImagedVolumeHeight() const;
+
+    void SetLevel(unsigned int level);
+
+    bool IsLevel(unsigned int level) const;
   };
 }


=====================================
Framework/Inputs/DicomPyramidLevel.cpp
=====================================
@@ -24,6 +24,8 @@
 #include "../PrecompiledHeadersWSI.h"
 #include "DicomPyramidLevel.h"
 
+#include "../ImageToolbox.h"
+
 #include <Logging.h>
 #include <OrthancException.h>
 
@@ -132,15 +134,39 @@ namespace OrthancWSI
       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
     {


=====================================
Framework/Outputs/DicomPyramidWriter.cpp
=====================================
@@ -25,6 +25,7 @@
 #include "DicomPyramidWriter.h"
 
 #include "../DicomToolbox.h"
+#include "../ImageToolbox.h"
 
 #include <Compatibility.h>  // For std::unique_ptr
 #include <Logging.h>
@@ -198,6 +199,21 @@ namespace OrthancWSI
   }
 
 
+  void DicomPyramidWriter::EncodeTileInternal(std::string& encoded,
+                                              const Orthanc::ImageAccessor& tile)
+  {
+    if (GetImageCompression() == ImageCompression_JpegLS)
+    {
+      // For JPEG-LS, images are first store uncompressed, then transcoded in "MultiframeDicomWriter"
+      ImageToolbox::EncodeUncompressedTile(encoded, tile);
+    }
+    else
+    {
+      ImageToolbox::EncodeTile(encoded, tile, GetImageCompression(), GetJpegQuality());
+    }
+  }
+
+
   DicomPyramidWriter::DicomPyramidWriter(IFileTarget&  target,
                                          const DcmDataset& dataset,
                                          Orthanc::PixelFormat pixelFormat,


=====================================
Framework/Outputs/DicomPyramidWriter.h
=====================================
@@ -65,6 +65,9 @@ namespace OrthancWSI
     {
     }
 
+    virtual void EncodeTileInternal(std::string& encoded,
+                                    const Orthanc::ImageAccessor& tile) ORTHANC_OVERRIDE;
+
   public:
     DicomPyramidWriter(IFileTarget&  target,
                        const DcmDataset& dataset,


=====================================
Framework/Outputs/HierarchicalTiffWriter.cpp
=====================================
@@ -24,6 +24,8 @@
 #include "../PrecompiledHeadersWSI.h"
 #include "HierarchicalTiffWriter.h"
 
+#include "../ImageToolbox.h"
+
 #include <Logging.h>
 #include <OrthancException.h>
 #include <TemporaryFile.h>
@@ -415,6 +417,13 @@ namespace OrthancWSI
   }
 
 
+  void HierarchicalTiffWriter::EncodeTileInternal(std::string& encoded,
+                                                  const Orthanc::ImageAccessor& tile)
+  {
+    ImageToolbox::EncodeTile(encoded, tile, GetImageCompression(), GetJpegQuality());
+  }
+
+
   HierarchicalTiffWriter::HierarchicalTiffWriter(const std::string& path,
                                                  Orthanc::PixelFormat pixelFormat, 
                                                  ImageCompression compression,


=====================================
Framework/Outputs/HierarchicalTiffWriter.h
=====================================
@@ -75,6 +75,8 @@ namespace OrthancWSI
         
     virtual void AddLevelInternal(const Level& level) ORTHANC_OVERRIDE;
 
+    virtual void EncodeTileInternal(std::string& encoded,
+                                    const Orthanc::ImageAccessor& tile) ORTHANC_OVERRIDE;
 
   public:
     HierarchicalTiffWriter(const std::string& path,


=====================================
Framework/Outputs/MultiframeDicomWriter.cpp
=====================================
@@ -31,6 +31,10 @@
 #include <Logging.h>
 #include <OrthancException.h>
 
+#if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1
+#  include <DicomParsing/DcmtkTranscoder.h>
+#endif
+
 #include <dcmtk/dcmdata/dcuid.h>
 #include <dcmtk/dcmdata/dcdeftag.h>
 #include <dcmtk/dcmdata/dcostrmb.h>
@@ -182,6 +186,10 @@ namespace OrthancWSI
         transferSyntax_ = EXS_JPEG2000LosslessOnly;
         break;
 
+      case ImageCompression_JpegLS:
+        transferSyntax_ = EXS_JPEGLSLossless;
+        break;
+
       default:
         throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
     }
@@ -251,7 +259,8 @@ namespace OrthancWSI
     // Free the functional group on error
     std::unique_ptr<DcmItem> functionalGroupRaii(functionalGroup);
 
-    if (compression_ == ImageCompression_None)
+    if (compression_ == ImageCompression_None ||
+        compression_ == ImageCompression_JpegLS)
     {
       if (frame.size() != uncompressedFrameSize_)
       {
@@ -313,24 +322,58 @@ namespace OrthancWSI
       DicomToolbox::SetUint16Tag(*dicom->getDataset(), DCM_InConcatenationNumber, countInstances_);
     }
 
-    switch (compression_)
+    if (compression_ == ImageCompression_JpegLS)
     {
-      case ImageCompression_None:
-        InjectUncompressedPixelData(*dicom);
-        break;
+#if (ORTHANC_ENABLE_DCMTK_TRANSCODING == 1) && (ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS == 1)
+      const Orthanc::DicomTransferSyntax syntax = Orthanc::DicomTransferSyntax_JPEGLSLossless;
 
-      case ImageCompression_Jpeg:
-      case ImageCompression_Jpeg2000:
-        offsetTable_->createOffsetTable(*offsetList_);
-        dicom->getDataset()->insert(compressedPixelSequence_.release());
-        break;
+      InjectUncompressedPixelData(*dicom);
 
-      default:
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+      Orthanc::IDicomTranscoder::DicomImage source;
+      source.AcquireParsed(dicom.release());  // "dicom" is invalid below this point
+
+      std::set<Orthanc::DicomTransferSyntax> s;
+      s.insert(syntax);
+
+      Orthanc::IDicomTranscoder::DicomImage transcoded;
+
+      Orthanc::DcmtkTranscoder transcoder(1);
+      if (transcoder.Transcode(transcoded, source, s, true))
+      {
+        ResetImage();
+        SaveDicomToMemory(target, transcoded.GetParsed(), transferSyntax_);
+        return;  // Success
+      }
+      else
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, "DCMTK cannot transcode to " +
+                                        std::string(Orthanc::GetTransferSyntaxUid(syntax)));
+      }
+#else
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, "DCMTK was compiled without support for JPEG-LS");
+#endif
     }
+    else
+    {
+      switch (compression_)
+      {
+        case ImageCompression_None:
+          InjectUncompressedPixelData(*dicom);
+          break;
+
+        case ImageCompression_Jpeg:
+        case ImageCompression_Jpeg2000:
+          offsetTable_->createOffsetTable(*offsetList_);
+          dicom->getDataset()->insert(compressedPixelSequence_.release());
+          break;
+
+        default:
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+      }
 
-    ResetImage();
+      ResetImage();
 
-    SaveDicomToMemory(target, *dicom, transferSyntax_);
+      SaveDicomToMemory(target, *dicom, transferSyntax_);
+    }
   }
 }


=====================================
Framework/Outputs/PyramidWriterBase.cpp
=====================================
@@ -148,7 +148,8 @@ namespace OrthancWSI
     const Level level = GetLevel(z);
 
     std::string raw;
-    ImageToolbox::EncodeTile(raw, tile, compression_, jpegQuality_);
+    EncodeTileInternal(raw, tile);
+
     WriteRawTileInternal(raw, level, x, y);
   }
 }


=====================================
Framework/Outputs/PyramidWriterBase.h
=====================================
@@ -51,6 +51,9 @@ namespace OrthancWSI
     // This function is invoked before any call to WriteRawTileInternal()
     virtual void AddLevelInternal(const Level& level) = 0;
 
+    virtual void EncodeTileInternal(std::string& encoded,
+                                    const Orthanc::ImageAccessor& tile) = 0;
+
   private:
     boost::mutex          mutex_;   // This mutex protects access to the levels
     Orthanc::PixelFormat  pixelFormat_;


=====================================
NEWS
=====================================
@@ -2,6 +2,17 @@ Pending changes in the mainline
 ===============================
 
 
+Version 3.2 (2025-04-08)
+========================
+
+* 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 more transfer syntaxes in the Web viewer plugin, including JPEG-LS
+* Added support for JPEG-LS in OrthancWSIDicomizer with argument "--compression=jpeg-ls"
+* Fix photometric interpretation of JPEG2000 images generated by OrthancWSIDicomizer
+  (now set to RGB instead of YBR_FULL_422)
+
+
 Version 3.1 (2025-03-17)
 ========================
 
@@ -29,7 +40,7 @@ Version 3.0 (2024-12-20)
 
 => Minimum SDK version: 1.7.0 <=
 
-* On-the-fly creation of pyramids from frames of DICOM instances
+* On-the-fly creation of pyramids from frames of DICOM instances ("deep zoom" button)
 
 
 Version 2.1 (2024-10-18)


=====================================
Resources/CMake/Version.cmake
=====================================
@@ -19,13 +19,13 @@
 # along with this program. If not, see <http://www.gnu.org/licenses/>.
 
 
-set(ORTHANC_WSI_VERSION "3.1")
+set(ORTHANC_WSI_VERSION "3.2")
 
 if (ORTHANC_WSI_VERSION STREQUAL "mainline")
   set(ORTHANC_FRAMEWORK_DEFAULT_VERSION "mainline")
   set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "hg")
 else()
-  set(ORTHANC_FRAMEWORK_DEFAULT_VERSION "1.12.6")
+  set(ORTHANC_FRAMEWORK_DEFAULT_VERSION "1.12.7")
   set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "web")
 endif()
 


=====================================
Resources/Orthanc/CMake/Compiler.cmake
=====================================
@@ -22,6 +22,16 @@
 
 # This file sets all the compiler-related flags
 
+if (${CMAKE_SYSTEM_NAME} STREQUAL "Darwin")
+  # Since Orthanc 1.12.7 that allows CMake 4.0, builds for macOS
+  # require the C++ standard to be explicitly set to C++11. Do *not*
+  # do this on GNU/Linux, as third-party system libraries could have
+  # been compiled with higher versions of the C++ standard.
+  set(CMAKE_CXX_STANDARD 11)
+  set(CMAKE_CXX_STANDARD_REQUIRED ON)
+  set(CMAKE_CXX_EXTENSIONS OFF)
+endif()
+
 
 # Save the current compiler flags to the cache every time cmake configures the project
 set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS}" CACHE STRING "compiler flags" FORCE)


=====================================
Resources/Orthanc/CMake/DownloadOrthancFramework.cmake
=====================================
@@ -169,6 +169,8 @@ if (ORTHANC_FRAMEWORK_SOURCE STREQUAL "hg" OR
         set(ORTHANC_FRAMEWORK_MD5 "5bb69f092981fdcfc11dec0a0f9a7db3")
       elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.12.6")
         set(ORTHANC_FRAMEWORK_MD5 "0e971f32f4f3e4951e0f3b5de49a3da6")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.12.7")
+        set(ORTHANC_FRAMEWORK_MD5 "f27c27d7a7a694dab1fd7f0a99d9715a")
 
       # Below this point are development snapshots that were used to
       # release some plugin, before an official release of the Orthanc
@@ -501,7 +503,15 @@ if (ORTHANC_FRAMEWORK_SOURCE STREQUAL "system")
   
   include(CheckIncludeFile)
   include(CheckIncludeFileCXX)
-  include(FindPythonInterp)
+  
+  if(CMAKE_VERSION VERSION_GREATER "3.11")
+    find_package(Python REQUIRED COMPONENTS Interpreter)
+    set(PYTHON_EXECUTABLE ${Python_EXECUTABLE})
+  else()
+    include(FindPythonInterp)
+    find_package(PythonInterp REQUIRED)
+  endif()
+  
   include(${CMAKE_CURRENT_LIST_DIR}/Compiler.cmake)
   include(${CMAKE_CURRENT_LIST_DIR}/DownloadPackage.cmake)
   include(${CMAKE_CURRENT_LIST_DIR}/AutoGeneratedCode.cmake)


=====================================
ViewerPlugin/CMakeLists.txt
=====================================
@@ -19,7 +19,7 @@
 # along with this program. If not, see <http://www.gnu.org/licenses/>.
 
 
-cmake_minimum_required(VERSION 2.8)
+cmake_minimum_required(VERSION 2.8...4.0)
 project(OrthancWSIPlugin)
 
 include(${CMAKE_SOURCE_DIR}/../Resources/CMake/Version.cmake)
@@ -54,7 +54,13 @@ include(${CMAKE_SOURCE_DIR}/../Resources/Orthanc/CMake/DownloadOrthancFramework.
 
 if (ORTHANC_FRAMEWORK_SOURCE STREQUAL "system")
   if (ORTHANC_FRAMEWORK_USE_SHARED)
-    include(FindBoost)
+    # https://cmake.org/cmake/help/latest/policy/CMP0167.html
+    if (CMAKE_VERSION VERSION_GREATER "3.30")
+      find_package(Boost CONFIG)
+    else()
+      include(FindBoost)
+    endif()
+
     find_package(Boost COMPONENTS system thread)
     
     if (NOT Boost_FOUND)


=====================================
ViewerPlugin/OrthancPyramidFrameFetcher.cpp
=====================================
@@ -26,6 +26,8 @@
 
 #include "../Framework/Inputs/OnTheFlyPyramid.h"
 
+#include <DicomFormat/DicomImageInformation.h>
+#include <DicomFormat/DicomMap.h>
 #include <Images/Image.h>
 #include <Images/ImageProcessing.h>
 
@@ -34,32 +36,6 @@
 
 namespace OrthancWSI
 {
-  void OrthancPyramidFrameFetcher::RenderGrayscale(Orthanc::ImageAccessor& target,
-                                                   const Orthanc::ImageAccessor& source)
-  {
-    Orthanc::Image converted(Orthanc::PixelFormat_Float32, source.GetWidth(), source.GetHeight(), false);
-    Orthanc::ImageProcessing::Convert(converted, source);
-
-    float minValue, maxValue;
-    Orthanc::ImageProcessing::GetMinMaxFloatValue(minValue, maxValue, converted);
-
-    assert(minValue <= maxValue);
-    if (std::abs(maxValue - minValue) < 0.0001)
-    {
-      Orthanc::ImageProcessing::Set(target, 0);
-    }
-    else
-    {
-      const float scaling = 255.0f / (maxValue - minValue);
-      const float offset = -minValue;
-
-      Orthanc::Image rescaled(Orthanc::PixelFormat_Grayscale8, source.GetWidth(), source.GetHeight(), false);
-      Orthanc::ImageProcessing::ShiftScale(rescaled, converted, static_cast<float>(offset), static_cast<float>(scaling), false);
-      Orthanc::ImageProcessing::Convert(target, rescaled);
-    }
-  }
-
-
   OrthancPyramidFrameFetcher::OrthancPyramidFrameFetcher(OrthancStone::IOrthancConnection* orthanc,
                                                          bool smooth) :
     orthanc_(orthanc),
@@ -123,106 +99,101 @@ namespace OrthancWSI
 
     OrthancPlugins::DicomInstance dicom(buffer.GetData(), buffer.GetSize());
 
-    uint8_t backgroundRed = defaultBackgroundRed_;
-    uint8_t backgroundGreen = defaultBackgroundGreen_;
-    uint8_t backgroundBlue = defaultBackgroundBlue_;
-
     Json::Value tags;
-    dicom.GetSimplifiedJson(tags);
+    dicom.GetJson(tags);
 
-    static const char* const PHOTOMETRIC_INTERPRETATION = "PhotometricInterpretation";
+    Orthanc::DicomMap m;
+    m.FromDicomAsJson(tags);
 
-    if (tags.isMember(PHOTOMETRIC_INTERPRETATION) &&
-        tags[PHOTOMETRIC_INTERPRETATION].type() == Json::stringValue)
-    {
-      std::string p = tags[PHOTOMETRIC_INTERPRETATION].asString();
-      if (p == "MONOCHROME1")
-      {
-        backgroundRed = 255;
-        backgroundGreen = 255;
-        backgroundBlue = 255;
-      }
-      else if (p == "MONOCHROME2")
-      {
-        backgroundRed = 0;
-        backgroundGreen = 0;
-        backgroundBlue = 0;
-      }
-    }
+    Orthanc::DicomImageInformation info(m);
 
-    std::unique_ptr<OrthancPlugins::OrthancImage> frame(dicom.GetDecodedFrame(frameNumber));
+    uint8_t backgroundRed, backgroundGreen, backgroundBlue;
 
-    Orthanc::PixelFormat format;
-    switch (frame->GetPixelFormat())
+    if (info.GetPhotometricInterpretation() == Orthanc::PhotometricInterpretation_Monochrome1 ||
+        info.GetPhotometricInterpretation() == Orthanc::PhotometricInterpretation_Monochrome2)
     {
-    case OrthancPluginPixelFormat_RGB24:
-      format = Orthanc::PixelFormat_RGB24;
-      break;
-
-    case OrthancPluginPixelFormat_Grayscale8:
-      format = Orthanc::PixelFormat_Grayscale8;
-      break;
-
-    case OrthancPluginPixelFormat_Grayscale16:
-      format = Orthanc::PixelFormat_Grayscale16;
-      break;
-
-    default:
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+      backgroundRed = 0;
+      backgroundGreen = 0;
+      backgroundBlue = 0;
+    }
+    else
+    {
+      backgroundRed = defaultBackgroundRed_;
+      backgroundGreen = defaultBackgroundGreen_;
+      backgroundBlue = defaultBackgroundBlue_;
     }
 
-    Orthanc::ImageAccessor source;
-    source.AssignReadOnly(format, frame->GetWidth(), frame->GetHeight(), frame->GetPitch(), frame->GetBuffer());
+    std::unique_ptr<OrthancPlugins::OrthancImage> frame(dicom.GetDecodedFrame(frameNumber));
 
     unsigned int paddedWidth, paddedHeight;
 
     if (paddingX_ >= 2)
     {
-      paddedWidth = OrthancWSI::CeilingDivision(source.GetWidth(), paddingX_) * paddingX_;
+      paddedWidth = OrthancWSI::CeilingDivision(frame->GetWidth(), paddingX_) * paddingX_;
     }
     else
     {
-      paddedWidth = source.GetWidth();
+      paddedWidth = frame->GetWidth();
     }
 
     if (paddingY_ >= 2)
     {
-      paddedHeight = OrthancWSI::CeilingDivision(source.GetHeight(), paddingY_) * paddingY_;
+      paddedHeight = OrthancWSI::CeilingDivision(frame->GetHeight(), paddingY_) * paddingY_;
     }
     else
     {
-      paddedHeight = source.GetHeight();
+      paddedHeight = frame->GetHeight();
     }
 
-    std::unique_ptr<Orthanc::ImageAccessor> rendered(new Orthanc::Image(Orthanc::PixelFormat_RGB24, paddedWidth, paddedHeight, false));
 
-    if (paddedWidth != source.GetWidth() ||
-        paddedHeight != source.GetHeight())
+    Orthanc::PixelFormat sourceFormat, targetFormat;
+    switch (frame->GetPixelFormat())
     {
-      Orthanc::ImageProcessing::Set(*rendered, backgroundRed, backgroundGreen, backgroundBlue, 255 /* alpha */);
+      case OrthancPluginPixelFormat_RGB24:
+        sourceFormat = Orthanc::PixelFormat_RGB24;
+        targetFormat = Orthanc::PixelFormat_RGB24;
+        break;
+
+      case OrthancPluginPixelFormat_Grayscale8:
+        sourceFormat = Orthanc::PixelFormat_Grayscale8;
+        targetFormat = Orthanc::PixelFormat_Grayscale8;
+        break;
+
+      case OrthancPluginPixelFormat_Grayscale16:
+        sourceFormat = Orthanc::PixelFormat_Grayscale16;
+        targetFormat = Orthanc::PixelFormat_Grayscale8;
+        break;
+
+      case OrthancPluginPixelFormat_SignedGrayscale16:
+        sourceFormat = Orthanc::PixelFormat_SignedGrayscale16;
+        targetFormat = Orthanc::PixelFormat_Grayscale8;
+        break;
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
     }
 
-    Orthanc::ImageAccessor region;
-    rendered->GetRegion(region, 0, 0, source.GetWidth(), source.GetHeight());
 
-    switch (format)
+    std::unique_ptr<Orthanc::ImageAccessor> rendered(new Orthanc::Image(targetFormat, paddedWidth, paddedHeight, false));
+
+    if (paddedWidth != frame->GetWidth() ||
+        paddedHeight != frame->GetHeight())
     {
-    case Orthanc::PixelFormat_RGB24:
-      Orthanc::ImageProcessing::Copy(region, source);
-      break;
+      Orthanc::ImageProcessing::Set(*rendered, backgroundRed, backgroundGreen, backgroundBlue, 255 /* alpha */);
+    }
+
 
-    case Orthanc::PixelFormat_Grayscale8:
-      Orthanc::ImageProcessing::Convert(region, source);
-      break;
+    {
+      Orthanc::ImageAccessor target;
+      rendered->GetRegion(target, 0, 0, frame->GetWidth(), frame->GetHeight());
 
-    case Orthanc::PixelFormat_Grayscale16:
-      RenderGrayscale(region, source);
-      break;
+      Orthanc::ImageAccessor source;
+      source.AssignReadOnly(sourceFormat, frame->GetWidth(), frame->GetHeight(), frame->GetPitch(), frame->GetBuffer());
 
-    default:
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+      Orthanc::ImageProcessing::RenderDefaultWindow(target, info, source);
     }
 
+
     std::unique_ptr<DecodedTiledPyramid> result(new OnTheFlyPyramid(rendered.release(), tileWidth_, tileHeight_, smooth_));
     result->SetBackgroundColor(backgroundRed, backgroundGreen, backgroundBlue);
 


=====================================
ViewerPlugin/OrthancPyramidFrameFetcher.h
=====================================
@@ -42,9 +42,6 @@ namespace OrthancWSI
     uint8_t                                            defaultBackgroundGreen_;
     uint8_t                                            defaultBackgroundBlue_;
 
-    static void RenderGrayscale(Orthanc::ImageAccessor& target,
-                                const Orthanc::ImageAccessor& source);
-
   public:
     OrthancPyramidFrameFetcher(OrthancStone::IOrthancConnection* orthanc,
                                bool smooth);


=====================================
ViewerPlugin/RawTile.cpp
=====================================
@@ -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>
@@ -56,7 +57,12 @@ namespace OrthancWSI
         std::unique_ptr<Jpeg2000Reader> decoded(new Jpeg2000Reader);
         decoded->ReadFromMemory(tile_);
 
-        if (photometric_ == Orthanc::PhotometricInterpretation_YBR_ICT)
+        if (photometric_ == Orthanc::PhotometricInterpretation_YBRFull ||
+            photometric_ == Orthanc::PhotometricInterpretation_YBRFull422 ||
+            photometric_ == Orthanc::PhotometricInterpretation_YBRPartial420 ||
+            photometric_ == Orthanc::PhotometricInterpretation_YBRPartial422 ||
+            photometric_ == Orthanc::PhotometricInterpretation_YBR_ICT ||
+            photometric_ == Orthanc::PhotometricInterpretation_YBR_RCT)
         {
           ImageToolbox::ConvertJpegYCbCrToRgb(*decoded);
         }
@@ -78,6 +84,19 @@ namespace OrthancWSI
         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);
     }



View it on GitLab: https://salsa.debian.org/med-team/orthanc-wsi/-/commit/7d17a3ba82f1e8243867734692427d31976ae7bc

-- 
View it on GitLab: https://salsa.debian.org/med-team/orthanc-wsi/-/commit/7d17a3ba82f1e8243867734692427d31976ae7bc
You're receiving this email because of your account on salsa.debian.org.


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/debian-med-commit/attachments/20250408/9b0a297f/attachment-0001.htm>


More information about the debian-med-commit mailing list