changeset 5054:255b02c68908

Added support for RGBA64 images in tools/create-dicom and /preview (Contribution from James Manners - Pliny)
author Alain Mazy <am@osimis.io>
date Fri, 08 Jul 2022 15:26:52 +0200
parents 1e435be86887
children 942eb3849d8d
files NEWS OrthancFramework/Sources/DicomFormat/DicomImageInformation.cpp OrthancFramework/Sources/DicomParsing/ParsedDicomFile.cpp OrthancFramework/Sources/Enumerations.cpp OrthancFramework/Sources/Enumerations.h OrthancFramework/Sources/Images/PngReader.cpp OrthancFramework/Sources/Images/PngWriter.cpp OrthancFramework/UnitTestsSources/FromDcmtkTests.cpp OrthancFramework/UnitTestsSources/ImageTests.cpp OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp
diffstat 10 files changed, 130 insertions(+), 4 deletions(-) [+]
line wrap: on
line diff
--- a/NEWS	Fri Jul 08 07:24:10 2022 +0200
+++ b/NEWS	Fri Jul 08 15:26:52 2022 +0200
@@ -1,3 +1,12 @@
+Pending changes in the mainline
+===============================
+
+General
+-------
+
+* Added support for RGBA64 images in tools/create-dicom and /preview
+
+
 Version 1.11.1 (2022-06-30)
 ===========================
 
--- a/OrthancFramework/Sources/DicomFormat/DicomImageInformation.cpp	Fri Jul 08 07:24:10 2022 +0200
+++ b/OrthancFramework/Sources/DicomFormat/DicomImageInformation.cpp	Fri Jul 08 15:26:52 2022 +0200
@@ -380,6 +380,15 @@
       return true;
     }
 
+    if (GetBitsStored() == 16 &&
+        GetChannelCount() == 3 &&
+        !IsSigned() &&
+        (ignorePhotometricInterpretation || photometric_ == PhotometricInterpretation_RGB))
+    {
+      format = PixelFormat_RGB48;
+      return true;
+    }
+
     return false;
   }
 
--- a/OrthancFramework/Sources/DicomParsing/ParsedDicomFile.cpp	Fri Jul 08 07:24:10 2022 +0200
+++ b/OrthancFramework/Sources/DicomParsing/ParsedDicomFile.cpp	Fri Jul 08 15:26:52 2022 +0200
@@ -1276,14 +1276,16 @@
         accessor.GetFormat() != PixelFormat_Grayscale16 &&
         accessor.GetFormat() != PixelFormat_SignedGrayscale16 &&
         accessor.GetFormat() != PixelFormat_RGB24 &&
-        accessor.GetFormat() != PixelFormat_RGBA32)
+        accessor.GetFormat() != PixelFormat_RGBA32 && 
+        accessor.GetFormat() != PixelFormat_RGBA64)
     {
       throw OrthancException(ErrorCode_NotImplemented);
     }
 
     InvalidateCache();
 
-    if (accessor.GetFormat() == PixelFormat_RGBA32)
+    if (accessor.GetFormat() == PixelFormat_RGBA32 || 
+        accessor.GetFormat() == PixelFormat_RGBA64)
     {
       LOG(WARNING) << "Getting rid of the alpha channel when embedding a RGBA image inside DICOM";
     }
@@ -1335,6 +1337,20 @@
         ReplacePlainString(DICOM_TAG_PLANAR_CONFIGURATION, "0");  // Color channels are interleaved
 
         break;
+      
+      case PixelFormat_RGBA64:
+        ReplacePlainString(DICOM_TAG_PHOTOMETRIC_INTERPRETATION, "RGB");
+        ReplacePlainString(DICOM_TAG_SAMPLES_PER_PIXEL, "3");
+        ReplacePlainString(DICOM_TAG_BITS_ALLOCATED, "16");
+        ReplacePlainString(DICOM_TAG_BITS_STORED, "16");
+        ReplacePlainString(DICOM_TAG_HIGH_BIT, "15");
+        bytesPerPixel = 6;
+
+        // "Planar configuration" must only present if "Samples per
+        // Pixel" is greater than 1
+        ReplacePlainString(DICOM_TAG_PLANAR_CONFIGURATION, "0");  // Color channels are interleaved
+
+        break;
 
       case PixelFormat_Grayscale16:
       case PixelFormat_SignedGrayscale16:
@@ -1394,6 +1410,23 @@
 
             break;
           }
+
+          case PixelFormat_RGBA64:
+          {
+            // The alpha channel is not supported by the DICOM standard
+            const Uint8* source = reinterpret_cast<const Uint8*>(accessor.GetConstRow(y));
+            for (unsigned int x = 0; x < width; x++, q += 6, source += 8)
+            {
+              q[0] = source[0];
+              q[1] = source[1];
+              q[2] = source[2];
+              q[3] = source[3];
+              q[4] = source[4];
+              q[5] = source[5];
+            }
+
+            break;
+          }
           
           default:
             throw OrthancException(ErrorCode_NotImplemented);
--- a/OrthancFramework/Sources/Enumerations.cpp	Fri Jul 08 07:24:10 2022 +0200
+++ b/OrthancFramework/Sources/Enumerations.cpp	Fri Jul 08 15:26:52 2022 +0200
@@ -1914,6 +1914,7 @@
         return 6;
 
       case PixelFormat_Grayscale64:
+      case PixelFormat_RGBA64:
         return 8;
 
       default:
--- a/OrthancFramework/Sources/Enumerations.h	Fri Jul 08 07:24:10 2022 +0200
+++ b/OrthancFramework/Sources/Enumerations.h	Fri Jul 08 15:26:52 2022 +0200
@@ -317,7 +317,14 @@
      * {summary}{Graylevel, unsigned 64bpp image.}
      * {description}{The image is graylevel. Each pixel is unsigned and stored in 8 bytes.}
      **/
-    PixelFormat_Grayscale64 = 10
+    PixelFormat_Grayscale64 = 10,
+
+    /**
+     * {summary}{Color image in RGBA64 format.}
+     * {description}{This format describes a color image. The pixels are stored in 8
+     * consecutive bytes. The memory layout is RGBA.}
+     **/
+    PixelFormat_RGBA64 = 11
   };
 
 
--- a/OrthancFramework/Sources/Images/PngReader.cpp	Fri Jul 08 07:24:10 2022 +0200
+++ b/OrthancFramework/Sources/Images/PngReader.cpp	Fri Jul 08 15:26:52 2022 +0200
@@ -175,6 +175,16 @@
       format = PixelFormat_RGBA32;
       pitch = 4 * width;
     }
