changeset 1902:8b0ee8d5e6d0

Refactoring leading to speedups with custom image decoders
author Sebastien Jodogne <s.jodogne@gmail.com>
date Tue, 05 Jan 2016 13:26:51 +0100
parents 50234539a0dd
children 554ec8422ec5
files NEWS OrthancServer/DefaultDicomImageDecoder.h OrthancServer/IDicomImageDecoder.h OrthancServer/Internals/DicomImageDecoder.cpp OrthancServer/Internals/DicomImageDecoder.h OrthancServer/OrthancRestApi/OrthancRestResources.cpp OrthancServer/ParsedDicomFile.cpp OrthancServer/ParsedDicomFile.h Plugins/Engine/OrthancPlugins.cpp Plugins/Engine/OrthancPlugins.h
diffstat 10 files changed, 188 insertions(+), 190 deletions(-) [+]
line wrap: on
line diff
--- a/NEWS	Mon Jan 04 14:20:41 2016 +0100
+++ b/NEWS	Tue Jan 05 13:26:51 2016 +0100
@@ -3,6 +3,7 @@
 
 * Support of optional tags for counting resources in C-Find:
   0008-0061, 0008-0062, 0020-1200, 0020-1202, 0020-1204, 0020-1206, 0020-1208, 0020-1209
+* Refactoring leading to speedups with custom image decoders (including Web viewer plugin)
 
 
 Version 1.0.0 (2015/12/15)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/DefaultDicomImageDecoder.h	Tue Jan 05 13:26:51 2016 +0100
@@ -0,0 +1,52 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "IDicomImageDecoder.h"
+#include "ParsedDicomFile.h"
+#include "Internals/DicomImageDecoder.h"
+
+namespace Orthanc
+{
+  class DefaultDicomImageDecoder : public IDicomImageDecoder
+  {
+  public:
+    virtual ImageAccessor* Decode(const void* dicom,
+                                  size_t size,
+                                  unsigned int frame)
+    {
+      ParsedDicomFile parsed(dicom, size);
+      return DicomImageDecoder::Decode(parsed, frame);
+    }
+  };
+}
--- a/OrthancServer/IDicomImageDecoder.h	Mon Jan 04 14:20:41 2016 +0100
+++ b/OrthancServer/IDicomImageDecoder.h	Tue Jan 05 13:26:51 2016 +0100
@@ -38,8 +38,6 @@
 
 namespace Orthanc
 {
-  class ParsedDicomFile;
-
   class IDicomImageDecoder : public boost::noncopyable
   {
   public:
@@ -47,7 +45,8 @@
     {
     }
 
-    virtual ImageAccessor* Decode(ParsedDicomFile& dicom,
+    virtual ImageAccessor* Decode(const void* dicom,
+                                  size_t size,
                                   unsigned int frame) = 0;
   };
 }
--- a/OrthancServer/Internals/DicomImageDecoder.cpp	Mon Jan 04 14:20:41 2016 +0100
+++ b/OrthancServer/Internals/DicomImageDecoder.cpp	Tue Jan 05 13:26:51 2016 +0100
@@ -84,6 +84,8 @@
 #include "../../Core/OrthancException.h"
 #include "../../Core/Images/Image.h"
 #include "../../Core/Images/ImageProcessing.h"
+#include "../../Core/Images/PngWriter.h"
+#include "../../Core/Images/JpegWriter.h"
 #include "../../Core/DicomFormat/DicomIntegerPixelAccessor.h"
 #include "../ToDcmtkBridge.h"
 #include "../FromDcmtkBridge.h"
@@ -571,7 +573,8 @@
       }
     }
 
-    return NULL;
+    LOG(ERROR) << "Cannot decode a DICOM image with the built-in decoder";
+    throw OrthancException(ErrorCode_BadFileFormat);
   }
 
 
@@ -653,4 +656,77 @@
         throw OrthancException(ErrorCode_NotImplemented);
     }
   }
