changeset 368:80011cd589e6

support of rgb images
author Sebastien Jodogne <s.jodogne@gmail.com>
date Mon, 18 Feb 2013 16:07:28 +0100
parents 301f2831489c
children 4632a044746e
files CMakeLists.txt Core/DicomFormat/DicomIntegerPixelAccessor.cpp Core/DicomFormat/DicomIntegerPixelAccessor.h Core/Enumerations.h Core/PngWriter.cpp OrthancServer/FromDcmtkBridge.cpp UnitTests/PngWriter.cpp
diffstat 7 files changed, 224 insertions(+), 24 deletions(-) [+]
line wrap: on
line diff
--- a/CMakeLists.txt	Thu Feb 07 22:08:59 2013 +0100
+++ b/CMakeLists.txt	Mon Feb 18 16:07:28 2013 +0100
@@ -190,14 +190,15 @@
     include(${CMAKE_SOURCE_DIR}/Resources/CMake/GoogleTestConfiguration.cmake)
     add_executable(UnitTests
       ${GTEST_SOURCES}
+      UnitTests/FileStorage.cpp
+      UnitTests/MemoryCache.cpp
+      UnitTests/PngWriter.cpp
       UnitTests/RestApi.cpp
       UnitTests/SQLite.cpp
       UnitTests/SQLiteChromium.cpp
       UnitTests/ServerIndex.cpp
       UnitTests/Versions.cpp
       UnitTests/Zip.cpp
-      UnitTests/FileStorage.cpp
-      UnitTests/MemoryCache.cpp
       UnitTests/main.cpp
       )
     target_link_libraries(UnitTests ServerLibrary CoreLibrary)
--- a/Core/DicomFormat/DicomIntegerPixelAccessor.cpp	Thu Feb 07 22:08:59 2013 +0100
+++ b/Core/DicomFormat/DicomIntegerPixelAccessor.cpp	Mon Feb 18 16:07:28 2013 +0100
@@ -40,6 +40,7 @@
 #include <boost/lexical_cast.hpp>
 #include <limits>
 #include <cassert>
+#include <stdio.h>
 
 namespace Orthanc
 {
@@ -50,6 +51,7 @@
   static const DicomTag BITS_STORED(0x0028, 0x0101);
   static const DicomTag HIGH_BIT(0x0028, 0x0102);
   static const DicomTag PIXEL_REPRESENTATION(0x0028, 0x0103);
+  static const DicomTag PLANAR_CONFIGURATION(0x0028, 0x0006);
 
   DicomIntegerPixelAccessor::DicomIntegerPixelAccessor(const DicomMap& values,
                                                        const void* pixelData,
@@ -61,6 +63,7 @@
     unsigned int bitsStored;
     unsigned int highBit;
     unsigned int pixelRepresentation;
+    planarConfiguration_ = 0;
 
     try
     {
@@ -71,11 +74,22 @@
       bitsStored = boost::lexical_cast<unsigned int>(values.GetValue(BITS_STORED).AsString());
       highBit = boost::lexical_cast<unsigned int>(values.GetValue(HIGH_BIT).AsString());
       pixelRepresentation = boost::lexical_cast<unsigned int>(values.GetValue(PIXEL_REPRESENTATION).AsString());
+
+      if (samplesPerPixel_ > 1)
+      {
+        // The "Planar Configuration" is only set when "Samples per Pixels" is greater than 1
+        // https://www.dabsoft.ch/dicom/3/C.7.6.3.1.3/
+        planarConfiguration_ = boost::lexical_cast<unsigned int>(values.GetValue(PLANAR_CONFIGURATION).AsString());
+      }
     }
     catch (boost::bad_lexical_cast)
     {
       throw OrthancException(ErrorCode_NotImplemented);
     }
+    catch (OrthancException)
+    {
+      throw OrthancException(ErrorCode_NotImplemented);
+    }
 
     frame_ = 0;
     try
@@ -94,7 +108,8 @@
 
     if ((bitsAllocated != 8 && bitsAllocated != 16 && 
          bitsAllocated != 24 && bitsAllocated != 32) ||
-        numberOfFrames_ == 0)
+        numberOfFrames_ == 0 ||
+        (planarConfiguration_ != 0 && planarConfiguration_ != 1))
     {
       throw OrthancException(ErrorCode_NotImplemented);
     }
@@ -106,21 +121,23 @@
       throw OrthancException(ErrorCode_NotImplemented);
     }
 
