changeset 2423:5a7c5c541a1d

Built-in decoding of palette images
author Sebastien Jodogne <s.jodogne@gmail.com>
date Mon, 09 Oct 2017 21:58:08 +0200
parents b340f0a9022c
children 7ef9207f31d4
files Core/DicomFormat/DicomImageInformation.cpp Core/DicomParsing/Internals/DicomImageDecoder.cpp Core/DicomParsing/Internals/DicomImageDecoder.h Core/Enumerations.cpp Core/Enumerations.h Core/Images/ImageProcessing.cpp NEWS
diffstat 7 files changed, 250 insertions(+), 32 deletions(-) [+]
line wrap: on
line diff
--- a/Core/DicomFormat/DicomImageInformation.cpp	Sun Oct 08 11:46:56 2017 +0200
+++ b/Core/DicomFormat/DicomImageInformation.cpp	Mon Oct 09 21:58:08 2017 +0200
@@ -219,9 +219,19 @@
   {
     if (photometric_ == PhotometricInterpretation_Palette)
     {
-      return false;
+      if (GetBitsStored() == 8 && GetChannelCount() == 1 && !IsSigned())
+      {
+        format = PixelFormat_RGB24;
+        return true;
+      }
+
+      if (GetBitsStored() == 16 && GetChannelCount() == 1 && !IsSigned())
+      {
+        format = PixelFormat_RGB48;
+        return true;
+      }
     }
-
+    
     if (ignorePhotometricInterpretation ||
         photometric_ == PhotometricInterpretation_Monochrome1 ||
         photometric_ == PhotometricInterpretation_Monochrome2)
--- a/Core/DicomParsing/Internals/DicomImageDecoder.cpp	Sun Oct 08 11:46:56 2017 +0200
+++ b/Core/DicomParsing/Internals/DicomImageDecoder.cpp	Mon Oct 09 21:58:08 2017 +0200
@@ -96,14 +96,17 @@
 
 #include <boost/lexical_cast.hpp>
 
+#include <dcmtk/dcmdata/dcdeftag.h>
 #include <dcmtk/dcmdata/dcfilefo.h>
 #include <dcmtk/dcmdata/dcrleccd.h>
 #include <dcmtk/dcmdata/dcrlecp.h>
+#include <dcmtk/dcmdata/dcrlerp.h>
 
 #if ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS == 1
+#  include <dcmtk/dcmjpeg/djrplol.h>
 #  include <dcmtk/dcmjpls/djcodecd.h>
 #  include <dcmtk/dcmjpls/djcparam.h>
-#  include <dcmtk/dcmjpeg/djrplol.h>
+#  include <dcmtk/dcmjpls/djrparam.h>
 #endif
 
 #if ORTHANC_ENABLE_DCMTK_JPEG == 1
@@ -115,6 +118,7 @@
 #  include <dcmtk/dcmjpeg/djdecpro.h>
 #  include <dcmtk/dcmjpeg/djdecsps.h>
 #  include <dcmtk/dcmjpeg/djdecsv1.h>
+#  include <dcmtk/dcmjpeg/djrploss.h>
 #endif
 
 #if DCMTK_VERSION_NUMBER <= 360
@@ -381,33 +385,155 @@
   }
 
 
