changeset 645:1e9ed656318e

Merge + ongoing measure work
author Benjamin Golinvaux <bgo@osimis.io>
date Mon, 13 May 2019 15:12:56 +0200
parents f939f449482c (current diff) 7ca8dc7ec17b (diff)
children b4fe9642e83b
files .hgignore Samples/Common/AngleMeasureTool.cpp Samples/Common/AngleMeasureTool.h Samples/Common/CreateAngleMeasureTracker.cpp Samples/Common/CreateAngleMeasureTracker.h Samples/Common/CreateLineMeasureTracker.cpp Samples/Common/CreateLineMeasureTracker.h Samples/Common/LineMeasureTool.cpp Samples/Common/LineMeasureTool.h Samples/Common/MeasureCommands.cpp Samples/Common/MeasureCommands.h Samples/Common/MeasureTools.cpp Samples/Common/MeasureTools.h Samples/Common/MeasureToolsToolbox.cpp Samples/Common/MeasureToolsToolbox.h Samples/Common/MeasureTrackers.cpp Samples/Common/MeasureTrackers.h Samples/Sdl/CMakeLists.txt Samples/Sdl/Loader.cpp Samples/Sdl/TrackerSample.cpp
diffstat 19 files changed, 1160 insertions(+), 338 deletions(-) [+]
line wrap: on
line diff
--- a/.hgignore	Fri May 10 16:15:55 2019 +0200
+++ b/.hgignore	Mon May 13 15:12:56 2019 +0200
@@ -30,3 +30,5 @@
 Resources/CommandTool/protoc-tests/generated_ts/
 Resources/CommandTool/protoc-tests/node_modules/
 Samples/Sdl/ThirdPartyDownloads/
+Samples/Sdl/CMakeLists.txt.orig
+
--- a/Framework/StoneEnumerations.h	Fri May 10 16:15:55 2019 +0200
+++ b/Framework/StoneEnumerations.h	Mon May 13 15:12:56 2019 +0200
@@ -172,6 +172,13 @@
     MessageType_Test1,
     MessageType_Test2,
 
