changeset 1259:69177b10e2b9

various fixes for RadiographyScene: support text layers outside the dicom layer, fix background in this case + extract dicom from rendered scene
author Alain Mazy <alain@mazy.be>
date Tue, 21 Jan 2020 16:52:37 +0100
parents 9c20ae049669
children 5a2d5380148d a989c7d46b9a
files Framework/Radiography/RadiographyLayer.cpp Framework/Radiography/RadiographyLayer.h Framework/Radiography/RadiographyMaskLayer.cpp Framework/Radiography/RadiographyScene.cpp Framework/Radiography/RadiographyScene.h Framework/Radiography/RadiographySceneReader.cpp Framework/Radiography/RadiographySceneReader.h Framework/Toolbox/AffineTransform2D.cpp Framework/Toolbox/AffineTransform2D.h
diffstat 9 files changed, 106 insertions(+), 95 deletions(-) [+]
line wrap: on
line diff
--- a/Framework/Radiography/RadiographyLayer.cpp	Tue Jan 14 15:22:10 2020 +0100
+++ b/Framework/Radiography/RadiographyLayer.cpp	Tue Jan 21 16:52:37 2020 +0100
@@ -59,15 +59,15 @@
 
   void RadiographyLayer::UpdateTransform()
   {
+    // important to update transform_ before getting the center to use the right scaling !!!
     transform_ = AffineTransform2D::CreateScaling(geometry_.GetScalingX(), geometry_.GetScalingY());
 
     double centerX, centerY;
     GetCenter(centerX, centerY);
 
     transform_ = AffineTransform2D::Combine(
-          AffineTransform2D::CreateOffset(geometry_.GetPanX() + centerX, geometry_.GetPanY() + centerY),
-          AffineTransform2D::CreateRotation(geometry_.GetAngle()),
-          AffineTransform2D::CreateOffset(-centerX, -centerY),
+          AffineTransform2D::CreateOffset(geometry_.GetPanX(), geometry_.GetPanY()),
+          AffineTransform2D::CreateRotation(geometry_.GetAngle(), centerX, centerY),
           transform_);
 
     transformInverse_ = AffineTransform2D::Invert(transform_);
--- a/Framework/Radiography/RadiographyLayer.h	Tue Jan 14 15:22:10 2020 +0100
+++ b/Framework/Radiography/RadiographyLayer.h	Tue Jan 21 16:52:37 2020 +0100
@@ -217,16 +217,6 @@
     const RadiographyScene&   scene_;
 
   protected:
-    virtual const AffineTransform2D& GetTransform() const
-    {
-      return transform_;
-    }
-
-    virtual const AffineTransform2D& GetTransformInverse() const
-    {
-      return transformInverse_;
-    }
-
     void SetPreferredPhotomotricDisplayMode(RadiographyPhotometricDisplayMode  prefferedPhotometricDisplayMode);
 
   private:
@@ -254,6 +244,16 @@
     {
     }
 
+    virtual const AffineTransform2D& GetTransform() const
+    {
+      return transform_;
+    }
+
+    virtual const AffineTransform2D& GetTransformInverse() const
+    {
+      return transformInverse_;
+    }
+
     size_t GetIndex() const
     {
       return index_;
@@ -358,8 +358,6 @@
     virtual bool GetRange(float& minValue,
                           float& maxValue) const = 0;
 
-    friend class RadiographyMaskLayer; // because it needs to GetTransform on the dicomLayer it relates to
-
     virtual size_t GetApproximateMemoryUsage() const // this is used to limit the number of scenes loaded in RAM when resources are limited (we actually only count the size used by the images, not the C structs)
     {
       return 0;
--- a/Framework/Radiography/RadiographyMaskLayer.cpp	Tue Jan 14 15:22:10 2020 +0100
+++ b/Framework/Radiography/RadiographyMaskLayer.cpp	Tue Jan 21 16:52:37 2020 +0100
@@ -82,7 +82,7 @@
                                     float windowWidth,
                                     bool applyWindowing) const
   {
-    if (dicomLayer_.GetWidth() == 0) // nothing to do if the DICOM layer is not displayed (or not loaded)
+    if (dicomLayer_.GetWidth() == 0 || dicomLayer_.GetSourceImage() == NULL) // nothing to do if the DICOM layer is not displayed (or not loaded)
       return;
 
     if (invalidated_)
--- a/Framework/Radiography/RadiographyScene.cpp	Tue Jan 14 15:22:10 2020 +0100
+++ b/Framework/Radiography/RadiographyScene.cpp	Tue Jan 21 16:52:37 2020 +0100
@@ -658,6 +658,28 @@
     }
   }
 
+  void RadiographyScene::ExtractLayerFromRenderedScene(Orthanc::ImageAccessor& layer,
+                                                       const Orthanc::ImageAccessor& renderedScene,
+                                                       size_t layerIndex,
+                                                       ImageInterpolation interpolation)
+  {
+    Extent2D sceneExtent = GetSceneExtent();
+
+    double pixelSpacingX = sceneExtent.GetWidth() / renderedScene.GetWidth();
+    double pixelSpacingY = sceneExtent.GetHeight() / renderedScene.GetHeight();
+
+    AffineTransform2D view = AffineTransform2D::Combine(
+          AffineTransform2D::CreateScaling(1.0 / pixelSpacingX, 1.0 / pixelSpacingY),
+          AffineTransform2D::CreateOffset(-sceneExtent.GetX1(), -sceneExtent.GetY1()));
+
+    AffineTransform2D layerToSceneTransform = AffineTransform2D::Combine(
+          view,
+          GetLayer(layerIndex).GetTransform());
+
+    AffineTransform2D sceneToLayerTransform = AffineTransform2D::Invert(layerToSceneTransform);
+    sceneToLayerTransform.Apply(layer, renderedScene, interpolation, false);
+  }
+
   Orthanc::Image* RadiographyScene::ExportToImage(double pixelSpacingX,
                                                   double pixelSpacingY,
                                                   ImageInterpolation interpolation,
@@ -690,7 +712,14 @@
           AffineTransform2D::CreateOffset(-extent.GetX1(), -extent.GetY1()));
 
     // wipe background before rendering
-    Orthanc::ImageProcessing::Set(layers, 0);
+    if (GetPreferredPhotomotricDisplayMode() == RadiographyPhotometricDisplayMode_Monochrome1)
+    {
+      Orthanc::ImageProcessing::Set(layers, 65535.0f);
+    }
+    else
+    {
+      Orthanc::ImageProcessing::Set(layers, 0);
+    }
 
     Render(layers, view, interpolation, applyWindowing);
 
--- a/Framework/Radiography/RadiographyScene.h	Tue Jan 14 15:22:10 2020 +0100
+++ b/Framework/Radiography/RadiographyScene.h	Tue Jan 21 16:52:37 2020 +0100
@@ -36,6 +36,7 @@
       public IObserver,
       public IObservable
   {
+    friend class RadiographySceneGeometryReader;
   public:
     class GeometryChangedMessage : public OriginMessage<RadiographyScene>
     {
@@ -167,9 +168,10 @@
     float   windowingWidth_;
     Layers  layers_;
 
-  protected:
+  public:
     RadiographyLayer& RegisterLayer(RadiographyLayer* layer);
 
+  protected:
     virtual void _RegisterLayer(RadiographyLayer* layer);
 
     void SetLayerIndex(RadiographyLayer* layer, size_t index)
@@ -347,5 +349,9 @@
                                   int64_t maxValue /* for inversion */,
                                   bool applyWindowing);
 
+    void ExtractLayerFromRenderedScene(Orthanc::ImageAccessor& layer,
+                                       const Orthanc::ImageAccessor& renderedScene,
+                                       size_t layerIndex,
+                                       ImageInterpolation interpolation);
   };
 }