+  static ImageAccessor* DecodeLookupTable(std::auto_ptr<ImageAccessor>& target,
+                                          const DicomImageInformation& info,
+                                          DcmDataset& dataset,
+                                          const uint8_t* pixelData,
+                                          unsigned long pixelLength)
+  {
+    LOG(INFO) << "Decoding a lookup table";
+
+    OFString r, g, b;
+    PixelFormat format;
+    const uint16_t* lutRed = NULL;
+    const uint16_t* lutGreen = NULL;
+    const uint16_t* lutBlue = NULL;
+    unsigned long rc = 0;
+    unsigned long gc = 0;
+    unsigned long bc = 0;
+
+    if (pixelData == NULL &&
+        !dataset.findAndGetUint8Array(DCM_PixelData, pixelData, &pixelLength).good())
+    {
+      throw OrthancException(ErrorCode_NotImplemented);
+    }
+
+    if (info.IsPlanar() ||
+        info.GetNumberOfFrames() != 1 ||
+        !info.ExtractPixelFormat(format, false) ||
+        !dataset.findAndGetOFStringArray(DCM_BluePaletteColorLookupTableDescriptor, b).good() ||
+        !dataset.findAndGetOFStringArray(DCM_GreenPaletteColorLookupTableDescriptor, g).good() ||
+        !dataset.findAndGetOFStringArray(DCM_RedPaletteColorLookupTableDescriptor, r).good() ||
+        !dataset.findAndGetUint16Array(DCM_BluePaletteColorLookupTableData, lutBlue, &bc).good() ||
+        !dataset.findAndGetUint16Array(DCM_GreenPaletteColorLookupTableData, lutGreen, &gc).good() ||
+        !dataset.findAndGetUint16Array(DCM_RedPaletteColorLookupTableData, lutRed, &rc).good() ||
+        r != g ||
+        r != b ||
+        g != b ||
+        lutRed == NULL ||
+        lutGreen == NULL ||
+        lutBlue == NULL ||
+        pixelData == NULL)
+    {
+      throw OrthancException(ErrorCode_NotImplemented);
+    }
+
+    switch (format)
+    {
+      case PixelFormat_RGB24:
+      {
+        if (r != "256\\0\\16" ||
+            rc != 256 ||
+            gc != 256 ||
+            bc != 256 ||
+            pixelLength != target->GetWidth() * target->GetHeight())
+        {
+          throw OrthancException(ErrorCode_NotImplemented);
+        }
+
+        const uint8_t* source = reinterpret_cast<const uint8_t*>(pixelData);
+        
+        for (unsigned int y = 0; y < target->GetHeight(); y++)
+        {
+          uint8_t* p = reinterpret_cast<uint8_t*>(target->GetRow(y));
+
+          for (unsigned int x = 0; x < target->GetWidth(); x++)
+          {
+            p[0] = lutRed[*source] >> 8;
+            p[1] = lutGreen[*source] >> 8;
+            p[2] = lutBlue[*source] >> 8;
+            source++;
+            p += 3;
+          }
+        }
+
+        return target.release();
+      }
+
+      case PixelFormat_RGB48:
+      {
+        if (r != "0\\0\\16" ||
+            rc != 65536 ||
+            gc != 65536 ||
+            bc != 65536 ||
+            pixelLength != 2 * target->GetWidth() * target->GetHeight())
+        {
+          throw OrthancException(ErrorCode_NotImplemented);
+        }
+
+        const uint16_t* source = reinterpret_cast<const uint16_t*>(pixelData);
+        
+        for (unsigned int y = 0; y < target->GetHeight(); y++)
+        {
+          uint16_t* p = reinterpret_cast<uint16_t*>(target->GetRow(y));
+
+          for (unsigned int x = 0; x < target->GetWidth(); x++)
+          {
+            p[0] = lutRed[*source];
+            p[1] = lutGreen[*source];
+            p[2] = lutBlue[*source];
+            source++;
+            p += 3;
+          }
+        }
+
+        return target.release();
+      }
+
+      default:
+        break;
+    }
+
+    throw OrthancException(ErrorCode_InternalError);
+  }                                          
+
+
   ImageAccessor* DicomImageDecoder::DecodeUncompressedImage(DcmDataset& dataset,
                                                             unsigned int frame)
   {
-    ImageSource source;
-    source.Setup(dataset, frame);
-
-
     /**
-     * Resize the target image.
+     * Create the target image.
      **/
 
     std::auto_ptr<ImageAccessor> target(CreateImage(dataset, false));
 
+    ImageSource source;
+    source.Setup(dataset, frame);
+
     if (source.GetWidth() != target->GetWidth() ||
         source.GetHeight() != target->GetHeight())
     {
       throw OrthancException(ErrorCode_InternalError);
     }
 
+    
+    /**
+     * Deal with lookup tables
+     **/
+
+    const DicomImageInformation& info = source.GetAccessor().GetInformation();
+
+    if (info.GetPhotometricInterpretation() == PhotometricInterpretation_Palette)
+    {
+      return DecodeLookupTable(target, info, dataset, NULL, 0);
+    }       
+
 
     /**
      * If the format of the DICOM buffer is natively supported, use a
      * direct access to copy its values.
      **/
 
-    const DicomImageInformation& info = source.GetAccessor().GetInformation();
-
     bool fastVersionSuccess = false;
     PixelFormat sourceFormat;
     if (!info.IsPlanar() &&
@@ -470,10 +596,12 @@
   }
 
 
-  ImageAccessor* DicomImageDecoder::ApplyCodec(const DcmCodec& codec,
-                                               const DcmCodecParameter& parameters,
-                                               DcmDataset& dataset,
-                                               unsigned int frame)
+  ImageAccessor* DicomImageDecoder::ApplyCodec
+  (const DcmCodec& codec,
+   const DcmCodecParameter& parameters,
+   const DcmRepresentationParameter& representationParameter,
+   DcmDataset& dataset,
+   unsigned int frame)
   {
     DcmPixelSequence* pixelSequence = FromDcmtkBridge::GetPixelSequence(dataset);
     if (pixelSequence == NULL)
@@ -481,24 +609,49 @@
       throw OrthancException(ErrorCode_BadFileFormat);
     }
 
+    DicomMap m;
+    FromDcmtkBridge::ExtractDicomSummary(m, dataset);
+    DicomImageInformation info(m);
+
     std::auto_ptr<ImageAccessor> target(CreateImage(dataset, true));
 
     Uint32 startFragment = 0;  // Default 
     OFString decompressedColorModel;  // Out
-    DJ_RPLossless representationParameter;
-    OFCondition c = codec.decodeFrame(&representationParameter, 
-                                      pixelSequence, &parameters, 
-                                      &dataset, frame, startFragment, target->GetBuffer(), 
-                                      target->GetSize(), decompressedColorModel);
+
+    OFCondition c;
+    
+    if (info.GetPhotometricInterpretation() == PhotometricInterpretation_Palette &&
+        info.GetChannelCount() == 1)
+    {
+      std::string uncompressed;
+      uncompressed.resize(info.GetWidth() * info.GetHeight() * info.GetBytesPerValue());
 
-    if (c.good())
-    {
-      return target.release();    
+      if (uncompressed.size() == 0 ||
+          !codec.decodeFrame(&representationParameter, 
+                             pixelSequence, &parameters, 
+                             &dataset, frame, startFragment, &uncompressed[0],
+                             uncompressed.size(), decompressedColorModel).good())
+      {
+        LOG(ERROR) << "Cannot decode a palette image";
+        throw OrthancException(ErrorCode_BadFileFormat);
+      }
+
+      return DecodeLookupTable(target, info, dataset,
+                               reinterpret_cast<const uint8_t*>(uncompressed.c_str()),
+                               uncompressed.size());
     }
     else
     {
-      LOG(ERROR) << "Cannot decode an image";
-      throw OrthancException(ErrorCode_BadFileFormat);
+      if (!codec.decodeFrame(&representationParameter, 
+                             pixelSequence, &parameters, 
+                             &dataset, frame, startFragment, target->GetBuffer(), 
+                             target->GetSize(), decompressedColorModel).good())
+      {
+        LOG(ERROR) << "Cannot decode a non-palette image";
+        throw OrthancException(ErrorCode_BadFileFormat);
+      }
+
+      return target.release();
     }
   }
 
@@ -532,6 +685,7 @@
         syntax == EXS_JPEGLSLossy)
     {
       DJLSCodecParameter parameters;
+      DJLSRepresentationParameter representationParameter;
       std::auto_ptr<DJLSDecoderBase> decoder;
 
       switch (syntax)
@@ -550,7 +704,7 @@
           throw OrthancException(ErrorCode_InternalError);
       }
     