-    if (samplesPerPixel_ != 1)
+    if (samplesPerPixel_ != 1 &&
+        samplesPerPixel_ != 3)
     {
       throw OrthancException(ErrorCode_NotImplemented);
     }
 
-    if (width_ * height_ * bitsAllocated / 8 * numberOfFrames_ > size)
+    bytesPerPixel_ = bitsAllocated / 8;
+    shift_ = highBit + 1 - bitsStored;
+    frameOffset_ = height_ * width_ * bytesPerPixel_ * samplesPerPixel_;
+
+    if (numberOfFrames_ * frameOffset_ > size)
     {
       throw OrthancException(ErrorCode_BadFileFormat);
     }
 
     /*printf("%d %d %d %d %d %d %d %d\n", width_, height_, samplesPerPixel_, bitsAllocated,
-           bitsStored, highBit, pixelRepresentation, numberOfFrames_);*/
-
-    bytesPerPixel_ = bitsAllocated / 8;
-    shift_ = highBit + 1 - bitsStored;
+      bitsStored, highBit, pixelRepresentation, numberOfFrames_);*/
 
     if (pixelRepresentation)
     {
@@ -133,8 +150,25 @@
       signMask_ = 0;
     }
 
-    rowOffset_ = width_ * bytesPerPixel_;
-    frameOffset_ = height_ * width_ * bytesPerPixel_;
+    if (planarConfiguration_ == 0)
+    {
+      /**
+       * The sample values for the first pixel are followed by the
+       * sample values for the second pixel, etc. For RGB images, this
+       * means the order of the pixel values sent shall be R1, G1, B1,
+       * R2, G2, B2, ..., etc.
+       **/
+      rowOffset_ = width_ * bytesPerPixel_ * samplesPerPixel_;
+    }
+    else
+    {
+      /**
+       * Each color plane shall be sent contiguously. For RGB images,
+       * this means the order of the pixel values sent is R1, R2, R3,
+       * ..., G1, G2, G3, ..., B1, B2, B3, etc.
+       **/
+      rowOffset_ = width_ * bytesPerPixel_;
+    }
   }
 
 
@@ -154,22 +188,49 @@
     {
       for (unsigned int x = 0; x < width_; x++)
       {
-        int32_t v = GetValue(x, y);
-        if (v < min)
-          min = v;
-        if (v > max)
-          max = v;
+        for (unsigned int c = 0; c < GetChannelCount(); c++)
+        {
+          int32_t v = GetValue(x, y);
+          if (v < min)
+            min = v;
+          if (v > max)
+            max = v;
+        }
       }
     }
   }
 
 
