# HG changeset patch # User Sebastien Jodogne # Date 1497017692 -7200 # Node ID e002430baa41f5cba500037f1b183b8dcd078ea1 # Parent 2e7a8ce24be29449d0638389ed54ee134a3d54c0 Fix issue #44 (Bad interpretation of photometric interpretation MONOCHROME1) diff -r 2e7a8ce24be2 -r e002430baa41 Core/Enumerations.cpp --- a/Core/Enumerations.cpp Mon May 22 20:39:53 2017 +0200 +++ b/Core/Enumerations.cpp Fri Jun 09 16:14:52 2017 +0200 @@ -636,10 +636,10 @@ return "RGB"; case PhotometricInterpretation_Monochrome1: - return "Monochrome1"; + return "MONOCHROME1"; case PhotometricInterpretation_Monochrome2: - return "Monochrome2"; + return "MONOCHROME2"; case PhotometricInterpretation_ARGB: return "ARGB"; @@ -651,25 +651,25 @@ return "HSV"; case PhotometricInterpretation_Palette: - return "Palette color"; + return "PALETTE COLOR"; case PhotometricInterpretation_YBRFull: - return "YBR full"; + return "YBR_FULL"; case PhotometricInterpretation_YBRFull422: - return "YBR full 422"; + return "YBR_FULL_422"; case PhotometricInterpretation_YBRPartial420: - return "YBR partial 420"; + return "YBR_PARTIAL_420"; case PhotometricInterpretation_YBRPartial422: - return "YBR partial 422"; + return "YBR_PARTIAL_422"; case PhotometricInterpretation_YBR_ICT: - return "YBR ICT"; + return "YBR_ICT"; case PhotometricInterpretation_YBR_RCT: - return "YBR RCT"; + return "YBR_RCT"; case PhotometricInterpretation_Unknown: return "Unknown"; @@ -1053,6 +1053,80 @@ } + PhotometricInterpretation StringToPhotometricInterpretation(const char* value) + { + // http://dicom.nema.org/medical/dicom/2017a/output/chtml/part03/sect_C.7.6.3.html#sect_C.7.6.3.1.2 + std::string s(value); + + if (s == "MONOCHROME1") + { + return PhotometricInterpretation_Monochrome1; + } + + if (s == "MONOCHROME2") + { + return PhotometricInterpretation_Monochrome2; + } + + if (s == "PALETTE COLOR") + { + return PhotometricInterpretation_Palette; + } + + if (s == "RGB") + { + return PhotometricInterpretation_RGB; + } + + if (s == "HSV") + { + return PhotometricInterpretation_HSV; + } + + if (s == "ARGB") + { + return PhotometricInterpretation_ARGB; + } + + if (s == "CMYK") + { + return PhotometricInterpretation_CMYK; + } + + if (s == "YBR_FULL") + { + return PhotometricInterpretation_YBRFull; + } + + if (s == "YBR_FULL_422") + { + return PhotometricInterpretation_YBRFull422; + } + + if (s == "YBR_PARTIAL_422") + { + return PhotometricInterpretation_YBRPartial422; + } + + if (s == "YBR_PARTIAL_420") + { + return PhotometricInterpretation_YBRPartial420; + } + + if (s == "YBR_ICT") + { + return PhotometricInterpretation_YBR_ICT; + } + + if (s == "YBR_RCT") + { + return PhotometricInterpretation_YBR_RCT; + } + + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + unsigned int GetBytesPerPixel(PixelFormat format) { switch (format) diff -r 2e7a8ce24be2 -r e002430baa41 Core/Enumerations.h --- a/Core/Enumerations.h Mon May 22 20:39:53 2017 +0200 +++ b/Core/Enumerations.h Fri Jun 09 16:14:52 2017 +0200 @@ -524,6 +524,8 @@ ValueRepresentation StringToValueRepresentation(const std::string& vr, bool throwIfUnsupported); + PhotometricInterpretation StringToPhotometricInterpretation(const char* value); + unsigned int GetBytesPerPixel(PixelFormat format); bool GetDicomEncoding(Encoding& encoding, diff -r 2e7a8ce24be2 -r e002430baa41 Core/Images/ImageProcessing.cpp --- a/Core/Images/ImageProcessing.cpp Mon May 22 20:39:53 2017 +0200 +++ b/Core/Images/ImageProcessing.cpp Fri Jun 09 16:14:52 2017 +0200 @@ -772,4 +772,29 @@ throw OrthancException(ErrorCode_NotImplemented); } } + + + void ImageProcessing::Invert(ImageAccessor& image) + { + switch (image.GetFormat()) + { + case PixelFormat_Grayscale8: + { + for (unsigned int y = 0; y < image.GetHeight(); y++) + { + uint8_t* p = reinterpret_cast(image.GetRow(y)); + + for (unsigned int x = 0; x < image.GetWidth(); x++, p++) + { + *p = 255 - (*p); + } + } + + return; + } + + default: + throw OrthancException(ErrorCode_NotImplemented); + } + } } diff -r 2e7a8ce24be2 -r e002430baa41 Core/Images/ImageProcessing.h --- a/Core/Images/ImageProcessing.h Mon May 22 20:39:53 2017 +0200 +++ b/Core/Images/ImageProcessing.h Fri Jun 09 16:14:52 2017 +0200 @@ -73,5 +73,7 @@ static void ShiftScale(ImageAccessor& image, float offset, float scaling); + + static void Invert(ImageAccessor& image); }; } diff -r 2e7a8ce24be2 -r e002430baa41 NEWS --- a/NEWS Mon May 22 20:39:53 2017 +0200 +++ b/NEWS Fri Jun 09 16:14:52 2017 +0200 @@ -16,6 +16,7 @@ * Ability to retrieve raw frames encoded as unsigned 32-bits integers * Fix issue 35 (AET name is not transferred to Orthanc using DCMTK 3.6.0) +* Fix issue 44 (Bad interpretation of photometric interpretation MONOCHROME1) Version 1.2.0 (2016/12/13) diff -r 2e7a8ce24be2 -r e002430baa41 OrthancServer/Internals/DicomImageDecoder.cpp --- a/OrthancServer/Internals/DicomImageDecoder.cpp Mon May 22 20:39:53 2017 +0200 +++ b/OrthancServer/Internals/DicomImageDecoder.cpp Fri Jun 09 16:14:52 2017 +0200 @@ -727,7 +727,8 @@ void DicomImageDecoder::ApplyExtractionMode(std::auto_ptr& image, - ImageExtractionMode mode) + ImageExtractionMode mode, + bool invert) { if (image.get() == NULL) { @@ -761,6 +762,11 @@ if (ok) { assert(image.get() != NULL); + + if (invert) + { + Orthanc::ImageProcessing::Invert(*image); + } } else { @@ -771,9 +777,10 @@ void DicomImageDecoder::ExtractPngImage(std::string& result, std::auto_ptr& image, - ImageExtractionMode mode) + ImageExtractionMode mode, + bool invert) { - ApplyExtractionMode(image, mode); + ApplyExtractionMode(image, mode, invert); PngWriter writer; writer.WriteToMemory(result, *image); @@ -783,6 +790,7 @@ void DicomImageDecoder::ExtractJpegImage(std::string& result, std::auto_ptr& image, ImageExtractionMode mode, + bool invert, uint8_t quality) { if (mode != ImageExtractionMode_UInt8 && @@ -791,7 +799,7 @@ throw OrthancException(ErrorCode_ParameterOutOfRange); } - ApplyExtractionMode(image, mode); + ApplyExtractionMode(image, mode, invert); JpegWriter writer; writer.SetQuality(quality); diff -r 2e7a8ce24be2 -r e002430baa41 OrthancServer/Internals/DicomImageDecoder.h --- a/OrthancServer/Internals/DicomImageDecoder.h Mon May 22 20:39:53 2017 +0200 +++ b/OrthancServer/Internals/DicomImageDecoder.h Fri Jun 09 16:14:52 2017 +0200 @@ -79,7 +79,8 @@ static bool PreviewDecodedImage(std::auto_ptr& image); static void ApplyExtractionMode(std::auto_ptr& image, - ImageExtractionMode mode); + ImageExtractionMode mode, + bool invert); public: static bool IsPsmctRle1(DcmDataset& dataset); @@ -92,11 +93,13 @@ static void ExtractPngImage(std::string& result, std::auto_ptr& image, - ImageExtractionMode mode); + ImageExtractionMode mode, + bool invert); static void ExtractJpegImage(std::string& result, std::auto_ptr& image, ImageExtractionMode mode, + bool invert, uint8_t quality); }; } diff -r 2e7a8ce24be2 -r e002430baa41 OrthancServer/OrthancRestApi/OrthancRestResources.cpp --- a/OrthancServer/OrthancRestApi/OrthancRestResources.cpp Mon May 22 20:39:53 2017 +0200 +++ b/OrthancServer/OrthancRestApi/OrthancRestResources.cpp Fri Jun 09 16:14:52 2017 +0200 @@ -267,14 +267,17 @@ private: std::auto_ptr& image_; ImageExtractionMode mode_; + bool invert_; std::string format_; std::string answer_; public: ImageToEncode(std::auto_ptr& image, - ImageExtractionMode mode) : + ImageExtractionMode mode, + bool invert) : image_(image), - mode_(mode) + mode_(mode), + invert_(invert) { } @@ -286,13 +289,13 @@ void EncodeUsingPng() { format_ = "image/png"; - DicomImageDecoder::ExtractPngImage(answer_, image_, mode_); + DicomImageDecoder::ExtractPngImage(answer_, image_, mode_, invert_); } void EncodeUsingJpeg(uint8_t quality) { format_ = "image/jpeg"; - DicomImageDecoder::ExtractJpegImage(answer_, image_, mode_, quality); + DicomImageDecoder::ExtractJpegImage(answer_, image_, mode_, invert_, quality); } }; @@ -373,6 +376,7 @@ return; } + bool invert = false; std::auto_ptr decoded; try @@ -393,6 +397,21 @@ * to decode the image. This allows us to take advantage of * the cache below. **/ + + if (mode == ImageExtractionMode_Preview && + decoded.get() != NULL) + { + // TODO Optimize this lookup for photometric interpretation: + // It should be implemented by the plugin to avoid parsing + // twice the DICOM file + ParsedDicomFile parsed(dicomContent); + + PhotometricInterpretation photometric; + if (parsed.LookupPhotometricInterpretation(photometric)) + { + invert = (photometric == PhotometricInterpretation_Monochrome1); + } + } } #endif @@ -400,8 +419,15 @@ { // Use Orthanc's built-in decoder, using the cache to speed-up // things on multi-frame images - ServerContext::DicomCacheLocker locker(OrthancRestApi::GetContext(call), publicId); + ServerContext::DicomCacheLocker locker(context, publicId); decoded.reset(DicomImageDecoder::Decode(locker.GetDicom(), frame)); + + PhotometricInterpretation photometric; + if (mode == ImageExtractionMode_Preview && + locker.GetDicom().LookupPhotometricInterpretation(photometric)) + { + invert = (photometric == PhotometricInterpretation_Monochrome1); + } } } catch (OrthancException& e) @@ -423,7 +449,7 @@ } } - ImageToEncode image(decoded, mode); + ImageToEncode image(decoded, mode, invert); HttpContentNegociation negociation; EncodePng png(image); negociation.Register("image/png", png); diff -r 2e7a8ce24be2 -r e002430baa41 OrthancServer/ParsedDicomFile.cpp --- a/OrthancServer/ParsedDicomFile.cpp Mon May 22 20:39:53 2017 +0200 +++ b/OrthancServer/ParsedDicomFile.cpp Fri Jun 09 16:14:52 2017 +0200 @@ -1409,4 +1409,25 @@ { return FromDcmtkBridge::LookupTransferSyntax(result, *pimpl_->file_); } + + + bool ParsedDicomFile::LookupPhotometricInterpretation(PhotometricInterpretation& result) const + { + DcmTagKey k(DICOM_TAG_PHOTOMETRIC_INTERPRETATION.GetGroup(), + DICOM_TAG_PHOTOMETRIC_INTERPRETATION.GetElement()); + + DcmDataset& dataset = *pimpl_->file_->getDataset(); + + const char *c = NULL; + if (dataset.findAndGetString(k, c).good() && + c != NULL) + { + result = StringToPhotometricInterpretation(c); + return true; + } + else + { + return false; + } + } } diff -r 2e7a8ce24be2 -r e002430baa41 OrthancServer/ParsedDicomFile.h --- a/OrthancServer/ParsedDicomFile.h Mon May 22 20:39:53 2017 +0200 +++ b/OrthancServer/ParsedDicomFile.h Fri Jun 09 16:14:52 2017 +0200 @@ -183,5 +183,7 @@ void ExtractDicomAsJson(Json::Value& target) const; bool LookupTransferSyntax(std::string& result); + + bool LookupPhotometricInterpretation(PhotometricInterpretation& result) const; }; } diff -r 2e7a8ce24be2 -r e002430baa41 UnitTestsSources/UnitTestsMain.cpp --- a/UnitTestsSources/UnitTestsMain.cpp Mon May 22 20:39:53 2017 +0200 +++ b/UnitTestsSources/UnitTestsMain.cpp Fri Jun 09 16:14:52 2017 +0200 @@ -631,6 +631,23 @@ ASSERT_EQ(ResourceType_Instance, StringToResourceType(EnumerationToString(ResourceType_Instance))); ASSERT_EQ(ImageFormat_Png, StringToImageFormat(EnumerationToString(ImageFormat_Png))); + + ASSERT_EQ(PhotometricInterpretation_ARGB, StringToPhotometricInterpretation(EnumerationToString(PhotometricInterpretation_ARGB))); + ASSERT_EQ(PhotometricInterpretation_CMYK, StringToPhotometricInterpretation(EnumerationToString(PhotometricInterpretation_CMYK))); + ASSERT_EQ(PhotometricInterpretation_HSV, StringToPhotometricInterpretation(EnumerationToString(PhotometricInterpretation_HSV))); + ASSERT_EQ(PhotometricInterpretation_Monochrome1, StringToPhotometricInterpretation(EnumerationToString(PhotometricInterpretation_Monochrome1))); + ASSERT_EQ(PhotometricInterpretation_Monochrome2, StringToPhotometricInterpretation(EnumerationToString(PhotometricInterpretation_Monochrome2))); + ASSERT_EQ(PhotometricInterpretation_Palette, StringToPhotometricInterpretation(EnumerationToString(PhotometricInterpretation_Palette))); + ASSERT_EQ(PhotometricInterpretation_RGB, StringToPhotometricInterpretation(EnumerationToString(PhotometricInterpretation_RGB))); + ASSERT_EQ(PhotometricInterpretation_YBRFull, StringToPhotometricInterpretation(EnumerationToString(PhotometricInterpretation_YBRFull))); + ASSERT_EQ(PhotometricInterpretation_YBRFull422, StringToPhotometricInterpretation(EnumerationToString(PhotometricInterpretation_YBRFull422))); + ASSERT_EQ(PhotometricInterpretation_YBRPartial420, StringToPhotometricInterpretation(EnumerationToString(PhotometricInterpretation_YBRPartial420))); + ASSERT_EQ(PhotometricInterpretation_YBRPartial422, StringToPhotometricInterpretation(EnumerationToString(PhotometricInterpretation_YBRPartial422))); + ASSERT_EQ(PhotometricInterpretation_YBR_ICT, StringToPhotometricInterpretation(EnumerationToString(PhotometricInterpretation_YBR_ICT))); + ASSERT_EQ(PhotometricInterpretation_YBR_RCT, StringToPhotometricInterpretation(EnumerationToString(PhotometricInterpretation_YBR_RCT))); + + ASSERT_STREQ("Unknown", EnumerationToString(PhotometricInterpretation_Unknown)); + ASSERT_THROW(StringToPhotometricInterpretation("Unknown"), OrthancException); }