changeset 338:b3b3fa0e3689 am-2

BitmapStack
author Sebastien Jodogne <s.jodogne@gmail.com>
date Fri, 19 Oct 2018 12:50:38 +0200
parents c4d4213f095c
children 5a7915b23138
files Applications/Samples/CMakeLists.txt Applications/Samples/SingleFrameEditorApplication.h Framework/Layers/GrayscaleFrameRenderer.cpp Framework/Toolbox/DicomFrameConverter.cpp Framework/Toolbox/DicomFrameConverter.h Framework/Toolbox/ImageGeometry.cpp Resources/CMake/OrthancStoneConfiguration.cmake Resources/CMake/OrthancStoneParameters.cmake
diffstat 8 files changed, 352 insertions(+), 53 deletions(-) [+]
line wrap: on
line diff
--- a/Applications/Samples/CMakeLists.txt	Thu Oct 18 20:07:09 2018 +0200
+++ b/Applications/Samples/CMakeLists.txt	Fri Oct 19 12:50:38 2018 +0200
@@ -8,6 +8,7 @@
 
 include(../../Resources/CMake/OrthancStoneParameters.cmake)
 
+#set(ENABLE_DCMTK ON)
 
 set(ENABLE_SDL OFF CACHE BOOL "Target SDL Native application")
 set(ENABLE_QT OFF CACHE BOOL "Target Qt Native application")
--- a/Applications/Samples/SingleFrameEditorApplication.h	Thu Oct 18 20:07:09 2018 +0200
+++ b/Applications/Samples/SingleFrameEditorApplication.h	Fri Oct 19 12:50:38 2018 +0200
@@ -25,22 +25,215 @@
 
 #include "../../Framework/Layers/OrthancFrameLayerSource.h"
 
+#include <Core/DicomFormat/DicomArray.h>
+#include <Core/Images/PamReader.h>
 #include <Core/Logging.h>
