changeset 847:03ea55da7429 jpeg

fully functional JPEG-LS conversion
author Sebastien Jodogne <s.jodogne@gmail.com>
date Thu, 05 Jun 2014 18:14:16 +0200
parents 715ab7674993
children 703e8d5b03fd
files OrthancServer/Internals/DicomImageDecoder.cpp OrthancServer/Internals/DicomImageDecoder.h OrthancServer/OrthancInitialization.cpp UnitTestsSources/JpegLossless.cpp
diffstat 4 files changed, 274 insertions(+), 12 deletions(-) [+]
line wrap: on
line diff
--- a/OrthancServer/Internals/DicomImageDecoder.cpp	Thu Jun 05 16:23:03 2014 +0200
+++ b/OrthancServer/Internals/DicomImageDecoder.cpp	Thu Jun 05 18:14:16 2014 +0200
@@ -78,14 +78,18 @@
 
 
 #include "../../Core/OrthancException.h"
+#include "../../Core/DicomFormat/DicomIntegerPixelAccessor.h"
 #include "../ToDcmtkBridge.h"
+#include "../FromDcmtkBridge.h"
 
+#include <glog/logging.h>
+
+#include <boost/lexical_cast.hpp>
+
+#if ORTHANC_JPEG_LOSSLESS_ENABLED == 1
 #include <dcmtk/dcmjpls/djcodecd.h>
 #include <dcmtk/dcmjpls/djcparam.h>
 #include <dcmtk/dcmjpeg/djrplol.h>
-#include <boost/lexical_cast.hpp>
-
-#if ORTHANC_JPEG_LOSSLESS_ENABLED == 1
 #endif
 
 
@@ -274,9 +278,183 @@
   }
 
 
