changeset 4065:d6362b2c4b61 framework

export dcmdata in shared library, rounding in convolution tests
author Sebastien Jodogne <s.jodogne@gmail.com>
date Thu, 11 Jun 2020 18:04:28 +0200
parents e00f3d089991
children 3a59a021b5de
files OrthancFramework/SharedLibrary/CMakeLists.txt OrthancFramework/Sources/Images/ImageProcessing.cpp OrthancFramework/Sources/Images/ImageProcessing.h OrthancFramework/UnitTestsSources/FromDcmtkTests.cpp OrthancFramework/UnitTestsSources/ImageProcessingTests.cpp OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp
diffstat 6 files changed, 121 insertions(+), 73 deletions(-) [+]
line wrap: on
line diff
--- a/OrthancFramework/SharedLibrary/CMakeLists.txt	Thu Jun 11 16:40:34 2020 +0200
+++ b/OrthancFramework/SharedLibrary/CMakeLists.txt	Thu Jun 11 18:04:28 2020 +0200
@@ -85,6 +85,12 @@
   set(ORTHANC_STATIC_PUGIXML OFF)
 endif()
 
+if (STATIC_BUILD OR NOT USE_SYSTEM_DCMTK)
+  set(ORTHANC_STATIC_DCMTK ON)
+else()
+  set(ORTHANC_STATIC_DCMTK OFF)
+endif()
+
 
 if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR
     CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
@@ -141,6 +147,13 @@
         )
     endif()
   endif()
+
+  # Control the visibility of DCMTK: We only export the "dcmdata" module
+  if (ORTHANC_STATIC_DCMTK)
+    set_source_files_properties(${DCMTK_SOURCES}
+      PROPERTIES COMPILE_DEFINITIONS "DCMTK_BUILD_IN_PROGRESS;DCMTK_BUILD_SINGLE_SHARED_LIBRARY;DCMTK_SHARED;HAVE_HIDDEN_VISIBILITY;dcmdata_EXPORTS"
+      )
+  endif()
 endif()
 
 
--- a/OrthancFramework/Sources/Images/ImageProcessing.cpp	Thu Jun 11 16:40:34 2020 +0200
+++ b/OrthancFramework/Sources/Images/ImageProcessing.cpp	Thu Jun 11 18:04:28 2020 +0200
@@ -366,6 +366,7 @@
         int64_t v;
         if (UseRound)
         {
+          assert(sizeof(long long) == sizeof(int64_t));
           // The "round" operation is very costly
           v = boost::math::llround(static_cast<float>(*p) * factor);
         }
@@ -442,8 +443,9 @@
           *p = minPixelValue;
         }
         else if (UseRound)
-        {
+        {         
           // The "round" operation is very costly
+          assert(sizeof(TargetType) < sizeof(int));
           *p = static_cast<TargetType>(boost::math::iround(v));
         }
         else
@@ -2197,7 +2199,7 @@
   // floating-point arithmetics, and an intermediate Float32
   // image. The out-of-image values are taken as the border
   // value. Further optimization is possible.
-  template <typename RawPixel, unsigned int ChannelsCount>
+  template <typename RawPixel, unsigned int ChannelsCount, bool UseRound>
   static void SeparableConvolutionFloat(ImageAccessor& image /* inplace */,
                                         const std::vector<float>& horizontal,
                                         size_t horizontalAnchor,
@@ -2335,7 +2337,15 @@
           }
           else
           {
-            *p = static_cast<RawPixel>(accumulator);
+            if (UseRound)
+            {
+              assert(sizeof(RawPixel) < sizeof(int));
+              *p = static_cast<RawPixel>(boost::math::iround(accumulator));
+            }
+            else
+            {
+              *p = static_cast<RawPixel>(accumulator);
+            }
           }
         }
       }
@@ -2347,7 +2357,8 @@
                                              const std::vector<float>& horizontal,
                                              size_t horizontalAnchor,
                                              const std::vector<float>& vertical,