+#include <Core/Toolbox.h>
+#include <Plugins/Samples/Common/FullOrthancDataset.h>
 
 namespace OrthancStone
 {
-
-  class GrayscaleBitmapStack :
-    public WorldSceneWidget,
+  class BitmapStack :
     public IObserver,
     public IObservable
   {
   public:
-    typedef OriginMessage<MessageType_Widget_GeometryChanged, GrayscaleBitmapStack> GeometryChangedMessage;
-    typedef OriginMessage<MessageType_Widget_ContentChanged, GrayscaleBitmapStack> ContentChangedMessage;
+    typedef OriginMessage<MessageType_Widget_GeometryChanged, BitmapStack> GeometryChangedMessage;
+    typedef OriginMessage<MessageType_Widget_ContentChanged, BitmapStack> ContentChangedMessage;
 
   private:
+    class Bitmap : public boost::noncopyable
+    {
+    private:
+      std::string                            uuid_;   // TODO is this necessary?
+      std::auto_ptr<Orthanc::ImageAccessor>  source_;
+      std::auto_ptr<Orthanc::ImageAccessor>  converted_;  // Float32 or RGB24
+      std::auto_ptr<Orthanc::Image>          alpha_;  // Grayscale8 (if any)
+      std::auto_ptr<DicomFrameConverter>     converter_;
+
+      void ApplyConverter()
+      {
+        if (source_.get() != NULL &&
+            converter_.get() != NULL)
+        {
+          printf("CONVERTED!\n");
+          converted_.reset(converter_->ConvertFrame(*source_));
+        }
+      }
+      
+    public:
+      Bitmap(const std::string& uuid) :
+        uuid_(uuid)
+      {
+      }
+      
+      void SetDicomTags(const OrthancPlugins::FullOrthancDataset& dataset)
+      {
+        converter_.reset(new DicomFrameConverter);
+        converter_->ReadParameters(dataset);
+        ApplyConverter();
+      }
+
+      void SetSourceImage(Orthanc::ImageAccessor* image)   // Takes ownership
+      {
+        source_.reset(image);
+        ApplyConverter();
+      }
+
+      bool GetDefaultWindowing(float& center,
+                               float& width) const
+      {
+        if (converter_.get() != NULL &&
+            converter_->HasDefaultWindow())
+        {
+          center = static_cast<float>(converter_->GetDefaultWindowCenter());
+          width = static_cast<float>(converter_->GetDefaultWindowWidth());
+        }
+      }
+    }; 
+
+
+    typedef std::map<std::string, Bitmap*>  Bitmaps;
+        
     OrthancApiClient&    orthanc_;
+    bool                 hasWindowing_;
+    float                windowingCenter_;
+    float                windowingWidth_;
+    Bitmaps              bitmaps_;
+
+  public:
+    BitmapStack(MessageBroker& broker,
+                OrthancApiClient& orthanc) :
+      IObserver(broker),
+      IObservable(broker),
+      orthanc_(orthanc),
+      hasWindowing_(false),
+      windowingCenter_(0),  // Dummy initialization
+      windowingWidth_(0)    // Dummy initialization
+    {
+    }
+
+    
+    virtual ~BitmapStack()
+    {
+      for (Bitmaps::iterator it = bitmaps_.begin(); it != bitmaps_.end(); it++)
+      {
+        assert(it->second != NULL);
+        delete it->second;
+      }
+    }
+    
+
+    std::string LoadFrame(const std::string& instance,
+                          unsigned int frame,
+                          bool httpCompression)
+    {
+      std::string uuid;
+      
+      for (;;)
+      {
+        uuid = Orthanc::Toolbox::GenerateUuid();
+        if (bitmaps_.find(uuid) == bitmaps_.end())
+        {
+          break;
+        }
+      }
+
+      bitmaps_[uuid] = new Bitmap(uuid);
+      
+
+      {
+        IWebService::Headers headers;
+        std::string uri = "/instances/" + instance + "/tags";
+        orthanc_.GetBinaryAsync(uri, headers,
+                                new Callable<BitmapStack, OrthancApiClient::BinaryResponseReadyMessage>
+                                (*this, &BitmapStack::OnTagsReceived), NULL,
+                                new Orthanc::SingleValueObject<std::string>(uuid));
+      }
+
+      {
+        IWebService::Headers headers;
+        headers["Accept"] = "image/x-portable-arbitrarymap";
+
+        if (httpCompression)
+        {
+          headers["Accept-Encoding"] = "gzip";
+        }
+        
+        std::string uri = "/instances/" + instance + "/frames/" + boost::lexical_cast<std::string>(frame) + "/image-uint16";
+        orthanc_.GetBinaryAsync(uri, headers,
+                                new Callable<BitmapStack, OrthancApiClient::BinaryResponseReadyMessage>
+                                (*this, &BitmapStack::OnFrameReceived), NULL,
+                                new Orthanc::SingleValueObject<std::string>(uuid));
+      }
+
+      return uuid;
+    }
+
+    
+    void OnTagsReceived(const OrthancApiClient::BinaryResponseReadyMessage& message)
+    {
+      const std::string& uuid = dynamic_cast<Orthanc::SingleValueObject<std::string>*>(message.Payload)->GetValue();
+      
+      printf("JSON received: [%s] (%d bytes) for bitmap %s\n",
+             message.Uri.c_str(), message.AnswerSize, uuid.c_str());
+
+      Bitmaps::iterator bitmap = bitmaps_.find(uuid);
+      if (bitmap != bitmaps_.end())
+      {
+        assert(bitmap->second != NULL);
+        
+        OrthancPlugins::FullOrthancDataset dicom(message.Answer, message.AnswerSize);
+        bitmap->second->SetDicomTags(dicom);
+
+        float c, w;
+        if (!hasWindowing_ &&
+            bitmap->second->GetDefaultWindowing(c, w))
+        {
+          hasWindowing_ = true;
+          windowingCenter_ = c;
+          windowingWidth_ = w;
+        }
+
+        EmitMessage(GeometryChangedMessage(*this));
+      }
+    }
+    
+
+    void OnFrameReceived(const OrthancApiClient::BinaryResponseReadyMessage& message)
+    {
+      const std::string& uuid = dynamic_cast<Orthanc::SingleValueObject<std::string>*>(message.Payload)->GetValue();
+
+      printf("Frame received: [%s] (%d bytes) for bitmap %s\n", message.Uri.c_str(), message.AnswerSize, uuid.c_str());
+      
+      Bitmaps::iterator bitmap = bitmaps_.find(uuid);
+      if (bitmap != bitmaps_.end())
+      {
+        assert(bitmap->second != NULL);
+
+        std::string content;
+        if (message.AnswerSize > 0)
+        {
+          content.assign(reinterpret_cast<const char*>(message.Answer), message.AnswerSize);
+        }
+        
+        std::auto_ptr<Orthanc::PamReader> reader(new Orthanc::PamReader);
+        reader->ReadFromMemory(content);
+        bitmap->second->SetSourceImage(reader.release());
+
+        EmitMessage(ContentChangedMessage(*this));
+      }
+    }
+  };
+
+  
+  class BitmapStackWidget :
+    public WorldSceneWidget,
+    public IObservable,
+    public IObserver
+  {
+  private:
+    BitmapStack&   stack_;
 
   protected:
     virtual Extent2D GetSceneExtent()
@@ -55,27 +248,29 @@
     }
 
   public:
-    GrayscaleBitmapStack(MessageBroker& broker,
-                         OrthancApiClient& orthanc,
-                         const std::string& name) :
+    BitmapStackWidget(MessageBroker& broker,
+                      BitmapStack& stack,
+                      const std::string& name) :
       WorldSceneWidget(name),
+      IObservable(broker),
       IObserver(broker),
-      IObservable(broker),
-      orthanc_(orthanc)
+      stack_(stack)
     {
+      stack.RegisterObserverCallback(new Callable<BitmapStackWidget, BitmapStack::GeometryChangedMessage>(*this, &BitmapStackWidget::OnGeometryChanged));
+      stack.RegisterObserverCallback(new Callable<BitmapStackWidget, BitmapStack::ContentChangedMessage>(*this, &BitmapStackWidget::OnContentChanged));
     }
 
-    void LoadDicom(const std::string& dicom)
+    void OnGeometryChanged(const BitmapStack::GeometryChangedMessage& message)
     {
-      orthanc_.GetBinaryAsync("/instances/" + dicom + "/file", "application/dicom",
-                              new Callable<GrayscaleBitmapStack, OrthancApiClient::BinaryResponseReadyMessage>(*this, &GrayscaleBitmapStack::OnDicomReceived));
-    }
-  
-    void OnDicomReceived(const OrthancApiClient::BinaryResponseReadyMessage& message)
-    {
-      printf("DICOM received: [%s] (%d bytes)\n", message.Uri.c_str(), message.AnswerSize);
+      printf("Geometry has changed\n");
+      FitContent();
     }
 
+    void OnContentChanged(const BitmapStack::ContentChangedMessage& message)
+    {
+      printf("Content has changed\n");
+      NotifyContentChanged();
+    }
   };
 
   