+
+
+  void DicomImageDecoder::ApplyExtractionMode(std::auto_ptr<ImageAccessor>& image,
+                                              ImageExtractionMode mode)
+  {
+    if (image.get() == NULL)
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    bool ok = false;
+
+    switch (mode)
+    {
+      case ImageExtractionMode_UInt8:
+        ok = TruncateDecodedImage(image, PixelFormat_Grayscale8, false);
+        break;
+
+      case ImageExtractionMode_UInt16:
+        ok = TruncateDecodedImage(image, PixelFormat_Grayscale16, false);
+        break;
+
+      case ImageExtractionMode_Int16:
+        ok = TruncateDecodedImage(image, PixelFormat_SignedGrayscale16, false);
+        break;
+
+      case ImageExtractionMode_Preview:
+        ok = PreviewDecodedImage(image);
+        break;
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    if (ok)
+    {
+      assert(image.get() != NULL);
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_NotImplemented);
+    }
+  }
+
+
+  void DicomImageDecoder::ExtractPngImage(std::string& result,
+                                          std::auto_ptr<ImageAccessor>& image,
+                                          ImageExtractionMode mode)
+  {
+    ApplyExtractionMode(image, mode);
+
+    PngWriter writer;
+    writer.WriteToMemory(result, *image);
+  }
+
+
+  void DicomImageDecoder::ExtractJpegImage(std::string& result,
+                                           std::auto_ptr<ImageAccessor>& image,
+                                           ImageExtractionMode mode,
+                                           uint8_t quality)
+  {
+    if (mode != ImageExtractionMode_UInt8 &&
+        mode != ImageExtractionMode_Preview)
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    ApplyExtractionMode(image, mode);
+
+    JpegWriter writer;
+    writer.SetQuality(quality);
+    writer.WriteToMemory(result, *image);
+  }
 }
--- a/OrthancServer/Internals/DicomImageDecoder.h	Mon Jan 04 14:20:41 2016 +0100
+++ b/OrthancServer/Internals/DicomImageDecoder.h	Tue Jan 05 13:26:51 2016 +0100
@@ -32,19 +32,23 @@
 
 #pragma once
 
-#include <memory>
+#include "../ParsedDicomFile.h"
 
-#include "../IDicomImageDecoder.h"
+#include <memory>
 
 class DcmDataset;
 
 namespace Orthanc
 {
-  class DicomImageDecoder : public IDicomImageDecoder
+  class DicomImageDecoder : public boost::noncopyable
   {
   private:
     class ImageSource;
 
+    DicomImageDecoder()   // This is a fully abstract class, no constructor
+    {
+    }
+
     static ImageAccessor* DecodeUncompressedImageInternal(DcmDataset& dataset,
                                                           unsigned int frame);
 
@@ -62,14 +66,26 @@
                                              unsigned int frame);
 #endif
 
-  public:
-    virtual ImageAccessor *Decode(ParsedDicomFile& dicom,
-                                  unsigned int frame);
-
     static bool TruncateDecodedImage(std::auto_ptr<ImageAccessor>& image,
                                      PixelFormat format,
                                      bool allowColorConversion);
 
     static bool PreviewDecodedImage(std::auto_ptr<ImageAccessor>& image);
+
+    static void ApplyExtractionMode(std::auto_ptr<ImageAccessor>& image,
+                                    ImageExtractionMode mode);
+
+  public:
+    static ImageAccessor *Decode(ParsedDicomFile& dicom,
+                                 unsigned int frame);
+
+    static void ExtractPngImage(std::string& result,
+                                std::auto_ptr<ImageAccessor>& image,
+                                ImageExtractionMode mode);
+
+    static void ExtractJpegImage(std::string& result,
+                                 std::auto_ptr<ImageAccessor>& image,
+                                 ImageExtractionMode mode,
+                                 uint8_t quality);
   };
 }
