Mercurial > hg > orthanc
changeset 4976:03632ed1eb67 more-tags
merged default -> more-tags
author | Alain Mazy <am@osimis.io> |
---|---|
date | Wed, 13 Apr 2022 14:58:58 +0200 |
parents | d68b3a2cea17 (current diff) 5e7404f23fa8 (diff) |
children | dad71e6da406 |
files | NEWS OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp |
diffstat | 6 files changed, 117 insertions(+), 31 deletions(-) [+] |
line wrap: on
line diff
--- 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 --------
--- 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<const uint8_t*>(pixelData); const unsigned int width = target->GetWidth(); const unsigned int height = target->GetHeight();
--- 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<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 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<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;
--- 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<uint64_t>(static_cast<size_t>(size)) != size) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(NotEnoughMemory); + } + + *isReadOnly = (that.StoreFile(WebDavConvertPath(pathSize, pathItems), data, + static_cast<size_t>(size)) ? 1 : 0); return OrthancPluginErrorCode_Success; } catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
--- 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']),