-      return ApplyCodec(*decoder, parameters, dataset, frame);
+      return ApplyCodec(*decoder, parameters, representationParameter, dataset, frame);
     }
 #endif
 
@@ -573,6 +727,7 @@
         EDC_photometricInterpretation,  // Perform color space conversion from YCbCr to RGB if DICOM photometric interpretation indicates YCbCr
         EUC_default,     // Mode for UID creation, unused for decompression
         EPC_default);    // Automatically determine whether color-by-plane is required from the SOP Class UID and decompressed photometric interpretation
+      DJ_RPLossy representationParameter;
       std::auto_ptr<DJCodecDecoder> decoder;
 
       switch (syntax)
@@ -611,7 +766,7 @@
           throw OrthancException(ErrorCode_InternalError);
       }
     
-      return ApplyCodec(*decoder, parameters, dataset, frame);      
+      return ApplyCodec(*decoder, parameters, representationParameter, dataset, frame);      
     }
 #endif
 
@@ -621,7 +776,8 @@
       LOG(INFO) << "Decoding a RLE lossless DICOM image";
       DcmRLECodecParameter parameters;
       DcmRLECodecDecoder decoder;
-      return ApplyCodec(decoder, parameters, dataset, frame);
+      DcmRLERepresentationParameter representationParameter;
+      return ApplyCodec(decoder, parameters, representationParameter, dataset, frame);
     }
 
 
@@ -678,7 +834,8 @@
     if (image->GetFormat() != format)
     {
       // A conversion is required
-      std::auto_ptr<ImageAccessor> target(new Image(format, image->GetWidth(), image->GetHeight(), false));
+      std::auto_ptr<ImageAccessor> target
+        (new Image(format, image->GetWidth(), image->GetHeight(), false));
       ImageProcessing::Convert(*target, *image);
       image = target;
     }
@@ -697,6 +854,15 @@
         return true;
       }
 
+      case PixelFormat_RGB48:
+      {
+        std::auto_ptr<ImageAccessor> target
+          (new Image(PixelFormat_RGB24, image->GetWidth(), image->GetHeight(), false));
+        ImageProcessing::Convert(*target, *image);
+        image = target;
+        return true;
+      }
+
       case PixelFormat_Grayscale8:
       case PixelFormat_Grayscale16:
       case PixelFormat_SignedGrayscale16:
@@ -711,13 +877,15 @@
         }
         else
         {
-          ImageProcessing::ShiftScale(*image, static_cast<float>(-a), 255.0f / static_cast<float>(b - a));
+          ImageProcessing::ShiftScale(*image, static_cast<float>(-a),
+                                      255.0f / static_cast<float>(b - a));
         }
 
         // If the source image is not grayscale 8bpp, convert it
         if (image->GetFormat() != PixelFormat_Grayscale8)
         {
-          std::auto_ptr<ImageAccessor> target(new Image(PixelFormat_Grayscale8, image->GetWidth(), image->GetHeight(), false));
+          std::auto_ptr<ImageAccessor> target
+            (new Image(PixelFormat_Grayscale8, image->GetWidth(), image->GetHeight(), false));
           ImageProcessing::Convert(*target, *image);
           image = target;
         }
--- a/Core/DicomParsing/Internals/DicomImageDecoder.h	Sun Oct 08 11:46:56 2017 +0200
+++ b/Core/DicomParsing/Internals/DicomImageDecoder.h	Mon Oct 09 21:58:08 2017 +0200
@@ -57,6 +57,7 @@
 class DcmDataset;
 class DcmCodec;
 class DcmCodecParameter;
+class DcmRepresentationParameter;
 
 namespace Orthanc
 {
@@ -77,6 +78,7 @@
 
     static ImageAccessor* ApplyCodec(const DcmCodec& codec,
                                      const DcmCodecParameter& parameters,
+                                     const DcmRepresentationParameter& representationParameter,
                                      DcmDataset& dataset,
                                      unsigned int frame);
 
--- a/Core/Enumerations.cpp	Sun Oct 08 11:46:56 2017 +0200
+++ b/Core/Enumerations.cpp	Mon Oct 09 21:58:08 2017 +0200
@@ -756,6 +756,9 @@
       case PixelFormat_Grayscale32:
         return "Grayscale (unsigned 32bpp)";
 
+      case PixelFormat_RGB48:
+        return "RGB48";
+
       default:
         throw OrthancException(ErrorCode_ParameterOutOfRange);
     }
@@ -1344,6 +1347,9 @@
         assert(sizeof(float) == 4);
         return 4;
 
+      case PixelFormat_RGB48:
+        return 6;
+
       default:
         throw OrthancException(ErrorCode_ParameterOutOfRange);
     }
--- a/Core/Enumerations.h	Sun Oct 08 11:46:56 2017 +0200
+++ b/Core/Enumerations.h	Mon Oct 09 21:58:08 2017 +0200
@@ -199,14 +199,21 @@
      **/
     PixelFormat_Float32 = 6,
 
-    // This is the memory layout for Cairo (internal use)
+    // This is the memory layout for Cairo (for internal use in Stone of Orthanc)
     PixelFormat_BGRA32 = 7,
 
     /**
      * {summary}{Graylevel, unsigned 32bpp image.}
      * {description}{The image is graylevel. Each pixel is unsigned and stored in 4 bytes.}
      **/
-    PixelFormat_Grayscale32 = 8
+    PixelFormat_Grayscale32 = 8,
+    
+    /**
+     * {summary}{Color image in RGB48 format.}
+     * {description}{This format describes a color image. The pixels are stored in 6
+     * consecutive bytes. The memory layout is RGB.}
+     **/
+    PixelFormat_RGB48 = 9
   };
 
 
--- a/Core/Images/ImageProcessing.cpp	Sun Oct 08 11:46:56 2017 +0200
+++ b/Core/Images/ImageProcessing.cpp	Mon Oct 09 21:58:08 2017 +0200
@@ -568,6 +568,26 @@
       return;
     }
 
+    if (target.GetFormat() == PixelFormat_RGB24 &&
+        source.GetFormat() == PixelFormat_RGB48)
+    {
+      for (unsigned int y = 0; y < source.GetHeight(); y++)
+      {
+        const uint16_t* p = reinterpret_cast<const uint16_t*>(source.GetConstRow(y));
+        uint8_t* q = reinterpret_cast<uint8_t*>(target.GetRow(y));
+        for (unsigned int x = 0; x < source.GetWidth(); x++)
+        {
+          q[0] = p[0] >> 8;
+          q[1] = p[1] >> 8;
+          q[2] = p[2] >> 8;
+          p += 3;
+          q += 3;
+        }
+      }
+
+      return;
+    }
+
     throw OrthancException(ErrorCode_NotImplemented);
   }
 
--- a/NEWS	Sun Oct 08 11:46:56 2017 +0200
+++ b/NEWS	Mon Oct 09 21:58:08 2017 +0200
@@ -1,6 +1,11 @@
 Pending changes in the mainline
 ===============================
 
+General
+-------
+
+* Built-in decoding of palette images
+
 REST API
 --------