changeset 388:a7194a77c591

added support for JPEG-LS in OrthancWSIDicomizer
author Sebastien Jodogne <s.jodogne@gmail.com>
date Tue, 08 Apr 2025 13:11:27 +0200 (6 weeks ago)
parents 263369b75f9e
children 7954645ed874
files Applications/ApplicationToolbox.cpp Applications/CMakeLists.txt Applications/DicomToTiff.cpp Applications/Dicomizer.cpp Framework/Enumerations.cpp Framework/Enumerations.h Framework/ImageToolbox.cpp Framework/ImageToolbox.h Framework/Outputs/MultiframeDicomWriter.cpp NEWS
diffstat 10 files changed, 117 insertions(+), 5 deletions(-) [+]
line wrap: on
line diff
--- a/Applications/ApplicationToolbox.cpp	Tue Apr 08 10:48:07 2025 +0200
+++ b/Applications/ApplicationToolbox.cpp	Tue Apr 08 13:11:27 2025 +0200
@@ -71,11 +71,19 @@
       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();
--- a/Applications/CMakeLists.txt	Tue Apr 08 10:48:07 2025 +0200
+++ b/Applications/CMakeLists.txt	Tue Apr 08 13:11:27 2025 +0200
@@ -75,7 +75,8 @@
   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 +90,19 @@
   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)
--- a/Applications/DicomToTiff.cpp	Tue Apr 08 10:48:07 2025 +0200
+++ b/Applications/DicomToTiff.cpp	Tue Apr 08 13:11:27 2025 +0200
@@ -333,7 +333,7 @@
   }
   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)
     {
--- a/Applications/Dicomizer.cpp	Tue Apr 08 10:48:07 2025 +0200
+++ b/Applications/Dicomizer.cpp	Tue Apr 08 13:11:27 2025 +0200
@@ -260,6 +260,10 @@
           targetPhotometric = Orthanc::PhotometricInterpretation_RGB;
           break;
 
+        case OrthancWSI::ImageCompression_JpegLS:
+          targetPhotometric = Orthanc::PhotometricInterpretation_RGB;
+          break;
+
         default:
           throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
       }
@@ -674,7 +678,7 @@
     (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),
@@ -960,6 +964,10 @@
     {
       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;
@@ -1297,7 +1305,7 @@
   }
   catch (Orthanc::OrthancException& e)
   {
-    LOG(ERROR) << "Terminating on exception: " << e.What();
+    LOG(ERROR) << "Terminating on exception: " << e.What() << ": " << e.GetDetails();
     exitStatus = -1;
   }
 
--- a/Framework/Enumerations.cpp	Tue Apr 08 10:48:07 2025 +0200
+++ b/Framework/Enumerations.cpp	Tue Apr 08 13:11:27 2025 +0200
@@ -63,6 +63,9 @@
       case ImageCompression_Dicom:
         return "DICOM";
 
+      case ImageCompression_JpegLS:
+        return "JPEG-LS";
+
       default:
         throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
     }
--- a/Framework/Enumerations.h	Tue Apr 08 10:48:07 2025 +0200
+++ b/Framework/Enumerations.h	Tue Apr 08 13:11:27 2025 +0200
@@ -43,7 +43,8 @@
     ImageCompression_Jpeg = 5,
     ImageCompression_Jpeg2000 = 6,
     ImageCompression_Tiff = 7,
-    ImageCompression_UseOrthancPreview = 8
+    ImageCompression_UseOrthancPreview = 8,
+    ImageCompression_JpegLS = 9
   };
 
   enum OpticalPath
--- a/Framework/ImageToolbox.cpp	Tue Apr 08 10:48:07 2025 +0200
+++ b/Framework/ImageToolbox.cpp	Tue Apr 08 13:11:27 2025 +0200
@@ -27,6 +27,13 @@
 #include "Jpeg2000Reader.h"
 #include "Jpeg2000Writer.h"
 
+#if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1
+#  include <DicomParsing/DcmtkTranscoder.h>
+#  include <DicomParsing/ParsedDicomFile.h>
+#  include <DicomParsing/FromDcmtkBridge.h>
+#  include <dcmtk/dcmdata/dcpxitem.h>
+#endif
+
 #include <Compatibility.h>  // For std::unique_ptr
 #include <OrthancException.h>
 #include <Images/ImageProcessing.h>
