diff Framework/Toolbox/OrthancSlicesLoader.cpp @ 93:5945e81734a3 wasm

decoding of JPEG images
author Sebastien Jodogne <s.jodogne@gmail.com>
date Mon, 29 May 2017 17:28:31 +0200
parents f5f54ed8d307
children 7b14c12a3be5
line wrap: on
line diff
--- a/Framework/Toolbox/OrthancSlicesLoader.cpp	Mon May 29 11:42:53 2017 +0200
+++ b/Framework/Toolbox/OrthancSlicesLoader.cpp	Mon May 29 17:28:31 2017 +0200
@@ -23,9 +23,13 @@
 
 #include "MessagingToolbox.h"
 
+#include "../../Resources/Orthanc/Core/Images/Image.h"
+#include "../../Resources/Orthanc/Core/Images/ImageProcessing.h"
+#include "../../Resources/Orthanc/Core/Images/JpegReader.h"
 #include "../../Resources/Orthanc/Core/Images/PngReader.h"
 #include "../../Resources/Orthanc/Core/Logging.h"
 #include "../../Resources/Orthanc/Core/OrthancException.h"
+#include "../../Resources/Orthanc/Core/Toolbox.h"
 #include "../../Resources/Orthanc/Plugins/Samples/Common/DicomDatasetReader.h"
 #include "../../Resources/Orthanc/Plugins/Samples/Common/FullOrthancDataset.h"
 
@@ -36,11 +40,12 @@
   class OrthancSlicesLoader::Operation : public Orthanc::IDynamicObject
   {
   private:
-    Mode          mode_;
-    unsigned int  frame_;
-    unsigned int  sliceIndex_;
-    const Slice*  slice_;
-    std::string   instanceId_;
+    Mode               mode_;
+    unsigned int       frame_;
+    unsigned int       sliceIndex_;
+    const Slice*       slice_;
+    std::string        instanceId_;
+    SliceImageQuality  quality_;
 
     Operation(Mode mode) :
       mode_(mode)
@@ -53,6 +58,12 @@
       return mode_;
     }
 
+    SliceImageQuality GetQuality() const
+    {
+      assert(mode_ == Mode_LoadImage);
+      return quality_;
+    }
+
     unsigned int GetSliceIndex() const
     {
       assert(mode_ == Mode_LoadImage);
@@ -92,11 +103,13 @@
     }
 
     static Operation* DownloadSliceImage(unsigned int  sliceIndex,
-                                         const Slice&  slice)
+                                         const Slice&  slice,
+                                         SliceImageQuality quality)
     {
       std::auto_ptr<Operation> tmp(new Operation(Mode_LoadImage));
       tmp->sliceIndex_ = sliceIndex;
       tmp->slice_ = &slice;
+      tmp->quality_ = quality;
       return tmp.release();
     }
   };
@@ -132,7 +145,22 @@
           break;
 
         case Mode_LoadImage:
-          that_.ParseSliceImage(*operation, answer, answerSize);
+          switch (operation->GetQuality())
+          {
+            case SliceImageQuality_Full:
+              that_.ParseSliceImagePng(*operation, answer, answerSize);
+              break;
+
+            case SliceImageQuality_Jpeg50:
+            case SliceImageQuality_Jpeg90:
+            case SliceImageQuality_Jpeg95:
+              that_.ParseSliceImageJpeg(*operation, answer, answerSize);
+              break;
+
+            default:
+              throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+          }
+                
           break;
 
         default:
@@ -155,7 +183,8 @@
 
         case Mode_LoadImage:
           that_.userCallback_.NotifySliceImageError(that_, operation->GetSliceIndex(),
-                                                    operation->GetSlice());
+                                                    operation->GetSlice(),
+                                                    operation->GetQuality());
           break;
           
         default:
@@ -164,6 +193,29 @@
     }     
   };
 
+
+  
+  void OrthancSlicesLoader::NotifySliceImageSuccess(const Operation& operation,
+                                                    Orthanc::ImageAccessor* image) const
+  {
+    if (image == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+    }
+    else
+    {
+      userCallback_.NotifySliceImageReady
+        (*this, operation.GetSliceIndex(), operation.GetSlice(), image, operation.GetQuality());
+    }
+  }
+
+  
+  void OrthancSlicesLoader::NotifySliceImageError(const Operation& operation) const
+  {
+    userCallback_.NotifySliceImageError
+      (*this, operation.GetSliceIndex(), operation.GetSlice(), operation.GetQuality());
+  }
+
   
   void OrthancSlicesLoader::ParseSeriesGeometry(const void* answer,
                                                 size_t size)
@@ -256,18 +308,32 @@
   }
 
 