@@ -197,13 +392,9 @@
         }
       };
 
-      void OnGeometryChanged(const GrayscaleBitmapStack::GeometryChangedMessage& message)
-      {
-        mainWidget_->FitContent();
-      }
-      
       std::auto_ptr<Interactor>        mainWidgetInteractor_;
       std::auto_ptr<OrthancApiClient>  orthancApiClient_;
+      std::auto_ptr<BitmapStack>       stack_;
       Tools                            currentTool_;
       const OrthancFrameLayerSource*   source_;
       unsigned int                     slice_;
@@ -251,15 +442,11 @@
 
         orthancApiClient_.reset(new OrthancApiClient(IObserver::broker_, context_->GetWebService()));
 
+        stack_.reset(new BitmapStack(IObserver::broker_, *orthancApiClient_));
+        stack_->LoadFrame(instance, frame, false);
+        stack_->LoadFrame(instance, frame, false);
         
-        mainWidget_ = new GrayscaleBitmapStack(broker_, *orthancApiClient_, "main-widget");
-        dynamic_cast<GrayscaleBitmapStack*>(mainWidget_)->LoadDicom(instance);
-
-        dynamic_cast<GrayscaleBitmapStack*>(mainWidget_)->RegisterObserverCallback(
-          new Callable<SingleFrameEditorApplication,
-          GrayscaleBitmapStack::GeometryChangedMessage>
-          (*this, &SingleFrameEditorApplication::OnGeometryChanged));
-
+        mainWidget_ = new BitmapStackWidget(IObserver::broker_, *stack_, "main-widget");
         mainWidget_->SetTransmitMouseOver(true);
 
         mainWidgetInteractor_.reset(new Interactor(*this));