@@ -185,6 +192,14 @@
           memcpy(&target[i * pitch], source.GetConstRow(i), pitch);
         }
       }
+      else if (compression == ImageCompression_JpegLS)
+      {
+#if (ORTHANC_ENABLE_DCMTK_TRANSCODING == 1) && (ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS == 1)
+        CompressFrameUsingDcmtk(target, source, Orthanc::DicomTransferSyntax_JPEGLSLossless);
+#else
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, "DCMTK was compiled without support for JPEG-LS");
+#endif
+      }
       else
       {
         std::unique_ptr<Orthanc::IImageWriter> writer;
@@ -405,5 +420,59 @@
                 p[3] == 0xe1);
       }
     }
+
+
+#if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1
+    void CompressFrameUsingDcmtk(std::string& target,
+                                 const Orthanc::ImageAccessor& frame,
+                                 Orthanc::DicomTransferSyntax syntax)
+    {
+      if (frame.GetFormat() != Orthanc::PixelFormat_RGB24)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+      }
+
+      Orthanc::ParsedDicomFile dicom(true);
+      dicom.EmbedImage(frame);
+
+      Orthanc::IDicomTranscoder::DicomImage source;
+      source.AcquireParsed(dicom);   // "dicom" is invalid below this point
+
+      Orthanc::IDicomTranscoder::DicomImage transcoded;
+
+      std::set<Orthanc::DicomTransferSyntax> s;
+      s.insert(syntax);
+
+      Orthanc::DcmtkTranscoder transcoder(1);
+
+      if (transcoder.Transcode(transcoded, source, s, true))
+      {
+        DcmPixelSequence* pixelSequence = Orthanc::FromDcmtkBridge::GetPixelSequence(*transcoded.GetParsed().getDataset());
+
+        DcmPixelItem* compressed = NULL;
+        Uint8* data = NULL;
+        if (pixelSequence != NULL &&
+            pixelSequence->card() == 2 &&
+            pixelSequence->getItem(compressed, 1).good() &&
+            compressed != NULL &&
+            compressed->getUint8Array(data).good() &&
+            data != NULL)
+        {
+          const unsigned int size = compressed->getNumberOfValues();
+
+          target.resize(size);
+          if (size != 0)
+          {
+            memcpy(&target[0], data, size);
+          }
+
+          return;  // Success
+        }
+      }
+
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, "DCMTK cannot transcode to " +
+                                      std::string(Orthanc::GetTransferSyntaxUid(syntax)));
+    }
+#endif
   }
 }
--- a/Framework/ImageToolbox.h	Tue Apr 08 10:48:07 2025 +0200
+++ b/Framework/ImageToolbox.h	Tue Apr 08 13:11:27 2025 +0200
@@ -81,5 +81,11 @@
     bool HasPngSignature(const std::string& buffer);
 
     bool HasJpegSignature(const std::string& buffer);
+
+#if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1
+    void CompressFrameUsingDcmtk(std::string& target,
+                                 const Orthanc::ImageAccessor& frame,
+                                 Orthanc::DicomTransferSyntax syntax);
+#endif
   }
 }
--- a/Framework/Outputs/MultiframeDicomWriter.cpp	Tue Apr 08 10:48:07 2025 +0200
+++ b/Framework/Outputs/MultiframeDicomWriter.cpp	Tue Apr 08 13:11:27 2025 +0200
@@ -182,6 +182,10 @@
         transferSyntax_ = EXS_JPEG2000LosslessOnly;
         break;
 
+      case ImageCompression_JpegLS:
+        transferSyntax_ = EXS_JPEGLSLossless;
+        break;
+
       default:
         throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
     }
@@ -321,6 +325,7 @@
 
       case ImageCompression_Jpeg:
       case ImageCompression_Jpeg2000:
+      case ImageCompression_JpegLS:
         offsetTable_->createOffsetTable(*offsetList_);
         dicom->getDataset()->insert(compressedPixelSequence_.release());
         break;
--- a/NEWS	Tue Apr 08 10:48:07 2025 +0200
+++ b/NEWS	Tue Apr 08 13:11:27 2025 +0200
@@ -4,6 +4,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 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)