# HG changeset patch # User Alain Mazy # Date 1649854738 -7200 # Node ID 03632ed1eb6756efd45b559bbb557ecd4a0f3a83 # Parent d68b3a2cea170ea9a998e5dc75bc941d9dd5cea7# Parent 5e7404f23fa8db79e58f73ae79ad87013d3fcf19 merged default -> more-tags diff -r d68b3a2cea17 -r 03632ed1eb67 NEWS --- a/NEWS Wed Mar 30 15:45:54 2022 +0200 +++ b/NEWS Wed Apr 13 14:58:58 2022 +0200 @@ -19,6 +19,7 @@ - W002_InconsistentDicomTagsInDb * C-Find and QIDO-RS can now return the InstanceAvailability tag. Value is always "ONLINE" +* Improved decoding of US Images with Implicit VR. REST API -------- diff -r d68b3a2cea17 -r 03632ed1eb67 OrthancFramework/Sources/DicomParsing/Internals/DicomImageDecoder.cpp --- a/OrthancFramework/Sources/DicomParsing/Internals/DicomImageDecoder.cpp Wed Mar 30 15:45:54 2022 +0200 +++ b/OrthancFramework/Sources/DicomParsing/Internals/DicomImageDecoder.cpp Wed Apr 13 14:58:58 2022 +0200 @@ -437,12 +437,35 @@ if (r != "256\\0\\16" || rc != 256 || gc != 256 || - bc != 256 || - pixelLength != target->GetWidth() * target->GetHeight()) + bc != 256) { throw OrthancException(ErrorCode_NotImplemented); } + if (pixelLength != target->GetWidth() * target->GetHeight()) + { + DcmElement *elem; + Uint16 bitsAllocated = 0; + + if (!dataset.findAndGetUint16(DCM_BitsAllocated, bitsAllocated).good()) + { + throw OrthancException(ErrorCode_NotImplemented); + } + + if (!dataset.findAndGetElement(DCM_PixelData, elem).good()) + { + throw OrthancException(ErrorCode_NotImplemented); + } + + // In implicit VR files, pixelLength is expressed in words (OW) although pixels can actually be 8 bits + // -> pixelLength is wrong by a factor of two and the image can still be decoded! + // seen in some Philips ClearVue 650 images (using 8 bits LUT) + if (!(elem->getVR() == EVR_OW && bitsAllocated == 8 && (2*pixelLength == target->GetWidth() * target->GetHeight()))) + { + throw OrthancException(ErrorCode_NotImplemented); + } + } + const uint8_t* source = reinterpret_cast(pixelData); const unsigned int width = target->GetWidth(); const unsigned int height = target->GetHeight(); diff -r d68b3a2cea17 -r 03632ed1eb67 OrthancFramework/Sources/Images/ImageProcessing.cpp --- a/OrthancFramework/Sources/Images/ImageProcessing.cpp Wed Mar 30 15:45:54 2022 +0200 +++ b/OrthancFramework/Sources/Images/ImageProcessing.cpp Wed Apr 13 14:58:58 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 d68b3a2cea17 -r 03632ed1eb67 OrthancFramework/UnitTestsSources/ImageProcessingTests.cpp --- a/OrthancFramework/UnitTestsSources/ImageProcessingTests.cpp Wed Mar 30 15:45:54 2022 +0200 +++ b/OrthancFramework/UnitTestsSources/ImageProcessingTests.cpp Wed Apr 13 14:58:58 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; diff -r d68b3a2cea17 -r 03632ed1eb67 OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp --- a/OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp Wed Mar 30 15:45:54 2022 +0200 +++ b/OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp Wed Apr 13 14:58:58 2022 +0200 @@ -3711,7 +3711,13 @@ try { - *isReadOnly = (that.StoreFile(WebDavConvertPath(pathSize, pathItems), data, size) ? 1 : 0); + if (static_cast(static_cast(size)) != size) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(NotEnoughMemory); + } + + *isReadOnly = (that.StoreFile(WebDavConvertPath(pathSize, pathItems), data, + static_cast(size)) ? 1 : 0); return OrthancPluginErrorCode_Success; } catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) diff -r d68b3a2cea17 -r 03632ed1eb67 OrthancServer/Resources/Samples/ImportDicomFiles/OrthancImport.py --- a/OrthancServer/Resources/Samples/ImportDicomFiles/OrthancImport.py Wed Mar 30 15:45:54 2022 +0200 +++ b/OrthancServer/Resources/Samples/ImportDicomFiles/OrthancImport.py Wed Apr 13 14:58:58 2022 +0200 @@ -63,7 +63,7 @@ WARNING: This script will remove all the content of your Orthanc instance running on %s! -Are you sure ["yes" to go on]?""" % args.server) +Are you sure ["yes" to go on]?""" % args.url) if sys.stdin.readline().strip() != 'yes': print('Aborting...') @@ -116,7 +116,8 @@ info = r.json() COUNT_DICOM += 1 - if not info['ParentStudy'] in IMPORTED_STUDIES: + if (isinstance(info, dict) and + not info['ParentStudy'] in IMPORTED_STUDIES): IMPORTED_STUDIES.add(info['ParentStudy']) r2 = requests.get('%s/instances/%s/tags?short' % (args.url, info['ID']),