# HG changeset patch # User Alain Mazy # Date 1657286812 -7200 # Node ID 255b02c68908b1e85c9662feb5069d1b3d0b5491 # Parent 1e435be86887c0ec988c1b830bb2895c194721e8 Added support for RGBA64 images in tools/create-dicom and /preview (Contribution from James Manners - Pliny) diff -r 1e435be86887 -r 255b02c68908 NEWS --- a/NEWS Fri Jul 08 07:24:10 2022 +0200 +++ b/NEWS Fri Jul 08 15:26:52 2022 +0200 @@ -1,3 +1,12 @@ +Pending changes in the mainline +=============================== + +General +------- + +* Added support for RGBA64 images in tools/create-dicom and /preview + + Version 1.11.1 (2022-06-30) =========================== diff -r 1e435be86887 -r 255b02c68908 OrthancFramework/Sources/DicomFormat/DicomImageInformation.cpp --- a/OrthancFramework/Sources/DicomFormat/DicomImageInformation.cpp Fri Jul 08 07:24:10 2022 +0200 +++ b/OrthancFramework/Sources/DicomFormat/DicomImageInformation.cpp Fri Jul 08 15:26:52 2022 +0200 @@ -380,6 +380,15 @@ return true; } + if (GetBitsStored() == 16 && + GetChannelCount() == 3 && + !IsSigned() && + (ignorePhotometricInterpretation || photometric_ == PhotometricInterpretation_RGB)) + { + format = PixelFormat_RGB48; + return true; + } + return false; } diff -r 1e435be86887 -r 255b02c68908 OrthancFramework/Sources/DicomParsing/ParsedDicomFile.cpp --- a/OrthancFramework/Sources/DicomParsing/ParsedDicomFile.cpp Fri Jul 08 07:24:10 2022 +0200 +++ b/OrthancFramework/Sources/DicomParsing/ParsedDicomFile.cpp Fri Jul 08 15:26:52 2022 +0200 @@ -1276,14 +1276,16 @@ accessor.GetFormat() != PixelFormat_Grayscale16 && accessor.GetFormat() != PixelFormat_SignedGrayscale16 && accessor.GetFormat() != PixelFormat_RGB24 && - accessor.GetFormat() != PixelFormat_RGBA32) + accessor.GetFormat() != PixelFormat_RGBA32 && + accessor.GetFormat() != PixelFormat_RGBA64) { throw OrthancException(ErrorCode_NotImplemented); } InvalidateCache(); - if (accessor.GetFormat() == PixelFormat_RGBA32) + if (accessor.GetFormat() == PixelFormat_RGBA32 || + accessor.GetFormat() == PixelFormat_RGBA64) { LOG(WARNING) << "Getting rid of the alpha channel when embedding a RGBA image inside DICOM"; } @@ -1335,6 +1337,20 @@ ReplacePlainString(DICOM_TAG_PLANAR_CONFIGURATION, "0"); // Color channels are interleaved break; + + case PixelFormat_RGBA64: + ReplacePlainString(DICOM_TAG_PHOTOMETRIC_INTERPRETATION, "RGB"); + ReplacePlainString(DICOM_TAG_SAMPLES_PER_PIXEL, "3"); + ReplacePlainString(DICOM_TAG_BITS_ALLOCATED, "16"); + ReplacePlainString(DICOM_TAG_BITS_STORED, "16"); + ReplacePlainString(DICOM_TAG_HIGH_BIT, "15"); + bytesPerPixel = 6; + + // "Planar configuration" must only present if "Samples per + // Pixel" is greater than 1 + ReplacePlainString(DICOM_TAG_PLANAR_CONFIGURATION, "0"); // Color channels are interleaved + + break; case PixelFormat_Grayscale16: case PixelFormat_SignedGrayscale16: @@ -1394,6 +1410,23 @@ break; } + + case PixelFormat_RGBA64: + { + // The alpha channel is not supported by the DICOM standard + const Uint8* source = reinterpret_cast(accessor.GetConstRow(y)); + for (unsigned int x = 0; x < width; x++, q += 6, source += 8) + { + q[0] = source[0]; + q[1] = source[1]; + q[2] = source[2]; + q[3] = source[3]; + q[4] = source[4]; + q[5] = source[5]; + } + + break; + } default: throw OrthancException(ErrorCode_NotImplemented); diff -r 1e435be86887 -r 255b02c68908 OrthancFramework/Sources/Enumerations.cpp --- a/OrthancFramework/Sources/Enumerations.cpp Fri Jul 08 07:24:10 2022 +0200 +++ b/OrthancFramework/Sources/Enumerations.cpp Fri Jul 08 15:26:52 2022 +0200 @@ -1914,6 +1914,7 @@ return 6; case PixelFormat_Grayscale64: + case PixelFormat_RGBA64: return 8; default: diff -r 1e435be86887 -r 255b02c68908 OrthancFramework/Sources/Enumerations.h --- a/OrthancFramework/Sources/Enumerations.h Fri Jul 08 07:24:10 2022 +0200 +++ b/OrthancFramework/Sources/Enumerations.h Fri Jul 08 15:26:52 2022 +0200 @@ -317,7 +317,14 @@ * {summary}{Graylevel, unsigned 64bpp image.} * {description}{The image is graylevel. Each pixel is unsigned and stored in 8 bytes.} **/ - PixelFormat_Grayscale64 = 10 + PixelFormat_Grayscale64 = 10, + + /** + * {summary}{Color image in RGBA64 format.} + * {description}{This format describes a color image. The pixels are stored in 8 + * consecutive bytes. The memory layout is RGBA.} + **/ + PixelFormat_RGBA64 = 11 }; diff -r 1e435be86887 -r 255b02c68908 OrthancFramework/Sources/Images/PngReader.cpp --- a/OrthancFramework/Sources/Images/PngReader.cpp Fri Jul 08 07:24:10 2022 +0200 +++ b/OrthancFramework/Sources/Images/PngReader.cpp Fri Jul 08 15:26:52 2022 +0200 @@ -175,6 +175,16 @@ format = PixelFormat_RGBA32; pitch = 4 * width; } + else if (color_type == PNG_COLOR_TYPE_RGBA && bit_depth == 16) + { + format = PixelFormat_RGBA64; + pitch = 8 * width; + + if (Toolbox::DetectEndianness() == Endianness_Little) + { + png_set_swap(rabi.png_); + } + } else { throw OrthancException(ErrorCode_NotImplemented); diff -r 1e435be86887 -r 255b02c68908 OrthancFramework/Sources/Images/PngWriter.cpp --- a/OrthancFramework/Sources/Images/PngWriter.cpp Fri Jul 08 07:24:10 2022 +0200 +++ b/OrthancFramework/Sources/Images/PngWriter.cpp Fri Jul 08 15:26:52 2022 +0200 @@ -165,6 +165,11 @@ bitDepth_ = 16; colorType_ = PNG_COLOR_TYPE_GRAY; break; + + case PixelFormat_RGBA64: + bitDepth_ = 16; + colorType_ = PNG_COLOR_TYPE_RGBA; + break; default: throw OrthancException(ErrorCode_NotImplemented); @@ -189,6 +194,7 @@ { case PixelFormat_Grayscale16: case PixelFormat_SignedGrayscale16: + case PixelFormat_RGBA64: { int transforms = 0; if (Toolbox::DetectEndianness() == Endianness_Little) diff -r 1e435be86887 -r 255b02c68908 OrthancFramework/UnitTestsSources/FromDcmtkTests.cpp --- a/OrthancFramework/UnitTestsSources/FromDcmtkTests.cpp Fri Jul 08 07:24:10 2022 +0200 +++ b/OrthancFramework/UnitTestsSources/FromDcmtkTests.cpp Fri Jul 08 15:26:52 2022 +0200 @@ -242,6 +242,16 @@ #if ORTHANC_SANDBOXED != 1 o.SaveToFile("UnitTestsResults/png4.dcm"); #endif + + // From http://www.schaik.com/pngsuite/pngsuite_bas_png.html + // 16Bit RGBA PNG + // License http://www.schaik.com/pngsuite/PngSuite.LICENSE + s = "data:image/png;base64,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"; + o.EmbedContent(s); + +#if ORTHANC_SANDBOXED != 1 + o.SaveToFile("UnitTestsResults/png5.dcm"); +#endif } } diff -r 1e435be86887 -r 255b02c68908 OrthancFramework/UnitTestsSources/ImageTests.cpp --- a/OrthancFramework/UnitTestsSources/ImageTests.cpp Fri Jul 08 07:24:10 2022 +0200 +++ b/OrthancFramework/UnitTestsSources/ImageTests.cpp Fri Jul 08 15:26:52 2022 +0200 @@ -83,6 +83,47 @@ ASSERT_EQ("604e785f53c99cae6ea4584870b2c41d", md5); } +TEST(PngWriter, Color16Pattern) +{ + Orthanc::PngWriter w; + unsigned int width = 17; + unsigned int height = 61; + unsigned int pitch = width * 8; + + std::vector image(height * pitch); + for (unsigned int y = 0; y < height; y++) + { + uint8_t *p = &image[0] + y * pitch; + for (unsigned int x = 0; x < width; x++, p += 8) + { + p[0] = (y % 8 == 0) ? 255 : 0; + p[1] = (y % 8 == 1) ? 255 : 0; + p[2] = (y % 8 == 2) ? 255 : 0; + p[3] = (y % 8 == 3) ? 255 : 0; + p[4] = (y % 8 == 4) ? 255 : 0; + p[5] = (y % 8 == 5) ? 255 : 0; + p[6] = (y % 8 == 6) ? 255 : 0; + p[7] = (y % 8 == 7) ? 255 : 0; + } + } + + Orthanc::ImageAccessor accessor; + accessor.AssignReadOnly(Orthanc::PixelFormat_RGBA64, width, height, pitch, &image[0]); + + std::string f; + +#if ORTHANC_SANDBOXED == 1 + Orthanc::IImageWriter::WriteToMemory(w, f, accessor); +#else + Orthanc::IImageWriter::WriteToFile(w, "UnitTestsResults/Color16Pattern.png", accessor); + Orthanc::SystemToolbox::ReadFile(f, "UnitTestsResults/Color16Pattern.png"); +#endif + + std::string md5; + Orthanc::Toolbox::ComputeMD5(md5, f); + ASSERT_EQ("1cca552b6bd152b6fdab35c4a9f02c2a", md5); +} + TEST(PngWriter, Gray8Pattern) { diff -r 1e435be86887 -r 255b02c68908 OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp --- a/OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp Fri Jul 08 07:24:10 2022 +0200 +++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp Fri Jul 08 15:26:52 2022 +0200 @@ -1064,7 +1064,7 @@ targetHeight = boost::math::iround(ratio * static_cast(decoded->GetHeight())); } - if (decoded->GetFormat() == PixelFormat_RGB24) + if (decoded->GetFormat() == PixelFormat_RGB24 || decoded->GetFormat() == PixelFormat_RGB48) { if (targetWidth == decoded->GetWidth() && targetHeight == decoded->GetHeight())