--- a/Framework/Layers/GrayscaleFrameRenderer.cpp	Thu Oct 18 20:07:09 2018 +0200
+++ b/Framework/Layers/GrayscaleFrameRenderer.cpp	Fri Oct 19 12:50:38 2018 +0200
@@ -27,6 +27,8 @@
 {
   CairoSurface* GrayscaleFrameRenderer::GenerateDisplay(const RenderStyle& style)
   {
+    assert(frame_->GetFormat() == Orthanc::PixelFormat_Float32);
+
     std::auto_ptr<CairoSurface> result;
 
     float windowCenter, windowWidth;
@@ -126,7 +128,7 @@
       throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
     }
 
-    converter.ConvertFrame(frame_);
+    converter.ConvertFrameInplace(frame_);
     assert(frame_.get() != NULL);
 
     if (frame_->GetFormat() != Orthanc::PixelFormat_Float32)
--- a/Framework/Toolbox/DicomFrameConverter.cpp	Thu Oct 18 20:07:09 2018 +0200
+++ b/Framework/Toolbox/DicomFrameConverter.cpp	Fri Oct 19 12:50:38 2018 +0200
@@ -30,6 +30,19 @@
 
 namespace OrthancStone
 {
+  static const Orthanc::DicomTag IMAGE_TAGS[] =
+  {
+    Orthanc::DICOM_TAG_BITS_STORED,
+    Orthanc::DICOM_TAG_DOSE_GRID_SCALING,
+    Orthanc::DICOM_TAG_PHOTOMETRIC_INTERPRETATION,
+    Orthanc::DICOM_TAG_PIXEL_REPRESENTATION,
+    Orthanc::DICOM_TAG_RESCALE_INTERCEPT,
+    Orthanc::DICOM_TAG_RESCALE_SLOPE,
+    Orthanc::DICOM_TAG_WINDOW_CENTER,
+    Orthanc::DICOM_TAG_WINDOW_WIDTH
+  };
+
+  
   void DicomFrameConverter::SetDefaultParameters()
   {
     isSigned_ = true;
@@ -37,6 +50,7 @@
     hasRescale_ = false;
     rescaleIntercept_ = 0;
     rescaleSlope_ = 1;
+    hasDefaultWindow_ = false;
     defaultWindowCenter_ = 128;
     defaultWindowWidth_ = 256;
     expectedPixelFormat_ = Orthanc::PixelFormat_Grayscale16;
@@ -53,6 +67,7 @@
         c.size() > 0 && 
         w.size() > 0)
     {
+      hasDefaultWindow_ = true;
       defaultWindowCenter_ = static_cast<float>(c[0]);
       defaultWindowWidth_ = static_cast<float>(w[0]);
     }
@@ -139,8 +154,27 @@
     }
   }
 
+  
+  void DicomFrameConverter::ReadParameters(const OrthancPlugins::IDicomDataset& dicom)
+  {
+    Orthanc::DicomMap converted;
 
-  void DicomFrameConverter::ConvertFrame(std::auto_ptr<Orthanc::ImageAccessor>& source) const
+    for (size_t i = 0; i < sizeof(IMAGE_TAGS) / sizeof(Orthanc::DicomTag); i++)
+    {
+      OrthancPlugins::DicomTag tag(IMAGE_TAGS[i].GetGroup(), IMAGE_TAGS[i].GetElement());
+    
+      std::string value;
+      if (dicom.GetStringValue(value, tag))
+      {
+        converted.SetValue(IMAGE_TAGS[i], value, false);
+      }
+    }
+
+    ReadParameters(converted);
+  }
+    
+
+  void DicomFrameConverter::ConvertFrameInplace(std::auto_ptr<Orthanc::ImageAccessor>& source) const
   {
     assert(sizeof(float) == 4);
 
@@ -149,7 +183,24 @@
       throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
     }
 
