# HG changeset patch # User Sebastien Jodogne # Date 1649411334 -7200 # Node ID fcdf399f9fc0e654b8706e4bca1704edfa4233ad # Parent 17c91e0546368f9972f9ff98bc1e50cf1d00c5b4 fix ImageProcessing::ShiftScale2() on floating-point images diff -r 17c91e054636 -r fcdf399f9fc0 OrthancFramework/Sources/Images/ImageProcessing.cpp --- 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::min(); const TargetType maxPixelValue = std::numeric_limits::max(); - const float minFloatValue = static_cast(LowestValue); + const float minFloatValue = static_cast(minPixelValue); const float maxFloatValue = static_cast(maxPixelValue); const unsigned int height = target.GetHeight(); @@ -477,6 +478,44 @@ } } + + template + 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(target.GetRow(y)); + const SourceType* q = reinterpret_cast(source.GetConstRow(y)); + + for (unsigned int x = 0; x < width; x++, p++, q++) + { + *p = a * static_cast(*q) + b; + } + } + } + + template 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::min(); const TargetType maxTargetValue = std::numeric_limits::max(); const float maxFloatValue = static_cast(maxTargetValue); @@ -564,11 +599,11 @@ if (invert) { - ShiftScaleInternal(target, source, a, b, minTargetValue); + ShiftScaleIntegerInternal(target, source, a, b); } else { - ShiftScaleInternal(target, source, a, b, minTargetValue); + ShiftScaleIntegerInternal(target, source, a, b); } } @@ -1435,45 +1470,44 @@ case PixelFormat_Grayscale8: if (useRound) { - ShiftScaleInternal(image, image, a, b, std::numeric_limits::min()); + ShiftScaleIntegerInternal(image, image, a, b); } else { - ShiftScaleInternal(image, image, a, b, std::numeric_limits::min()); + ShiftScaleIntegerInternal(image, image, a, b); } return; case PixelFormat_Grayscale16: if (useRound) { - ShiftScaleInternal(image, image, a, b, std::numeric_limits::min()); + ShiftScaleIntegerInternal(image, image, a, b); } else { - ShiftScaleInternal(image, image, a, b, std::numeric_limits::min()); + ShiftScaleIntegerInternal(image, image, a, b); } return; case PixelFormat_SignedGrayscale16: if (useRound) { - ShiftScaleInternal(image, image, a, b, std::numeric_limits::min()); + ShiftScaleIntegerInternal(image, image, a, b); } else { - ShiftScaleInternal(image, image, a, b, std::numeric_limits::min()); + ShiftScaleIntegerInternal(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(image, image, a, b, -std::numeric_limits::max()); + ShiftScaleFloatInternal(image, image, a, b); } else { - ShiftScaleInternal(image, image, a, b, -std::numeric_limits::max()); + ShiftScaleFloatInternal(image, image, a, b); } return; @@ -1509,13 +1543,11 @@ case PixelFormat_Float32: if (useRound) { - ShiftScaleInternal( - target, source, a, b, std::numeric_limits::min()); + ShiftScaleIntegerInternal(target, source, a, b); } else { - ShiftScaleInternal( - target, source, a, b, std::numeric_limits::min()); + ShiftScaleIntegerInternal(target, source, a, b); } return; diff -r 17c91e054636 -r fcdf399f9fc0 OrthancFramework/UnitTestsSources/ImageProcessingTests.cpp --- 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::SetFloatPixel(image, -1.0f, 0, 0); + ImageTraits::SetFloatPixel(image, 0.0f, 1, 0); + ImageTraits::SetFloatPixel(image, 1.0f, 2, 0); + + std::unique_ptr cloned(Image::Clone(image)); + + ImageProcessing::ShiftScale2(image, 0, 0.000539, true); + ASSERT_FLOAT_EQ(-0.000539f, ImageTraits::GetFloatPixel(image, 0, 0)); + ASSERT_FLOAT_EQ(0.0f, ImageTraits::GetFloatPixel(image, 1, 0)); + ASSERT_FLOAT_EQ(0.000539f, ImageTraits::GetFloatPixel(image, 2, 0)); + + ImageProcessing::ShiftScale2(*cloned, 0, 0.000539, false); + ASSERT_FLOAT_EQ(-0.000539f, ImageTraits::GetFloatPixel(*cloned, 0, 0)); + ASSERT_FLOAT_EQ(0.0f, ImageTraits::GetFloatPixel(*cloned, 1, 0)); + ASSERT_FLOAT_EQ(0.000539f, ImageTraits::GetFloatPixel(*cloned, 2, 0)); +} + + TEST(ImageProcessing, ShiftScale2) { std::vector va;