-                                             size_t verticalAnchor)
+                                             size_t verticalAnchor,
+                                             bool useRound)
   {
     if (horizontal.size() == 0 ||
         vertical.size() == 0 ||
@@ -2390,13 +2401,29 @@
     switch (image.GetFormat())
     {
       case PixelFormat_Grayscale8:
-        SeparableConvolutionFloat<uint8_t, 1u>
-          (image, horizontal, horizontalAnchor, vertical, verticalAnchor, normalization);
+        if (useRound)
+        {
+          SeparableConvolutionFloat<uint8_t, 1u, true>
+            (image, horizontal, horizontalAnchor, vertical, verticalAnchor, normalization);
+        }
+        else
+        {
+          SeparableConvolutionFloat<uint8_t, 1u, false>
+            (image, horizontal, horizontalAnchor, vertical, verticalAnchor, normalization);
+        }
         break;
 
       case PixelFormat_RGB24:
-        SeparableConvolutionFloat<uint8_t, 3u>
-          (image, horizontal, horizontalAnchor, vertical, verticalAnchor, normalization);
+        if (useRound)
+        {
+          SeparableConvolutionFloat<uint8_t, 3u, true>
+            (image, horizontal, horizontalAnchor, vertical, verticalAnchor, normalization);
+        }
+        else
+        {
+          SeparableConvolutionFloat<uint8_t, 3u, false>
+            (image, horizontal, horizontalAnchor, vertical, verticalAnchor, normalization);
+        }
         break;
 
       default:
@@ -2405,7 +2432,8 @@
   }
 
 
-  void ImageProcessing::SmoothGaussian5x5(ImageAccessor& image)
+  void ImageProcessing::SmoothGaussian5x5(ImageAccessor& image,
+                                          bool useRound)
   {
     std::vector<float> kernel(5);
     kernel[0] = 1;
@@ -2414,7 +2442,7 @@
     kernel[3] = 4;
     kernel[4] = 1;
 
-    SeparableConvolution(image, kernel, 2, kernel, 2);
+    SeparableConvolution(image, kernel, 2, kernel, 2, useRound);
   }
 
 
@@ -2445,6 +2473,7 @@
 
     unsigned int sw = std::min(static_cast<unsigned int>(boost::math::iround(cw * r)), target.GetWidth());  
     unsigned int sh = std::min(static_cast<unsigned int>(boost::math::iround(ch * r)), target.GetHeight());
+
     Image resized(target.GetFormat(), sw, sh, false);
   
     //ImageProcessing::SmoothGaussian5x5(source);
--- a/OrthancFramework/Sources/Images/ImageProcessing.h	Thu Jun 11 16:40:34 2020 +0200
+++ b/OrthancFramework/Sources/Images/ImageProcessing.h	Thu Jun 11 18:04:28 2020 +0200
@@ -184,9 +184,11 @@
                                      const std::vector<float>& horizontal,
                                      size_t horizontalAnchor,
                                      const std::vector<float>& vertical,
-                                     size_t verticalAnchor);
+                                     size_t verticalAnchor,
+                                     bool useRound /* this is expensive */);
 
-    static void SmoothGaussian5x5(ImageAccessor& image);
+    static void SmoothGaussian5x5(ImageAccessor& image,
+                                  bool useRound /* this is expensive */);
 
     static void FitSize(ImageAccessor& target,
                         const ImageAccessor& source);
--- a/OrthancFramework/UnitTestsSources/FromDcmtkTests.cpp	Thu Jun 11 16:40:34 2020 +0200
+++ b/OrthancFramework/UnitTestsSources/FromDcmtkTests.cpp	Thu Jun 11 18:04:28 2020 +0200
@@ -2179,7 +2179,7 @@
                     a == DicomTransferSyntax_JPEGProcess2_4 ||
                     a == DicomTransferSyntax_JPEGLSLossy);
       
