changeset 4974:fcdf399f9fc0

fix ImageProcessing::ShiftScale2() on floating-point images
author Sebastien Jodogne <s.jodogne@gmail.com>
date Fri, 08 Apr 2022 11:48:54 +0200
parents 17c91e054636
children 5e7404f23fa8
files OrthancFramework/Sources/Images/ImageProcessing.cpp OrthancFramework/UnitTestsSources/ImageProcessingTests.cpp
diffstat 2 files changed, 81 insertions(+), 26 deletions(-) [+]
line wrap: on
line diff
--- a/OrthancFramework/Sources/Images/ImageProcessing.cpp	Thu Apr 07 12:04:48 2022 +0200
+++ b/OrthancFramework/Sources/Images/ImageProcessing.cpp	Fri Apr 08 11:48:54 2022 +0200
@@ -414,13 +414,14 @@
             typename SourceType,
             bool UseRound,
             bool Invert>
-  static void ShiftScaleInternal(ImageAccessor& target,
-                                 const ImageAccessor& source,
-                                 float a,
-                                 float b,
-                                 const TargetType LowestValue)
+  static void ShiftScaleIntegerInternal(ImageAccessor& target,
+                                        const ImageAccessor& source,
+                                        float a,
+                                        float b)
   // This function can be applied inplace (source == target)
   {
+    assert(target.GetFormat() != PixelFormat_Float32);
+    
     if (source.GetWidth() != target.GetWidth() ||
         source.GetHeight() != target.GetHeight())
     {
@@ -433,9 +434,9 @@
       throw OrthancException(ErrorCode_IncompatibleImageFormat);
     }
     
-    const TargetType minPixelValue = LowestValue;
+    const TargetType minPixelValue = std::numeric_limits<TargetType>::min();
     const TargetType maxPixelValue = std::numeric_limits<TargetType>::max();
-    const float minFloatValue = static_cast<float>(LowestValue);
+    const float minFloatValue = static_cast<float>(minPixelValue);
     const float maxFloatValue = static_cast<float>(maxPixelValue);
 
     const unsigned int height = target.GetHeight();
@@ -477,6 +478,44 @@
     }
   }
 
+
+  template <typename SourceType>
+  static void ShiftScaleFloatInternal(ImageAccessor& target,
+                                      const ImageAccessor& source,
+                                      float a,
+                                      float b)
+  // This function can be applied inplace (source == target)
+  {
+    assert(target.GetFormat() == PixelFormat_Float32);
+    
+    if (source.GetWidth() != target.GetWidth() ||
+        source.GetHeight() != target.GetHeight())
+    {
+      throw OrthancException(ErrorCode_IncompatibleImageSize);
+    }
+
+    if (&source == &target &&
+        source.GetFormat() != target.GetFormat())
+    {
+      throw OrthancException(ErrorCode_IncompatibleImageFormat);
+    }
+    
+    const unsigned int height = target.GetHeight();
+    const unsigned int width = target.GetWidth();
+    
+    for (unsigned int y = 0; y < height; y++)
+    {
+      float* p = reinterpret_cast<float*>(target.GetRow(y));
+      const SourceType* q = reinterpret_cast<const SourceType*>(source.GetConstRow(y));
+
+      for (unsigned int x = 0; x < width; x++, p++, q++)
+      {
+        *p = a * static_cast<float>(*q) + b;
+      }
+    }
+  }
+  
+
   template <typename PixelType>
   static void ShiftRightInternal(ImageAccessor& image,
                                  unsigned int shift)
@@ -549,10 +588,6 @@
     assert(sizeof(SourceType) == source.GetBytesPerPixel() &&
            sizeof(TargetType) == target.GetBytesPerPixel());
     
-    // WARNING - "::min()" should be replaced by "::lowest()" if
-    // dealing with float or double (which is not the case so far)
-    assert(sizeof(TargetType) <= 2);  // Safeguard to remember about "float/double"
-    const TargetType minTargetValue = std::numeric_limits<TargetType>::min();
     const TargetType maxTargetValue = std::numeric_limits<TargetType>::max();
     const float maxFloatValue = static_cast<float>(maxTargetValue);
     
@@ -564,11 +599,11 @@
 
     if (invert)
     {
-      ShiftScaleInternal<TargetType, SourceType, false, true>(target, source, a, b, minTargetValue);
+      ShiftScaleIntegerInternal<TargetType, SourceType, false, true>(target, source, a, b);
     }
     else
     {
-      ShiftScaleInternal<TargetType, SourceType, false, false>(target, source, a, b, minTargetValue);
+      ShiftScaleIntegerInternal<TargetType, SourceType, false, false>(target, source, a, b);
     }
   }
 