--- a/Framework/Radiography/RadiographySceneReader.cpp	Tue Jan 14 15:22:10 2020 +0100
+++ b/Framework/Radiography/RadiographySceneReader.cpp	Tue Jan 21 16:52:37 2020 +0100
@@ -53,6 +53,16 @@
     return dynamic_cast<RadiographyDicomLayer*>(&(scene_.LoadDicomFrame(orthancApiClient_, instanceId, frame, false, geometry)));
   }
 
+  RadiographyDicomLayer* RadiographySceneGeometryReader::LoadDicom(const std::string& instanceId, unsigned int frame, RadiographyLayer::Geometry* geometry)
+  {
+    std::auto_ptr<RadiographyPlaceholderLayer>  layer(new RadiographyPlaceholderLayer(dynamic_cast<IObservable&>(scene_).GetBroker(), scene_));
+    layer->SetGeometry(*geometry);
+    layer->SetSize(dicomImageWidth_, dicomImageHeight_);
+    scene_.RegisterLayer(layer.get());
+
+    return layer.release();
+  }
+
   void RadiographySceneBuilder::Read(const Json::Value& input)
   {
     unsigned int version = input["version"].asUInt();
@@ -130,82 +140,8 @@
     }
   }
 
-  void RadiographySceneReader::Read(const Json::Value& input)
-  {
-    unsigned int version = input["version"].asUInt();
-
-    if (version != 1)
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
-
-    if (input.isMember("hasWindowing") && input["hasWindowing"].asBool())
-    {
-      scene_.SetWindowing(input["windowCenter"].asFloat(), input["windowWidth"].asFloat());
-    }
-
-    RadiographyDicomLayer* dicomLayer = NULL;
-    for(size_t layerIndex = 0; layerIndex < input["layers"].size(); layerIndex++)
-    {
-      const Json::Value& jsonLayer = input["layers"][(int)layerIndex];
-      RadiographyLayer::Geometry geometry;
-
-      if (jsonLayer["type"].asString() == "dicom")
-      {
-        ReadLayerGeometry(geometry, jsonLayer);
-        dicomLayer = dynamic_cast<RadiographyDicomLayer*>(&(scene_.LoadDicomFrame(orthancApiClient_, jsonLayer["instanceId"].asString(), jsonLayer["frame"].asUInt(), false, &geometry)));
-      }
-      else if (jsonLayer["type"].asString() == "mask")
-      {
-        if (dicomLayer == NULL)
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); // we always assumed the dicom layer was read before the mask
-        }
-        ReadLayerGeometry(geometry, jsonLayer);
 
-        float foreground = jsonLayer["foreground"].asFloat();
-        std::vector<Orthanc::ImageProcessing::ImagePoint> corners;
-        for (size_t i = 0; i < jsonLayer["corners"].size(); i++)
-        {
-          Orthanc::ImageProcessing::ImagePoint corner(jsonLayer["corners"][(int)i]["x"].asInt(),
-              jsonLayer["corners"][(int)i]["y"].asInt());
-          corners.push_back(corner);
-        }
 
-        scene_.LoadMask(corners, *dicomLayer, foreground, &geometry);
-      }
-      else if (jsonLayer["type"].asString() == "text")
-      {
-        ReadLayerGeometry(geometry, jsonLayer);
-        scene_.LoadText(jsonLayer["text"].asString(), jsonLayer["font"].asString(), jsonLayer["fontSize"].asUInt(), static_cast<uint8_t>(jsonLayer["foreground"].asUInt()), &geometry, false);
-      }
-      else if (jsonLayer["type"].asString() == "alpha")
-      {
-        ReadLayerGeometry(geometry, jsonLayer);
-
-        const std::string& pngContentBase64 = jsonLayer["content"].asString();
-        std::string pngContent;
-        std::string mimeType;
-        Orthanc::Toolbox::DecodeDataUriScheme(mimeType, pngContent, pngContentBase64);
-
-        std::auto_ptr<Orthanc::ImageAccessor>  image;
-        if (mimeType == "image/png")
-        {
-          image.reset(new Orthanc::PngReader());
-          dynamic_cast<Orthanc::PngReader*>(image.get())->ReadFromMemory(pngContent);
-        }
-        else
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
-
-        RadiographyAlphaLayer& layer = dynamic_cast<RadiographyAlphaLayer&>(scene_.LoadAlphaBitmap(image.release(), &geometry));
-
-        if (!jsonLayer["isUsingWindowing"].asBool())
-        {
-          layer.SetForegroundValue((float)(jsonLayer["foreground"].asDouble()));
-        }
-      }
-      else
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
-    }
-  }
 
   void RadiographySceneBuilder::ReadDicomLayerGeometry(RadiographyLayer::Geometry& geometry, const Json::Value& input)
   {
--- a/Framework/Radiography/RadiographySceneReader.h	Tue Jan 14 15:22:10 2020 +0100
+++ b/Framework/Radiography/RadiographySceneReader.h	Tue Jan 21 16:52:37 2020 +0100
@@ -33,6 +33,18 @@
 
 namespace OrthancStone
 {
+  // a layer containing only the geometry of a DICOM layer (bit hacky !)
+  class RadiographyPlaceholderLayer : public RadiographyDicomLayer
+  {
+  public:
+    RadiographyPlaceholderLayer(MessageBroker& broker, const RadiographyScene& scene) :
+      RadiographyDicomLayer(broker, scene)
+    {
+    }
+
+  };
+
+
   // HACK: I had to introduce this builder class in order to be able to recreate a RadiographyScene
   // from a serialized scene that is passed to web-workers.
   // It needs some architecturing...
@@ -77,7 +89,23 @@
     {
     }
 
-    void Read(const Json::Value& input);
+  protected:
+    virtual RadiographyDicomLayer*  LoadDicom(const std::string& instanceId, unsigned int frame, RadiographyLayer::Geometry* geometry);
+  };
+
+  // reads the whole scene but the DICOM image such that we have the full geometry
+  class RadiographySceneGeometryReader : public RadiographySceneBuilder
+  {
+    unsigned int dicomImageWidth_;
+    unsigned int dicomImageHeight_;
+
+  public:
+    RadiographySceneGeometryReader(RadiographyScene& scene, unsigned int dicomImageWidth, unsigned int dicomImageHeight) :
+      RadiographySceneBuilder(scene),
+      dicomImageWidth_(dicomImageWidth),
+      dicomImageHeight_(dicomImageHeight)
+    {
+    }
 
   protected:
     virtual RadiographyDicomLayer*  LoadDicom(const std::string& instanceId, unsigned int frame, RadiographyLayer::Geometry* geometry);
--- a/Framework/Toolbox/AffineTransform2D.cpp	Tue Jan 14 15:22:10 2020 +0100
+++ b/Framework/Toolbox/AffineTransform2D.cpp	Tue Jan 21 16:52:37 2020 +0100
@@ -246,6 +246,16 @@
     return t;
   }
 
+  AffineTransform2D AffineTransform2D::CreateRotation(double angle, // CW rotation
+                                                      double cx,    // rotation center
+                                                      double cy)    // rotation center
+  {
+    return Combine(
+          CreateOffset(cx, cy),
+          CreateRotation(angle),
+          CreateOffset(-cx, -cy)
+          );
+  }
 
   AffineTransform2D AffineTransform2D::CreateOpenGLClipspace(unsigned int canvasWidth,
                                                              unsigned int canvasHeight)
--- a/Framework/Toolbox/AffineTransform2D.h	Tue Jan 14 15:22:10 2020 +0100
+++ b/Framework/Toolbox/AffineTransform2D.h	Tue Jan 21 16:52:37 2020 +0100
@@ -90,8 +90,12 @@
 
     static AffineTransform2D CreateScaling(double sx,
                                            double sy);
-    
-    static AffineTransform2D CreateRotation(double angle);
+
+    static AffineTransform2D CreateRotation(double angle); // CW rotation in radians
+
+    static AffineTransform2D CreateRotation(double angle, // CW rotation in radians
+                                            double cx,    // rotation center
+                                            double cy);   // rotation center
 
     static AffineTransform2D CreateOpenGLClipspace(unsigned int canvasWidth,
                                                    unsigned int canvasHeight);