--- a/OrthancServer/OrthancRestApi/OrthancRestResources.cpp	Mon Jan 04 14:20:41 2016 +0100
+++ b/OrthancServer/OrthancRestApi/OrthancRestResources.cpp	Tue Jan 05 13:26:51 2016 +0100
@@ -266,58 +266,34 @@
     class ImageToEncode
     {
     private:
-      IDicomImageDecoder& decoder_;
-      std::string         format_;
-      std::string         encoded_;
-      ParsedDicomFile&    dicom_;
-      unsigned int        frame_;
-      ImageExtractionMode mode_;
+      std::auto_ptr<ImageAccessor>&  image_;
+      ImageExtractionMode            mode_;
+      std::string                    format_;
+      std::string                    answer_;
 
     public:
-      ImageToEncode(IDicomImageDecoder& decoder,
-                    ParsedDicomFile& dicom,
-                    unsigned int frame,
+      ImageToEncode(std::auto_ptr<ImageAccessor>& image,
                     ImageExtractionMode mode) : 
-        decoder_(decoder),
-        dicom_(dicom),
-        frame_(frame),
+        image_(image),
         mode_(mode)
       {
       }
 
-      ParsedDicomFile& GetDicom() const
-      {
-        return dicom_;
-      }
-
-      unsigned int GetFrame() const
+      void Answer(RestApiOutput& output)
       {
-        return frame_;
-      }
-
-      ImageExtractionMode GetMode() const
-      {
-        return mode_;
+        output.AnswerBuffer(answer_, format_);
       }
 
-      void SetFormat(const std::string& format)
+      void EncodeUsingPng()
       {
-        format_ = format;
+        format_ = "image/png";
+        DicomImageDecoder::ExtractPngImage(answer_, image_, mode_);
       }
 
-      std::string& GetTarget()
-      {
-        return encoded_;
-      }
-
-      void Answer(RestApiOutput& output)
+      void EncodeUsingJpeg(uint8_t quality)
       {
-        output.AnswerBuffer(encoded_, format_);
-      }
-
-      IDicomImageDecoder& GetDecoder() const
-      {
-        return decoder_;
+        format_ = "image/jpeg";
+        DicomImageDecoder::ExtractJpegImage(answer_, image_, mode_, quality);
       }
     };
 
@@ -336,9 +312,7 @@
       {
         assert(type == "image");
         assert(subtype == "png");
-        image_.GetDicom().ExtractPngImage(image_.GetTarget(), image_.GetDecoder(), 
-                                          image_.GetFrame(), image_.GetMode());
-        image_.SetFormat("image/png");
+        image_.EncodeUsingPng();
       }
     };
 
@@ -377,9 +351,7 @@
       {
         assert(type == "image");
         assert(subtype == "jpeg");
-        image_.GetDicom().ExtractJpegImage(image_.GetTarget(), image_.GetDecoder(), 
-                                           image_.GetFrame(), image_.GetMode(), quality_);
-        image_.SetFormat("image/jpeg");
+        image_.EncodeUsingJpeg(quality_);
       }
     };
   }
@@ -406,17 +378,17 @@
     std::string dicomContent;
     context.ReadFile(dicomContent, publicId, FileContentType_Dicom);
 
-    ParsedDicomFile dicom(dicomContent);
-
     try
     {
 #if ORTHANC_PLUGINS_ENABLED == 1
       IDicomImageDecoder& decoder = context.GetPlugins();
 #else
-      DicomImageDecoder decoder;  // This is Orthanc's built-in decoder
+      DefaultDicomImageDecoder decoder;  // This is Orthanc's built-in decoder
 #endif
 
-      ImageToEncode image(decoder, dicom, frame, mode);
+      std::auto_ptr<ImageAccessor> decoded(decoder.Decode(dicomContent.c_str(), dicomContent.size(), frame));
+
+      ImageToEncode image(decoded, mode);
 
       HttpContentNegociation negociation;
       EncodePng png(image);          negociation.Register("image/png", png);
@@ -471,11 +443,10 @@
 #if ORTHANC_PLUGINS_ENABLED == 1
     IDicomImageDecoder& decoder = context.GetPlugins();
 #else
-    DicomImageDecoder decoder;  // This is Orthanc's built-in decoder
+    DefaultDicomImageDecoder decoder;  // This is Orthanc's built-in decoder
 #endif
 
-    ParsedDicomFile dicom(dicomContent);
-    std::auto_ptr<ImageAccessor> decoded(dicom.ExtractImage(decoder, frame));
+    std::auto_ptr<ImageAccessor> decoded(decoder.Decode(dicomContent.c_str(), dicomContent.size(), frame));
 
     std::string result;
     decoded->ToMatlabString(result);
--- a/OrthancServer/ParsedDicomFile.cpp	Mon Jan 04 14:20:41 2016 +0100
+++ b/OrthancServer/ParsedDicomFile.cpp	Tue Jan 05 13:26:51 2016 +0100
@@ -84,12 +84,8 @@
 #include "ServerToolbox.h"
 #include "FromDcmtkBridge.h"
 #include "ToDcmtkBridge.h"
-#include "Internals/DicomImageDecoder.h"
-#include "../Core/DicomFormat/DicomIntegerPixelAccessor.h"
-#include "../Core/Images/JpegWriter.h"
 #include "../Core/Images/JpegReader.h"
 #include "../Core/Images/PngReader.h"
-#include "../Core/Images/PngWriter.h"
 #include "../Core/Logging.h"
 #include "../Core/OrthancException.h"
 #include "../Core/Toolbox.h"
@@ -1047,99 +1043,6 @@
   }
 
   