+  bool DicomImageDecoder::IsUncompressedImage(const DcmDataset& dataset)
+  {
+    return (dataset.getOriginalXfer() == EXS_Unknown ||
+            dataset.getOriginalXfer() == EXS_LittleEndianImplicit ||
+            dataset.getOriginalXfer() == EXS_BigEndianImplicit ||
+            dataset.getOriginalXfer() == EXS_LittleEndianExplicit ||
+            dataset.getOriginalXfer() == EXS_BigEndianExplicit);
+  }
+
+
+  template <typename PixelType>
+  static void CopyPixels(ImageAccessor& target,
+                         const DicomIntegerPixelAccessor& source)
+  {
+    const PixelType minValue = std::numeric_limits<PixelType>::min();
+    const PixelType maxValue = std::numeric_limits<PixelType>::max();
+
+    for (unsigned int y = 0; y < source.GetHeight(); y++)
+    {
+      PixelType* pixel = reinterpret_cast<PixelType*>(target.GetRow(y));
+      for (unsigned int x = 0; x < source.GetWidth(); x++)
+      {
+        for (unsigned int c = 0; c < source.GetChannelCount(); c++, pixel++)
+        {
+          int32_t v = source.GetValue(x, y, c);
+          if (v < static_cast<int32_t>(minValue))
+          {
+            *pixel = minValue;
+          }
+          else if (v > static_cast<int32_t>(maxValue))
+          {
+            *pixel = maxValue;
+          }
+          else
+          {
+            *pixel = static_cast<PixelType>(v);
+          }
+        }
+      }
+    }
+  }
+
+
+  void DicomImageDecoder::DecodeUncompressedImage(ImageBuffer& target,
+                                                  DcmDataset& dataset,
+                                                  unsigned int frame)
+  {
+    if (!IsUncompressedImage(dataset))
+    {
+      throw OrthancException(ErrorCode_BadParameterType);
+    }
+
+    DecodeUncompressedImageInternal(target, dataset, frame);
+  }
+
+
+  void DicomImageDecoder::DecodeUncompressedImageInternal(ImageBuffer& target,
+                                                          DcmDataset& dataset,
+                                                          unsigned int frame)
+  {
+    // See also: http://support.dcmtk.org/wiki/dcmtk/howto/accessing-compressed-data
+
+    std::auto_ptr<DicomIntegerPixelAccessor> source;
+
+    DicomMap m;
+    FromDcmtkBridge::Convert(m, dataset);
+
+
+    /**
+     * Create an accessor to the raw values of the DICOM image.
+     **/
+
+    std::string privateContent;
+
+    DcmElement* e;
+    if (dataset.findAndGetElement(ToDcmtkBridge::Convert(DICOM_TAG_PIXEL_DATA), e).good() &&
+        e != NULL)
+    {
+      Uint8* pixData = NULL;
+      if (e->getUint8Array(pixData) == EC_Normal)
+      {    
+        source.reset(new DicomIntegerPixelAccessor(m, pixData, e->getLength()));
+      }
+    }
+    else if (DicomImageDecoder::DecodePsmctRle1(privateContent, dataset))
+    {
+      LOG(INFO) << "The PMSCT_RLE1 decoding has succeeded";
+      Uint8* pixData = NULL;
+      if (privateContent.size() > 0)
+      {
+        pixData = reinterpret_cast<Uint8*>(&privateContent[0]);
+      }
+
+      source.reset(new DicomIntegerPixelAccessor(m, pixData, privateContent.size()));
+    }
+    
+    if (source.get() == NULL)
+    {
+      throw OrthancException(ErrorCode_BadFileFormat);
+    }
+
+    source->SetCurrentFrame(frame);
+
+
+    /**
+     * Resize the target image, with some sanity checks.
+     **/
+
+    SetupImageBuffer(target, dataset);
+
+    if (target.GetWidth() != target.GetWidth() ||
+        target.GetHeight() != target.GetHeight())
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+
+    bool ok;
+    switch (target.GetFormat())
+    {
+      case PixelFormat_RGB24:
+        ok = source->GetChannelCount() == 3;
+        break;
+
+      case PixelFormat_RGBA32:
+        ok = source->GetChannelCount() == 4;
+        break;
+
+      case PixelFormat_Grayscale8:
+      case PixelFormat_Grayscale16:
+      case PixelFormat_SignedGrayscale16:
+        ok = source->GetChannelCount() == 1;
+        break;
+
+      default:
+        ok = false;   // (*)
+        break;
+    }
+
+    if (!ok)
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+
+
+    /**
+     * Loop over the DICOM buffer, storing its value into the target
+     * image.
+     **/
+
+    ImageAccessor accessor(target.GetAccessor());
+
+    switch (target.GetFormat())
+    {
+      case PixelFormat_RGB24:
+      case PixelFormat_RGBA32:
+      case PixelFormat_Grayscale8:
+        CopyPixels<uint8_t>(accessor, *source);
+        break;
+
+      case PixelFormat_Grayscale16:
+        CopyPixels<uint16_t>(accessor, *source);
+        break;
+
+      case PixelFormat_SignedGrayscale16:
+        CopyPixels<int16_t>(accessor, *source);
+        break;
+
+      default:
+        throw OrthancException(ErrorCode_InternalError);
+    }
+  }
+
+
 #if ORTHANC_JPEG_LOSSLESS_ENABLED == 1
   void DicomImageDecoder::DecodeJpegLossless(ImageBuffer& target,
-                                             DcmDataset& dataset)
+                                             DcmDataset& dataset,
+                                             unsigned int frame)
   {
     if (!IsJpegLossless(dataset))
     {
@@ -315,7 +493,7 @@
     OFString decompressedColorModel;  // Out
     DJ_RPLossless representationParameter;
     OFCondition c = decoder.decodeFrame(&representationParameter, pixelSequence, &parameters, 
-                                        &dataset, 0, startFragment, accessor.GetBuffer(), 
+                                        &dataset, frame, startFragment, accessor.GetBuffer(), 
                                         accessor.GetSize(), decompressedColorModel);
 
     if (!c.good())
@@ -326,4 +504,47 @@
 #endif
 
 
+
+  bool DicomImageDecoder::Decode(ImageBuffer& target,
+                                 DcmDataset& dataset,
+                                 unsigned int frame)
+  {
+    if (IsUncompressedImage(dataset))
+    {
+      DecodeUncompressedImage(target, dataset, frame);
+      return true;
+    }
+
+#if ORTHANC_JPEG_LOSSLESS_ENABLED == 1
+    if (IsJpegLossless(dataset))
+    {
+      LOG(INFO) << "Decoding a JPEG-LS image";
+      DecodeJpegLossless(target, dataset, frame);
+      return true;
+    }
+#endif
+
+    /**
+     * This DICOM image format is not natively supported by
+     * Orthanc. As a last resort, try and decode it through
+     * DCMTK. This will result in higher memory consumption. This is
+     * actually the second example of the following page:
+     * http://support.dcmtk.org/docs/mod_dcmjpeg.html#Examples
+     **/
+    
+    {
+      LOG(INFO) << "Using DCMTK to decode a compressed image";
+
+      std::auto_ptr<DcmDataset> converted(dynamic_cast<DcmDataset*>(dataset.clone()));
+      converted->chooseRepresentation(EXS_LittleEndianExplicit, NULL);
+
+      if (converted->canWriteXfer(EXS_LittleEndianExplicit))
+      {
+        DecodeUncompressedImageInternal(target, *converted, frame);
+        return true;
+      }
+    }
+
+    return false;
+  }
 }
--- a/OrthancServer/Internals/DicomImageDecoder.h	Thu Jun 05 16:23:03 2014 +0200
+++ b/OrthancServer/Internals/DicomImageDecoder.h	Thu Jun 05 18:14:16 2014 +0200
@@ -42,6 +42,10 @@
   {
   public:   // TODO SWITCH TO PRIVATE
     //private:
+    static void DecodeUncompressedImageInternal(ImageBuffer& target,
+                                                DcmDataset& dataset,
+                                                unsigned int frame);
+
     static bool IsPsmctRle1(DcmDataset& dataset);
 
     static void SetupImageBuffer(ImageBuffer& target,
@@ -51,12 +55,22 @@
                                 DcmDataset& dataset);
 
   public:
+    static bool IsUncompressedImage(const DcmDataset& dataset);
+
     static bool IsJpegLossless(const DcmDataset& dataset);
 
+    static void DecodeUncompressedImage(ImageBuffer& target,
+                                        DcmDataset& dataset,
+                                        unsigned int frame);
+
 #if ORTHANC_JPEG_LOSSLESS_ENABLED == 1
     static void DecodeJpegLossless(ImageBuffer& target,
-                                   DcmDataset& dataset);
+                                   DcmDataset& dataset,
+                                   unsigned int frame);
 #endif
 
+    static bool Decode(ImageBuffer& target,
+                       DcmDataset& dataset,
+                       unsigned int frame);
   };
 }