@@ -1435,45 +1470,44 @@
       case PixelFormat_Grayscale8:
         if (useRound)
         {
-          ShiftScaleInternal<uint8_t, uint8_t, true, false>(image, image, a, b, std::numeric_limits<uint8_t>::min());
+          ShiftScaleIntegerInternal<uint8_t, uint8_t, true, false>(image, image, a, b);
         }
         else
         {
-          ShiftScaleInternal<uint8_t, uint8_t, false, false>(image, image, a, b, std::numeric_limits<uint8_t>::min());
+          ShiftScaleIntegerInternal<uint8_t, uint8_t, false, false>(image, image, a, b);
         }
         return;
 
       case PixelFormat_Grayscale16:
         if (useRound)
         {
-          ShiftScaleInternal<uint16_t, uint16_t, true, false>(image, image, a, b, std::numeric_limits<uint16_t>::min());
+          ShiftScaleIntegerInternal<uint16_t, uint16_t, true, false>(image, image, a, b);
         }
         else
         {
-          ShiftScaleInternal<uint16_t, uint16_t, false, false>(image, image, a, b, std::numeric_limits<uint16_t>::min());
+          ShiftScaleIntegerInternal<uint16_t, uint16_t, false, false>(image, image, a, b);
         }
         return;
 
       case PixelFormat_SignedGrayscale16:
         if (useRound)
         {
-          ShiftScaleInternal<int16_t, int16_t, true, false>(image, image, a, b, std::numeric_limits<int16_t>::min());
+          ShiftScaleIntegerInternal<int16_t, int16_t, true, false>(image, image, a, b);
         }
         else
         {
-          ShiftScaleInternal<int16_t, int16_t, false, false>(image, image, a, b, std::numeric_limits<int16_t>::min());
+          ShiftScaleIntegerInternal<int16_t, int16_t, false, false>(image, image, a, b);
         }
         return;
 
       case PixelFormat_Float32:
-        // "::min()" must be replaced by "::lowest()" or "-::max()" if dealing with float or double.
         if (useRound)
         {
-          ShiftScaleInternal<float, float, true, false>(image, image, a, b, -std::numeric_limits<float>::max());
+          ShiftScaleFloatInternal<float>(image, image, a, b);
         }
         else
         {
-          ShiftScaleInternal<float, float, false, false>(image, image, a, b, -std::numeric_limits<float>::max());
+          ShiftScaleFloatInternal<float>(image, image, a, b);
         }
         return;
 
@@ -1509,13 +1543,11 @@
           case PixelFormat_Float32:
             if (useRound)
             {
-              ShiftScaleInternal<uint8_t, float, true, false>(
-                target, source, a, b, std::numeric_limits<uint8_t>::min());
+              ShiftScaleIntegerInternal<uint8_t, float, true, false>(target, source, a, b);
             }
             else
             {
-              ShiftScaleInternal<uint8_t, float, false, false>(
-                target, source, a, b, std::numeric_limits<uint8_t>::min());
+              ShiftScaleIntegerInternal<uint8_t, float, false, false>(target, source, a, b);
             }
             return;
 
--- a/OrthancFramework/UnitTestsSources/ImageProcessingTests.cpp	Thu Apr 07 12:04:48 2022 +0200
+++ b/OrthancFramework/UnitTestsSources/ImageProcessingTests.cpp	Fri Apr 08 11:48:54 2022 +0200
@@ -1058,6 +1058,29 @@
 }
 
 
+TEST(ImageProcessing, ShiftFloatBuggy)
+{
+  // This test failed in Orthanc 1.10.1
+  
+  Image image(PixelFormat_Float32, 3, 1, false);
+  ImageTraits<PixelFormat_Float32>::SetFloatPixel(image, -1.0f, 0, 0);
+  ImageTraits<PixelFormat_Float32>::SetFloatPixel(image, 0.0f, 1, 0);
+  ImageTraits<PixelFormat_Float32>::SetFloatPixel(image, 1.0f, 2, 0);
+
+  std::unique_ptr<Image> cloned(Image::Clone(image));
+
+  ImageProcessing::ShiftScale2(image, 0, 0.000539, true);
+  ASSERT_FLOAT_EQ(-0.000539f, ImageTraits<PixelFormat_Float32>::GetFloatPixel(image, 0, 0));
+  ASSERT_FLOAT_EQ(0.0f, ImageTraits<PixelFormat_Float32>::GetFloatPixel(image, 1, 0));
+  ASSERT_FLOAT_EQ(0.000539f, ImageTraits<PixelFormat_Float32>::GetFloatPixel(image, 2, 0));
+
+  ImageProcessing::ShiftScale2(*cloned, 0, 0.000539, false);
+  ASSERT_FLOAT_EQ(-0.000539f, ImageTraits<PixelFormat_Float32>::GetFloatPixel(*cloned, 0, 0));
+  ASSERT_FLOAT_EQ(0.0f, ImageTraits<PixelFormat_Float32>::GetFloatPixel(*cloned, 1, 0));
+  ASSERT_FLOAT_EQ(0.000539f, ImageTraits<PixelFormat_Float32>::GetFloatPixel(*cloned, 2, 0));
+}
+
+
 TEST(ImageProcessing, ShiftScale2)
 {
   std::vector<float> va;