+
+
+    MessageType_OrthancRestApiCommand,
+    MessageType_GetOrthancImageCommand,
+    MessageType_GetOrthancWebViewerJpegCommand,
+    MessageType_OracleCommandExceptionMessage,
+    
     MessageType_CustomMessage // Custom messages ids ust be greater than this (this one must remain in last position)
   };
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Samples/Common/AngleMeasureTool.cpp	Mon May 13 15:12:56 2019 +0200
@@ -0,0 +1,199 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * 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
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#include "AngleMeasureTool.h"
+#include "MeasureToolsToolbox.h"
+
+#include <Core/Logging.h>
+
+#include <boost/math/constants/constants.hpp>
+
+namespace OrthancStone
+{
+  AngleMeasureTool::~AngleMeasureTool()
+  {
+    // this measuring tool is a RABI for the corresponding visual layers
+    // stored in the 2D scene
+    Disable();
+    RemoveFromScene();
+  }
+
+  void AngleMeasureTool::RemoveFromScene()
+  {
+    if (layersCreated)
+    {
+      assert(GetScene().HasLayer(polylineZIndex_));
+      assert(GetScene().HasLayer(textZIndex_));
+      GetScene().DeleteLayer(polylineZIndex_);
+      GetScene().DeleteLayer(textZIndex_);
+    }
+  }
+
+  void AngleMeasureTool::SetSide1End(ScenePoint2D pt)
+  {
+    side1End_ = pt;
+    RefreshScene();
+  }
+
+  void AngleMeasureTool::SetSide2End(ScenePoint2D pt)
+  {
+    side2End_ = pt;
+    RefreshScene();
+  }
+
+  void AngleMeasureTool::SetCenter(ScenePoint2D pt)
+  {
+    center_ = pt;
+    RefreshScene();
+  }
+  
+  PolylineSceneLayer* AngleMeasureTool::GetPolylineLayer()
+  {
+    assert(GetScene().HasLayer(polylineZIndex_));
+    ISceneLayer* layer = &(GetScene().GetLayer(polylineZIndex_));
+    PolylineSceneLayer* concreteLayer = dynamic_cast<PolylineSceneLayer*>(layer);
+    assert(concreteLayer != NULL);
+    return concreteLayer;
+  }
+
+  TextSceneLayer* AngleMeasureTool::GetTextLayer()
+  {
+    assert(GetScene().HasLayer(textZIndex_));
+    ISceneLayer* layer = &(GetScene().GetLayer(textZIndex_));
+    TextSceneLayer* concreteLayer = dynamic_cast<TextSceneLayer*>(layer);
+    assert(concreteLayer != NULL);
+    return concreteLayer;
+  }
+
+
+  void AngleMeasureTool::RefreshScene()
+  {
+    if (IsEnabled())
+    {
+
+      // get the scaling factor 
+      const double pixelToScene =
+        GetScene().GetCanvasToSceneTransform().ComputeZoom();
+
+      if (!layersCreated)
+      {
+        // Create the layers if need be
+
+        assert(textZIndex_ == -1);
+        {
+          polylineZIndex_ = GetScene().GetMaxDepth() + 100;
+          //LOG(INFO) << "set polylineZIndex_ to: " << polylineZIndex_;
+          std::auto_ptr<PolylineSceneLayer> layer(new PolylineSceneLayer());
+          GetScene().SetLayer(polylineZIndex_, layer.release());
+        }
+        {
+          textZIndex_ = GetScene().GetMaxDepth() + 100;
+          //LOG(INFO) << "set textZIndex_ to: " << textZIndex_;
+          std::auto_ptr<TextSceneLayer> layer(new TextSceneLayer());
+          GetScene().SetLayer(textZIndex_, layer.release());
+        }
+        layersCreated = true;
+      }
+      else
+      {
+        assert(GetScene().HasLayer(polylineZIndex_));
+        assert(GetScene().HasLayer(textZIndex_));
+      }
+      {
+        // Fill the polyline layer with the measurement line
+
+        PolylineSceneLayer* polylineLayer = GetPolylineLayer();
+        polylineLayer->ClearAllChains();
+        polylineLayer->SetColor(0, 223, 21);
+
+        // sides
+        {
+          {
+            PolylineSceneLayer::Chain chain;
+            chain.push_back(side1End_);
+            chain.push_back(center_);
+            polylineLayer->AddChain(chain, false);
+          }
+          {
+            PolylineSceneLayer::Chain chain;
+            chain.push_back(side2End_);
+            chain.push_back(center_);
+            polylineLayer->AddChain(chain, false);
+          }
+        }
+
+        // handles
+        {
+          //void AddSquare(PolylineSceneLayer::Chain& chain,const Scene2D& scene,const ScenePoint2D& centerS,const double& sideLength)
+
+          {
+            PolylineSceneLayer::Chain chain;
+            AddSquare(chain, GetScene(), side1End_, 10.0); //TODO: take DPI into account
+            polylineLayer->AddChain(chain, true);
+          }
+
+          {
+            PolylineSceneLayer::Chain chain;
+            AddSquare(chain, GetScene(), side2End_, 10.0); //TODO: take DPI into account
+            polylineLayer->AddChain(chain, true);
+          }
+        }
+
+        // arc
+        {
+          PolylineSceneLayer::Chain chain;
+
+          AddArc(chain, GetScene(), side1End_, center_, side2End_, 
+            20.0*pixelToScene, true); //TODO: true means always clockwise
+          polylineLayer->AddChain(chain, true);
+        }
+      }
+      {
+        // Set the text layer proporeties
+
+        // the angle is measured in a clockwise way between the points
+        double angleRad = MeasureAngle(side1End_, center_, side2End_);
+        double angleDeg = RadiansToDegrees(angleRad);
+               
+        TextSceneLayer* textLayer = GetTextLayer();
+
+        char buf[64];
+        sprintf(buf, "%0.02f deg", angleDeg);
+        textLayer->SetText(buf);
+        textLayer->SetColor(0, 223, 21);
+
+        ScenePoint2D textAnchor;
+        GetPositionOnBisectingLine(
+          textAnchor, side1End_, center_, side2End_, 40.0*pixelToScene);
+        textLayer->SetPosition(textAnchor.GetX(), textAnchor.GetY());
+      }
+    }
+    else
+    {
+      if (layersCreated)
+      {
+        RemoveFromScene();
+        layersCreated = false;
+      }
+    }
+  }
+
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Samples/Common/AngleMeasureTool.h	Mon May 13 15:12:56 2019 +0200
@@ -0,0 +1,74 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * 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
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#pragma once
+
+#include "MeasureTools.h"
+
+#include <Framework/Scene2D/Scene2D.h>
+#include <Framework/Scene2D/ScenePoint2D.h>
+#include <Framework/Scene2D/PolylineSceneLayer.h>
+#include <Framework/Scene2D/TextSceneLayer.h>
+
+#include <boost/shared_ptr.hpp>
+#include <boost/weak_ptr.hpp>
+
+#include <vector>
+#include <cmath>
+
+namespace OrthancStone
+{
+  class AngleMeasureTool : public MeasureTool
+  {
+  public:
+    AngleMeasureTool(Scene2D& scene)
+      : MeasureTool(scene)
+      , layersCreated(false)
+      , polylineZIndex_(-1)
+      , textZIndex_(-1)
+    {
+
+    }
+
+    ~AngleMeasureTool();
+
+    void SetSide1End(ScenePoint2D start);
+    void SetCenter(ScenePoint2D start);
+    void SetSide2End(ScenePoint2D start);
+
+  private:
+    PolylineSceneLayer* GetPolylineLayer();
+    TextSceneLayer*     GetTextLayer();
+    virtual void        RefreshScene() ORTHANC_OVERRIDE;
+    void                RemoveFromScene();
+
+  private:
+    ScenePoint2D side1End_;
+    ScenePoint2D side2End_;
+    ScenePoint2D center_;
+    bool         layersCreated;
+    int          polylineZIndex_;
+    int          textZIndex_;
+  };
+
+  typedef boost::shared_ptr<AngleMeasureTool> AngleMeasureToolPtr;
+}
+
+
--- a/Samples/Common/CreateAngleMeasureTracker.cpp	Fri May 10 16:15:55 2019 +0200
+++ b/Samples/Common/CreateAngleMeasureTracker.cpp	Mon May 13 15:12:56 2019 +0200
@@ -18,6 +18,109 @@
  * along with this program. If not, see <http://www.gnu.org/licenses/>.
  **/
 
+#include "CreateAngleMeasureTracker.h"
+#include <Core/OrthancException.h>
+
+using namespace Orthanc;
+
 namespace OrthancStone
 {
+  CreateAngleMeasureTracker::CreateAngleMeasureTracker(
+    Scene2D&                        scene,
+    std::vector<TrackerCommandPtr>& undoStack,
+    std::vector<MeasureToolPtr>&    measureTools,
+    const PointerEvent&             e)
+    : CreateMeasureTracker(scene, undoStack, measureTools)
+    , state_(CreatingSide1)
+  {
+    command_.reset(
+      new CreateAngleMeasureCommand(
+        scene,
+        measureTools,
+        e.GetMainPosition().Apply(scene.GetCanvasToSceneTransform())));
+  }
+
+  CreateAngleMeasureTracker::~CreateAngleMeasureTracker()
+  {
+  }
+
+  void CreateAngleMeasureTracker::PointerMove(const PointerEvent& event)
+  {
+    if (!active_)
+    {
+      throw OrthancException(ErrorCode_InternalError,
+        "Internal error: wrong state in CreateAngleMeasureTracker::"
+        "PointerMove: active_ == false");
+    }
+
+    ScenePoint2D scenePos = event.GetMainPosition().Apply(
+      scene_.GetCanvasToSceneTransform());
+
+    switch (state_)
+    {
+    case CreatingSide1:
+      GetCommand()->SetCenter(scenePos);
+      break;
+    case CreatingSide2:
+      GetCommand()->SetSide2End(scenePos);
+      break;
+    default:
+      throw OrthancException(ErrorCode_InternalError,
+        "Wrong state in CreateAngleMeasureTracker::"
+        "PointerMove: state_ invalid");
+    }
+    //LOG(TRACE) << "scenePos.GetX() = " << scenePos.GetX() << "     " <<
+    //  "scenePos.GetY() = " << scenePos.GetY();
+  }
+
+  void CreateAngleMeasureTracker::PointerUp(const PointerEvent& e)
+  {
+    // TODO: the current app does not prevent multiple PointerDown AND
+    // PointerUp to be sent to the tracker.
+    // Unless we augment the PointerEvent structure with the button index, 
+    // we cannot really tell if this pointer up event matches the initial
+    // pointer down event. Let's make it simple for now.
+
+    switch (state_)
+    {
+    case CreatingSide1:
+      state_ = CreatingSide2;
+      break;
+    case CreatingSide2:
+      throw OrthancException(ErrorCode_InternalError,
+        "Wrong state in CreateAngleMeasureTracker::"
+        "PointerUp: state_ == CreatingSide2 ; this should not happen");
+      break;
+    default:
+      throw OrthancException(ErrorCode_InternalError,
+        "Wrong state in CreateAngleMeasureTracker::"
+        "PointerMove: state_ invalid");
+    }
+  }
+
+  void CreateAngleMeasureTracker::PointerDown(const PointerEvent& e)
+  {
+    switch (state_)
+    {
+    case CreatingSide1:
+      throw OrthancException(ErrorCode_InternalError,
+        "Wrong state in CreateAngleMeasureTracker::"
+        "PointerDown: state_ == CreatingSide1 ; this should not happen");
+      break;
+    case CreatingSide2:
+      // we are done
+      active_ = false;
+      break;
+    default:
+      throw OrthancException(ErrorCode_InternalError,
+        "Wrong state in CreateAngleMeasureTracker::"
+        "PointerMove: state_ invalid");
+    }
+  }
+
+  CreateAngleMeasureCommandPtr CreateAngleMeasureTracker::GetCommand()
+  {
+    return boost::dynamic_pointer_cast<CreateAngleMeasureCommand>(command_);
+  }
+
 }
--- a/Samples/Common/CreateAngleMeasureTracker.h	Fri May 10 16:15:55 2019 +0200
+++ b/Samples/Common/CreateAngleMeasureTracker.h	Mon May 13 15:12:56 2019 +0200
@@ -20,6 +20,45 @@
 
 #pragma once
 
+#include "MeasureTrackers.h"
+#include "MeasureCommands.h"
+
+#include <vector>
+
 namespace OrthancStone
 {
+  class CreateAngleMeasureTracker : public CreateMeasureTracker
+  {
+  public:
+    /**
+    When you create this tracker, you need to supply it with the undo stack
+    where it will store the commands that perform the actual measure tool
+    creation and modification.
+    In turn, a container for these commands to store the actual measuring
+    must be supplied, too
+    */
+    CreateAngleMeasureTracker(
+      Scene2D&                        scene,
+      std::vector<TrackerCommandPtr>& undoStack,
+      std::vector<MeasureToolPtr>&    measureTools,
+      const PointerEvent&             e);
+
+    ~CreateAngleMeasureTracker();
+
+    virtual void PointerMove(const PointerEvent& e) ORTHANC_OVERRIDE;
+    virtual void PointerUp(const PointerEvent& e) ORTHANC_OVERRIDE;
+    virtual void PointerDown(const PointerEvent& e) ORTHANC_OVERRIDE;
+
+  private:
+    CreateAngleMeasureCommandPtr GetCommand();
+
+    enum State
+    {
+      CreatingSide1,
+      CreatingSide2,
+      Finished // just for debug
+    };
+    State state_;
+
+  };
 }
--- a/Samples/Common/CreateLineMeasureTracker.cpp	Fri May 10 16:15:55 2019 +0200
+++ b/Samples/Common/CreateLineMeasureTracker.cpp	Mon May 13 15:12:56 2019 +0200
@@ -62,7 +62,7 @@
     CreateLineMeasureTracker* concreteThis =
       dynamic_cast<CreateLineMeasureTracker*>(this);
     assert(concreteThis != NULL);
-    command_->Update(scenePos);
+    GetCommand()->SetEnd(scenePos);
   }
 
   void CreateLineMeasureTracker::PointerUp(const PointerEvent& e)