+    else if (color_type == PNG_COLOR_TYPE_RGBA && bit_depth == 16)
+    {
+      format = PixelFormat_RGBA64;
+      pitch = 8 * width;
+
+      if (Toolbox::DetectEndianness() == Endianness_Little)
+      {
+        png_set_swap(rabi.png_);
+      }
+    }
     else
     {
       throw OrthancException(ErrorCode_NotImplemented);
--- a/OrthancFramework/Sources/Images/PngWriter.cpp	Fri Jul 08 07:24:10 2022 +0200
+++ b/OrthancFramework/Sources/Images/PngWriter.cpp	Fri Jul 08 15:26:52 2022 +0200
@@ -165,6 +165,11 @@
           bitDepth_ = 16;
           colorType_ = PNG_COLOR_TYPE_GRAY;
           break;
+        
+        case PixelFormat_RGBA64:
+          bitDepth_ = 16;
+          colorType_ = PNG_COLOR_TYPE_RGBA;
+          break;
 
         default:
           throw OrthancException(ErrorCode_NotImplemented);
@@ -189,6 +194,7 @@
         {
           case PixelFormat_Grayscale16:
           case PixelFormat_SignedGrayscale16:
+          case PixelFormat_RGBA64:
           {
             int transforms = 0;
             if (Toolbox::DetectEndianness() == Endianness_Little)
--- a/OrthancFramework/UnitTestsSources/FromDcmtkTests.cpp	Fri Jul 08 07:24:10 2022 +0200
+++ b/OrthancFramework/UnitTestsSources/FromDcmtkTests.cpp	Fri Jul 08 15:26:52 2022 +0200
@@ -242,6 +242,16 @@
 #if ORTHANC_SANDBOXED != 1
     o.SaveToFile("UnitTestsResults/png4.dcm");
 #endif
+
+  // From http://www.schaik.com/pngsuite/pngsuite_bas_png.html
+  // 16Bit RGBA PNG
+  // License http://www.schaik.com/pngsuite/PngSuite.LICENSE
+  s = "data:image/png;base64,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";
+  o.EmbedContent(s);
+
+#if ORTHANC_SANDBOXED != 1
+  o.SaveToFile("UnitTestsResults/png5.dcm");
+#endif
   }
 }
 
--- a/OrthancFramework/UnitTestsSources/ImageTests.cpp	Fri Jul 08 07:24:10 2022 +0200
+++ b/OrthancFramework/UnitTestsSources/ImageTests.cpp	Fri Jul 08 15:26:52 2022 +0200
@@ -83,6 +83,47 @@
   ASSERT_EQ("604e785f53c99cae6ea4584870b2c41d", md5);
 }
 
+TEST(PngWriter, Color16Pattern)
+{
+  Orthanc::PngWriter w;
+  unsigned int width = 17;
+  unsigned int height = 61;
+  unsigned int pitch = width * 8;
+
+  std::vector<uint8_t> image(height * pitch);
+  for (unsigned int y = 0; y < height; y++)
+  {
+    uint8_t *p = &image[0] + y * pitch;
+    for (unsigned int x = 0; x < width; x++, p += 8)
+    {
+      p[0] = (y % 8 == 0) ? 255 : 0;
+      p[1] = (y % 8 == 1) ? 255 : 0;
+      p[2] = (y % 8 == 2) ? 255 : 0;
+      p[3] = (y % 8 == 3) ? 255 : 0;
+      p[4] = (y % 8 == 4) ? 255 : 0;
+      p[5] = (y % 8 == 5) ? 255 : 0;
+      p[6] = (y % 8 == 6) ? 255 : 0;
+      p[7] = (y % 8 == 7) ? 255 : 0;
+    }
+  }
+
+  Orthanc::ImageAccessor accessor;
+  accessor.AssignReadOnly(Orthanc::PixelFormat_RGBA64, width, height, pitch, &image[0]);
+
+  std::string f;
+
+#if ORTHANC_SANDBOXED == 1
+  Orthanc::IImageWriter::WriteToMemory(w, f, accessor);
+#else
+  Orthanc::IImageWriter::WriteToFile(w, "UnitTestsResults/Color16Pattern.png", accessor);
+  Orthanc::SystemToolbox::ReadFile(f, "UnitTestsResults/Color16Pattern.png");
+#endif
+
+  std::string md5;
+  Orthanc::Toolbox::ComputeMD5(md5, f);
+  ASSERT_EQ("1cca552b6bd152b6fdab35c4a9f02c2a", md5);
+}
+
 
 TEST(PngWriter, Gray8Pattern)
 {
--- a/OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp	Fri Jul 08 07:24:10 2022 +0200
+++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp	Fri Jul 08 15:26:52 2022 +0200
@@ -1064,7 +1064,7 @@
           targetHeight = boost::math::iround(ratio * static_cast<float>(decoded->GetHeight()));
         }
         
-        if (decoded->GetFormat() == PixelFormat_RGB24)
+        if (decoded->GetFormat() == PixelFormat_RGB24 || decoded->GetFormat() == PixelFormat_RGB48)
         {
           if (targetWidth == decoded->GetWidth() &&
               targetHeight == decoded->GetHeight())