--- a/OrthancServer/OrthancInitialization.cpp	Thu Jun 05 16:23:03 2014 +0200
+++ b/OrthancServer/OrthancInitialization.cpp	Thu Jun 05 18:14:16 2014 +0200
@@ -45,6 +45,17 @@
 #include <boost/thread.hpp>
 #include <glog/logging.h>
 
+
+#if ORTHANC_JPEG_ENABLED == 1
+#include <dcmtk/dcmjpeg/djdecode.h>
+#endif
+
+
+#if ORTHANC_JPEG_LOSSLESS_ENABLED == 1
+#include <dcmtk/dcmjpls/djdecode.h>
+#endif
+
+
 namespace Orthanc
 {
   static boost::mutex globalMutex_;
@@ -182,6 +193,16 @@
     RegisterUserContentType();
 
     DicomServer::InitializeDictionary();
+
+#if ORTHANC_JPEG_LOSSLESS_ENABLED == 1
+    LOG(WARNING) << "Registering JPEG Lossless codecs";
+    DJLSDecoderRegistration::registerCodecs();    
+#endif
+
+#if ORTHANC_JPEG_ENABLED == 1
+    LOG(WARNING) << "Registering JPEG codecs";
+    DJDecoderRegistration::registerCodecs(); 
+#endif
   }
 
 
@@ -191,6 +212,16 @@
     boost::mutex::scoped_lock lock(globalMutex_);
     HttpClient::GlobalFinalize();
     configuration_.reset(NULL);
+
+#if ORTHANC_JPEG_LOSSLESS_ENABLED == 1
+    // Unregister JPEG-LS codecs
+    DJLSDecoderRegistration::cleanup();
+#endif
+
+#if ORTHANC_JPEG_ENABLED == 1
+    // Unregister JPEG codecs
+    DJDecoderRegistration::cleanup();
+#endif
   }
 
 
--- a/UnitTestsSources/JpegLossless.cpp	Thu Jun 05 16:23:03 2014 +0200
+++ b/UnitTestsSources/JpegLossless.cpp	Thu Jun 05 18:14:16 2014 +0200
@@ -49,8 +49,6 @@
 
 TEST(JpegLossless, Basic)
 {
-  //DJLSDecoderRegistration::registerCodecs( EJLSUC_default, EJLSPC_restore,OFFalse );
-
 #if 0
   // Fallback
 
@@ -89,7 +87,8 @@
     ASSERT_TRUE(DicomImageDecoder::IsJpegLossless(dataset));
 
     ImageBuffer image;
-    DicomImageDecoder::DecodeJpegLossless(image, dataset);
+    //DicomImageDecoder::DecodeJpegLossless(image, dataset, 0);
+    DicomImageDecoder::Decode(image, dataset, 0);
 
     ImageAccessor accessor(image.GetAccessor());
 
@@ -108,9 +107,6 @@
   }
 
 #endif
-
-
-  //DJLSDecoderRegistration::cleanup();
 }