@@ -80,4 +80,10 @@
     LOG(WARNING) << "Additional touches (fingers, pen, mouse buttons...) "
       "are ignored when the line measure creation tracker is active";
   }
+
+  CreateLineMeasureCommandPtr CreateLineMeasureTracker::GetCommand()
+  {
+    return boost::dynamic_pointer_cast<CreateLineMeasureCommand>(command_);
+  }
+
 }
--- a/Samples/Common/CreateLineMeasureTracker.h	Fri May 10 16:15:55 2019 +0200
+++ b/Samples/Common/CreateLineMeasureTracker.h	Mon May 13 15:12:56 2019 +0200
@@ -45,5 +45,8 @@
     virtual void PointerMove(const PointerEvent& e) ORTHANC_OVERRIDE;
     virtual void PointerUp(const PointerEvent& e) ORTHANC_OVERRIDE;
     virtual void PointerDown(const PointerEvent& e) ORTHANC_OVERRIDE;
+
+  private:
+    CreateLineMeasureCommandPtr GetCommand();
   };
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Samples/Common/LineMeasureTool.cpp	Mon May 13 15:12:56 2019 +0200
@@ -0,0 +1,200 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * 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
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#include "LineMeasureTool.h"
+#include "MeasureToolsToolbox.h"
+
+#include <Core/Logging.h>
+
+
+namespace OrthancStone
+{
+  LineMeasureTool::~LineMeasureTool()
+  {
+    // this measuring tool is a RABI for the corresponding visual layers
+    // stored in the 2D scene
+    Disable();
+    RemoveFromScene();
+  }
+
+  void LineMeasureTool::RemoveFromScene()
+  {
+    if (layersCreated)
+    {
+      assert(GetScene().HasLayer(polylineZIndex_));
+      assert(GetScene().HasLayer(textZIndex_));
+      GetScene().DeleteLayer(polylineZIndex_);
+      GetScene().DeleteLayer(textZIndex_);
+    }
+  }
+
+
+  void LineMeasureTool::SetStart(ScenePoint2D start)
+  {
+    start_ = start;
+    RefreshScene();
+  }
+
+  void LineMeasureTool::SetEnd(ScenePoint2D end)
+  {
+    end_ = end;
+    RefreshScene();
+  }
+
+  void LineMeasureTool::Set(ScenePoint2D start, ScenePoint2D end)
+  {
+    start_ = start;
+    end_ = end;
+    RefreshScene();
+  }
+
+  PolylineSceneLayer* LineMeasureTool::GetPolylineLayer()
+  {
+    assert(GetScene().HasLayer(polylineZIndex_));
+    ISceneLayer* layer = &(GetScene().GetLayer(polylineZIndex_));
+    PolylineSceneLayer* concreteLayer = dynamic_cast<PolylineSceneLayer*>(layer);
+    assert(concreteLayer != NULL);
+    return concreteLayer;
+  }
+
+  TextSceneLayer* LineMeasureTool::GetTextLayer()
+  {
+    assert(GetScene().HasLayer(textZIndex_));
+    ISceneLayer* layer = &(GetScene().GetLayer(textZIndex_));
+    TextSceneLayer* concreteLayer = dynamic_cast<TextSceneLayer*>(layer);
+    assert(concreteLayer != NULL);
+    return concreteLayer;
+  }
+
+  void LineMeasureTool::RefreshScene()
+  {
+    if (IsEnabled())
+    {
+      if (!layersCreated)
+      {
+        // Create the layers if need be
+
+        assert(textZIndex_ == -1);
+        {
+          polylineZIndex_ = GetScene().GetMaxDepth() + 100;
+          //LOG(INFO) << "set polylineZIndex_ to: " << polylineZIndex_;
+          std::auto_ptr<PolylineSceneLayer> layer(new PolylineSceneLayer());
+          GetScene().SetLayer(polylineZIndex_, layer.release());
+        }
+        {
+          textZIndex_ = GetScene().GetMaxDepth() + 100;
+          //LOG(INFO) << "set textZIndex_ to: " << textZIndex_;
+          std::auto_ptr<TextSceneLayer> layer(new TextSceneLayer());
+          GetScene().SetLayer(textZIndex_, layer.release());
+        }
+        layersCreated = true;
+      }
+      else
+      {
+        assert(GetScene().HasLayer(polylineZIndex_));
+        assert(GetScene().HasLayer(textZIndex_));
+      }
+      {
+        // Fill the polyline layer with the measurement line
+
+        PolylineSceneLayer* polylineLayer = GetPolylineLayer();
+        polylineLayer->ClearAllChains();
+        polylineLayer->SetColor(0, 223, 21);
+
+        {
+          PolylineSceneLayer::Chain chain;
+          chain.push_back(start_);
+          chain.push_back(end_);
+          polylineLayer->AddChain(chain, false);
+        }
+
+        // handles
+        {
+          //void AddSquare(PolylineSceneLayer::Chain& chain,const Scene2D& scene,const ScenePoint2D& centerS,const double& sideLength)
+
+          {
+            PolylineSceneLayer::Chain chain;
+            AddSquare(chain, GetScene(), start_, 10.0); //TODO: take DPI into account
+            polylineLayer->AddChain(chain, true);
+          }
+
+          {
+            PolylineSceneLayer::Chain chain;
+            AddSquare(chain, GetScene(), end_, 10.0); //TODO: take DPI into account
+            polylineLayer->AddChain(chain, true);
+          }
+
+          //ScenePoint2D startC = start_.Apply(GetScene().GetSceneToCanvasTransform());
+          //double squareSize = 10.0; 
+          //double startHandleLX = startC.GetX() - squareSize/2;
+          //double startHandleTY = startC.GetY() - squareSize / 2;
+          //double startHandleRX = startC.GetX() + squareSize / 2;
+          //double startHandleBY = startC.GetY() + squareSize / 2;
+          //ScenePoint2D startLTC(startHandleLX, startHandleTY);
+          //ScenePoint2D startRTC(startHandleRX, startHandleTY);
+          //ScenePoint2D startRBC(startHandleRX, startHandleBY);
+          //ScenePoint2D startLBC(startHandleLX, startHandleBY);
+
+          //ScenePoint2D startLT = startLTC.Apply(GetScene().GetCanvasToSceneTransform());
+          //ScenePoint2D startRT = startRTC.Apply(GetScene().GetCanvasToSceneTransform());
+          //ScenePoint2D startRB = startRBC.Apply(GetScene().GetCanvasToSceneTransform());
+          //ScenePoint2D startLB = startLBC.Apply(GetScene().GetCanvasToSceneTransform());
+
+          //PolylineSceneLayer::Chain chain;
+          //chain.push_back(startLT);
+          //chain.push_back(startRT);
+          //chain.push_back(startRB);
+          //chain.push_back(startLB);
+          //polylineLayer->AddChain(chain, true);
+        }
+
+      }
+      {
+        // Set the text layer proporeties
+
+        TextSceneLayer* textLayer = GetTextLayer();
+        double deltaX = end_.GetX() - start_.GetX();
+        double deltaY = end_.GetY() - start_.GetY();
+        double squareDist = deltaX * deltaX + deltaY * deltaY;
+        double dist = sqrt(squareDist);
+        char buf[64];
+        sprintf(buf, "%0.02f units", dist);
+        textLayer->SetText(buf);
+        textLayer->SetColor(0, 223, 21);
+
+        // TODO: for now we simply position the text overlay at the middle
+        // of the measuring segment
+        double midX = 0.5*(end_.GetX() + start_.GetX());
+        double midY = 0.5*(end_.GetY() + start_.GetY());
+        textLayer->SetPosition(midX, midY);
+      }
+    }
+    else
+    {
+      if (layersCreated)
+      {
+        RemoveFromScene();
+        layersCreated = false;
+      }
+    }
+  }
+
+
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Samples/Common/LineMeasureTool.h	Mon May 13 15:12:56 2019 +0200
@@ -0,0 +1,72 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * 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
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#pragma once
+
+#include "MeasureTools.h"
+
+#include <Framework/Scene2D/Scene2D.h>
+#include <Framework/Scene2D/ScenePoint2D.h>
+#include <Framework/Scene2D/PolylineSceneLayer.h>
+#include <Framework/Scene2D/TextSceneLayer.h>
+
+#include <boost/shared_ptr.hpp>
+#include <boost/weak_ptr.hpp>
+
+#include <vector>
+#include <cmath>
+
+namespace OrthancStone
+{
+  class LineMeasureTool : public MeasureTool
+  {
+  public:
+    LineMeasureTool(Scene2D& scene)
+      : MeasureTool(scene)
+      , layersCreated(false)
+      , polylineZIndex_(-1)
+      , textZIndex_(-1)
+    {
+
+    }
+
+    ~LineMeasureTool();
+
+    void SetStart(ScenePoint2D start);
+    void SetEnd(ScenePoint2D end);
+    void Set(ScenePoint2D start, ScenePoint2D end);
+
+  private:
+    PolylineSceneLayer* GetPolylineLayer();
+    TextSceneLayer*     GetTextLayer();
+    virtual void        RefreshScene() ORTHANC_OVERRIDE;
+    void                RemoveFromScene();
+
+  private:
+    ScenePoint2D start_;
+    ScenePoint2D end_;
+    bool         layersCreated;
+    int          polylineZIndex_;
+    int          textZIndex_;
+  };
+
+  typedef boost::shared_ptr<LineMeasureTool> LineMeasureToolPtr;
+}
+
--- a/Samples/Common/MeasureCommands.cpp	Fri May 10 16:15:55 2019 +0200
+++ b/Samples/Common/MeasureCommands.cpp	Mon May 13 15:12:56 2019 +0200
@@ -52,15 +52,36 @@
     : CreateMeasureCommand(scene, measureTools)
     , measureTool_(new LineMeasureTool(scene))
   {
-    measureTool_ = LineMeasureToolPtr(new LineMeasureTool(scene));
     measureTools_.push_back(measureTool_);
     measureTool_->Set(point, point);
   }
 
-  void CreateLineMeasureCommand::Update(ScenePoint2D scenePos)
+  void CreateLineMeasureCommand::SetEnd(ScenePoint2D scenePos)
   {
     measureTool_->SetEnd(scenePos);
   }
 
+  CreateAngleMeasureCommand::CreateAngleMeasureCommand(
+    Scene2D& scene, MeasureToolList& measureTools, ScenePoint2D point)
+    : CreateMeasureCommand(scene, measureTools)
+    , measureTool_(new AngleMeasureTool(scene))
+  {
+    measureTools_.push_back(measureTool_);
+    measureTool_->SetSide1End(point);
+    measureTool_->SetCenter(point);
+    measureTool_->SetSide2End(point);
+  }
+
+  /** This method sets center*/
+  void CreateAngleMeasureCommand::SetCenter(ScenePoint2D scenePos)
+  {
+    measureTool_->SetCenter(scenePos);
+  }
+
+  /** This method sets end of side 2*/
+  void CreateAngleMeasureCommand::SetSide2End(ScenePoint2D scenePos)
+  {
+    measureTool_->SetSide2End(scenePos);
+  }
 
 }