-  ImageAccessor* ParsedDicomFile::ExtractImage(IDicomImageDecoder& decoder,
-                                               unsigned int frame)
-  {
-    std::auto_ptr<ImageAccessor> decoded(decoder.Decode(*this, frame));
-
-    if (decoded.get() == NULL)
-    {
-      LOG(ERROR) << "Cannot decode a DICOM image";
-      throw OrthancException(ErrorCode_BadFileFormat);
-    }
-    else
-    {
-      return decoded.release();
-    }
-  }
-
-
-  ImageAccessor* ParsedDicomFile::ExtractImage(IDicomImageDecoder& decoder,
-                                               unsigned int frame,
-                                               ImageExtractionMode mode)
-  {
-    std::auto_ptr<ImageAccessor> decoded(ExtractImage(decoder, frame));
-
-    bool ok = false;
-
-    switch (mode)
-    {
-      case ImageExtractionMode_UInt8:
-        ok = DicomImageDecoder::TruncateDecodedImage(decoded, PixelFormat_Grayscale8, false);
-        break;
-
-      case ImageExtractionMode_UInt16:
-        ok = DicomImageDecoder::TruncateDecodedImage(decoded, PixelFormat_Grayscale16, false);
-        break;
-
-      case ImageExtractionMode_Int16:
-        ok = DicomImageDecoder::TruncateDecodedImage(decoded, PixelFormat_SignedGrayscale16, false);
-        break;
-
-      case ImageExtractionMode_Preview:
-        ok = DicomImageDecoder::PreviewDecodedImage(decoded);
-        break;
-
-      default:
-        throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-
-    if (ok)
-    {
-      assert(decoded.get() != NULL);
-      return decoded.release();
-    }
-    else
-    {
-      throw OrthancException(ErrorCode_NotImplemented);
-    }
-  }
-
-
-  void ParsedDicomFile::ExtractPngImage(std::string& result,
-                                        IDicomImageDecoder& decoder,
-                                        unsigned int frame,
-                                        ImageExtractionMode mode)
-  {
-    std::auto_ptr<ImageAccessor> decoded(ExtractImage(decoder, frame, mode));
-    assert(decoded.get() != NULL);
-
-    PngWriter writer;
-    writer.WriteToMemory(result, *decoded);
-  }
-
-
-  void ParsedDicomFile::ExtractJpegImage(std::string& result,
-                                         IDicomImageDecoder& decoder,
-                                         unsigned int frame,
-                                         ImageExtractionMode mode,
-                                         uint8_t quality)
-  {
-    if (mode != ImageExtractionMode_UInt8 &&
-        mode != ImageExtractionMode_Preview)
-    {
-      throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-
-    std::auto_ptr<ImageAccessor> decoded(ExtractImage(decoder, frame, mode));
-    assert(decoded.get() != NULL);
-
-    JpegWriter writer;
-    writer.SetQuality(quality);
-    writer.WriteToMemory(result, *decoded);
-  }
-
-
   Encoding ParsedDicomFile::GetEncoding() const
   {
     return FromDcmtkBridge::DetectEncoding(*pimpl_->file_->getDataset());
--- a/OrthancServer/ParsedDicomFile.h	Mon Jan 04 14:20:41 2016 +0100
+++ b/OrthancServer/ParsedDicomFile.h	Tue Jan 05 13:26:51 2016 +0100
@@ -33,9 +33,9 @@
 #pragma once
 
 #include "../Core/DicomFormat/DicomInstanceHasher.h"
+#include "../Core/Images/ImageAccessor.h"
 #include "../Core/IDynamicObject.h"
 #include "../Core/RestApi/RestApiOutput.h"
-#include "IDicomImageDecoder.h"
 #include "ServerEnumerations.h"
 
 class DcmDataset;
@@ -126,24 +126,6 @@
     void EmbedImage(const std::string& mime,
                     const std::string& content);
 
-    ImageAccessor* ExtractImage(IDicomImageDecoder& decoder,
-                                unsigned int frame);
-
-    ImageAccessor* ExtractImage(IDicomImageDecoder& decoder,
-                                unsigned int frame,
-                                ImageExtractionMode mode);
-
-    void ExtractPngImage(std::string& result,
-                         IDicomImageDecoder& decoder,
-                         unsigned int frame,
-                         ImageExtractionMode mode);
-
-    void ExtractJpegImage(std::string& result,
-                          IDicomImageDecoder& decoder,
-                          unsigned int frame,
-                          ImageExtractionMode mode,
-                          uint8_t quality);
-
     Encoding GetEncoding() const;
 
     void SetEncoding(Encoding encoding);
--- a/Plugins/Engine/OrthancPlugins.cpp	Mon Jan 04 14:20:41 2016 +0100
+++ b/Plugins/Engine/OrthancPlugins.cpp	Tue Jan 05 13:26:51 2016 +0100
@@ -58,6 +58,7 @@
 #include "../../Core/Images/JpegReader.h"
 #include "../../Core/Images/JpegWriter.h"
 #include "../../Core/Images/ImageProcessing.h"
+#include "../../OrthancServer/DefaultDicomImageDecoder.h"
 #include "PluginsEnumerations.h"
 
 #include <boost/regex.hpp> 
@@ -1309,8 +1310,7 @@
 
       case OrthancPluginImageFormat_Dicom:
       {
-        ParsedDicomFile dicom(p.data, p.size);
-        image.reset(Decode(dicom, 0));
+        image.reset(Decode(p.data, p.size, 0));
         break;
       }
 
@@ -1563,8 +1563,7 @@
 
       case _OrthancPluginService_DecodeDicomImage:
       {
-        ParsedDicomFile dicom(p.constBuffer, p.bufferSize);
-        result.reset(Decode(dicom, p.frameIndex));
+        result.reset(Decode(p.constBuffer, p.bufferSize, p.frameIndex));
         break;
       }
 
@@ -2376,18 +2375,16 @@
   }
 
 
-  ImageAccessor*  OrthancPlugins::Decode(ParsedDicomFile& dicom, 
+  ImageAccessor*  OrthancPlugins::Decode(const void* dicom,
+                                         size_t size,
                                          unsigned int frame)
   {
     {
       boost::mutex::scoped_lock lock(pimpl_->decodeImageCallbackMutex_);
       if (pimpl_->decodeImageCallback_ != NULL)
       {
-        std::string s;
-        dicom.SaveToMemoryBuffer(s);
-
         OrthancPluginImage* pluginImage = NULL;
-        if (pimpl_->decodeImageCallback_(&pluginImage, s.c_str(), s.size(), frame) == OrthancPluginErrorCode_Success &&
+        if (pimpl_->decodeImageCallback_(&pluginImage, dicom, size, frame) == OrthancPluginErrorCode_Success &&
             pluginImage != NULL)
         {
           return reinterpret_cast<ImageAccessor*>(pluginImage);
@@ -2397,7 +2394,7 @@
       }
     }
 
-    DicomImageDecoder defaultDecoder;
-    return defaultDecoder.Decode(dicom, frame);
+    DefaultDicomImageDecoder defaultDecoder;
+    return defaultDecoder.Decode(dicom, size, frame);
   }
 }
--- a/Plugins/Engine/OrthancPlugins.h	Mon Jan 04 14:20:41 2016 +0100
+++ b/Plugins/Engine/OrthancPlugins.h	Tue Jan 05 13:26:51 2016 +0100
@@ -234,7 +234,8 @@
 
     virtual IWorklistRequestHandler* ConstructWorklistRequestHandler();
 
-    virtual ImageAccessor* Decode(ParsedDicomFile& dicom, 
+    virtual ImageAccessor* Decode(const void* dicom,
+                                  size_t size,
                                   unsigned int frame);
   };
 }