# HG changeset patch # User Sebastien Jodogne # Date 1507579088 -7200 # Node ID 5a7c5c541a1df8003ca2239a283b4583a8026fee # Parent b340f0a9022c635425753eb48742574705e0454c Built-in decoding of palette images diff -r b340f0a9022c -r 5a7c5c541a1d Core/DicomFormat/DicomImageInformation.cpp --- a/Core/DicomFormat/DicomImageInformation.cpp Sun Oct 08 11:46:56 2017 +0200 +++ b/Core/DicomFormat/DicomImageInformation.cpp Mon Oct 09 21:58:08 2017 +0200 @@ -219,9 +219,19 @@ { if (photometric_ == PhotometricInterpretation_Palette) { - return false; + if (GetBitsStored() == 8 && GetChannelCount() == 1 && !IsSigned()) + { + format = PixelFormat_RGB24; + return true; + } + + if (GetBitsStored() == 16 && GetChannelCount() == 1 && !IsSigned()) + { + format = PixelFormat_RGB48; + return true; + } } - + if (ignorePhotometricInterpretation || photometric_ == PhotometricInterpretation_Monochrome1 || photometric_ == PhotometricInterpretation_Monochrome2) diff -r b340f0a9022c -r 5a7c5c541a1d Core/DicomParsing/Internals/DicomImageDecoder.cpp --- a/Core/DicomParsing/Internals/DicomImageDecoder.cpp Sun Oct 08 11:46:56 2017 +0200 +++ b/Core/DicomParsing/Internals/DicomImageDecoder.cpp Mon Oct 09 21:58:08 2017 +0200 @@ -96,14 +96,17 @@ #include +#include #include #include #include +#include #if ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS == 1 +# include # include # include -# include +# include #endif #if ORTHANC_ENABLE_DCMTK_JPEG == 1 @@ -115,6 +118,7 @@ # include # include # include +# include #endif #if DCMTK_VERSION_NUMBER <= 360 @@ -381,33 +385,155 @@ } + static ImageAccessor* DecodeLookupTable(std::auto_ptr& target, + const DicomImageInformation& info, + DcmDataset& dataset, + const uint8_t* pixelData, + unsigned long pixelLength) + { + LOG(INFO) << "Decoding a lookup table"; + + OFString r, g, b; + PixelFormat format; + const uint16_t* lutRed = NULL; + const uint16_t* lutGreen = NULL; + const uint16_t* lutBlue = NULL; + unsigned long rc = 0; + unsigned long gc = 0; + unsigned long bc = 0; + + if (pixelData == NULL && + !dataset.findAndGetUint8Array(DCM_PixelData, pixelData, &pixelLength).good()) + { + throw OrthancException(ErrorCode_NotImplemented); + } + + if (info.IsPlanar() || + info.GetNumberOfFrames() != 1 || + !info.ExtractPixelFormat(format, false) || + !dataset.findAndGetOFStringArray(DCM_BluePaletteColorLookupTableDescriptor, b).good() || + !dataset.findAndGetOFStringArray(DCM_GreenPaletteColorLookupTableDescriptor, g).good() || + !dataset.findAndGetOFStringArray(DCM_RedPaletteColorLookupTableDescriptor, r).good() || + !dataset.findAndGetUint16Array(DCM_BluePaletteColorLookupTableData, lutBlue, &bc).good() || + !dataset.findAndGetUint16Array(DCM_GreenPaletteColorLookupTableData, lutGreen, &gc).good() || + !dataset.findAndGetUint16Array(DCM_RedPaletteColorLookupTableData, lutRed, &rc).good() || + r != g || + r != b || + g != b || + lutRed == NULL || + lutGreen == NULL || + lutBlue == NULL || + pixelData == NULL) + { + throw OrthancException(ErrorCode_NotImplemented); + } + + switch (format) + { + case PixelFormat_RGB24: + { + if (r != "256\\0\\16" || + rc != 256 || + gc != 256 || + bc != 256 || + pixelLength != target->GetWidth() * target->GetHeight()) + { + throw OrthancException(ErrorCode_NotImplemented); + } + + const uint8_t* source = reinterpret_cast(pixelData); + + for (unsigned int y = 0; y < target->GetHeight(); y++) + { + uint8_t* p = reinterpret_cast(target->GetRow(y)); + + for (unsigned int x = 0; x < target->GetWidth(); x++) + { + p[0] = lutRed[*source] >> 8; + p[1] = lutGreen[*source] >> 8; + p[2] = lutBlue[*source] >> 8; + source++; + p += 3; + } + } + + return target.release(); + } + + case PixelFormat_RGB48: + { + if (r != "0\\0\\16" || + rc != 65536 || + gc != 65536 || + bc != 65536 || + pixelLength != 2 * target->GetWidth() * target->GetHeight()) + { + throw OrthancException(ErrorCode_NotImplemented); + } + + const uint16_t* source = reinterpret_cast(pixelData); + + for (unsigned int y = 0; y < target->GetHeight(); y++) + { + uint16_t* p = reinterpret_cast(target->GetRow(y)); + + for (unsigned int x = 0; x < target->GetWidth(); x++) + { + p[0] = lutRed[*source]; + p[1] = lutGreen[*source]; + p[2] = lutBlue[*source]; + source++; + p += 3; + } + } + + return target.release(); + } + + default: + break; + } + + throw OrthancException(ErrorCode_InternalError); + } + + ImageAccessor* DicomImageDecoder::DecodeUncompressedImage(DcmDataset& dataset, unsigned int frame) { - ImageSource source; - source.Setup(dataset, frame); - - /** - * Resize the target image. + * Create the target image. **/ std::auto_ptr target(CreateImage(dataset, false)); + ImageSource source; + source.Setup(dataset, frame); + if (source.GetWidth() != target->GetWidth() || source.GetHeight() != target->GetHeight()) { throw OrthancException(ErrorCode_InternalError); } + + /** + * Deal with lookup tables + **/ + + const DicomImageInformation& info = source.GetAccessor().GetInformation(); + + if (info.GetPhotometricInterpretation() == PhotometricInterpretation_Palette) + { + return DecodeLookupTable(target, info, dataset, NULL, 0); + } + /** * If the format of the DICOM buffer is natively supported, use a * direct access to copy its values. **/ - const DicomImageInformation& info = source.GetAccessor().GetInformation(); - bool fastVersionSuccess = false; PixelFormat sourceFormat; if (!info.IsPlanar() && @@ -470,10 +596,12 @@ } - ImageAccessor* DicomImageDecoder::ApplyCodec(const DcmCodec& codec, - const DcmCodecParameter& parameters, - DcmDataset& dataset, - unsigned int frame) + ImageAccessor* DicomImageDecoder::ApplyCodec + (const DcmCodec& codec, + const DcmCodecParameter& parameters, + const DcmRepresentationParameter& representationParameter, + DcmDataset& dataset, + unsigned int frame) { DcmPixelSequence* pixelSequence = FromDcmtkBridge::GetPixelSequence(dataset); if (pixelSequence == NULL) @@ -481,24 +609,49 @@ throw OrthancException(ErrorCode_BadFileFormat); } + DicomMap m; + FromDcmtkBridge::ExtractDicomSummary(m, dataset); + DicomImageInformation info(m); + std::auto_ptr target(CreateImage(dataset, true)); Uint32 startFragment = 0; // Default OFString decompressedColorModel; // Out - DJ_RPLossless representationParameter; - OFCondition c = codec.decodeFrame(&representationParameter, - pixelSequence, ¶meters, - &dataset, frame, startFragment, target->GetBuffer(), - target->GetSize(), decompressedColorModel); + + OFCondition c; + + if (info.GetPhotometricInterpretation() == PhotometricInterpretation_Palette && + info.GetChannelCount() == 1) + { + std::string uncompressed; + uncompressed.resize(info.GetWidth() * info.GetHeight() * info.GetBytesPerValue()); - if (c.good()) - { - return target.release(); + if (uncompressed.size() == 0 || + !codec.decodeFrame(&representationParameter, + pixelSequence, ¶meters, + &dataset, frame, startFragment, &uncompressed[0], + uncompressed.size(), decompressedColorModel).good()) + { + LOG(ERROR) << "Cannot decode a palette image"; + throw OrthancException(ErrorCode_BadFileFormat); + } + + return DecodeLookupTable(target, info, dataset, + reinterpret_cast(uncompressed.c_str()), + uncompressed.size()); } else { - LOG(ERROR) << "Cannot decode an image"; - throw OrthancException(ErrorCode_BadFileFormat); + if (!codec.decodeFrame(&representationParameter, + pixelSequence, ¶meters, + &dataset, frame, startFragment, target->GetBuffer(), + target->GetSize(), decompressedColorModel).good()) + { + LOG(ERROR) << "Cannot decode a non-palette image"; + throw OrthancException(ErrorCode_BadFileFormat); + } + + return target.release(); } } @@ -532,6 +685,7 @@ syntax == EXS_JPEGLSLossy) { DJLSCodecParameter parameters; + DJLSRepresentationParameter representationParameter; std::auto_ptr decoder; switch (syntax) @@ -550,7 +704,7 @@ throw OrthancException(ErrorCode_InternalError); } - return ApplyCodec(*decoder, parameters, dataset, frame); + return ApplyCodec(*decoder, parameters, representationParameter, dataset, frame); } #endif @@ -573,6 +727,7 @@ EDC_photometricInterpretation, // Perform color space conversion from YCbCr to RGB if DICOM photometric interpretation indicates YCbCr EUC_default, // Mode for UID creation, unused for decompression EPC_default); // Automatically determine whether color-by-plane is required from the SOP Class UID and decompressed photometric interpretation + DJ_RPLossy representationParameter; std::auto_ptr decoder; switch (syntax) @@ -611,7 +766,7 @@ throw OrthancException(ErrorCode_InternalError); } - return ApplyCodec(*decoder, parameters, dataset, frame); + return ApplyCodec(*decoder, parameters, representationParameter, dataset, frame); } #endif @@ -621,7 +776,8 @@ LOG(INFO) << "Decoding a RLE lossless DICOM image"; DcmRLECodecParameter parameters; DcmRLECodecDecoder decoder; - return ApplyCodec(decoder, parameters, dataset, frame); + DcmRLERepresentationParameter representationParameter; + return ApplyCodec(decoder, parameters, representationParameter, dataset, frame); } @@ -678,7 +834,8 @@ if (image->GetFormat() != format) { // A conversion is required - std::auto_ptr target(new Image(format, image->GetWidth(), image->GetHeight(), false)); + std::auto_ptr target + (new Image(format, image->GetWidth(), image->GetHeight(), false)); ImageProcessing::Convert(*target, *image); image = target; } @@ -697,6 +854,15 @@ return true; } + case PixelFormat_RGB48: + { + std::auto_ptr target + (new Image(PixelFormat_RGB24, image->GetWidth(), image->GetHeight(), false)); + ImageProcessing::Convert(*target, *image); + image = target; + return true; + } + case PixelFormat_Grayscale8: case PixelFormat_Grayscale16: case PixelFormat_SignedGrayscale16: @@ -711,13 +877,15 @@ } else { - ImageProcessing::ShiftScale(*image, static_cast(-a), 255.0f / static_cast(b - a)); + ImageProcessing::ShiftScale(*image, static_cast(-a), + 255.0f / static_cast(b - a)); } // If the source image is not grayscale 8bpp, convert it if (image->GetFormat() != PixelFormat_Grayscale8) { - std::auto_ptr target(new Image(PixelFormat_Grayscale8, image->GetWidth(), image->GetHeight(), false)); + std::auto_ptr target + (new Image(PixelFormat_Grayscale8, image->GetWidth(), image->GetHeight(), false)); ImageProcessing::Convert(*target, *image); image = target; } diff -r b340f0a9022c -r 5a7c5c541a1d Core/DicomParsing/Internals/DicomImageDecoder.h --- a/Core/DicomParsing/Internals/DicomImageDecoder.h Sun Oct 08 11:46:56 2017 +0200 +++ b/Core/DicomParsing/Internals/DicomImageDecoder.h Mon Oct 09 21:58:08 2017 +0200 @@ -57,6 +57,7 @@ class DcmDataset; class DcmCodec; class DcmCodecParameter; +class DcmRepresentationParameter; namespace Orthanc { @@ -77,6 +78,7 @@ static ImageAccessor* ApplyCodec(const DcmCodec& codec, const DcmCodecParameter& parameters, + const DcmRepresentationParameter& representationParameter, DcmDataset& dataset, unsigned int frame); diff -r b340f0a9022c -r 5a7c5c541a1d Core/Enumerations.cpp --- a/Core/Enumerations.cpp Sun Oct 08 11:46:56 2017 +0200 +++ b/Core/Enumerations.cpp Mon Oct 09 21:58:08 2017 +0200 @@ -756,6 +756,9 @@ case PixelFormat_Grayscale32: return "Grayscale (unsigned 32bpp)"; + case PixelFormat_RGB48: + return "RGB48"; + default: throw OrthancException(ErrorCode_ParameterOutOfRange); } @@ -1344,6 +1347,9 @@ assert(sizeof(float) == 4); return 4; + case PixelFormat_RGB48: + return 6; + default: throw OrthancException(ErrorCode_ParameterOutOfRange); } diff -r b340f0a9022c -r 5a7c5c541a1d Core/Enumerations.h --- a/Core/Enumerations.h Sun Oct 08 11:46:56 2017 +0200 +++ b/Core/Enumerations.h Mon Oct 09 21:58:08 2017 +0200 @@ -199,14 +199,21 @@ **/ PixelFormat_Float32 = 6, - // This is the memory layout for Cairo (internal use) + // This is the memory layout for Cairo (for internal use in Stone of Orthanc) PixelFormat_BGRA32 = 7, /** * {summary}{Graylevel, unsigned 32bpp image.} * {description}{The image is graylevel. Each pixel is unsigned and stored in 4 bytes.} **/ - PixelFormat_Grayscale32 = 8 + PixelFormat_Grayscale32 = 8, + + /** + * {summary}{Color image in RGB48 format.} + * {description}{This format describes a color image. The pixels are stored in 6 + * consecutive bytes. The memory layout is RGB.} + **/ + PixelFormat_RGB48 = 9 }; diff -r b340f0a9022c -r 5a7c5c541a1d Core/Images/ImageProcessing.cpp --- a/Core/Images/ImageProcessing.cpp Sun Oct 08 11:46:56 2017 +0200 +++ b/Core/Images/ImageProcessing.cpp Mon Oct 09 21:58:08 2017 +0200 @@ -568,6 +568,26 @@ return; } + if (target.GetFormat() == PixelFormat_RGB24 && + source.GetFormat() == PixelFormat_RGB48) + { + for (unsigned int y = 0; y < source.GetHeight(); y++) + { + const uint16_t* p = reinterpret_cast(source.GetConstRow(y)); + uint8_t* q = reinterpret_cast(target.GetRow(y)); + for (unsigned int x = 0; x < source.GetWidth(); x++) + { + q[0] = p[0] >> 8; + q[1] = p[1] >> 8; + q[2] = p[2] >> 8; + p += 3; + q += 3; + } + } + + return; + } + throw OrthancException(ErrorCode_NotImplemented); } diff -r b340f0a9022c -r 5a7c5c541a1d NEWS --- a/NEWS Sun Oct 08 11:46:56 2017 +0200 +++ b/NEWS Mon Oct 09 21:58:08 2017 +0200 @@ -1,6 +1,11 @@ Pending changes in the mainline =============================== +General +------- + +* Built-in decoding of palette images + REST API --------