--- a/Samples/Common/MeasureCommands.h	Fri May 10 16:15:55 2019 +0200
+++ b/Samples/Common/MeasureCommands.h	Mon May 13 15:12:56 2019 +0200
@@ -24,9 +24,16 @@
 
 // to be moved into Stone
 #include "MeasureTools.h"
+#include "LineMeasureTool.h"
+#include "AngleMeasureTool.h"
 
 namespace OrthancStone
 {
+  //class LineMeasureTool;
+  //typedef boost::shared_ptr<LineMeasureTool> LineMeasureToolPtr;
+  //class AngleMeasureTool;
+  //typedef boost::shared_ptr<AngleMeasureTool> AngleMeasureToolPtr;
+   
   class TrackerCommand
   {
   public:
@@ -36,8 +43,6 @@
     }
     virtual void Undo() = 0;
     virtual void Redo() = 0;
-    virtual void Update(ScenePoint2D scenePos) = 0;
-
     Scene2D& GetScene()
     {
       return scene_;
@@ -74,7 +79,8 @@
     CreateLineMeasureCommand(
       Scene2D& scene, MeasureToolList& measureTools, ScenePoint2D point);
     
-    void Update(ScenePoint2D scenePos) ORTHANC_OVERRIDE;
+    // the starting position is set in the ctor
+    void SetEnd(ScenePoint2D scenePos);
 
   private:
     virtual MeasureToolPtr GetMeasureTool() ORTHANC_OVERRIDE
@@ -82,10 +88,31 @@
       return measureTool_;
     }
     LineMeasureToolPtr measureTool_;
-    ScenePoint2D       start_;
-    ScenePoint2D       end_;
   };
 
   typedef boost::shared_ptr<CreateLineMeasureCommand> CreateLineMeasureCommandPtr;
+
+  class CreateAngleMeasureCommand : public CreateMeasureCommand
+  {
+  public:
+    /** Ctor sets end of side 1*/
+    CreateAngleMeasureCommand(
+      Scene2D& scene, MeasureToolList& measureTools, ScenePoint2D point);
+
+    /** This method sets center*/
+    void SetCenter(ScenePoint2D scenePos);
+
+    /** This method sets end of side 2*/
+    void SetSide2End(ScenePoint2D scenePos);
+
+  private:
+    virtual MeasureToolPtr GetMeasureTool() ORTHANC_OVERRIDE
+    {
+      return measureTool_;
+    }
+    AngleMeasureToolPtr measureTool_;
+  };
+
+  typedef boost::shared_ptr<CreateAngleMeasureCommand> CreateAngleMeasureCommandPtr;
 }
 
