changeset 2281:e002430baa41

Fix issue #44 (Bad interpretation of photometric interpretation MONOCHROME1)
author Sebastien Jodogne <s.jodogne@gmail.com>
date Fri, 09 Jun 2017 16:14:52 +0200
parents 2e7a8ce24be2
children 4e27c2a598d4
files Core/Enumerations.cpp Core/Enumerations.h Core/Images/ImageProcessing.cpp Core/Images/ImageProcessing.h NEWS OrthancServer/Internals/DicomImageDecoder.cpp OrthancServer/Internals/DicomImageDecoder.h OrthancServer/OrthancRestApi/OrthancRestResources.cpp OrthancServer/ParsedDicomFile.cpp OrthancServer/ParsedDicomFile.h UnitTestsSources/UnitTestsMain.cpp
diffstat 11 files changed, 202 insertions(+), 21 deletions(-) [+]
line wrap: on
line diff
--- 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)
--- 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,
--- 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<uint8_t*>(image.GetRow(y));
+
+          for (unsigned int x = 0; x < image.GetWidth(); x++, p++)
+          {
+            *p = 255 - (*p);
+          }
+        }
+        
+        return;
+      }
+
+      default:
+        throw OrthancException(ErrorCode_NotImplemented);
+    }   
+  }
 }
--- 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);
   };
 }
--- 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)
--- 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<ImageAccessor>& 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<ImageAccessor>& 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<ImageAccessor>& 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);
--- 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<ImageAccessor>& image);
 
     static void ApplyExtractionMode(std::auto_ptr<ImageAccessor>& 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<ImageAccessor>& image,
-                                ImageExtractionMode mode);
+                                ImageExtractionMode mode,
+                                bool invert);
 
     static void ExtractJpegImage(std::string& result,
                                  std::auto_ptr<ImageAccessor>& image,
                                  ImageExtractionMode mode,
+                                 bool invert,
                                  uint8_t quality);
   };
 }
--- 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<ImageAccessor>&  image_;
       ImageExtractionMode            mode_;
+      bool                           invert_;
       std::string                    format_;
       std::string                    answer_;
 
     public:
       ImageToEncode(std::auto_ptr<ImageAccessor>& 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<ImageAccessor> 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);
--- 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;
+    }
+  }
 }
--- 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;
   };
 }
--- 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);
 }