-  void OrthancSlicesLoader::ParseSliceImage(const Operation& operation,
-                                            const void* answer,
-                                            size_t size)
+  void OrthancSlicesLoader::ParseSliceImagePng(const Operation& operation,
+                                               const void* answer,
+                                               size_t size)
   {
     std::auto_ptr<Orthanc::PngReader>  image(new Orthanc::PngReader);
-    image->ReadFromMemory(answer, size);
 
-    bool ok = (image->GetWidth() == operation.GetSlice().GetWidth() ||
-               image->GetHeight() == operation.GetSlice().GetHeight());
+    bool ok = false;
+    
+    try
+    {
+      image->ReadFromMemory(answer, size);
+    }
+    catch (Orthanc::OrthancException&)
+    {
+      NotifySliceImageError(operation);
+      return;
+    }
+
+    if (image->GetWidth() != operation.GetSlice().GetWidth() ||
+        image->GetHeight() != operation.GetSlice().GetHeight())
+    {
+      NotifySliceImageError(operation);
+      return;
+    }
       
-    if (ok &&
-        operation.GetSlice().GetConverter().GetExpectedPixelFormat() ==
+    if (operation.GetSlice().GetConverter().GetExpectedPixelFormat() ==
         Orthanc::PixelFormat_SignedGrayscale16)
     {
       if (image->GetFormat() == Orthanc::PixelFormat_Grayscale16)
@@ -276,20 +342,149 @@
       }
       else
       {
-        ok = false;
+        NotifySliceImageError(operation);
+        return;
+      }
+    }
+
+    NotifySliceImageSuccess(operation, image.release());
+  }
+    
+    
+  void OrthancSlicesLoader::ParseSliceImageJpeg(const Operation& operation,
+                                                const void* answer,
+                                                size_t size)
+  {
+    Json::Value encoded;
+    if (!MessagingToolbox::ParseJson(encoded, answer, size) ||
+        encoded.type() != Json::objectValue ||
+        !encoded.isMember("Orthanc") ||
+        encoded["Orthanc"].type() != Json::objectValue)
+    {
+      NotifySliceImageError(operation);
+      return;
+    }
+
+    Json::Value& info = encoded["Orthanc"];
+    if (!info.isMember("PixelData") ||
+        !info.isMember("Stretched") ||
+        !info.isMember("Compression") ||
+        info["Compression"].type() != Json::stringValue ||
+        info["PixelData"].type() != Json::stringValue ||
+        info["Stretched"].type() != Json::booleanValue ||
+        info["Compression"].asString() != "Jpeg")
+    {
+      NotifySliceImageError(operation);
+      return;
+    }          
+
+    bool isSigned = false;
+    bool isStretched = info["Stretched"].asBool();
+
+    if (info.isMember("IsSigned"))
+    {
+      if (info["IsSigned"].type() != Json::booleanValue)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
+      }          
+      else
+      {
+        isSigned = info["IsSigned"].asBool();
       }
     }
 
-    if (ok)
+    std::string jpeg;
+    Orthanc::Toolbox::DecodeBase64(jpeg, info["PixelData"].asString());
+
+    std::auto_ptr<Orthanc::JpegReader> reader(new Orthanc::JpegReader);
+    try
+    {
+      reader->ReadFromMemory(jpeg);
+    }
+    catch (Orthanc::OrthancException&)
+    {
+      NotifySliceImageError(operation);
+      return;
+    }
+
+    Orthanc::PixelFormat expectedFormat =
+      operation.GetSlice().GetConverter().GetExpectedPixelFormat();
+
+    if (reader->GetFormat() == Orthanc::PixelFormat_RGB24)  // This is a color image
     {
-      userCallback_.NotifySliceImageReady(*this, operation.GetSliceIndex(),
-                                          operation.GetSlice(), image.release());
+      if (expectedFormat != Orthanc::PixelFormat_RGB24)
+      {
+        NotifySliceImageError(operation);
+        return;
+      }
+
+      if (isSigned || isStretched)
+      {
+        NotifySliceImageError(operation);
+        return;
+      }
+      else
+      {
+        NotifySliceImageSuccess(operation, reader.release());
+        return;
+      }
+    }
+
+    if (reader->GetFormat() != Orthanc::PixelFormat_Grayscale8)
+    {
+      NotifySliceImageError(operation);
+      return;
     }
-    else
+
+    if (!isStretched)
+    {
+      if (expectedFormat != reader->GetFormat())
+      {
+        NotifySliceImageError(operation);
+        return;
+      }
+      else
+      {
+        NotifySliceImageSuccess(operation, reader.release());
+        return;
+      }
+    }
+
+    int32_t stretchLow = 0;
+    int32_t stretchHigh = 0;
+
+    if (!info.isMember("StretchLow") ||
+        !info.isMember("StretchHigh") ||
+        info["StretchLow"].type() != Json::intValue ||
+        info["StretchHigh"].type() != Json::intValue)
     {
-      userCallback_.NotifySliceImageError(*this, operation.GetSliceIndex(),
-                                          operation.GetSlice());
+      NotifySliceImageError(operation);
+      return;
     }