--- a/Samples/Common/MeasureTools.cpp	Fri May 10 16:15:55 2019 +0200
+++ b/Samples/Common/MeasureTools.cpp	Mon May 13 15:12:56 2019 +0200
@@ -18,8 +18,10 @@
  * along with this program. If not, see <http://www.gnu.org/licenses/>.
  **/
 
+#include "MeasureTools.h"
+
 #include <Core/Logging.h>
-#include "MeasureTools.h"
+
 #include <boost/math/constants/constants.hpp>
 
 namespace OrthancStone
@@ -35,276 +37,5 @@
     enabled_ = false;
     RefreshScene();
   }
-
-  LineMeasureTool::~LineMeasureTool()
-  {
-    // this measuring tool is a RABI for the corresponding visual layers
-    // stored in the 2D scene
-    Disable();
-    RemoveFromScene();
-  }
-
-  void LineMeasureTool::RemoveFromScene()
-  {
-    if (layersCreated)
-    {
-      assert(GetScene().HasLayer(polylineZIndex_));
-      assert(GetScene().HasLayer(textZIndex_));
-      GetScene().DeleteLayer(polylineZIndex_);
-      GetScene().DeleteLayer(textZIndex_);
-    }
-  }
-
-
-  void LineMeasureTool::SetStart(ScenePoint2D start)
-  {
-    start_ = start;
-    RefreshScene();
-  }
-
-  void LineMeasureTool::SetEnd(ScenePoint2D end)
-  {
-    end_ = end;
-    RefreshScene();
-  }
-
-  void LineMeasureTool::Set(ScenePoint2D start, ScenePoint2D end)
-  {
-    start_ = start;
-    end_ = end;
-    RefreshScene();
-  }
-
-  PolylineSceneLayer* LineMeasureTool::GetPolylineLayer()
-  {
-    assert(GetScene().HasLayer(polylineZIndex_));
-    ISceneLayer* layer = &(GetScene().GetLayer(polylineZIndex_));
-    PolylineSceneLayer* concreteLayer = dynamic_cast<PolylineSceneLayer*>(layer);
-    assert(concreteLayer != NULL);
-    return concreteLayer;
-  }
-
-  TextSceneLayer* LineMeasureTool::GetTextLayer()
-  {
-    assert(GetScene().HasLayer(textZIndex_));
-    ISceneLayer* layer = &(GetScene().GetLayer(textZIndex_));
-    TextSceneLayer* concreteLayer = dynamic_cast<TextSceneLayer*>(layer);
-    assert(concreteLayer != NULL);
-    return concreteLayer;
-  }
-
-  namespace
-  {
-    /**
-    This function will create a square around the center point supplied in
-    scene coordinates, with a side length given in canvas coordinates. The
-    square sides are parallel to the canvas boundaries.
-    */
-    void AddSquare(PolylineSceneLayer::Chain& chain,
-      const Scene2D&      scene,
-      const ScenePoint2D& centerS, 
-      const double&       sideLength)
-    {
-      chain.clear();
-      chain.reserve(4);
-      ScenePoint2D centerC = centerS.Apply(scene.GetSceneToCanvasTransform());
-      //TODO: take DPI into account
-      double handleLX = centerC.GetX() - sideLength / 2;
-      double handleTY = centerC.GetY() - sideLength / 2;
-      double handleRX = centerC.GetX() + sideLength / 2;
-      double handleBY = centerC.GetY() + sideLength / 2;
-      ScenePoint2D LTC(handleLX, handleTY);
-      ScenePoint2D RTC(handleRX, handleTY);
-      ScenePoint2D RBC(handleRX, handleBY);
-      ScenePoint2D LBC(handleLX, handleBY);
-
-      ScenePoint2D startLT = LTC.Apply(scene.GetCanvasToSceneTransform());
-      ScenePoint2D startRT = RTC.Apply(scene.GetCanvasToSceneTransform());
-      ScenePoint2D startRB = RBC.Apply(scene.GetCanvasToSceneTransform());
-      ScenePoint2D startLB = LBC.Apply(scene.GetCanvasToSceneTransform());
-
-      chain.push_back(startLT);
-      chain.push_back(startRT);
-      chain.push_back(startRB);
-      chain.push_back(startLB);
-    }
-
-    void AddCircle(PolylineSceneLayer::Chain& chain,
-      const Scene2D&      scene,
-      const ScenePoint2D& centerS,
-      const double&       radiusS)
-    {
-      chain.clear();
-      chain.reserve(4);
-      //ScenePoint2D centerC = centerS.Apply(scene.GetSceneToCanvasTransform());
-      //TODO: take DPI into account
-      
-      // TODO: automatically compute the number for segments for smooth 
-      // display based on the radius in pixels.
-      int lineCount = 50;
-      
-      double angleIncr = (2.0 * boost::math::constants::pi<double>())
-        / static_cast<double>(lineCount);
-
-      double theta = 0;
-      for (int i = 0; i < lineCount; ++i)
-      {
-        double offsetX = radiusS * cos(theta);
-        double offsetY = radiusS * sin(theta);
-        double pointX = centerS.GetX() + offsetX;
-        double pointY = centerS.GetY() + offsetY;
-        chain.push_back(ScenePoint2D(pointX, pointY));
-        theta += angleIncr;
-      }
-    }
-
-#if 0
-    void AddEllipse(PolylineSceneLayer::Chain& chain,
-      const Scene2D&      scene,
-      const ScenePoint2D& centerS,
-      const double&       halfHAxis,
-      const double&       halfVAxis)
-    {
-      chain.clear();
-      chain.reserve(4);
-      ScenePoint2D centerC = centerS.Apply(scene.GetSceneToCanvasTransform());
-      //TODO: take DPI into account
-      double handleLX = centerC.GetX() - sideLength / 2;
-      double handleTY = centerC.GetY() - sideLength / 2;
-      double handleRX = centerC.GetX() + sideLength / 2;
-      double handleBY = centerC.GetY() + sideLength / 2;
-      ScenePoint2D LTC(handleLX, handleTY);
-      ScenePoint2D RTC(handleRX, handleTY);
-      ScenePoint2D RBC(handleRX, handleBY);
-      ScenePoint2D LBC(handleLX, handleBY);
-
-      ScenePoint2D startLT = LTC.Apply(scene.GetCanvasToSceneTransform());
-      ScenePoint2D startRT = RTC.Apply(scene.GetCanvasToSceneTransform());
-      ScenePoint2D startRB = RBC.Apply(scene.GetCanvasToSceneTransform());
-      ScenePoint2D startLB = LBC.Apply(scene.GetCanvasToSceneTransform());
-
-      chain.push_back(startLT);
-      chain.push_back(startRT);
-      chain.push_back(startRB);
-      chain.push_back(startLB);
-    }
-#endif
-
-
-  }
-
-
-  void LineMeasureTool::RefreshScene()
-  {
-    if (IsEnabled())
-    {
-      if (!layersCreated)
-      {
-        // Create the layers if need be
-
-        assert(textZIndex_ == -1);
-        {
-          polylineZIndex_ = GetScene().GetMaxDepth() + 100;
-          //LOG(INFO) << "set polylineZIndex_ to: " << polylineZIndex_;
-          std::auto_ptr<PolylineSceneLayer> layer(new PolylineSceneLayer());
-          GetScene().SetLayer(polylineZIndex_, layer.release());
-        }
-        {
-          textZIndex_ = GetScene().GetMaxDepth() + 100;
-          //LOG(INFO) << "set textZIndex_ to: " << textZIndex_;
-          std::auto_ptr<TextSceneLayer> layer(new TextSceneLayer());
-          GetScene().SetLayer(textZIndex_, layer.release());
-        }
-        layersCreated = true;
-      }
-      else
-      {
-        assert(GetScene().HasLayer(polylineZIndex_));
-        assert(GetScene().HasLayer(textZIndex_));
-      }
-      {
-        // Fill the polyline layer with the measurement line
-
-        PolylineSceneLayer* polylineLayer = GetPolylineLayer();
-        polylineLayer->ClearAllChains();
-        polylineLayer->SetColor(0, 223, 21);
-
-        {
-          PolylineSceneLayer::Chain chain;
-          chain.push_back(start_);
-          chain.push_back(end_);
-          polylineLayer->AddChain(chain, false);
-        }
-
-        // handles
-        {
-          //void AddSquare(PolylineSceneLayer::Chain& chain,const Scene2D& scene,const ScenePoint2D& centerS,const double& sideLength)
-
-          {
-            PolylineSceneLayer::Chain chain;
-            AddSquare(chain, GetScene(), start_, 10.0); //TODO: take DPI into account
-            polylineLayer->AddChain(chain, true);
-          }
-
-          {
-            PolylineSceneLayer::Chain chain;
-            AddSquare(chain, GetScene(), end_, 10.0); //TODO: take DPI into account
-            polylineLayer->AddChain(chain, true);
-          }
-
-          //ScenePoint2D startC = start_.Apply(GetScene().GetSceneToCanvasTransform());
-          //double squareSize = 10.0; 
-          //double startHandleLX = startC.GetX() - squareSize/2;
-          //double startHandleTY = startC.GetY() - squareSize / 2;
-          //double startHandleRX = startC.GetX() + squareSize / 2;
-          //double startHandleBY = startC.GetY() + squareSize / 2;
-          //ScenePoint2D startLTC(startHandleLX, startHandleTY);
-          //ScenePoint2D startRTC(startHandleRX, startHandleTY);
-          //ScenePoint2D startRBC(startHandleRX, startHandleBY);
-          //ScenePoint2D startLBC(startHandleLX, startHandleBY);
-
-          //ScenePoint2D startLT = startLTC.Apply(GetScene().GetCanvasToSceneTransform());
-          //ScenePoint2D startRT = startRTC.Apply(GetScene().GetCanvasToSceneTransform());
-          //ScenePoint2D startRB = startRBC.Apply(GetScene().GetCanvasToSceneTransform());
-          //ScenePoint2D startLB = startLBC.Apply(GetScene().GetCanvasToSceneTransform());
-
-          //PolylineSceneLayer::Chain chain;
-          //chain.push_back(startLT);
-          //chain.push_back(startRT);
-          //chain.push_back(startRB);
-          //chain.push_back(startLB);
-          //polylineLayer->AddChain(chain, true);
-        }
-       
-      }
-      {
-        // Set the text layer proporeties
-
-        TextSceneLayer* textLayer = GetTextLayer();
-        double deltaX = end_.GetX() - start_.GetX();
-        double deltaY = end_.GetY() - start_.GetY();
-        double squareDist = deltaX * deltaX + deltaY * deltaY;
-        double dist = sqrt(squareDist);
-        char buf[64];
-        sprintf(buf, "%0.02f units", dist);
-        textLayer->SetText(buf);
-        textLayer->SetColor(0, 223, 21);
-
-        // TODO: for now we simply position the text overlay at the middle
-        // of the measuring segment
-        double midX = 0.5*(end_.GetX() + start_.GetX());
-        double midY = 0.5*(end_.GetY() + start_.GetY());
-        textLayer->SetPosition(midX, midY);
-      }
-    }
-    else
-    {
-      if (layersCreated)
-      {
-        RemoveFromScene();
-        layersCreated = false;
-      }
-    }
-  }
 }
 