-    Orthanc::PixelFormat sourceFormat = source->GetFormat();
+    if (source->GetFormat() == GetExpectedPixelFormat() &&
+        source->GetFormat() == Orthanc::PixelFormat_RGB24)
+    {
+      // No conversion has to be done, check out (*)
+      return;
+    }
+    else
+    {
+      source.reset(ConvertFrame(*source));
+    }
+  }
+
+
+  Orthanc::ImageAccessor* DicomFrameConverter::ConvertFrame(const Orthanc::ImageAccessor& source) const
+  {
+    assert(sizeof(float) == 4);
+
+    Orthanc::PixelFormat sourceFormat = source.GetFormat();
 
     if (sourceFormat != GetExpectedPixelFormat())
     {
@@ -158,27 +209,32 @@
 
     if (sourceFormat == Orthanc::PixelFormat_RGB24)
     {
-      // No conversion has to be done
-      return;
+      // This is the case of a color image. No conversion has to be done (*)
+      std::auto_ptr<Orthanc::Image> converted(new Orthanc::Image(Orthanc::PixelFormat_RGB24, 
+                                                                 source.GetWidth(), 
+                                                                 source.GetHeight(),
+                                                                 false));
+      Orthanc::ImageProcessing::Copy(*converted, source);
+      return converted.release();
     }
-
-    assert(sourceFormat == Orthanc::PixelFormat_Grayscale16 ||
-           sourceFormat == Orthanc::PixelFormat_Grayscale32 ||
-           sourceFormat == Orthanc::PixelFormat_SignedGrayscale16);
+    else
+    {
+      assert(sourceFormat == Orthanc::PixelFormat_Grayscale16 ||
+             sourceFormat == Orthanc::PixelFormat_Grayscale32 ||
+             sourceFormat == Orthanc::PixelFormat_SignedGrayscale16);
 
-    // This is the case of a grayscale frame. Convert it to Float32.
-    std::auto_ptr<Orthanc::Image> converted(new Orthanc::Image(Orthanc::PixelFormat_Float32, 
-                                                               source->GetWidth(), 
-                                                               source->GetHeight(),
-                                                               false));
-    Orthanc::ImageProcessing::Convert(*converted, *source);
+      // This is the case of a grayscale frame. Convert it to Float32.
+      std::auto_ptr<Orthanc::Image> converted(new Orthanc::Image(Orthanc::PixelFormat_Float32, 
+                                                                 source.GetWidth(), 
+                                                                 source.GetHeight(),
+                                                                 false));
+      Orthanc::ImageProcessing::Convert(*converted, source);
 
-    source.reset(NULL);  // We don't need the source frame anymore
-
-    // Correct rescale slope/intercept if need be
-    ApplyRescale(*converted, sourceFormat != Orthanc::PixelFormat_Grayscale32);
+      // Correct rescale slope/intercept if need be
+      ApplyRescale(*converted, sourceFormat != Orthanc::PixelFormat_Grayscale32);
       
-    source = converted;
+      return converted.release();
+    }
   }
 
 
--- a/Framework/Toolbox/DicomFrameConverter.h	Thu Oct 18 20:07:09 2018 +0200
+++ b/Framework/Toolbox/DicomFrameConverter.h	Fri Oct 19 12:50:38 2018 +0200
@@ -21,6 +21,7 @@
 
 #pragma once
 
+#include <Plugins/Samples/Common/IDicomDataset.h>
 #include <Core/DicomFormat/DicomMap.h>
 #include <Core/Images/ImageAccessor.h>
 
@@ -43,6 +44,7 @@
     bool    hasRescale_;
     double  rescaleIntercept_;
     double  rescaleSlope_;
+    bool    hasDefaultWindow_;
     double  defaultWindowCenter_;
     double  defaultWindowWidth_;
     
@@ -69,6 +71,13 @@
 
     void ReadParameters(const Orthanc::DicomMap& dicom);
 
+    void ReadParameters(const OrthancPlugins::IDicomDataset& dicom);
+
+    bool HasDefaultWindow() const
+    {
+      return hasDefaultWindow_;
+    }
+    
     double GetDefaultWindowCenter() const
     {
       return defaultWindowCenter_;
@@ -89,7 +98,9 @@
       return rescaleSlope_;
     }
 
-    void ConvertFrame(std::auto_ptr<Orthanc::ImageAccessor>& source) const;
+    void ConvertFrameInplace(std::auto_ptr<Orthanc::ImageAccessor>& source) const;
+
+    Orthanc::ImageAccessor* ConvertFrame(const Orthanc::ImageAccessor& source) const;
 
     void ApplyRescale(Orthanc::ImageAccessor& image,
                       bool useDouble) const;
--- a/Framework/Toolbox/ImageGeometry.cpp	Thu Oct 18 20:07:09 2018 +0200
+++ b/Framework/Toolbox/ImageGeometry.cpp	Fri Oct 19 12:50:38 2018 +0200
@@ -341,6 +341,24 @@
         }
         break;
 