+
+    stretchLow = info["StretchLow"].asInt();
+    stretchHigh = info["StretchHigh"].asInt();
+
+    if (stretchLow < -32768 ||
+        stretchHigh > 65535 ||
+        (stretchLow < 0 && stretchHigh > 32767))
+    {
+      // This range cannot be represented with a uint16_t or an int16_t
+      NotifySliceImageError(operation);
+      return;
+    }
+
+    // Decode a grayscale JPEG 8bpp image coming from the Web viewer
+    std::auto_ptr<Orthanc::ImageAccessor> image
+      (new Orthanc::Image(expectedFormat, reader->GetWidth(), reader->GetHeight(), false));
+
+    float scaling = static_cast<float>(stretchHigh - stretchLow) / 255.0f;
+    float offset = static_cast<float>(stretchLow) / scaling;
+      
+    Orthanc::ImageProcessing::Convert(*image, *reader);
+    Orthanc::ImageProcessing::ShiftScale(*image, offset, scaling);
+
+    NotifySliceImageSuccess(operation, image.release());
   }
     
     
@@ -375,38 +570,86 @@
   }
   
 
-  void OrthancSlicesLoader::ScheduleLoadSliceImage(size_t index)
+  void OrthancSlicesLoader::ScheduleSliceImagePng(size_t index)
+  {
+    const Slice& slice = GetSlice(index);
+
+    std::string uri = ("/instances/" + slice.GetOrthancInstanceId() + "/frames/" + 
+                       boost::lexical_cast<std::string>(slice.GetFrame()));
+
+    switch (slice.GetConverter().GetExpectedPixelFormat())
+    {
+      case Orthanc::PixelFormat_RGB24:
+        uri += "/preview";
+        break;
+
+      case Orthanc::PixelFormat_Grayscale16:
+        uri += "/image-uint16";
+        break;
+
+      case Orthanc::PixelFormat_SignedGrayscale16:
+        uri += "/image-int16";
+        break;
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    orthanc_.ScheduleGetRequest(*webCallback_, uri,
+                                Operation::DownloadSliceImage(index, slice, SliceImageQuality_Full));
+  }
+
+
+  void OrthancSlicesLoader::ScheduleSliceImageJpeg(size_t index,
+                                                   SliceImageQuality quality)
+  {
+    unsigned int value;
+
+    switch (quality)
+    {
+      case SliceImageQuality_Jpeg50:
+        value = 50;
+        break;
+    
+      case SliceImageQuality_Jpeg90:
+        value = 90;
+        break;
+    
+      case SliceImageQuality_Jpeg95:
+        value = 95;
+        break;
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+    
+    // This requires the official Web viewer plugin to be installed!
+    const Slice& slice = GetSlice(index);
+    std::string uri = ("web-viewer/instances/jpeg" + 
+                       boost::lexical_cast<std::string>(value) + 
+                       "-" + slice.GetOrthancInstanceId() + "_" + 
+                       boost::lexical_cast<std::string>(slice.GetFrame()));
+
+    orthanc_.ScheduleGetRequest(*webCallback_, uri,
+                                Operation::DownloadSliceImage(index, slice, quality));
+  }
+
+
+  void OrthancSlicesLoader::ScheduleLoadSliceImage(size_t index,
+                                                   SliceImageQuality quality)
   {
     if (state_ != State_GeometryReady)
     {
       throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
     }
+
+    if (quality == SliceImageQuality_Full)
+    {
+      ScheduleSliceImagePng(index);
+    }
     else
     {
-      const Slice& slice = GetSlice(index);
-
-      std::string uri = ("/instances/" + slice.GetOrthancInstanceId() + "/frames/" + 
-                         boost::lexical_cast<std::string>(slice.GetFrame()));
-
-      switch (slice.GetConverter().GetExpectedPixelFormat())
-      {
-        case Orthanc::PixelFormat_RGB24:
-          uri += "/preview";
-          break;
-
-        case Orthanc::PixelFormat_Grayscale16:
-          uri += "/image-uint16";
-          break;
-
-        case Orthanc::PixelFormat_SignedGrayscale16:
-          uri += "/image-int16";
-          break;
-
-        default:
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-      }
-
-      orthanc_.ScheduleGetRequest(*webCallback_, uri, Operation::DownloadSliceImage(index, slice));
+      ScheduleSliceImageJpeg(index, quality);
     }
   }
 }