--- a/Samples/Common/MeasureTools.h	Fri May 10 16:15:55 2019 +0200
+++ b/Samples/Common/MeasureTools.h	Mon May 13 15:12:56 2019 +0200
@@ -17,6 +17,7 @@
  * You should have received a copy of the GNU Affero General Public License
  * along with this program. If not, see <http://www.gnu.org/licenses/>.
  **/
+
 #pragma once
 
 #include <Framework/Scene2D/Scene2D.h>
@@ -86,40 +87,6 @@
   };
 
   typedef boost::shared_ptr<MeasureTool> MeasureToolPtr;
-
-  class LineMeasureTool : public MeasureTool
-  {
-  public:
-    LineMeasureTool(Scene2D& scene)
-      : MeasureTool(scene)
-      , layersCreated(false)
-      , polylineZIndex_(-1)
-      , textZIndex_(-1)
-    {
-
-    }
-
-    ~LineMeasureTool();
-
-    void SetStart(ScenePoint2D start);
-    void SetEnd(ScenePoint2D end);
-    void Set(ScenePoint2D start, ScenePoint2D end);
-
-  private:
-    PolylineSceneLayer* GetPolylineLayer();
-    TextSceneLayer*     GetTextLayer();
-    virtual void        RefreshScene() ORTHANC_OVERRIDE;
-    void                RemoveFromScene();
-
-  private:
-    ScenePoint2D start_;
-    ScenePoint2D end_;
-    bool         layersCreated;
-    int          polylineZIndex_;
-    int          textZIndex_;
-  };
-
-  typedef boost::shared_ptr<LineMeasureTool> LineMeasureToolPtr;
   typedef std::vector<MeasureToolPtr> MeasureToolList;
 }
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Samples/Common/MeasureToolsToolbox.cpp	Mon May 13 15:12:56 2019 +0200
@@ -0,0 +1,229 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * 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
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#include "MeasureToolsToolbox.h"
+
+#include <boost/math/constants/constants.hpp>
+
+namespace
+{
+  double g_pi = boost::math::constants::pi<double>();
+}
+
+namespace OrthancStone
+{
+
+  void AddSquare(PolylineSceneLayer::Chain& chain,
+    const Scene2D&      scene,
+    const ScenePoint2D& centerS,
+    const double&       sideLength)
+  {
+    chain.clear();
+    chain.reserve(4);
+    ScenePoint2D centerC = centerS.Apply(scene.GetSceneToCanvasTransform());
+    //TODO: take DPI into account
+    double handleLX = centerC.GetX() - sideLength / 2;
+    double handleTY = centerC.GetY() - sideLength / 2;
+    double handleRX = centerC.GetX() + sideLength / 2;
+    double handleBY = centerC.GetY() + sideLength / 2;
+    ScenePoint2D LTC(handleLX, handleTY);
+    ScenePoint2D RTC(handleRX, handleTY);
+    ScenePoint2D RBC(handleRX, handleBY);
+    ScenePoint2D LBC(handleLX, handleBY);
+
+    ScenePoint2D startLT = LTC.Apply(scene.GetCanvasToSceneTransform());
+    ScenePoint2D startRT = RTC.Apply(scene.GetCanvasToSceneTransform());
+    ScenePoint2D startRB = RBC.Apply(scene.GetCanvasToSceneTransform());
+    ScenePoint2D startLB = LBC.Apply(scene.GetCanvasToSceneTransform());
+
+    chain.push_back(startLT);
+    chain.push_back(startRT);
+    chain.push_back(startRB);
+    chain.push_back(startLB);
+  }
+
+  void AddArc(
+      PolylineSceneLayer::Chain& chain
+    , const Scene2D&      scene
+    , const ScenePoint2D& p1
+    , const ScenePoint2D& c
+    , const ScenePoint2D& p2
+    , const double&       radiusS
+    , const bool          clockwise
+    , const int           subdivisionsCount)
+  {
+    double p1cAngle = atan2(p1.GetY() - c.GetY(), p1.GetX() - c.GetX());
+    double p2cAngle = atan2(p2.GetY() - c.GetY(), p2.GetX() - c.GetX());
+    AddArc(
+      chain, scene, c, radiusS, p1cAngle, p2cAngle, 
+      clockwise, subdivisionsCount);
+  }
+
+  double RadiansToDegrees(double angleRad)
+  {
+    static const double factor = 180.0 / g_pi;
+    return angleRad * factor;
+  }
+
+  void GetPositionOnBisectingLine(
+    ScenePoint2D&       result
+    , const ScenePoint2D& p1
+    , const ScenePoint2D& c
+    , const ScenePoint2D& p2
+    , const double d)
+  {
+    // TODO: fix correct half-plane
+    double p1cAngle = atan2(p1.GetY() - c.GetY(), p1.GetX() - c.GetX());
+    double p2cAngle = atan2(p2.GetY() - c.GetY(), p2.GetX() - c.GetX());
+    double angle = 0.5*(p1cAngle + p2cAngle);
+    double unitVectorX = cos(angle);
+    double unitVectorY = sin(angle);
+    double posX = c.GetX() + d * unitVectorX;
+    double posY = c.GetX() + d * unitVectorY;
+    result = ScenePoint2D(posX, posY);
+  }
+   
+  void AddArc(
+      PolylineSceneLayer::Chain& chain
+    , const Scene2D&      scene
+    , const ScenePoint2D& centerS
+    , const double&       radiusS
+    , const double        startAngleRad
+    , const double        endAngleRad
+    , const bool          clockwise
+    , const int           subdivisionsCount)
+  {
+    double startAngleRadN = NormalizeAngle(startAngleRad);
+    double endAngleRadN = NormalizeAngle(endAngleRad);
+
+    double angle1Rad = std::min(startAngleRadN, endAngleRadN);
+    double angle2Rad = std::max(startAngleRadN, endAngleRadN);
+
+    // now we are sure angle1Rad < angle2Rad
+    // this means that if we draw from 1 to 2, it will be clockwise (
+    // increasing angles).
+    // let's fix this:
+    if (!clockwise)
+    {
+      angle2Rad -= 2 * g_pi;
+      // now we are sure angle2Rad < angle1Rad (since they were normalized) 
+      // and, thus, going from 1 to 2 means the angle values will DECREASE,
+      // which is the definition of anticlockwise
+    }
+
+    chain.clear();
+    chain.reserve(subdivisionsCount + 1);
+
+    double angleIncr = (angle2Rad - angle1Rad)
+      / static_cast<double>(subdivisionsCount);
+
+    double theta = angle1Rad;
+    for (int i = 0; i < subdivisionsCount + 1; ++i)
+    {
+      double offsetX = radiusS * cos(theta);
+      double offsetY = radiusS * sin(theta);
+      double pointX = centerS.GetX() + offsetX;
+      double pointY = centerS.GetY() + offsetY;
+      chain.push_back(ScenePoint2D(pointX, pointY));
+      theta += angleIncr;
+    }
+  }
+
+  void AddCircle(PolylineSceneLayer::Chain& chain,
+    const Scene2D&      scene,
+    const ScenePoint2D& centerS,
+    const double&       radiusS,
+    const int           numSubdivisions)
+  {
+    //ScenePoint2D centerC = centerS.Apply(scene.GetSceneToCanvasTransform());
+    //TODO: take DPI into account
+
+    // TODO: automatically compute the number for segments for smooth 
+    // display based on the radius in pixels.
+
+    chain.clear();
+    chain.reserve(numSubdivisions);
+
+    double angleIncr = (2.0 * g_pi)
+      / static_cast<double>(numSubdivisions);
+
+    double theta = 0;
+    for (int i = 0; i < numSubdivisions; ++i)
+    {
+      double offsetX = radiusS * cos(theta);
+      double offsetY = radiusS * sin(theta);
+      double pointX = centerS.GetX() + offsetX;
+      double pointY = centerS.GetY() + offsetY;
+      chain.push_back(ScenePoint2D(pointX, pointY));
+      theta += angleIncr;
+    }
+  }
+
+  double NormalizeAngle(double angle)
+  {
+    double retAngle = angle;
+    while (retAngle < 0)
+      retAngle += 2 * g_pi;
+    while (retAngle >= 2 * g_pi)
+      retAngle -= 2 * g_pi;
+    return retAngle;
+  }
+
+  double MeasureAngle(const ScenePoint2D& p1, const ScenePoint2D& c, const ScenePoint2D& p2)
+  {
+    double p1cAngle = atan2(p1.GetY() - c.GetY(), p1.GetX() - c.GetX());
+    double p2cAngle = atan2(p2.GetY() - c.GetY(), p2.GetX() - c.GetX());
+    double delta = p2cAngle - p1cAngle;
+    return NormalizeAngle(delta);
+  }
+
+
+#if 0
+  void AddEllipse(PolylineSceneLayer::Chain& chain,
+    const Scene2D&      scene,
+    const ScenePoint2D& centerS,
+    const double&       halfHAxis,
+    const double&       halfVAxis)
+  {
+    chain.clear();
+    chain.reserve(4);
+    ScenePoint2D centerC = centerS.Apply(scene.GetSceneToCanvasTransform());
+    //TODO: take DPI into account
+    double handleLX = centerC.GetX() - sideLength / 2;
+    double handleTY = centerC.GetY() - sideLength / 2;
+    double handleRX = centerC.GetX() + sideLength / 2;
+    double handleBY = centerC.GetY() + sideLength / 2;
+    ScenePoint2D LTC(handleLX, handleTY);
+    ScenePoint2D RTC(handleRX, handleTY);
+    ScenePoint2D RBC(handleRX, handleBY);
+    ScenePoint2D LBC(handleLX, handleBY);
+
+    ScenePoint2D startLT = LTC.Apply(scene.GetCanvasToSceneTransform());
+    ScenePoint2D startRT = RTC.Apply(scene.GetCanvasToSceneTransform());
+    ScenePoint2D startRB = RBC.Apply(scene.GetCanvasToSceneTransform());
+    ScenePoint2D startLB = LBC.Apply(scene.GetCanvasToSceneTransform());
+
+    chain.push_back(startLT);
+    chain.push_back(startRT);
+    chain.push_back(startRB);
+    chain.push_back(startLB);
+}
+#endif
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Samples/Common/MeasureToolsToolbox.h	Mon May 13 15:12:56 2019 +0200
@@ -0,0 +1,135 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * 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
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#include <Framework/Scene2D/PolylineSceneLayer.h>
+#include <Framework/Scene2D/Scene2D.h>
+
+namespace OrthancStone
+{
+
+  /**
+  This function will create a square around the center point supplied in
+  scene coordinates, with a side length given in canvas coordinates. The
+  square sides are parallel to the canvas boundaries.
+  */
+  void AddSquare(PolylineSceneLayer::Chain& chain,
+    const Scene2D&      scene,
+    const ScenePoint2D& centerS,
+    const double&       sideLength);
+
+  /**
+    Creates an arc centered pm c that goes
+    - from a point r1:
+      - so that r1 belongs to the p1,c line
+      - so that the distance from c to r1 equals radius
+    - to a point r2:
+      - so that r2 belongs to the p2,c line
+      - so that the distance from c to r2 equals radius
+
+    if clockwise is true, the arc is drawn from r1 to r2 with increasing 
+    angle values. Otherwise, the angle values decrease.
+
+    Warning: the existing chain content will be wiped out.
+  */
+
+  void AddArc(
+      PolylineSceneLayer::Chain& chain
+    , const Scene2D&      scene
+    , const ScenePoint2D& p1
+    , const ScenePoint2D& c
+    , const ScenePoint2D& p2
+    , const double&       radiusS
+    , const bool          clockwise
+    , const int           subdivisionsCount = 63);
+    
+  /**
+    Creates an arc (open curve) with "numSubdivisions"
+    (N + 1 points) from start angle to end angle.
+
+    if clockwise is true, the arc is drawn from start to end 
+    by increasing the angle values.
+
+    otherwise, the angle value decreases from start to end.
+
+    Warning: the existing chain content will be wiped out.
+  */
+  void AddArc(
+      PolylineSceneLayer::Chain& chain
+    , const Scene2D&      scene
+    , const ScenePoint2D& centerS
+    , const double&       radiusS
+    , const double        startAngleRad
+    , const double        endAngleRad
+    , const bool          clockwise
+    , const int           subdivisionsCount = 63);
+
+  /**
+    Creates a circle (closed curve) with "numSubdivisions"
+    (N points)
+
+    Warning: the existing chain content will be wiped out.
+  */
+  void AddCircle(PolylineSceneLayer::Chain& chain,
+    const Scene2D&      scene,
+    const ScenePoint2D& centerS,
+    const double&       radiusS,
+    const int           numSubdivisions = 63);
+
+  /**
+    Adds or subtracts 2*pi as many times as need to shift the specified
+    angle to a value such as:   0 <= value < 2*pi
+   */
+  double NormalizeAngle(double angle);
+
+  /**
+    Returns the angle magnitude between the p1,c and p2,c lines. 
+    The returned angle is between 0 and 2*pi
+
+    If the angle is between 0 and pi, this means that the shortest arc 
+    from p1 to p2 is clockwise.
+
+    If the angle is between pi and 2*pi, this means that the shortest arc
+    from p1 to p2 is COUNTERclockwise.
+
+  */
+  double MeasureAngle(
+      const ScenePoint2D& p1
+    , const ScenePoint2D& c
+    , const ScenePoint2D& p2);
+
+  /**
+  RadiansToDegrees
+  */
+  double RadiansToDegrees(double angleRad);
+
+  /**
+  This function will return the coordinates of a point that:
+  - belongs to the two bisecting lines of the p1 c p2 angle.
+  - is a distance d from c.
+  Among the four possible points, the one returned will be the one belonging
+  to the *smallest* half-plane defined by the [c,p1[ and [c,p2[ half-lines.
+  */
+  void GetPositionOnBisectingLine(
+      ScenePoint2D&       result
+    , const ScenePoint2D& p1
+    , const ScenePoint2D& c
+    , const ScenePoint2D& p2
+    , const double d);
+}
--- a/Samples/Common/MeasureTrackers.cpp	Fri May 10 16:15:55 2019 +0200
+++ b/Samples/Common/MeasureTrackers.cpp	Mon May 13 15:12:56 2019 +0200
@@ -17,15 +17,15 @@
  * You should have received a copy of the GNU Affero General Public License
  * along with this program. If not, see <http://www.gnu.org/licenses/>.
  **/
-
-#include "MeasureTrackers.h"
+
+#include "MeasureTrackers.h"
 #include <Core/OrthancException.h>
-
-using namespace Orthanc;
-
-namespace OrthancStone
-{
-
+
+using namespace Orthanc;
+
+namespace OrthancStone
+{
+
   CreateMeasureTracker::CreateMeasureTracker(
     Scene2D&                        scene,
     std::vector<TrackerCommandPtr>& undoStack,
@@ -35,20 +35,20 @@
     , undoStack_(undoStack)
     , measureTools_(measureTools)
     , commitResult_(true)
-  {
-  }
-
-  void CreateMeasureTracker::Cancel()
-  {
-    commitResult_ = false;
-    active_ = false;
-  }
+  {
+  }
+
+  void CreateMeasureTracker::Cancel()
+  {
+    commitResult_ = false;
+    active_ = false;
+  }
 
   bool CreateMeasureTracker::IsActive() const
   {
     return active_;
   }
-
+
   CreateMeasureTracker::~CreateMeasureTracker()
   {
     // if the tracker completes successfully, we add the command
@@ -60,8 +60,6 @@
     else
       command_->Undo();
   }
-  
+}
 
-}
-
-
+
--- a/Samples/Common/MeasureTrackers.h	Fri May 10 16:15:55 2019 +0200
+++ b/Samples/Common/MeasureTrackers.h	Mon May 13 15:12:56 2019 +0200
@@ -45,13 +45,13 @@
     ~CreateMeasureTracker();
   
   protected:
+    CreateMeasureCommandPtr         command_;
     Scene2D&                        scene_;
-    CreateMeasureCommandPtr         command_;
     bool                            active_;
   private:
     std::vector<TrackerCommandPtr>& undoStack_;
     std::vector<MeasureToolPtr>&    measureTools_;
     bool                            commitResult_;
   };
+}
 
