diff OrthancServer/OrthancRestApi/OrthancRestResources.cpp @ 3683:12253ddefe5a

skeleton for new route: /instances/{id}/rendered
author Sebastien Jodogne <s.jodogne@gmail.com>
date Mon, 24 Feb 2020 17:19:37 +0100
parents 94f4a18a79cc
children 2cc34837d694
line wrap: on
line diff
--- a/OrthancServer/OrthancRestApi/OrthancRestResources.cpp	Mon Feb 24 16:26:59 2020 +0100
+++ b/OrthancServer/OrthancRestApi/OrthancRestResources.cpp	Mon Feb 24 17:19:37 2020 +0100
@@ -35,11 +35,11 @@
 #include "OrthancRestApi.h"
 
 #include "../../Core/Compression/GzipCompressor.h"
+#include "../../Core/DicomFormat/DicomImageInformation.h"
 #include "../../Core/DicomParsing/DicomWebJsonVisitor.h"
 #include "../../Core/DicomParsing/FromDcmtkBridge.h"
 #include "../../Core/DicomParsing/Internals/DicomImageDecoder.h"
 #include "../../Core/HttpServer/HttpContentNegociation.h"
-#include "../../Core/Images/ImageProcessing.h"
 #include "../../Core/Logging.h"
 #include "../DefaultDicomImageDecoder.h"
 #include "../OrthancConfiguration.h"
@@ -503,152 +503,222 @@
   }
 
 
-  void LookupWindowingTags(const ParsedDicomFile& dicom, float& windowCenter, float& windowWidth, float& rescaleSlope, float& rescaleIntercept, bool& invert)
+  namespace
   {
-    DicomMap dicomTags;
-    dicom.ExtractDicomSummary(dicomTags);
+    class IDecodedFrameHandler : public boost::noncopyable
+    {
+    public:
+      virtual ~IDecodedFrameHandler()
+      {
+      }
+
+      virtual void Handle(RestApiGetCall& call,
+                          std::auto_ptr<ImageAccessor>& decoded,
+                          const DicomMap& dicom) = 0;
+
+      virtual bool RequiresDicomTags() const = 0;
+
+      static void Apply(RestApiGetCall& call,
+                        IDecodedFrameHandler& handler)
+      {
+        ServerContext& context = OrthancRestApi::GetContext(call);
+
+        std::string frameId = call.GetUriComponent("frame", "0");
+
+        unsigned int frame;
+        try
+        {
+          frame = boost::lexical_cast<unsigned int>(frameId);
+        }
+        catch (boost::bad_lexical_cast&)
+        {
+          return;
+        }
+
+        DicomMap dicom;
+        std::auto_ptr<ImageAccessor> decoded;
+
+        try
+        {
+          std::string publicId = call.GetUriComponent("id", "");
+
+#if ORTHANC_ENABLE_PLUGINS == 1
+          if (context.GetPlugins().HasCustomImageDecoder())
+          {
+            // TODO create a cache of file
+            std::string dicomContent;
+            context.ReadDicom(dicomContent, publicId);
+            decoded.reset(context.GetPlugins().DecodeUnsafe(dicomContent.c_str(), dicomContent.size(), frame));
+
+            /**
+             * Note that we call "DecodeUnsafe()": We do not fallback to
+             * the builtin decoder if no installed decoder plugin is able
+             * to decode the image. This allows us to take advantage of
+             * the cache below.
+             **/
+
+            if (handler.RequiresDicomTags() &&
+                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);
+              parsed.ExtractDicomSummary(dicom);
+            }
+          }
+#endif
+
+          if (decoded.get() == NULL)
+          {
+            // Use Orthanc's built-in decoder, using the cache to speed-up
+            // things on multi-frame images
+            ServerContext::DicomCacheLocker locker(context, publicId);        
+            decoded.reset(DicomImageDecoder::Decode(locker.GetDicom(), frame));
+
+            if (handler.RequiresDicomTags())
+            {
+              locker.GetDicom().ExtractDicomSummary(dicom);
+            }
+          }
+        }
+        catch (OrthancException& e)
+        {
+          if (e.GetErrorCode() == ErrorCode_ParameterOutOfRange ||
+              e.GetErrorCode() == ErrorCode_UnknownResource)
+          {
+            // The frame number is out of the range for this DICOM
+            // instance, the resource is not existent
+          }
+          else
+          {
+            std::string root = "";
+            for (size_t i = 1; i < call.GetFullUri().size(); i++)
+            {
+              root += "../";
+            }
+
+            call.GetOutput().Redirect(root + "app/images/unsupported.png");
+          }
+          return;
+        }
+
+        handler.Handle(call, decoded, dicom);
+      }
+    };
 
 
-    unsigned int bitsStored = boost::lexical_cast<unsigned int>(dicomTags.GetStringValue(Orthanc::DICOM_TAG_BITS_STORED, "8", false));
-    windowWidth = static_cast<float>(2 << (bitsStored - 1));
-    windowCenter = windowWidth / 2;
-    rescaleSlope = 1.0f;
-    rescaleIntercept = 0.0f;
-    invert = false;
+    class GetImageHandler : public IDecodedFrameHandler
+    {
+    private:
+      ImageExtractionMode mode_;
+
+    public:
+      GetImageHandler(ImageExtractionMode mode) :
+        mode_(mode)
+      {
+      }
+
+      virtual void Handle(RestApiGetCall& call,
+                          std::auto_ptr<ImageAccessor>& decoded,
+                          const DicomMap& dicom) ORTHANC_OVERRIDE
+      {
+        bool invert = false;
+
+        if (mode_ == ImageExtractionMode_Preview)
+        {
+          DicomImageInformation info(dicom);
+          invert = (info.GetPhotometricInterpretation() == PhotometricInterpretation_Monochrome1);
+        }
 
-    if (dicomTags.HasTag(Orthanc::DICOM_TAG_WINDOW_CENTER) && dicomTags.HasTag(Orthanc::DICOM_TAG_WINDOW_WIDTH))
-    {
-      dicomTags.ParseFloat(windowCenter, Orthanc::DICOM_TAG_WINDOW_CENTER);
-      dicomTags.ParseFloat(windowWidth, Orthanc::DICOM_TAG_WINDOW_WIDTH);
-    }
+        ImageToEncode image(decoded, mode_, invert);
+
+        HttpContentNegociation negociation;
+        EncodePng png(image);
+        negociation.Register(MIME_PNG, png);
+
+        EncodeJpeg jpeg(image, call);
+        negociation.Register(MIME_JPEG, jpeg);
+
+        EncodePam pam(image);
+        negociation.Register(MIME_PAM, pam);
+
+        if (negociation.Apply(call.GetHttpHeaders()))
+        {
+          image.Answer(call.GetOutput());
+        }
+      }
+
+      virtual bool RequiresDicomTags() const ORTHANC_OVERRIDE
+      {
+        return mode_ == ImageExtractionMode_Preview;
+      }
+    };
+
 