-  int32_t DicomIntegerPixelAccessor::GetValue(unsigned int x, unsigned int y) const
+  int32_t DicomIntegerPixelAccessor::GetValue(unsigned int x, 
+                                              unsigned int y,
+                                              unsigned int channel) const
   {
-    assert(x < width_ && y < height_);
+    assert(x < width_ && y < height_ && channel < samplesPerPixel_);
     
     const uint8_t* pixel = reinterpret_cast<const uint8_t*>(pixelData_) + 
-      y * rowOffset_ + x * bytesPerPixel_ + frame_ * frameOffset_;
+      y * rowOffset_ + frame_ * frameOffset_;
+
+    // https://www.dabsoft.ch/dicom/3/C.7.6.3.1.3/
+    if (planarConfiguration_ == 0)
+    {
+      /**
+       * The sample values for the first pixel are followed by the
+       * sample values for the second pixel, etc. For RGB images, this
+       * means the order of the pixel values sent shall be R1, G1, B1,
+       * R2, G2, B2, ..., etc.
+       **/
+      pixel += channel * bytesPerPixel_ + x * samplesPerPixel_ * bytesPerPixel_;
+    }
+    else
+    {
+      /**
+       * Each color plane shall be sent contiguously. For RGB images,
+       * this means the order of the pixel values sent is R1, R2, R3,
+       * ..., G1, G2, G3, ..., B1, B2, B3, etc.
+       **/
+      assert(frameOffset_ % samplesPerPixel_ == 0);
+      pixel += channel * frameOffset_ / samplesPerPixel_ + x * bytesPerPixel_;
+    }
 
     int32_t v;
     v = pixel[0];
--- a/Core/DicomFormat/DicomIntegerPixelAccessor.h	Thu Feb 07 22:08:59 2013 +0100
+++ b/Core/DicomFormat/DicomIntegerPixelAccessor.h	Mon Feb 18 16:07:28 2013 +0100
@@ -45,6 +45,7 @@
     unsigned int height_;
     unsigned int samplesPerPixel_;
     unsigned int numberOfFrames_;
+    unsigned int planarConfiguration_;
     const void* pixelData_;
     size_t size_;
 
@@ -87,6 +88,11 @@
     void GetExtremeValues(int32_t& min, 
                           int32_t& max) const;
 
-    int32_t GetValue(unsigned int x, unsigned int y) const;
+    unsigned int GetChannelCount() const
+    {
+      return samplesPerPixel_;
+    }
+
+    int32_t GetValue(unsigned int x, unsigned int y, unsigned int channel = 0) const;
   };
 }
--- a/Core/Enumerations.h	Thu Feb 07 22:08:59 2013 +0100
+++ b/Core/Enumerations.h	Mon Feb 18 16:07:28 2013 +0100
@@ -63,7 +63,7 @@
 
   enum PixelFormat
   {
-    PixelFormat_RGB,
+    PixelFormat_RGB24,
     PixelFormat_Grayscale8,
     PixelFormat_Grayscale16
   };
--- a/Core/PngWriter.cpp	Thu Feb 07 22:08:59 2013 +0100
+++ b/Core/PngWriter.cpp	Mon Feb 18 16:07:28 2013 +0100
@@ -134,6 +134,11 @@
 
     switch (format)
     {
+    case PixelFormat_RGB24:
+      pimpl_->bitDepth_ = 8;
+      pimpl_->colorType_ = PNG_COLOR_TYPE_RGB;
+      break;
+
     case PixelFormat_Grayscale8:
       pimpl_->bitDepth_ = 8;
       pimpl_->colorType_ = PNG_COLOR_TYPE_GRAY;
--- a/OrthancServer/FromDcmtkBridge.cpp	Thu Feb 07 22:08:59 2013 +0100
+++ b/OrthancServer/FromDcmtkBridge.cpp	Mon Feb 18 16:07:28 2013 +0100
@@ -1020,9 +1020,41 @@
   }
 
 
-  static void ExtractPngImagePreview(std::string& result,
-                                     DicomIntegerPixelAccessor& accessor)
+  static void ExtractPngImageColorPreview(std::string& result,
+                                          DicomIntegerPixelAccessor& accessor)
   {
+    assert(accessor.GetChannelCount() == 3);
+    PngWriter w;
+
+    std::vector<uint8_t> image(accessor.GetWidth() * accessor.GetHeight() * 3, 0);
+    uint8_t* pixel = &image[0];
+
+    for (unsigned int y = 0; y < accessor.GetHeight(); y++)
+    {
+      for (unsigned int x = 0; x < accessor.GetWidth(); x++)
+      {
+        for (unsigned int c = 0; c < 3; c++, pixel++)
+        {
+          int32_t v = accessor.GetValue(x, y, c);
+          if (v < 0)
+            *pixel = 0;
+          else if (v > 255)
+            *pixel = 255;
+          else
+            *pixel = v;
+        }
+      }
+    }
+
+    w.WriteToMemory(result, accessor.GetWidth(), accessor.GetHeight(),
+                    accessor.GetWidth() * 3, PixelFormat_RGB24, &image[0]);
+  }
+
+
+  static void ExtractPngImageGrayscalePreview(std::string& result,
+                                              DicomIntegerPixelAccessor& accessor)
+  {
+    assert(accessor.GetChannelCount() == 1);
     PngWriter w;
 
     int32_t min, max;
@@ -1054,6 +1086,8 @@
                                       DicomIntegerPixelAccessor& accessor,
                                       PixelFormat format)
   {
+    assert(accessor.GetChannelCount() == 1);
+
     PngWriter w;
 
     std::vector<T> image(accessor.GetWidth() * accessor.GetHeight(), 0);
@@ -1208,9 +1242,32 @@
     }
 
     PixelFormat format;