-}
--- a/Samples/Sdl/CMakeLists.txt	Fri May 10 16:15:55 2019 +0200
+++ b/Samples/Sdl/CMakeLists.txt	Mon May 13 15:12:56 2019 +0200
@@ -69,6 +69,9 @@
   LIST(APPEND TRACKERSAMPLE_SOURCE "../../../SDL-Console/SDL_Console.h")
 endif()
 
+LIST(APPEND TRACKERSAMPLE_SOURCE "../Common/AngleMeasureTool.cpp")
+LIST(APPEND TRACKERSAMPLE_SOURCE "../Common/AngleMeasureTool.h")
+
 LIST(APPEND TRACKERSAMPLE_SOURCE "../Common/CreateAngleMeasureTracker.cpp")
 LIST(APPEND TRACKERSAMPLE_SOURCE "../Common/CreateAngleMeasureTracker.h")
 LIST(APPEND TRACKERSAMPLE_SOURCE "../Common/CreateCircleMeasureTracker.cpp")
@@ -90,14 +93,20 @@
 
 LIST(APPEND TRACKERSAMPLE_SOURCE "../Common/IFlexiblePointerTracker.h")
 
+LIST(APPEND TRACKERSAMPLE_SOURCE "../Common/LineMeasureTool.cpp")
+LIST(APPEND TRACKERSAMPLE_SOURCE "../Common/LineMeasureTool.h")
+
 LIST(APPEND TRACKERSAMPLE_SOURCE "../Common/MeasureCommands.cpp")
 LIST(APPEND TRACKERSAMPLE_SOURCE "../Common/MeasureCommands.h")
 LIST(APPEND TRACKERSAMPLE_SOURCE "../Common/MeasureTools.cpp")
 LIST(APPEND TRACKERSAMPLE_SOURCE "../Common/MeasureTools.h")
+
+LIST(APPEND TRACKERSAMPLE_SOURCE "../Common/MeasureToolsToolbox.cpp")
+LIST(APPEND TRACKERSAMPLE_SOURCE "../Common/MeasureToolsToolbox.h")
+
 LIST(APPEND TRACKERSAMPLE_SOURCE "../Common/MeasureTrackers.cpp")
 LIST(APPEND TRACKERSAMPLE_SOURCE "../Common/MeasureTrackers.h")
 
-
 LIST(APPEND TRACKERSAMPLE_SOURCE "TrackerSample.cpp")
 LIST(APPEND TRACKERSAMPLE_SOURCE "TrackerSampleApp.cpp")
 LIST(APPEND TRACKERSAMPLE_SOURCE "TrackerSampleApp.h")