-    if (dicomTags.HasTag(Orthanc::DICOM_TAG_RESCALE_SLOPE) && dicomTags.HasTag(Orthanc::DICOM_TAG_RESCALE_INTERCEPT))
+    class RenderedFrameHandler : public IDecodedFrameHandler
     {
-      dicomTags.ParseFloat(rescaleSlope, Orthanc::DICOM_TAG_RESCALE_SLOPE);
-      dicomTags.ParseFloat(rescaleIntercept, Orthanc::DICOM_TAG_RESCALE_INTERCEPT);
-    }
+    private:
+      static void LookupWindowingTags(const DicomMap& dicom,
+                                      float& windowCenter,
+                                      float& windowWidth,
+                                      float& rescaleSlope,
+                                      float& rescaleIntercept,
+                                      bool& invert)
+      {
+        DicomImageInformation info(dicom);
+    
+        windowWidth = static_cast<float>(1 << info.GetBitsStored());
+        windowCenter = windowWidth / 2.0f;
+        rescaleSlope = 1.0f;
+        rescaleIntercept = 0.0f;
+        invert = false;
+
+        if (dicom.HasTag(Orthanc::DICOM_TAG_WINDOW_CENTER) &&
+            dicom.HasTag(Orthanc::DICOM_TAG_WINDOW_WIDTH))
+        {
+          dicom.ParseFloat(windowCenter, Orthanc::DICOM_TAG_WINDOW_CENTER);
+          dicom.ParseFloat(windowWidth, Orthanc::DICOM_TAG_WINDOW_WIDTH);
+        }
 
-    PhotometricInterpretation photometric;
-    if (dicom.LookupPhotometricInterpretation(photometric))
-    {
-      invert = (photometric == PhotometricInterpretation_Monochrome1);
-    }
+        if (dicom.HasTag(Orthanc::DICOM_TAG_RESCALE_SLOPE) &&
+            dicom.HasTag(Orthanc::DICOM_TAG_RESCALE_INTERCEPT))
+        {
+          dicom.ParseFloat(rescaleSlope, Orthanc::DICOM_TAG_RESCALE_SLOPE);
+          dicom.ParseFloat(rescaleIntercept, Orthanc::DICOM_TAG_RESCALE_INTERCEPT);
+        }
+
+        invert = (info.GetPhotometricInterpretation() == PhotometricInterpretation_Monochrome1);
+      }
+      
+    public:
+      virtual void Handle(RestApiGetCall& call,
+                          std::auto_ptr<ImageAccessor>& decoded,
+                          const DicomMap& dicom) ORTHANC_OVERRIDE
+      {
+        // TODO
+      }
+
+      virtual bool RequiresDicomTags() const ORTHANC_OVERRIDE
+      {
+        return true;
+      }
+    };
   }
 