-      printf("SIZE: %lu\n", t.size());
+      printf("SIZE: %u\n", t.size());
       if (sourceUid == IDicomTranscoder::GetSopInstanceUid(target.GetParsed()))
       {
         ASSERT_FALSE(lossy);
--- a/OrthancFramework/UnitTestsSources/ImageProcessingTests.cpp	Thu Jun 11 16:40:34 2020 +0200
+++ b/OrthancFramework/UnitTestsSources/ImageProcessingTests.cpp	Thu Jun 11 18:04:28 2020 +0200
@@ -523,26 +523,26 @@
   {
     Image image(PixelFormat_Grayscale8, 1, 1, false);
     SetGrayscale8Pixel(image, 0, 0, 100);    
-    ImageProcessing::SeparableConvolution(image, k1, 2, k2, 0);
+    ImageProcessing::SeparableConvolution(image, k1, 2, k2, 0, true /* round */);
     ASSERT_TRUE(TestGrayscale8Pixel(image, 0, 0, 100));
-    ImageProcessing::SeparableConvolution(image, k1, 2, k1, 2);
+    ImageProcessing::SeparableConvolution(image, k1, 2, k1, 2, true /* round */);
     ASSERT_TRUE(TestGrayscale8Pixel(image, 0, 0, 100));
-    ImageProcessing::SeparableConvolution(image, k2, 0, k1, 2);
+    ImageProcessing::SeparableConvolution(image, k2, 0, k1, 2, true /* round */);
     ASSERT_TRUE(TestGrayscale8Pixel(image, 0, 0, 100));
-    ImageProcessing::SeparableConvolution(image, k2, 0, k2, 0);
+    ImageProcessing::SeparableConvolution(image, k2, 0, k2, 0, true /* round */);
     ASSERT_TRUE(TestGrayscale8Pixel(image, 0, 0, 100));
   }
   
   {
     Image image(PixelFormat_RGB24, 1, 1, false);
     SetRGB24Pixel(image, 0, 0, 10, 20, 30);    
-    ImageProcessing::SeparableConvolution(image, k1, 2, k2, 0);
+    ImageProcessing::SeparableConvolution(image, k1, 2, k2, 0, true /* round */);
     ASSERT_TRUE(TestRGB24Pixel(image, 0, 0, 10, 20, 30));
-    ImageProcessing::SeparableConvolution(image, k1, 2, k1, 2);
+    ImageProcessing::SeparableConvolution(image, k1, 2, k1, 2, true /* round */);
     ASSERT_TRUE(TestRGB24Pixel(image, 0, 0, 10, 20, 30));
-    ImageProcessing::SeparableConvolution(image, k2, 0, k1, 2);
+    ImageProcessing::SeparableConvolution(image, k2, 0, k1, 2, true /* round */);
     ASSERT_TRUE(TestRGB24Pixel(image, 0, 0, 10, 20, 30));
-    ImageProcessing::SeparableConvolution(image, k2, 0, k2, 0);
+    ImageProcessing::SeparableConvolution(image, k2, 0, k2, 0, true /* round */);
     ASSERT_TRUE(TestRGB24Pixel(image, 0, 0, 10, 20, 30));
   }
 
@@ -553,7 +553,7 @@
 
     {
       std::unique_ptr<ImageAccessor> image(Image::Clone(dirac));
-      ImageProcessing::SeparableConvolution(*image, k1, 2, k2, 0);
+      ImageProcessing::SeparableConvolution(*image, k1, 2, k2, 0, true /* round */);
       ASSERT_TRUE(TestGrayscale8Pixel(*image, 0, 0, 0));
       ASSERT_TRUE(TestGrayscale8Pixel(*image, 1, 0, 0));
       ASSERT_TRUE(TestGrayscale8Pixel(*image, 2, 0, 20));
@@ -567,7 +567,7 @@
 
     {
       std::unique_ptr<ImageAccessor> image(Image::Clone(dirac));
-      ImageProcessing::SeparableConvolution(*image, k2, 0, k1, 2);
+      ImageProcessing::SeparableConvolution(*image, k2, 0, k1, 2, true /* round */);
       ASSERT_TRUE(TestGrayscale8Pixel(*image, 0, 0, 0));
       ASSERT_TRUE(TestGrayscale8Pixel(*image, 1, 0, 0));
       ASSERT_TRUE(TestGrayscale8Pixel(*image, 2, 0, 0));
@@ -581,7 +581,7 @@
 
     {
       std::unique_ptr<ImageAccessor> image(Image::Clone(dirac));
-      ImageProcessing::SeparableConvolution(*image, k2, 0, k2, 0);
+      ImageProcessing::SeparableConvolution(*image, k2, 0, k2, 0, true /* round */);
       ASSERT_TRUE(TestGrayscale8Pixel(*image, 0, 0, 0));
       ASSERT_TRUE(TestGrayscale8Pixel(*image, 1, 0, 0));
       ASSERT_TRUE(TestGrayscale8Pixel(*image, 2, 0, 0));
@@ -601,7 +601,7 @@
 
     {
       std::unique_ptr<ImageAccessor> image(Image::Clone(dirac));
-      ImageProcessing::SeparableConvolution(*image, k2, 0, k1, 2);
+      ImageProcessing::SeparableConvolution(*image, k2, 0, k1, 2, true /* round */);
       ASSERT_TRUE(TestGrayscale8Pixel(*image, 0, 0, 0));
       ASSERT_TRUE(TestGrayscale8Pixel(*image, 0, 1, 0));
       ASSERT_TRUE(TestGrayscale8Pixel(*image, 0, 2, 20));
@@ -615,7 +615,7 @@
 
     {
       std::unique_ptr<ImageAccessor> image(Image::Clone(dirac));
-      ImageProcessing::SeparableConvolution(*image, k1, 2, k2, 0);
+      ImageProcessing::SeparableConvolution(*image, k1, 2, k2, 0, true /* round */);
       ASSERT_TRUE(TestGrayscale8Pixel(*image, 0, 0, 0));
       ASSERT_TRUE(TestGrayscale8Pixel(*image, 0, 1, 0));
       ASSERT_TRUE(TestGrayscale8Pixel(*image, 0, 2, 0));
@@ -629,7 +629,7 @@
 
     {
       std::unique_ptr<ImageAccessor> image(Image::Clone(dirac));
-      ImageProcessing::SeparableConvolution(*image, k2, 0, k2, 0);
+      ImageProcessing::SeparableConvolution(*image, k2, 0, k2, 0, true /* round */);
       ASSERT_TRUE(TestGrayscale8Pixel(*image, 0, 0, 0));
       ASSERT_TRUE(TestGrayscale8Pixel(*image, 0, 1, 0));
       ASSERT_TRUE(TestGrayscale8Pixel(*image, 0, 2, 0));
@@ -649,7 +649,7 @@
 
     {
       std::unique_ptr<ImageAccessor> image(Image::Clone(dirac));
-      ImageProcessing::SeparableConvolution(*image, k1, 2, k2, 0);
+      ImageProcessing::SeparableConvolution(*image, k1, 2, k2, 0, true /* round */);
       ASSERT_TRUE(TestRGB24Pixel(*image, 0, 0, 0, 0, 0));
       ASSERT_TRUE(TestRGB24Pixel(*image, 1, 0, 0, 0, 0));
       ASSERT_TRUE(TestRGB24Pixel(*image, 2, 0, 20, 24, 28));
@@ -663,7 +663,7 @@
 
     {
       std::unique_ptr<ImageAccessor> image(Image::Clone(dirac));
-      ImageProcessing::SeparableConvolution(*image, k2, 0, k1, 2);
+      ImageProcessing::SeparableConvolution(*image, k2, 0, k1, 2, true /* round */);
       ASSERT_TRUE(TestRGB24Pixel(*image, 0, 0, 0, 0, 0));
       ASSERT_TRUE(TestRGB24Pixel(*image, 1, 0, 0, 0, 0));
       ASSERT_TRUE(TestRGB24Pixel(*image, 2, 0, 0, 0, 0));
@@ -677,7 +677,7 @@
 
     {
       std::unique_ptr<ImageAccessor> image(Image::Clone(dirac));
-      ImageProcessing::SeparableConvolution(*image, k2, 0, k2, 0);
+      ImageProcessing::SeparableConvolution(*image, k2, 0, k2, 0, true /* round */);
       ASSERT_TRUE(TestRGB24Pixel(*image, 0, 0, 0, 0, 0));
       ASSERT_TRUE(TestRGB24Pixel(*image, 1, 0, 0, 0, 0));
       ASSERT_TRUE(TestRGB24Pixel(*image, 2, 0, 0, 0, 0));
@@ -697,7 +697,7 @@
 
     {
       std::unique_ptr<ImageAccessor> image(Image::Clone(dirac));
-      ImageProcessing::SeparableConvolution(*image, k2, 0, k1, 2);
+      ImageProcessing::SeparableConvolution(*image, k2, 0, k1, 2, true /* round */);
       ASSERT_TRUE(TestRGB24Pixel(*image, 0, 0, 0, 0, 0));
       ASSERT_TRUE(TestRGB24Pixel(*image, 0, 1, 0, 0, 0));
       ASSERT_TRUE(TestRGB24Pixel(*image, 0, 2, 20, 24, 28));
@@ -711,7 +711,7 @@
 
     {
       std::unique_ptr<ImageAccessor> image(Image::Clone(dirac));
-      ImageProcessing::SeparableConvolution(*image, k1, 2, k2, 0);
+      ImageProcessing::SeparableConvolution(*image, k1, 2, k2, 0, true /* round */);
       ASSERT_TRUE(TestRGB24Pixel(*image, 0, 0, 0, 0, 0));
       ASSERT_TRUE(TestRGB24Pixel(*image, 0, 1, 0, 0, 0));
       ASSERT_TRUE(TestRGB24Pixel(*image, 0, 2, 0, 0, 0));
@@ -725,7 +725,7 @@
 
     {
       std::unique_ptr<ImageAccessor> image(Image::Clone(dirac));
-      ImageProcessing::SeparableConvolution(*image, k2, 0, k2, 0);
+      ImageProcessing::SeparableConvolution(*image, k2, 0, k2, 0, true /* round */);
       ASSERT_TRUE(TestRGB24Pixel(*image, 0, 0, 0, 0, 0));
       ASSERT_TRUE(TestRGB24Pixel(*image, 0, 1, 0, 0, 0));
       ASSERT_TRUE(TestRGB24Pixel(*image, 0, 2, 0, 0, 0));
@@ -755,32 +755,33 @@
     Image image(PixelFormat_Grayscale8, 5, 5, false);
     ImageProcessing::Set(image, 0);
     SetGrayscale8Pixel(image, 2, 2, 100);
-    ImageProcessing::SmoothGaussian5x5(image);
+    ImageProcessing::SmoothGaussian5x5(image, true /* round */);
 
+    // In Octave: round(conv2([1 4 6 4 1],[1 4 6 4 1]')/256*100)
     ASSERT_TRUE(TestGrayscale8Pixel(image, 0, 0, 0));
-    ASSERT_TRUE(TestGrayscale8Pixel(image, 1, 0, 1));
+    ASSERT_TRUE(TestGrayscale8Pixel(image, 1, 0, 2));
     ASSERT_TRUE(TestGrayscale8Pixel(image, 2, 0, 2));
-    ASSERT_TRUE(TestGrayscale8Pixel(image, 3, 0, 1));
+    ASSERT_TRUE(TestGrayscale8Pixel(image, 3, 0, 2));
     ASSERT_TRUE(TestGrayscale8Pixel(image, 4, 0, 0));
-    ASSERT_TRUE(TestGrayscale8Pixel(image, 0, 1, 1));
+    ASSERT_TRUE(TestGrayscale8Pixel(image, 0, 1, 2));
     ASSERT_TRUE(TestGrayscale8Pixel(image, 1, 1, 6));
     ASSERT_TRUE(TestGrayscale8Pixel(image, 2, 1, 9));
     ASSERT_TRUE(TestGrayscale8Pixel(image, 3, 1, 6));
-    ASSERT_TRUE(TestGrayscale8Pixel(image, 4, 1, 1));
+    ASSERT_TRUE(TestGrayscale8Pixel(image, 4, 1, 2));
     ASSERT_TRUE(TestGrayscale8Pixel(image, 0, 2, 2));
     ASSERT_TRUE(TestGrayscale8Pixel(image, 1, 2, 9));
     ASSERT_TRUE(TestGrayscale8Pixel(image, 2, 2, 14));
     ASSERT_TRUE(TestGrayscale8Pixel(image, 3, 2, 9));
     ASSERT_TRUE(TestGrayscale8Pixel(image, 4, 2, 2));
-    ASSERT_TRUE(TestGrayscale8Pixel(image, 0, 3, 1));
+    ASSERT_TRUE(TestGrayscale8Pixel(image, 0, 3, 2));
     ASSERT_TRUE(TestGrayscale8Pixel(image, 1, 3, 6));
     ASSERT_TRUE(TestGrayscale8Pixel(image, 2, 3, 9));
     ASSERT_TRUE(TestGrayscale8Pixel(image, 3, 3, 6));
-    ASSERT_TRUE(TestGrayscale8Pixel(image, 4, 3, 1));
+    ASSERT_TRUE(TestGrayscale8Pixel(image, 4, 3, 2));
     ASSERT_TRUE(TestGrayscale8Pixel(image, 0, 4, 0));
-    ASSERT_TRUE(TestGrayscale8Pixel(image, 1, 4, 1));
+    ASSERT_TRUE(TestGrayscale8Pixel(image, 1, 4, 2));
     ASSERT_TRUE(TestGrayscale8Pixel(image, 2, 4, 2));
-    ASSERT_TRUE(TestGrayscale8Pixel(image, 3, 4, 1));
+    ASSERT_TRUE(TestGrayscale8Pixel(image, 3, 4, 2));
     ASSERT_TRUE(TestGrayscale8Pixel(image, 4, 4, 0));
   }
 
@@ -788,33 +789,36 @@
     Image image(PixelFormat_RGB24, 5, 5, false);
     ImageProcessing::Set(image, 0);
     SetRGB24Pixel(image, 2, 2, 100, 100, 200);
-    ImageProcessing::SmoothGaussian5x5(image);
+    ImageProcessing::SmoothGaussian5x5(image, true /* round */);
 
-    ASSERT_TRUE(TestRGB24Pixel(image, 0, 0, 0, 0, 0));
-    ASSERT_TRUE(TestRGB24Pixel(image, 1, 0, 1, 1, 3));
-    ASSERT_TRUE(TestRGB24Pixel(image, 2, 0, 2, 2, 4));
-    ASSERT_TRUE(TestRGB24Pixel(image, 3, 0, 1, 1, 3));
-    ASSERT_TRUE(TestRGB24Pixel(image, 4, 0, 0, 0, 0));
-    ASSERT_TRUE(TestRGB24Pixel(image, 0, 1, 1, 1, 3));
-    ASSERT_TRUE(TestRGB24Pixel(image, 1, 1, 6, 6, 12));
-    ASSERT_TRUE(TestRGB24Pixel(image, 2, 1, 9, 9, 18));
-    ASSERT_TRUE(TestRGB24Pixel(image, 3, 1, 6, 6, 12));
-    ASSERT_TRUE(TestRGB24Pixel(image, 4, 1, 1, 1, 3));
-    ASSERT_TRUE(TestRGB24Pixel(image, 0, 2, 2, 2, 4));
-    ASSERT_TRUE(TestRGB24Pixel(image, 1, 2, 9, 9, 18));
+    // In Octave:
+    // R,G = round(conv2([1 4 6 4 1],[1 4 6 4 1]')/256*100)
+    // B = round(conv2([1 4 6 4 1],[1 4 6 4 1]')/256*200)
+    ASSERT_TRUE(TestRGB24Pixel(image, 0, 0, 0, 0, 1));
+    ASSERT_TRUE(TestRGB24Pixel(image, 1, 0, 2, 2, 3));
+    ASSERT_TRUE(TestRGB24Pixel(image, 2, 0, 2, 2, 5));
+    ASSERT_TRUE(TestRGB24Pixel(image, 3, 0, 2, 2, 3));
+    ASSERT_TRUE(TestRGB24Pixel(image, 4, 0, 0, 0, 1));
+    ASSERT_TRUE(TestRGB24Pixel(image, 0, 1, 2, 2, 3));
+    ASSERT_TRUE(TestRGB24Pixel(image, 1, 1, 6, 6, 13));
+    ASSERT_TRUE(TestRGB24Pixel(image, 2, 1, 9, 9, 19));
+    ASSERT_TRUE(TestRGB24Pixel(image, 3, 1, 6, 6, 13));
+    ASSERT_TRUE(TestRGB24Pixel(image, 4, 1, 2, 2, 3));
+    ASSERT_TRUE(TestRGB24Pixel(image, 0, 2, 2, 2, 5));
+    ASSERT_TRUE(TestRGB24Pixel(image, 1, 2, 9, 9, 19));
     ASSERT_TRUE(TestRGB24Pixel(image, 2, 2, 14, 14, 28));
-    ASSERT_TRUE(TestRGB24Pixel(image, 3, 2, 9, 9, 18));
-    ASSERT_TRUE(TestRGB24Pixel(image, 4, 2, 2, 2, 4));
-    ASSERT_TRUE(TestRGB24Pixel(image, 0, 3, 1, 1, 3));
-    ASSERT_TRUE(TestRGB24Pixel(image, 1, 3, 6, 6, 12));
-    ASSERT_TRUE(TestRGB24Pixel(image, 2, 3, 9, 9, 18));
-    ASSERT_TRUE(TestRGB24Pixel(image, 3, 3, 6, 6, 12));
-    ASSERT_TRUE(TestRGB24Pixel(image, 4, 3, 1, 1, 3));
-    ASSERT_TRUE(TestRGB24Pixel(image, 0, 4, 0, 0, 0));
-    ASSERT_TRUE(TestRGB24Pixel(image, 1, 4, 1, 1, 3));
-    ASSERT_TRUE(TestRGB24Pixel(image, 2, 4, 2, 2, 4));
-    ASSERT_TRUE(TestRGB24Pixel(image, 3, 4, 1, 1, 3));
-    ASSERT_TRUE(TestRGB24Pixel(image, 4, 4, 0, 0, 0));
+    ASSERT_TRUE(TestRGB24Pixel(image, 3, 2, 9, 9, 19));
+    ASSERT_TRUE(TestRGB24Pixel(image, 4, 2, 2, 2, 5));
+    ASSERT_TRUE(TestRGB24Pixel(image, 0, 3, 2, 2, 3));
+    ASSERT_TRUE(TestRGB24Pixel(image, 1, 3, 6, 6, 13));
+    ASSERT_TRUE(TestRGB24Pixel(image, 2, 3, 9, 9, 19));
+    ASSERT_TRUE(TestRGB24Pixel(image, 3, 3, 6, 6, 13));
+    ASSERT_TRUE(TestRGB24Pixel(image, 4, 3, 2, 2, 3));
+    ASSERT_TRUE(TestRGB24Pixel(image, 0, 4, 0, 0, 1));
+    ASSERT_TRUE(TestRGB24Pixel(image, 1, 4, 2, 2, 3));
+    ASSERT_TRUE(TestRGB24Pixel(image, 2, 4, 2, 2, 5));
+    ASSERT_TRUE(TestRGB24Pixel(image, 3, 4, 2, 2, 3));
+    ASSERT_TRUE(TestRGB24Pixel(image, 4, 4, 0, 0, 1));
   }
 }
 
@@ -974,10 +978,10 @@
   SetGrayscale8Pixel(image, 4, 0, 255);
 
   ImageProcessing::ShiftScale(image, -1.1f, 1.5f, true);
-  ASSERT_TRUE(TestGrayscale8Pixel(image, 0, 0, 0));
-  ASSERT_TRUE(TestGrayscale8Pixel(image, 1, 0, 1));
-  ASSERT_TRUE(TestGrayscale8Pixel(image, 2, 0, 6));
-  ASSERT_TRUE(TestGrayscale8Pixel(image, 3, 0, 13));
+  ASSERT_TRUE(TestGrayscale8Pixel(image, 0, 0, 0));   // (0 - 1.1) * 1.5 = -1.65 ==> 0
+  ASSERT_TRUE(TestGrayscale8Pixel(image, 1, 0, 1));   // (2 - 1.1) * 1.5 = 1.35 => 1
+  ASSERT_TRUE(TestGrayscale8Pixel(image, 2, 0, 6));   // (5 - 1.1) * 1.5 = 5.85 => 6
+  ASSERT_TRUE(TestGrayscale8Pixel(image, 3, 0, 13));  // (10 - 1.1) * 1.5 = 13.35 => 13
   ASSERT_TRUE(TestGrayscale8Pixel(image, 4, 0, 255));
 }
 
--- a/OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp	Thu Jun 11 16:40:34 2020 +0200
+++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp	Thu Jun 11 18:04:28 2020 +0200
@@ -860,7 +860,7 @@
                 (targetWidth < decoded->GetWidth() ||
                  targetHeight < decoded->GetHeight()))
             {
-              ImageProcessing::SmoothGaussian5x5(*decoded);
+              ImageProcessing::SmoothGaussian5x5(*decoded, false /* be fast, don't round */);
             }
             
             ImageProcessing::Resize(*resized, *decoded);
@@ -906,7 +906,7 @@
                 (targetWidth < decoded->GetWidth() ||
                  targetHeight < decoded->GetHeight()))
             {
-              ImageProcessing::SmoothGaussian5x5(*rescaled);
+              ImageProcessing::SmoothGaussian5x5(*rescaled, false /* be fast, don't round */);
             }
             
             ImageProcessing::Resize(*resized, *rescaled);