+      case Orthanc::PixelFormat_Float32:
+        switch (interpolation)
+        {
+          case ImageInterpolation_Nearest:
+            ApplyAffineInternal<Orthanc::PixelFormat_Float32, 
+                                ImageInterpolation_Nearest>(target, source, a);
+            break;
+
+          case ImageInterpolation_Bilinear:
+            ApplyAffineInternal<Orthanc::PixelFormat_Float32, 
+                                ImageInterpolation_Bilinear>(target, source, a);
+            break;
+
+          default:
+            throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+        }
+        break;
+
       case Orthanc::PixelFormat_RGB24:
         switch (interpolation)
         {
@@ -539,6 +557,24 @@
         }
         break;
 
+      case Orthanc::PixelFormat_Float32:
+        switch (interpolation)
+        {
+          case ImageInterpolation_Nearest:
+            ApplyProjectiveInternal<Orthanc::PixelFormat_Float32, 
+                                    ImageInterpolation_Nearest>(target, source, a, inva);
+            break;
+
+          case ImageInterpolation_Bilinear:
+            ApplyProjectiveInternal<Orthanc::PixelFormat_Float32, 
+                                    ImageInterpolation_Bilinear>(target, source, a, inva);
+            break;
+
+          default:
+            throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+        }
+        break;
+
       case Orthanc::PixelFormat_RGB24:
         switch (interpolation)
         {
--- a/Resources/CMake/OrthancStoneConfiguration.cmake	Thu Oct 18 20:07:09 2018 +0200
+++ b/Resources/CMake/OrthancStoneConfiguration.cmake	Fri Oct 19 12:50:38 2018 +0200
@@ -22,6 +22,12 @@
 ## Configure the Orthanc Framework
 #####################################################################
 
+if (ENABLE_DCMTK)
+  set(ENABLE_LOCALE ON)
+else()
+  set(ENABLE_LOCALE OFF)  # Disable support for locales (notably in Boost)
+endif()
+
 include(${ORTHANC_ROOT}/Resources/CMake/OrthancFrameworkConfiguration.cmake)
 include_directories(${ORTHANC_ROOT})
 
--- a/Resources/CMake/OrthancStoneParameters.cmake	Thu Oct 18 20:07:09 2018 +0200
+++ b/Resources/CMake/OrthancStoneParameters.cmake	Fri Oct 19 12:50:38 2018 +0200
@@ -25,7 +25,7 @@
 include(${CMAKE_CURRENT_LIST_DIR}/../../Resources/Orthanc/DownloadOrthancFramework.cmake)
 include(${ORTHANC_ROOT}/Resources/CMake/OrthancFrameworkParameters.cmake)
 
-set(ENABLE_LOCALE OFF)         # Disable support for locales (notably in Boost)
+set(ENABLE_DCMTK OFF)
 set(ENABLE_GOOGLE_TEST ON)
 set(ENABLE_SQLITE OFF)
 set(ENABLE_JPEG ON)