+
   template <enum ImageExtractionMode mode>
   static void GetImage(RestApiGetCall& call)
   {
-    ServerContext& context = OrthancRestApi::GetContext(call);
-
-    std::string frameId = call.GetUriComponent("frame", "0");
-
-    unsigned int frame;
-    try
-    {
-      frame = boost::lexical_cast<unsigned int>(frameId);
-    }
-    catch (boost::bad_lexical_cast&)
-    {
-      return;
-    }
-
-    bool invert = false;
-    float windowCenter = 128.0f;
-    float windowWidth = 256.0f;
-    float rescaleSlope = 1.0f;
-    float rescaleIntercept = 0.0f;
-
-    std::auto_ptr<ImageAccessor> decoded;
-
-    try
-    {
-      std::string publicId = call.GetUriComponent("id", "");
+    GetImageHandler handler(mode);
+    IDecodedFrameHandler::Apply(call, handler);
+  }
 
-#if ORTHANC_ENABLE_PLUGINS == 1
-      if (context.GetPlugins().HasCustomImageDecoder())
-      {
-        // TODO create a cache of file
-        std::string dicomContent;
-        context.ReadDicom(dicomContent, publicId);
-        decoded.reset(context.GetPlugins().DecodeUnsafe(dicomContent.c_str(), dicomContent.size(), frame));
-
-        /**
-         * Note that we call "DecodeUnsafe()": We do not fallback to
-         * the builtin decoder if no installed decoder plugin is able
-         * 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);
-          
-          LookupWindowingTags(dicomContent, windowCenter, windowWidth, rescaleSlope, rescaleIntercept, invert);
-        }
-      }
-#endif
 
-      if (decoded.get() == NULL)
-      {
-        // Use Orthanc's built-in decoder, using the cache to speed-up
-        // things on multi-frame images
-        ServerContext::DicomCacheLocker locker(context, publicId);        
-        decoded.reset(DicomImageDecoder::Decode(locker.GetDicom(), frame));
-        LookupWindowingTags(locker.GetDicom(), windowCenter, windowWidth, rescaleSlope, rescaleIntercept, invert);
-
-        if (mode != ImageExtractionMode_Preview)
-        {
-          invert = false;
-        }
-      }
-    }
-    catch (OrthancException& e)
-    {
-      if (e.GetErrorCode() == ErrorCode_ParameterOutOfRange || e.GetErrorCode() == ErrorCode_UnknownResource)
-      {
-        // The frame number is out of the range for this DICOM
-        // instance, the resource is not existent
-      }
-      else
-      {
-        std::string root = "";
-        for (size_t i = 1; i < call.GetFullUri().size(); i++)
-        {
-          root += "../";
-        }
-
-        call.GetOutput().Redirect(root + "app/images/unsupported.png");
-      }
-      return;
-    }
-
-    if (mode == ImageExtractionMode_Preview
-        && (decoded->GetFormat() == Orthanc::PixelFormat_Grayscale8 || decoded->GetFormat() == Orthanc::PixelFormat_Grayscale16))
-    {
-      ImageProcessing::ApplyWindowing(*decoded, *decoded, windowCenter, windowWidth, rescaleSlope, rescaleIntercept, invert);
-      invert = false; // don't invert it later on when encoding it, it has been inverted in the ApplyWindowing function
-    }
-
-    ImageToEncode image(decoded, mode, invert);
-
-    HttpContentNegociation negociation;
-    EncodePng png(image);
-    negociation.Register(MIME_PNG, png);
-
-    EncodeJpeg jpeg(image, call);
-    negociation.Register(MIME_JPEG, jpeg);
-
-    EncodePam pam(image);
-    negociation.Register(MIME_PAM, pam);
-
-    if (negociation.Apply(call.GetHttpHeaders()))
-    {
-      image.Answer(call.GetOutput());
-    }
+  static void GetRenderedFrame(RestApiGetCall& call)
+  {
+    RenderedFrameHandler handler;
+    IDecodedFrameHandler::Apply(call, handler);
   }
 
 
@@ -1802,6 +1872,7 @@
     Register("/instances/{id}/frames", ListFrames);
 
     Register("/instances/{id}/frames/{frame}/preview", GetImage<ImageExtractionMode_Preview>);
+    Register("/instances/{id}/frames/{frame}/rendered", GetRenderedFrame);
     Register("/instances/{id}/frames/{frame}/image-uint8", GetImage<ImageExtractionMode_UInt8>);
     Register("/instances/{id}/frames/{frame}/image-uint16", GetImage<ImageExtractionMode_UInt16>);
     Register("/instances/{id}/frames/{frame}/image-int16", GetImage<ImageExtractionMode_Int16>);
@@ -1810,6 +1881,7 @@
     Register("/instances/{id}/frames/{frame}/raw.gz", GetRawFrame<true>);
     Register("/instances/{id}/pdf", ExtractPdf);
     Register("/instances/{id}/preview", GetImage<ImageExtractionMode_Preview>);
+    Register("/instances/{id}/rendered", GetRenderedFrame);
     Register("/instances/{id}/image-uint8", GetImage<ImageExtractionMode_UInt8>);
     Register("/instances/{id}/image-uint16", GetImage<ImageExtractionMode_UInt16>);
     Register("/instances/{id}/image-int16", GetImage<ImageExtractionMode_Int16>);