+
+    if (accessor->GetChannelCount() != 1 &&
+        (mode == ImageExtractionMode_UInt8 ||
+         mode == ImageExtractionMode_UInt16))
+    {
+      throw OrthancException(ErrorCode_NotImplemented);
+    }
+    
     switch (mode)
     {
       case ImageExtractionMode_Preview:
+        switch (accessor->GetChannelCount())
+        {
+          case 1:
+            format = PixelFormat_Grayscale8;
+            break;
+
+          case 3:
+            format = PixelFormat_RGB24;
+            break;
+
+          default:
+            throw OrthancException(ErrorCode_NotImplemented);
+        }
+        break;
+
       case ImageExtractionMode_UInt8:
         format = PixelFormat_Grayscale8;
         break;
@@ -1235,7 +1292,10 @@
       switch (mode)
       {
         case ImageExtractionMode_Preview:
-          ExtractPngImagePreview(result, *accessor);
+          if (format == PixelFormat_Grayscale8)
+            ExtractPngImageGrayscalePreview(result, *accessor);
+          else
+            ExtractPngImageColorPreview(result, *accessor);
           break;
 
         case ImageExtractionMode_UInt8:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/UnitTests/PngWriter.cpp	Mon Feb 18 16:07:28 2013 +0100
@@ -0,0 +1,67 @@
+#include "gtest/gtest.h"
+
+#include <stdint.h>
+#include "../Core/PngWriter.h"
+
+TEST(PngWriter, ColorPattern)
+{
+  Orthanc::PngWriter w;
+  int width = 17;
+  int height = 61;
+  int pitch = width * 3;
+
+  std::vector<uint8_t> image(height * pitch);
+  for (int y = 0; y < height; y++)
+  {
+    uint8_t *p = &image[0] + y * pitch;
+    for (int x = 0; x < width; x++, p += 3)
+    {
+      p[0] = (y % 3 == 0) ? 255 : 0;
+      p[1] = (y % 3 == 1) ? 255 : 0;
+      p[2] = (y % 3 == 2) ? 255 : 0;
+    }
+  }
+
+  w.WriteToFile("ColorPattern.png", width, height, pitch, Orthanc::PixelFormat_RGB24, &image[0]);
+}
+
+TEST(PngWriter, Gray8Pattern)
+{
+  Orthanc::PngWriter w;
+  int width = 17;
+  int height = 256;
+  int pitch = width;
+
+  std::vector<uint8_t> image(height * pitch);
+  for (int y = 0; y < height; y++)
+  {
+    uint8_t *p = &image[0] + y * pitch;
+    for (int x = 0; x < width; x++, p++)
+    {
+      *p = y;
+    }
+  }
+
+  w.WriteToFile("Gray8Pattern.png", width, height, pitch, Orthanc::PixelFormat_Grayscale8, &image[0]);
+}
+
+TEST(PngWriter, Gray16Pattern)
+{
+  Orthanc::PngWriter w;
+  int width = 256;
+  int height = 256;
+  int pitch = width * 2 + 17;
+
+  std::vector<uint8_t> image(height * pitch);
+  int v = 0;
+  for (int y = 0; y < height; y++)
+  {
+    uint16_t *p = reinterpret_cast<uint16_t*>(&image[0] + y * pitch);
+    for (int x = 0; x < width; x++, p++, v++)
+    {
+      *p = v;
+    }
+  }
+
+  w.WriteToFile("Gray16Pattern.png", width, height, pitch, Orthanc::PixelFormat_Grayscale16, &image[0]);
+}