changeset 998:38b6bb0bdd72

added a new set of classes that correctly handle non-convex polygons (not used yet because of limitations in coordinates computing): DicomStructure2, DicomStructureSet2, DicomStructurePolygon2, DicomStructureSetSlicer2. Too many shortcuts have been taken when computing the actual position.
author Benjamin Golinvaux <bgo@osimis.io>
date Fri, 20 Sep 2019 11:58:00 +0200
parents 9893fa8cd7a6
children 2d69b8bee484
files Framework/Loaders/DicomStructureSetLoader2.cpp Framework/Loaders/DicomStructureSetLoader2.h Framework/Toolbox/DicomStructure2.cpp Framework/Toolbox/DicomStructure2.h Framework/Toolbox/DicomStructurePolygon2.cpp Framework/Toolbox/DicomStructurePolygon2.h Framework/Toolbox/DicomStructureSet2.cpp Framework/Toolbox/DicomStructureSet2.h Framework/Toolbox/DicomStructureSetUtils.cpp Framework/Toolbox/DicomStructureSetUtils.h Framework/Volumes/DicomStructureSetSlicer2.cpp Framework/Volumes/DicomStructureSetSlicer2.h
diffstat 12 files changed, 1774 insertions(+), 806 deletions(-) [+]
line wrap: on
line diff
--- a/Framework/Loaders/DicomStructureSetLoader2.cpp	Sat Sep 14 17:27:41 2019 +0200
+++ b/Framework/Loaders/DicomStructureSetLoader2.cpp	Fri Sep 20 11:58:00 2019 +0200
@@ -0,0 +1,119 @@
+/**
+ * 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 "DicomStructureSetLoader2.h"
+
+#include "../Messages/IObservable.h"
+#include "../Oracle/IOracle.h"
+#include "../Oracle/OracleCommandExceptionMessage.h"
+
+namespace OrthancStone
+{
+
+  DicomStructureSetLoader2::DicomStructureSetLoader2(
+    DicomStructureSet2& structureSet
+    , IOracle& oracle
+    , IObservable& oracleObservable)
+    : IObserver(oracleObservable.GetBroker())
+    , IObservable(oracleObservable.GetBroker())
+    , structureSet_(structureSet)
+    , oracle_(oracle)
+    , oracleObservable_(oracleObservable)
+    , structuresReady_(false)
+  {
+    LOG(TRACE) << "DicomStructureSetLoader2(" << std::hex << this << std::dec << ")::DicomStructureSetLoader2()";
+
+    oracleObservable.RegisterObserverCallback(
+      new Callable<DicomStructureSetLoader2, OrthancRestApiCommand::SuccessMessage>
+      (*this, &DicomStructureSetLoader2::HandleSuccessMessage));
+
+    oracleObservable.RegisterObserverCallback(
+      new Callable<DicomStructureSetLoader2, OracleCommandExceptionMessage>
+      (*this, &DicomStructureSetLoader2::HandleExceptionMessage));
+  }
+
+  DicomStructureSetLoader2::~DicomStructureSetLoader2()
+  {
+    LOG(TRACE) << "DicomStructureSetLoader2(" << std::hex << this << std::dec << ")::~DicomStructureSetLoader2()";
+    oracleObservable_.Unregister(this);
+  }
+
+  void DicomStructureSetLoader2::LoadInstanceFromString(const std::string& body)
+  {
+    OrthancPlugins::FullOrthancDataset dicom(body);
+    //loader.content_.reset(new DicomStructureSet(dicom));
+    structureSet_.Clear();
+    structureSet_.SetContents(dicom);
+    SetStructuresReady();
+  }
+
+  void DicomStructureSetLoader2::HandleSuccessMessage(const OrthancRestApiCommand::SuccessMessage& message)
+  {
+    const std::string& body = message.GetAnswer();
+    LoadInstanceFromString(body);
+  }
+
+  void DicomStructureSetLoader2::HandleExceptionMessage(const OracleCommandExceptionMessage& message)
+  {
+    LOG(ERROR) << "DicomStructureSetLoader2::HandleExceptionMessage: error when trying to load data. "
+      << "Error: " << message.GetException().What() << " Details: "
+      << message.GetException().GetDetails();
+  }
+
+  void DicomStructureSetLoader2::LoadInstance(const std::string& instanceId)
+  {
+    std::auto_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand);
+    command->SetHttpHeader("Accept-Encoding", "gzip");
+
+    std::string uri = "/instances/" + instanceId + "/tags?ignore-length=3006-0050";
+
+    command->SetUri(uri);
+    oracle_.Schedule(*this, command.release());
+  }
+
+  void DicomStructureSetLoader2::SetStructuresReady()
+  {
+    structuresReady_ = true;
+  }
+
+  bool DicomStructureSetLoader2::AreStructuresReady() const
+  {
+    return structuresReady_;
+  }
+
+  /*
+
+    void LoaderStateMachine::HandleExceptionMessage(const OracleCommandExceptionMessage& message)
+    {
+      LOG(ERROR) << "LoaderStateMachine::HandleExceptionMessage: error in the state machine, stopping all processing";
+      LOG(ERROR) << "Error: " << message.GetException().What() << " Details: " <<
+        message.GetException().GetDetails();
+        Clear();
+    }
+
+    LoaderStateMachine::~LoaderStateMachine()
+    {
+      Clear();
+    }
+
+
+  */
+
+}
--- a/Framework/Loaders/DicomStructureSetLoader2.h	Sat Sep 14 17:27:41 2019 +0200
+++ b/Framework/Loaders/DicomStructureSetLoader2.h	Fri Sep 20 11:58:00 2019 +0200
@@ -0,0 +1,80 @@
+/**
+ * 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 "../Toolbox/DicomStructureSet2.h"
+#include "../Messages/IMessage.h"
+#include "../Messages/IObserver.h"
+#include "../Messages/IObservable.h"
+#include "../Oracle/OrthancRestApiCommand.h"
+
+#include <boost/noncopyable.hpp>
+
+namespace OrthancStone
+{
+  class IOracle;
+  class IObservable;
+  class OrthancRestApiCommand;
+  class OracleCommandExceptionMessage;
+
+  class DicomStructureSetLoader2 : public IObserver, public IObservable
+  {
+  public:
+    ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, StructuresReady, DicomStructureSetLoader2);
+
+    /**
+    Warning: the structureSet, oracle and oracleObservable objects must live
+    at least as long as this object (TODO: shared_ptr?)
+    */
+    DicomStructureSetLoader2(DicomStructureSet2& structureSet, IOracle& oracle, IObservable& oracleObservable);
+
+    ~DicomStructureSetLoader2();
+
+    void LoadInstance(const std::string& instanceId);
+
+    /** Internal use */
+    void LoadInstanceFromString(const std::string& body);
+
+    void SetStructuresReady();
+    bool AreStructuresReady() const;
+  
+  private:
+    /**
+    Called back by the oracle when data is ready!
+    */
+    void HandleSuccessMessage(const OrthancRestApiCommand::SuccessMessage& message);
+
+    /**
+    Called back by the oracle when shit hits the fan
+    */
+    void HandleExceptionMessage(const OracleCommandExceptionMessage& message);
+
+    /**
+    The structure set that will be (cleared and) filled with data from the 
+    loader
+    */
+    DicomStructureSet2& structureSet_;
+
+    IOracle&            oracle_;
+    IObservable&        oracleObservable_;
+    bool                structuresReady_;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Toolbox/DicomStructure2.cpp	Fri Sep 20 11:58:00 2019 +0200
@@ -0,0 +1,287 @@
+/**
+ * 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 "DicomStructure2.h"
+
+#include "../Toolbox/GeometryToolbox.h"
+#include "../Toolbox/DisjointDataSet.h"
+
+namespace OrthancStone
+{
+  // see header
+  //void DicomStructure2::ComputeNormal()
+  //{
+  //  try
+  //  {
+  //    if (polygons_.size() > 0)
+  //    {
+
+  //      // TODO: check all polygons are OK
+  //      const DicomStructurePolygon2 polygon = polygons_[0];
+  //      $$$$$$$$$$$$$$$$$
+  //        state_ = NormalComputed;
+  //    }
+  //    else
+  //    {
+  //      // bogus! no polygons. Let's assign a "nothing here" value
+  //      LinearAlgebra::AssignVector(normal_, 0, 0, 0);
+  //      state_ = Invalid;
+  //    }
+  //  }
+  //  catch (const Orthanc::OrthancException& e)
+  //  {
+  //    state_ = Invalid;
+  //    if (e.HasDetails())
+  //    {
+  //      LOG(ERROR) << "OrthancException in ComputeNormal: " << e.What() << " Details: " << e.GetDetails();
+  //    }
+  //    else
+  //    {
+  //      LOG(ERROR) << "OrthancException in ComputeNormal: " << e.What();
+  //    }
+  //    throw;
+  //  }
+  //  catch (const std::exception& e)
+  //  {
+  //    state_ = Invalid;
+  //    LOG(ERROR) << "std::exception in ComputeNormal: " << e.what();
+  //    throw;
+  //  }
+  //  catch (...)
+  //  {
+  //    state_ = Invalid;
+  //    LOG(ERROR) << "Unknown exception in ComputeNormal";
+  //    throw;
+  //  }
+  //}
+
+  void DicomStructure2::ComputeSliceThickness()
+  {
+    if (state_ != NormalComputed)
+    {
+      LOG(ERROR) << "DicomStructure2::ComputeSliceThickness - state must be NormalComputed";
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+    if (polygons_.size() < 2)
+    {
+      // cannot compute thickness if there are not at least 2 slabs (contours)
+      sliceThickness_ = 1.0;
+      state_ = Invalid;
+    }
+    else
+    {
+      // normal can be (1,0,0), (0,1,0) or (0,0,1), nothing else.
+      // these can be compared with == (exact double representation)
+      if (normal_[0] == 1)
+      {
+        // in a single polygon, all the points have the same X
+        sliceThickness_ = fabs(polygons_[0].GetPoint(0)[0] - polygons_[1].GetPoint(0)[0]);
+      }
+      else if (normal_[1] == 1)
+      {
+        // in a single polygon, all the points have the same X
+        sliceThickness_ = fabs(polygons_[0].GetPoint(0)[1] - polygons_[1].GetPoint(0)[1]);
+      }
+      else if (normal_[2] == 1)
+      {
+        // in a single polygon, all the points have the same X
+        sliceThickness_ = fabs(polygons_[0].GetPoint(0)[2] - polygons_[1].GetPoint(0)[2]);
+      }
+      else
+      {
+        ORTHANC_ASSERT(false);
+        state_ = Invalid;
+      }
+    }
+    state_ = Valid;
+  }
+
+  void DicomStructure2::AddPolygon(const DicomStructurePolygon2& polygon)
+  {
+    if (state_ != Building)
+    {
+      LOG(ERROR) << "DicomStructure2::AddPolygon - can only add polygon while building";
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+    polygons_.push_back(polygon);
+  }
+
+  void DicomStructure2::ComputeDependentProperties()
+  {
+    if (state_ != Building)
+    {
+      LOG(ERROR) << "DicomStructure2::ComputeDependentProperties - can only be called once";
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+    for (size_t i = 0; i < polygons_.size(); ++i)
+    {
+      // "compute" the polygon normal
+      polygons_[i].ComputeDependentProperties();
+    }
+    if (polygons_.size() > 0)
+    {
+      normal_ = polygons_[0].GetNormal();
+      state_ = NormalComputed;
+    }
+    else
+    {
+      LinearAlgebra::AssignVector(normal_, 0, 0, 0);
+      state_ = Invalid; // THIS MAY HAPPEN !!! (for instance for instance 72c773ac-5059f2c4-2e6a9120-4fd4bca1-45701661 :) )
+    }
+    if (polygons_.size() >= 2)
+      ComputeSliceThickness(); // this will change state_ from NormalComputed to Valid
+  }
+
+  OrthancStone::Vector DicomStructure2::GetNormal() const
+  {
+    if (state_ != Valid && state_ != Invalid)
+    {
+      LOG(ERROR) << "DicomStructure2::GetNormal() -- please call ComputeDependentProperties first.";
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+    if (state_ == Invalid)
+    {
+      LOG(ERROR) << "DicomStructure2::GetNormal() -- The Dicom structure is invalid. The normal is set to 0,0,0";
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+    return normal_;
+  }
+
+  const DicomStructurePolygon2* DicomStructure2::GetPolygonClosestToSlice(
+    const CoordinateSystem3D& plane) const
+  {
+    ORTHANC_ASSERT(state_ == Valid);
+
+    // we assume 0,0,1 for now
+    ORTHANC_ASSERT(LinearAlgebra::IsNear(plane.GetNormal()[0], 0.0));
+    ORTHANC_ASSERT(LinearAlgebra::IsNear(plane.GetNormal()[1], 0.0));
+
+    for (size_t i = 0; i < polygons_.size(); ++i)
+    {
+      const DicomStructurePolygon2& polygon = polygons_[i];
+
+      // "height" of cutting plane
+      double cutZ = plane.GetOrigin()[2];
+
+      if (LinearAlgebra::IsNear(
+        cutZ, polygon.GetZ(),
+        sliceThickness_ / 2.0 /* in mm */))
+        return &polygon;
+    }
+    return NULL;
+  }
+
+
+    bool DicomStructure2::Project(std::vector< std::pair<Point2D, Point2D> > & segments, const CoordinateSystem3D & plane) const
+    {
+      segments.clear();
+
+      Vector normal = GetNormal();
+
+      size_t totalRectCount = 0;
+
+      // dummy var
+      bool isOpposite = false;
+
+      // This is an axial projection
+      if (GeometryToolbox::IsParallelOrOpposite(isOpposite, normal, plane.GetNormal()))
+      {
+        const DicomStructurePolygon2* polygon = GetPolygonClosestToSlice(plane);
+        if (polygon)
+        {
+          polygon->ProjectOnParallelPlane(segments, plane);
+        }
+      }
+      else
+      {
+        // let's compute the dot product of the plane normal and the polygons
+        // normal.
+        double dot = LinearAlgebra::DotProduct(plane.GetNormal(), normal);
+
+        if (LinearAlgebra::IsNear(dot, 0))
+        {
+          // Coronal or sagittal projection
+
+          // vector of vector of rectangles that will be merged in a single big contour:
+
+          // each polygon slab cut by a perpendicular plane yields 0..* rectangles
+          std::vector< RtStructRectanglesInSlab > rectanglesForEachSlab;
+
+          for (size_t i = 0; i < polygons_.size(); ++i)
+          {
+            // book an entry for this slab
+            rectanglesForEachSlab.push_back(RtStructRectanglesInSlab());
+
+            // let's compute the intersection between the polygon and the plane
+            // intersections are in plane coords
+            std::vector<Point2D> intersections;
+
+            polygons_[i].ProjectOnConstantPlane(intersections, plane);
+
+            // for each pair of intersections, we add a rectangle.
+            if ((intersections.size() % 2) != 0)
+            {
+              LOG(WARNING) << "Odd number of intersections between structure "
+                << name_ << ", polygon # " << i
+                << " and plane where X axis is parallel to polygon normal vector";
+            }
+
+            size_t numRects = intersections.size() / 2;
+            
+            // we keep count of the total number of rects for vector pre-allocations
+            totalRectCount += numRects;
+            
+            for (size_t iRect = 0; iRect < numRects; ++iRect)
+            {
+              RtStructRectangleInSlab rectangle;
+              ORTHANC_ASSERT(LinearAlgebra::IsNear(intersections[2 * iRect].y, intersections[2 * iRect + 1].y));
+              ORTHANC_ASSERT((2 * iRect + 1) < intersections.size());
+              double x1 = intersections[2 * iRect].x;
+              double x2 = intersections[2 * iRect + 1].x;
+              double y1 = intersections[2 * iRect].y - sliceThickness_ * 0.5;
+              double y2 = intersections[2 * iRect].y + sliceThickness_ * 0.5;
+
+              rectangle.xmin = std::min(x1, x2);
+              rectangle.xmax = std::max(x1, x2);
+              rectangle.ymin = std::min(y1, y2);
+              rectangle.ymax = std::max(y1, y2);
+
+              // TODO: keep them sorted!!!!
+
+              rectanglesForEachSlab.back().push_back(rectangle);
+            }
+          }
+          // now we need to merge all the slabs into a set of polygons (1 or more)
+          ConvertListOfSlabsToSegments(segments, rectanglesForEachSlab, totalRectCount);
+        }
+        else
+        {
+          // plane is not perpendicular to the polygons
+          // 180.0 / [Math]::Pi = 57.2957795130823
+          double acDot = 57.2957795130823 * acos(dot);
+          LOG(ERROR) << "DicomStructure2::Project -- cutting plane must be "
+            << "perpendicular to the contours, but dot product is: "
+            << dot << " and (180/pi)*acos(dot) = " << acDot;
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+        }
+      }
+      return segments.size() != 0;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Toolbox/DicomStructure2.h	Fri Sep 20 11:58:00 2019 +0200
@@ -0,0 +1,155 @@
+/**
+ * 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 "DicomStructurePolygon2.h"
+#include "DicomStructureSetUtils.h"
+
+namespace OrthancStone
+{
+
+  /*
+    A structure has a color, a name, a set of slices..
+
+    Each slice is a polygon.
+  */
+  struct DicomStructure2
+  {
+    DicomStructure2() :
+      red_(0), green_(0), blue_(0), sliceThickness_(0), state_(Building) {}
+
+    void AddPolygon(const DicomStructurePolygon2& polygon);
+
+    /**
+    Once all polygons have been added, this method will determine:
+    - the slice orientation (through the normal vector)
+    - the spacing between slices (slice thickness)
+
+    it will also set up the info required to efficiently compute plane
+    intersections later on.
+    */
+    void ComputeDependentProperties();
+
+    /**
+    Being given a plane that is PARALLEL to the set of polygon contours, this 
+    returns a pointer to the polygon located at that position (if it is closer
+    than thickness/2) or NULL if there is none.
+
+    TODO: use sorted vector to improve
+
+    DO NOT STORE THE RETURNED POINTER!
+    */
+    const DicomStructurePolygon2* GetPolygonClosestToSlice(const CoordinateSystem3D& plane) const;
+
+    Vector GetNormal() const;
+
+    Color GetColor() const
+    {
+      return Color(red_, green_, blue_);
+    }
+
+    bool IsValid() const
+    {
+      return state_ == Valid;
+    }
+
+    /**
+    This method is used to project the 3D structure on a 2D plane.
+
+    A structure is a stack of polygons, representing a volume.
+
+    We need to compute the intersection between this volume and the supplied 
+    cutting plane (the "slice"). This is more than a cutting plane: it is also
+    a 2D-coordinate system (the plane has axes vectors)
+
+    The cutting plane is always parallel to the plane defined by two of the
+    world coordinate system axes.
+    
+    The result is a set of closed polygons.
+
+    If the cut is parallel to the polygons, we pick the polygon closest to 
+    the slice, project it on the slice and return it in slice coordinates.
+
+    If the cut is perpendicular to the polygons, for each polygon, we compute 
+    the intersection between the cutting plane and the polygon slab (imaginary 
+    volume created by extruding the polygon above and below its plane by 
+    thickness/2) :
+    - each slab, intersected by the plane, gives a set of 0..* rectangles \
+      (only one if the polygon is convex)
+    - when doing this for the whole stack of slabs, we get a set of rectangles:
+      To compute these rectangles, for each polygon, we compute the intersection
+      between :
+       - the line defined by the intersection of the polygon plane and the cutting
+         plane
+       - the polygon itself
+      This yields 0 or 2*K points along the line C. These are turned into K
+      rectangles by taking two consecutive points along the line and extruding 
+      this segment by sliceThickness/2 in the orientation of the polygon normal,
+      in both directions.
+
+    Then, once this list of rectangles is computed, we need to group the 
+    connected rectangles together. Connected, here, means sharing at least part
+    of an edge --> union/find data structures and algorithm.
+    */
+    bool Project(std::vector< std::pair<Point2D, Point2D> >& polygons, const CoordinateSystem3D& plane) const;
+
+    std::string                         interpretation_;
+    std::string                         name_;
+    uint8_t                             red_;
+    uint8_t                             green_;
+    uint8_t                             blue_;
+  
+    /** Internal */
+    const std::vector<DicomStructurePolygon2>& GetPolygons() const
+    {
+      return polygons_;
+    }
+
+    /** Internal */
+    double GetSliceThickness() const
+    {
+      return sliceThickness_;
+    }
+
+  private:
+    enum State
+    {
+      Building,
+      NormalComputed,
+      Valid, // When normal components AND slice thickness are computed
+      Invalid
+    };
+
+    void ComputeNormal();
+    void ComputeSliceThickness();
+
+    std::vector<DicomStructurePolygon2> polygons_;
+    Vector3D                            normal_;
+    double                              sliceThickness_;
+
+    /*
+      After creation (and while polygons are added), state is Building.
+      After ComputeDependentProperties() is called, state can either be
+      Valid or Invalid. In any case, the object becomes immutable.
+    */
+    State state_;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Toolbox/DicomStructurePolygon2.cpp	Fri Sep 20 11:58:00 2019 +0200
@@ -0,0 +1,306 @@
+/**
+ * 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 "DicomStructurePolygon2.h"
+
+#include "../Toolbox/LinearAlgebra.h"
+
+namespace OrthancStone
+{
+  void DicomStructurePolygon2::ComputeDependentProperties()
+  {
+    ORTHANC_ASSERT(state_ == Building);
+
+    for (size_t j = 0; j < points_.size(); ++j)
+    {
+      // TODO: move to AddPoint!
+      const Point3D& p = points_[j];
+      if (p[0] < minX_)
+        minX_ = p[0];
+      if (p[0] > maxX_)
+        maxX_ = p[0];
+
+      if (p[1] < minY_)
+        minY_ = p[1];
+      if (p[1] > maxY_)
+        maxY_ = p[1];
+
+      if (p[2] < minZ_)
+        minZ_ = p[2];
+      if (p[2] > maxZ_)
+        maxZ_ = p[2];
+    }
+
+    if (LinearAlgebra::IsNear(minX_, maxX_))
+    {
+      LinearAlgebra::AssignVector(normal_, 1, 0, 0);
+      //ORTHANC_ASSERT(!LinearAlgebra::IsNear(minX, maxX));
+      ORTHANC_ASSERT(!LinearAlgebra::IsNear(minY_, maxY_));
+      ORTHANC_ASSERT(!LinearAlgebra::IsNear(minZ_, maxZ_));
+    }
+    else if (LinearAlgebra::IsNear(minY_, maxY_))
+    {
+      LinearAlgebra::AssignVector(normal_, 0, 1, 0);
+      ORTHANC_ASSERT(!LinearAlgebra::IsNear(minX_, maxX_));
+      ORTHANC_ASSERT(!LinearAlgebra::IsNear(minZ_, maxZ_));
+    }
+    else if (LinearAlgebra::IsNear(minZ_, maxZ_))
+    {
+      LinearAlgebra::AssignVector(normal_, 0, 0, 1);
+      ORTHANC_ASSERT(!LinearAlgebra::IsNear(minX_, maxX_));
+      ORTHANC_ASSERT(!LinearAlgebra::IsNear(minY_, maxY_));
+    }
+    else
+    {
+      LOG(ERROR) << "The contour is not coplanar and not parallel to any axis.";
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+    state_ = Valid;
+  }
+
+
+  void DicomStructurePolygon2::ProjectOnConstantPlane(
+    std::vector<Point2D>& intersections, const CoordinateSystem3D& plane) const
+  {
+    // the plane can either have constant X, or constant Y.
+    // - for constant Z planes, use the ProjectOnParallelPlane method
+    // - other type of planes are not supported
+
+    // V is the coordinate that is constant in the plane
+    double planeV = 0.0;
+
+    // if true, then "u" in the code is "x" and "v" is "y". 
+    // (v is constant in the plane)
+    bool uvxy = false;
+    // if true, then "u" in the code is "y" and "v" is "x"
+    // (v is constant in the plane)
+    bool uvyx = false;
+
+    size_t uindex = static_cast<size_t>(-1);
+    size_t vindex = static_cast<size_t>(-1);
+
+    ORTHANC_ASSERT(LinearAlgebra::IsNear(plane.GetNormal()[2], 0.0));
+
+    if (LinearAlgebra::IsNear(plane.GetNormal()[1], 0.0))
+    {
+      // normal is 1,0,0 (or -1,0,0). 
+      // plane is constant X
+      uindex = 1;
+      vindex = 0;
+
+      uvxy = false;
+      uvyx = true;
+      planeV = plane.GetOrigin()[0];
+      if (planeV < minX_)
+        return;
+      if (planeV > maxX_)
+        return;
+    }
+    else if (LinearAlgebra::IsNear(plane.GetNormal()[0], 0.0))
+    {
+      // normal is 0,1,0 (or 0,-1,0). 
+      // plane is constant Y
+      uindex = 0;
+      vindex = 1;
+
+      uvxy = true;
+      uvyx = false;
+      planeV = plane.GetOrigin()[1];
+      if (planeV < minY_)
+        return;
+      if (planeV > maxY_)
+        return;
+    }
+    else
+    {
+      // if the following assertion(s) fail(s), it means the plane is NOT a constant-X or constant-Y plane
+      LOG(ERROR) << "Plane normal must be (a,0,0) or (0,a,0), with a == -1 or a == 1";
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    size_t pointCount = GetPointCount();
+    if (pointCount >= 3)
+    {
+      // this vector will contain the coordinates of the intersection points
+      // between the plane and the polygon.
+      // these are expressed in the U coordinate, that is either X or Y, 
+      // depending upon the plane orientation
+      std::vector<double> uIntersections;
+
+      // we loop on the segments of the polygon (TODO: optimize)
+      // and we compute the intersection between each segment and the cut
+      // cutting plane (slice) has a constant X
+
+      for (size_t iPoint = 0; iPoint < pointCount; ++iPoint)
+      {
+        double u1 = points_[iPoint][uindex];
+        double v1 = points_[iPoint][vindex];
+
+        double u2 = 0;
+        double v2 = 0;
+
+        if (iPoint < pointCount - 1)
+        {
+          u2 = points_[iPoint + 1][uindex];
+          v2 = points_[iPoint + 1][vindex];
+        }
+        else
+        {
+          u2 = points_[0][uindex];
+          v2 = points_[0][vindex];
+        }
+
+        // Check if the segment intersects the plane
+        if ((std::min(v1, v2) <= planeV) && (std::max(v1, v2) >= planeV))
+        {
+          // special case: the segment is parallel to the plane but close to it
+          if (LinearAlgebra::IsNear(v1, v2))
+          {
+            // in that case, we choose to label both points as an intersection
+            double x, y;
+            plane.ProjectPoint(x, y, points_[iPoint]);
+            intersections.push_back(Point2D(x, y));
+
+            plane.ProjectPoint(x, y, points_[iPoint + 1]);
+            intersections.push_back(Point2D(x, y));
+          }
+          else
+          {
+            // we are looking for u so that (u,planeV) belongs to the segment
+            // let's define alpha = (u-u2)/(u1-u2) --> u = alpha*(u1-u2) + u2
+            // alpha = (v2-planeV)/(v2-v1)
+            // because the following two triangles are similar
+            // [ (planeY,x)  , (y2,x2), (planeY,x2) ] or
+            // [ (planeX,y)  , (x2,y2), (planeX,y2) ]
+            // and
+            // [ (y1    ,x1) , (y2,x2), (y1    ,x2) ] or
+            // [ (x1    ,y1) , (x2,y2), (x1    ,y2) ]
+
+            /*
+              void CoordinateSystem3D::ProjectPoint(double& offsetX,
+                double& offsetY,
+                const Vector& point) const
+            */
+            double alpha = (v2 - planeV) / (v2 - v1);
+
+            // get rid of numerical oddities
+            if (alpha < 0.0)
+              alpha = 0.0;
+            if (alpha > 1.0)
+              alpha = 1.0;
+            double u = alpha * (u1 - u2) + u2;
+
+            // here is the intersection in world coordinates
+            Vector intersection;
+            if(uvxy)
+              LinearAlgebra::AssignVector(intersection, u, planeV, minZ_);
+            else
+              LinearAlgebra::AssignVector(intersection, planeV, u, minZ_);
+
+            // and we convert it to plane coordinates
+            {
+              double xi, yi;
+              plane.ProjectPoint(xi, yi, intersection);
+
+              // we consider that the x axis is always parallel to the polygons
+              // TODO: is this hypothesis safe??????
+              uIntersections.insert(std::lower_bound(uIntersections.begin(), uIntersections.end(), xi), xi);
+            }
+          }
+        }
+      } // end of for (size_t iPoint = 0; iPoint < pointCount; ++iPoint)
+    
+      // now we convert the intersections to plane points
+      // we consider that the x axis is always parallel to the polygons
+      // TODO: same hypothesis as above: plane is perpendicular to polygons, 
+      // plane is parallel to the XZ (constant Y) or YZ (constant X) 3D planes
+      for (size_t i = 0; i < uIntersections.size(); ++i)
+      {
+        double x = uIntersections[i];
+        intersections.push_back(Point2D(x, minZ_));
+      }
+    } // end of if (pointCount >= 3)
+    else
+    {
+      LOG(ERROR) << "This polygon has " << pointCount << " vertices, which is less than 3 --> skipping";
+    }
+  } 
+
+void OrthancStone::DicomStructurePolygon2::ProjectOnParallelPlane(
+  std::vector< std::pair<Point2D, Point2D> >& segments, 
+  const CoordinateSystem3D& plane) const
+{
+  if (points_.size() < 3)
+    return;
+
+  // the plane is horizontal
+  ORTHANC_ASSERT(LinearAlgebra::IsNear(plane.GetNormal()[0], 0.0));
+  ORTHANC_ASSERT(LinearAlgebra::IsNear(plane.GetNormal()[1], 0.0));
+
+  size_t pointCount = GetPointCount();
+
+  segments.clear();
+  segments.reserve(points_.size());
+  // since the returned values need to be expressed in the supplied coordinate
+  // system, we need to subtract origin_ from the returned points
+
+  double planeOriginX = plane.GetOrigin()[0];
+  double planeOriginY = plane.GetOrigin()[1];
+
+  // precondition: points_.size() >= 3
+  for (size_t j = 0; j < points_.size()-1; ++j)
+  {
+    // segment between point j and j+1
+
+    const Point3D& point0 = GetPoint(j);
+    // subtract plane origin x and y
+    Point2D p0(point0[0] - planeOriginX, point0[1] - planeOriginY);
+    
+    const Point3D& point1 = GetPoint(j+1);
+    // subtract plane origin x and y
+    Point2D p1(point1[0] - planeOriginX, point1[1] - planeOriginY);
+
+    segments.push_back(std::pair<Point2D, Point2D>(p0,p1));
+  }
+
+
+  // final segment 
+
+  const Point3D& point0 = GetPoint(points_.size() - 1);
+  // subtract plane origin x and y
+  Point2D p0(point0[0] - planeOriginX, point0[1] - planeOriginY);
+
+  const Point3D& point1 = GetPoint(0);
+  // subtract plane origin x and y
+  Point2D p1(point1[0] - planeOriginX, point1[1] - planeOriginY);
+
+  segments.push_back(std::pair<Point2D, Point2D>(p0, p1));
+}
+
+double OrthancStone::DicomStructurePolygon2::GetZ() const
+{
+  ORTHANC_ASSERT(LinearAlgebra::IsNear(normal_[0], 0.0));
+  ORTHANC_ASSERT(LinearAlgebra::IsNear(normal_[1], 0.0));
+  ORTHANC_ASSERT(LinearAlgebra::IsNear(minZ_, maxZ_));
+  return minZ_;
+}
+
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Toolbox/DicomStructurePolygon2.h	Fri Sep 20 11:58:00 2019 +0200
@@ -0,0 +1,155 @@
+/**
+ * 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 "CoordinateSystem3D.h"
+#include "DicomStructureSetUtils.h"
+#include "Extent2D.h"
+
+#include "../Scene2D/Color.h"
+#include "../StoneException.h"
+
+#include <Plugins/Samples/Common/FullOrthancDataset.h>
+
+#include <list>
+#include <string>
+
+namespace OrthancStone
+{
+
+  /**
+  Only polygons that are planar and parallel to either the X,Y or Z plane
+  ("X plane" == plane where X is equal to a constant for each point) are
+  supported.
+  */
+  class DicomStructurePolygon2
+  {
+  public:
+    enum Type
+    {
+      ClosedPlanar,
+      Unsupported
+    };
+
+    DicomStructurePolygon2(std::string referencedSopInstanceUid, const std::string& type)
+      : referencedSopInstanceUid_(referencedSopInstanceUid)
+      , state_(Building)
+      , minX_(std::numeric_limits<double>::max())
+      , maxX_(-std::numeric_limits<double>::max())
+      , minY_(std::numeric_limits<double>::max())
+      , maxY_(-std::numeric_limits<double>::max())
+      , minZ_(std::numeric_limits<double>::max())
+      , maxZ_(-std::numeric_limits<double>::max())
+      , type_(TypeFromString(type))
+    {
+      ORTHANC_ASSERT(type_ == ClosedPlanar);
+    }
+
+    void ComputeDependentProperties();
+
+    size_t GetPointCount() const
+    {
+      ORTHANC_ASSERT(state_ == Valid);
+      return points_.size();
+    }
+
+    const Point3D& GetPoint(size_t i) const
+    {
+      ORTHANC_ASSERT(state_ == Valid);
+      return points_.at(i);
+    }
+
+    void AddPoint(const Point3D& v)
+    {
+      ORTHANC_ASSERT(state_ == Building);
+      points_.push_back(v);
+    }
+
+    void Reserve(size_t n)
+    {
+      ORTHANC_ASSERT(state_ == Building);
+      points_.reserve(n);
+    }
+
+    /**
+    This method takes a plane+coord system  that is parallel to the polygon
+    and adds to polygons a new vector with the ordered set of points projected
+    on the plane, in the plane coordinate system.
+    */
+    void ProjectOnParallelPlane(
+      std::vector< std::pair<Point2D,Point2D> >& segments,
+      const CoordinateSystem3D& plane) const;
+
+    /**
+    Returns the coordinates of the intersection of the polygon and a plane 
+    that is perpendicular to the polygons (plane has either constant X or 
+    constant Y)
+    */
+    void ProjectOnConstantPlane(
+      std::vector<Point2D>& intersections,
+      const CoordinateSystem3D& plane) const;
+
+    /**
+    This method assumes polygon has a normal equal to 0,0,-1 and 0,0,1 (thus,
+    the polygon is parallel to the XY plane) and returns the Z coordinate of 
+    all the polygon points
+    */
+    double GetZ() const;
+
+    /**
+    The normal sign is left undefined for now
+    */
+    Vector3D GetNormal() const
+    {
+      return normal_;
+    }
+
+    /**
+    This method will compute the intersection between a polygon and
+    a plane where either X, Y or Z is constant.
+    The plane is given with an origin and a normal. If the normal is
+    not parallel to an axis, an error is raised.
+    */
+    void ComputeIntersectionWithPlane(const CoordinateSystem3D& plane);
+
+  private:
+    static Type TypeFromString(const std::string& s)
+    {
+      if (s == "CLOSED_PLANAR")
+        return ClosedPlanar;
+      else
+        return Unsupported;
+    }
+    enum State
+    {
+      Building,
+      Valid
+    };
+    std::string           referencedSopInstanceUid_;
+    CoordinateSystem3D    geometry_;
+    std::vector<Point3D>  points_;
+    Vector3D              normal_; // sign is irrelevant for now
+    State                 state_;
+    double                minX_, maxX_, minY_, maxY_, minZ_, maxZ_;
+    Type                  type_;
+  };
+}
+
--- a/Framework/Toolbox/DicomStructureSet2.cpp	Sat Sep 14 17:27:41 2019 +0200
+++ b/Framework/Toolbox/DicomStructureSet2.cpp	Fri Sep 20 11:58:00 2019 +0200
@@ -18,73 +18,19 @@
  * along with this program. If not, see <http://www.gnu.org/licenses/>.
  **/
 
-
 #include "DicomStructureSet2.h"
 
-#include "../Toolbox/GeometryToolbox.h"
+#include "../Toolbox/LinearAlgebra.h"
+#include "../StoneException.h"
 
 #include <Core/Logging.h>
 #include <Core/OrthancException.h>
 #include <Core/Toolbox.h>
+#include <Core/DicomFormat/DicomTag.h>
+
 #include <Plugins/Samples/Common/FullOrthancDataset.h>
 #include <Plugins/Samples/Common/DicomDatasetReader.h>
 
-#include <limits>
-#include <stdio.h>
-#include <boost/geometry.hpp>
-#include <boost/geometry/geometries/point_xy.hpp>
-#include <boost/geometry/geometries/polygon.hpp>
-#include <boost/geometry/multi/geometries/multi_polygon.hpp>
-
-typedef boost::geometry::model::d2::point_xy<double> BoostPoint;
-typedef boost::geometry::model::polygon<BoostPoint> BoostPolygon;
-typedef boost::geometry::model::multi_polygon<BoostPolygon>  BoostMultiPolygon;
-
-
-static void Union(BoostMultiPolygon& output,
-                  std::vector<BoostPolygon>& input)
-{
-  for (size_t i = 0; i < input.size(); i++)
-  {
-    boost::geometry::correct(input[i]);
-  }
-  
-  if (input.size() == 0)
-  {
-    output.clear();
-  }
-  else if (input.size() == 1)
-  {
-    output.resize(1);
-    output[0] = input[0];
-  }
-  else
-  {
-    boost::geometry::union_(input[0], input[1], output);
-
-    for (size_t i = 0; i < input.size(); i++)
-    {
-      BoostMultiPolygon tmp;
-      boost::geometry::union_(output, input[i], tmp);
-      output = tmp;
-    }
-  }
-}
-
-
-static BoostPolygon CreateRectangle(float x1, float y1,
-                                    float x2, float y2)
-{
-  BoostPolygon r;
-  boost::geometry::append(r, BoostPoint(x1, y1));
-  boost::geometry::append(r, BoostPoint(x1, y2));
-  boost::geometry::append(r, BoostPoint(x2, y2));
-  boost::geometry::append(r, BoostPoint(x2, y1));
-  return r;
-}
-
-
-
 namespace OrthancStone
 {
   static const OrthancPlugins::DicomTag DICOM_TAG_CONTOUR_GEOMETRIC_TYPE(0x3006, 0x0042);
@@ -100,8 +46,7 @@
   static const OrthancPlugins::DicomTag DICOM_TAG_RT_ROI_OBSERVATIONS_SEQUENCE(0x3006, 0x0080);
   static const OrthancPlugins::DicomTag DICOM_TAG_STRUCTURE_SET_ROI_SEQUENCE(0x3006, 0x0020);
 
-
-  static uint8_t ConvertColor(double v)
+  static inline uint8_t ConvertAndClipToByte(double v)
   {
     if (v < 0)
     {
@@ -117,325 +62,98 @@
     }
   }
 
-
-  static bool ParseVector(Vector& target,
-                          const OrthancPlugins::IDicomDataset& dataset,
-                          const OrthancPlugins::DicomPath& tag)
+  static bool ReadDicomToVector(Vector& target,
+    const OrthancPlugins::IDicomDataset& dataset,
+    const OrthancPlugins::DicomPath& tag)
   {
     std::string value;
     return (dataset.GetStringValue(value, tag) &&
-            LinearAlgebra::ParseVector(target, value));
+      LinearAlgebra::ParseVector(target, value));
   }
-
-  void DicomStructureSet2::Polygon::CheckPointIsOnSlice(const Vector& v) const
+  
+  std::ostream& operator<<(std::ostream& s, const OrthancPlugins::DicomPath& dicomPath)
   {
-    if (hasSlice_)
-    {
-      double magnitude =
-        GeometryToolbox::ProjectAlongNormal(v, geometry_.GetNormal());
-      if(!LinearAlgebra::IsNear(
-        magnitude,
-        projectionAlongNormal_,
-        sliceThickness_ / 2.0 /* in mm */ ))
-      {
-        LOG(ERROR) << "This RT-STRUCT contains a point that is off the "
-          << "slice of its instance | "
-          << "magnitude = " << magnitude << " | "
-          << "projectionAlongNormal_ = " << projectionAlongNormal_ << " | "
-          << "tolerance (sliceThickness_ / 2.0) = " << (sliceThickness_ / 2.0);
-
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
-      }
-    }
-  }
-
-  bool DicomStructureSet2::Polygon::IsPointOnSliceIfAny(const Vector& v) const
-  {
-    if (hasSlice_)
+    std::stringstream tmp;
+    for (size_t i = 0; i < dicomPath.GetPrefixLength(); ++i)
     {
-      double magnitude = 
-        GeometryToolbox::ProjectAlongNormal(v, geometry_.GetNormal());
-      bool onSlice = LinearAlgebra::IsNear(
-        magnitude,
-        projectionAlongNormal_,
-        sliceThickness_ / 2.0 /* in mm */);
-      if (!onSlice)
-      {
-        LOG(WARNING) << "This RT-STRUCT contains a point that is off the "
-          << "slice of its instance | "
-          << "magnitude = " << magnitude << " | "
-          << "projectionAlongNormal_ = " << projectionAlongNormal_ << " | "
-          << "tolerance (sliceThickness_ / 2.0) = " << (sliceThickness_ / 2.0);
-      }
-      return onSlice;
+      OrthancPlugins::DicomTag tag = dicomPath.GetPrefixTag(i);
+      
+      // We use this other object to be able to use GetMainTagsName
+      // and Format
+      Orthanc::DicomTag tag2(tag.GetGroup(), tag.GetElement());
+      size_t index = dicomPath.GetPrefixIndex(i);
+      tmp << tag2.GetMainTagsName() << " (" << tag2.Format() << ") [" << index << "] / ";
     }
-    else
-    {
-      return false;
-    }
-  }
-
-  void DicomStructureSet2::Polygon::AddPoint(const Vector& v)
-  {
-#if 1
-    // BGO 2019-09-03
-    if (IsPointOnSliceIfAny(v))
-    {
-      points_.push_back(v);
-    }
-#else
-    CheckPoint(v);
-    points_.push_back(v);
-#endif 
+    const OrthancPlugins::DicomTag& tag = dicomPath.GetFinalTag();
+    Orthanc::DicomTag tag2(tag.GetGroup(), tag.GetElement());
+    tmp << tag2.GetMainTagsName() << " (" << tag2.Format() << ")";
+    s << tmp.str();
+    return s;
   }
 
 
-  bool DicomStructureSet2::Polygon::UpdateReferencedSlice(const ReferencedSlices& slices)
+  void DicomStructureSet2::SetContents(const OrthancPlugins::FullOrthancDataset& tags)
   {
-    if (hasSlice_)
-    {
-      return true;
-    }
-    else
+    FillStructuresFromDataset(tags);
+    ComputeDependentProperties();
+  }
+
+  void DicomStructureSet2::ComputeDependentProperties()
+  {
+    for (size_t i = 0; i < structures_.size(); ++i)
     {
-      ReferencedSlices::const_iterator it = slices.find(sopInstanceUid_);
-      
-      if (it == slices.end())
-      {
-        return false;
-      }
-      else
-      {
-        const CoordinateSystem3D& geometry = it->second.geometry_;
-        
-        hasSlice_ = true;
-        geometry_ = geometry;
-        projectionAlongNormal_ = GeometryToolbox::ProjectAlongNormal(geometry.GetOrigin(), geometry.GetNormal());
-        sliceThickness_ = it->second.thickness_;
-
-        extent_.Reset();
-        
-        for (Points::const_iterator it = points_.begin(); it != points_.end(); ++it)
-        {
-          if (IsPointOnSliceIfAny(*it))
-          {
-            double x, y;
-            geometry.ProjectPoint(x, y, *it);
-            extent_.AddPoint(x, y);
-          }
-        }
-        return true;
-      }
+      structures_[i].ComputeDependentProperties();
     }
   }
 
-  bool DicomStructureSet2::Polygon::IsOnSlice(const CoordinateSystem3D& slice) const
-  {
-    bool isOpposite = false;
-
-    if (points_.empty() ||
-        !hasSlice_ ||
-        !GeometryToolbox::IsParallelOrOpposite(isOpposite, slice.GetNormal(), geometry_.GetNormal()))
-    {
-      return false;
-    }
-    
-    double d = GeometryToolbox::ProjectAlongNormal(slice.GetOrigin(), geometry_.GetNormal());
-
-    return (LinearAlgebra::IsNear(d, projectionAlongNormal_,
-                                  sliceThickness_ / 2.0));
-  }
-    
-  bool DicomStructureSet2::Polygon::Project(double& x1,
-                                           double& y1,
-                                           double& x2,
-                                           double& y2,
-                                           const CoordinateSystem3D& slice) const
-  {
-    // TODO: optimize this method using a sweep-line algorithm for polygons
-    
-    if (!hasSlice_ ||
-        points_.size() <= 1)
-    {
-      return false;
-    }
-
-    double x, y;
-    geometry_.ProjectPoint(x, y, slice.GetOrigin());
-      
-    bool isOpposite;
-    if (GeometryToolbox::IsParallelOrOpposite
-        (isOpposite, slice.GetNormal(), geometry_.GetAxisY()))
-    {
-      if (y < extent_.GetY1() ||
-          y > extent_.GetY2())
-      {
-        // The polygon does not intersect the input slice
-        return false;
-      }
-        
-      bool isFirst = true;
-      double xmin = std::numeric_limits<double>::infinity();
-      double xmax = -std::numeric_limits<double>::infinity();
-
-      double prevX, prevY;
-      geometry_.ProjectPoint(prevX, prevY, points_[points_.size() - 1]);
-        
-      for (size_t i = 0; i < points_.size(); i++)
-      {
-        // Reference: ../../Resources/Computations/IntersectSegmentAndHorizontalLine.py
-        double curX, curY;
-        geometry_.ProjectPoint(curX, curY, points_[i]);
-
-        if ((prevY < y && curY > y) ||
-            (prevY > y && curY < y))
-        {
-          double p = (curX * prevY - curY * prevX + y * (prevX - curX)) / (prevY - curY);
-          xmin = std::min(xmin, p);
-          xmax = std::max(xmax, p);
-          isFirst = false;
-        }
-
-        prevX = curX;
-        prevY = curY;
-      }
-        
-      if (isFirst)
-      {
-        return false;
-      }
-      else
-      {
-        Vector p1 = (geometry_.MapSliceToWorldCoordinates(xmin, y) +
-                     sliceThickness_ / 2.0 * geometry_.GetNormal());
-        Vector p2 = (geometry_.MapSliceToWorldCoordinates(xmax, y) -
-                     sliceThickness_ / 2.0 * geometry_.GetNormal());
-          
-        slice.ProjectPoint(x1, y1, p1);
-        slice.ProjectPoint(x2, y2, p2);
-        return true;
-      }
-    }
-    else if (GeometryToolbox::IsParallelOrOpposite
-             (isOpposite, slice.GetNormal(), geometry_.GetAxisX()))
-    {
-      if (x < extent_.GetX1() ||
-          x > extent_.GetX2())
-      {
-        return false;
-      }
-
-      bool isFirst = true;
-      double ymin = std::numeric_limits<double>::infinity();
-      double ymax = -std::numeric_limits<double>::infinity();
-
-      double prevX, prevY;
-      geometry_.ProjectPoint(prevX, prevY, points_[points_.size() - 1]);
-        
-      for (size_t i = 0; i < points_.size(); i++)
-      {
-        // Reference: ../../Resources/Computations/IntersectSegmentAndVerticalLine.py
-        double curX, curY;
-        geometry_.ProjectPoint(curX, curY, points_[i]);
-
-        if ((prevX < x && curX > x) ||
-            (prevX > x && curX < x))
-        {
-          double p = (curX * prevY - curY * prevX + x * (curY - prevY)) / (curX - prevX);
-          ymin = std::min(ymin, p);
-          ymax = std::max(ymax, p);
-          isFirst = false;
-        }
-
-        prevX = curX;
-        prevY = curY;
-      }
-        
-      if (isFirst)
-      {
-        return false;
-      }
-      else
-      {
-        Vector p1 = (geometry_.MapSliceToWorldCoordinates(x, ymin) +
-                     sliceThickness_ / 2.0 * geometry_.GetNormal());
-        Vector p2 = (geometry_.MapSliceToWorldCoordinates(x, ymax) -
-                     sliceThickness_ / 2.0 * geometry_.GetNormal());
-
-        slice.ProjectPoint(x1, y1, p1);
-        slice.ProjectPoint(x2, y2, p2);
-
-        // TODO WHY THIS???
-        y1 = -y1;
-        y2 = -y2;
-
-        return true;
-      }
-    }
-    else
-    {
-      // Should not happen
-      return false;
-    }
-  }
-
-  
-  const DicomStructureSet2::Structure& DicomStructureSet2::GetStructure(size_t index) const
-  {
-    if (index >= structures_.size())
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-    }
-
-    return structures_[index];
-  }
-
-
-  DicomStructureSet2::Structure& DicomStructureSet2::GetStructure(size_t index)
-  {
-    if (index >= structures_.size())
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-    }
-
-    return structures_[index];
-  }
-
-  DicomStructureSet2::DicomStructureSet2(const OrthancPlugins::FullOrthancDataset& tags)
+  void DicomStructureSet2::FillStructuresFromDataset(const OrthancPlugins::FullOrthancDataset& tags)
   {
     OrthancPlugins::DicomDatasetReader reader(tags);
-    
-    size_t count, tmp;
+
+    // a few sanity checks
+    size_t count = 0, tmp = 0;
+
+    //  DICOM_TAG_RT_ROI_OBSERVATIONS_SEQUENCE (0x3006, 0x0080);
+    //  DICOM_TAG_ROI_CONTOUR_SEQUENCE         (0x3006, 0x0039);
+    //  DICOM_TAG_STRUCTURE_SET_ROI_SEQUENCE   (0x3006, 0x0020);
     if (!tags.GetSequenceSize(count, DICOM_TAG_RT_ROI_OBSERVATIONS_SEQUENCE) ||
-        !tags.GetSequenceSize(tmp, DICOM_TAG_ROI_CONTOUR_SEQUENCE) ||
-        tmp != count ||
-        !tags.GetSequenceSize(tmp, DICOM_TAG_STRUCTURE_SET_ROI_SEQUENCE) ||
-        tmp != count)
+      !tags.GetSequenceSize(tmp, DICOM_TAG_ROI_CONTOUR_SEQUENCE) ||
+      tmp != count ||
+      !tags.GetSequenceSize(tmp, DICOM_TAG_STRUCTURE_SET_ROI_SEQUENCE) ||
+      tmp != count)
     {
       throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
     }
 
+    // let's now parse the structures stored in the dicom file
+    //  DICOM_TAG_RT_ROI_OBSERVATIONS_SEQUENCE  (0x3006, 0x0080)
+    //  DICOM_TAG_RT_ROI_INTERPRETED_TYPE       (0x3006, 0x00a4)
+    //  DICOM_TAG_ROI_DISPLAY_COLOR             (0x3006, 0x002a)
+    //  DICOM_TAG_ROI_NAME                      (0x3006, 0x0026)
     structures_.resize(count);
     for (size_t i = 0; i < count; i++)
     {
+      // (0x3006, 0x0080)[i]/(0x3006, 0x00a4)
       structures_[i].interpretation_ = reader.GetStringValue
-        (OrthancPlugins::DicomPath(DICOM_TAG_RT_ROI_OBSERVATIONS_SEQUENCE, i,
-                                   DICOM_TAG_RT_ROI_INTERPRETED_TYPE),
-         "No interpretation");
+      (OrthancPlugins::DicomPath(DICOM_TAG_RT_ROI_OBSERVATIONS_SEQUENCE, i,
+        DICOM_TAG_RT_ROI_INTERPRETED_TYPE),
+        "No interpretation");
 
+      // (0x3006, 0x0020)[i]/(0x3006, 0x0026)
       structures_[i].name_ = reader.GetStringValue
-        (OrthancPlugins::DicomPath(DICOM_TAG_STRUCTURE_SET_ROI_SEQUENCE, i,
-                                   DICOM_TAG_ROI_NAME),
-         "No name");
+      (OrthancPlugins::DicomPath(DICOM_TAG_STRUCTURE_SET_ROI_SEQUENCE, i,
+        DICOM_TAG_ROI_NAME),
+        "No name");
 
       Vector color;
-      if (ParseVector(color, tags, OrthancPlugins::DicomPath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, i,
-                                                             DICOM_TAG_ROI_DISPLAY_COLOR)) &&
-          color.size() == 3)
+      // (0x3006, 0x0039)[i]/(0x3006, 0x002a)
+      if (ReadDicomToVector(color, tags, OrthancPlugins::DicomPath(
+        DICOM_TAG_ROI_CONTOUR_SEQUENCE, i, DICOM_TAG_ROI_DISPLAY_COLOR)) 
+        && color.size() == 3)
       {
-        structures_[i].red_ = ConvertColor(color[0]);
-        structures_[i].green_ = ConvertColor(color[1]);
-        structures_[i].blue_ = ConvertColor(color[2]);
+        structures_[i].red_   = ConvertAndClipToByte(color[0]);
+        structures_[i].green_ = ConvertAndClipToByte(color[1]);
+        structures_[i].blue_  = ConvertAndClipToByte(color[2]);
       }
       else
       {
@@ -445,91 +163,103 @@
       }
 
       size_t countSlices;
-      if (!tags.GetSequenceSize(countSlices, OrthancPlugins::DicomPath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, i,
-                                                                       DICOM_TAG_CONTOUR_SEQUENCE)))
+      //  DICOM_TAG_ROI_CONTOUR_SEQUENCE          (0x3006, 0x0039);
+      //  DICOM_TAG_CONTOUR_SEQUENCE              (0x3006, 0x0040);
+      if (!tags.GetSequenceSize(countSlices, OrthancPlugins::DicomPath(
+        DICOM_TAG_ROI_CONTOUR_SEQUENCE, i, DICOM_TAG_CONTOUR_SEQUENCE)))
       {
+        LOG(WARNING) << "DicomStructureSet2::SetContents | structure \"" << structures_[i].name_ << "\" has no slices!";
         countSlices = 0;
       }
 
-      LOG(INFO) << "New RT structure: \"" << structures_[i].name_ 
-                   << "\" with interpretation \"" << structures_[i].interpretation_
-                   << "\" containing " << countSlices << " slices (color: " 
-                   << static_cast<int>(structures_[i].red_) << "," 
-                   << static_cast<int>(structures_[i].green_) << ","
-                   << static_cast<int>(structures_[i].blue_) << ")";
+      LOG(INFO) << "New RT structure: \"" << structures_[i].name_
+        << "\" with interpretation \"" << structures_[i].interpretation_
+        << "\" containing " << countSlices << " slices (color: "
+        << static_cast<int>(structures_[i].red_) << ","
+        << static_cast<int>(structures_[i].green_) << ","
+        << static_cast<int>(structures_[i].blue_) << ")";
 
       // These temporary variables avoid allocating many vectors in the loop below
-      OrthancPlugins::DicomPath countPointsPath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, i,
-                                                DICOM_TAG_CONTOUR_SEQUENCE, 0,
-                                                DICOM_TAG_NUMBER_OF_CONTOUR_POINTS);
+      
+      // (0x3006, 0x0039)[i]/(0x3006, 0x0040)[0]/(0x3006, 0x0046)
+      OrthancPlugins::DicomPath countPointsPath(
+        DICOM_TAG_ROI_CONTOUR_SEQUENCE, i,
+        DICOM_TAG_CONTOUR_SEQUENCE, 0,
+        DICOM_TAG_NUMBER_OF_CONTOUR_POINTS);
 
-      OrthancPlugins::DicomPath geometricTypePath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, i,
-                                                  DICOM_TAG_CONTOUR_SEQUENCE, 0,
-                                                  DICOM_TAG_CONTOUR_GEOMETRIC_TYPE);
-      
-      OrthancPlugins::DicomPath imageSequencePath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, i,
-                                                  DICOM_TAG_CONTOUR_SEQUENCE, 0,
-                                                  DICOM_TAG_CONTOUR_IMAGE_SEQUENCE);
+      OrthancPlugins::DicomPath geometricTypePath(
+        DICOM_TAG_ROI_CONTOUR_SEQUENCE, i,
+        DICOM_TAG_CONTOUR_SEQUENCE, 0,
+        DICOM_TAG_CONTOUR_GEOMETRIC_TYPE);
+
+      OrthancPlugins::DicomPath imageSequencePath(
+        DICOM_TAG_ROI_CONTOUR_SEQUENCE, i,
+        DICOM_TAG_CONTOUR_SEQUENCE, 0,
+        DICOM_TAG_CONTOUR_IMAGE_SEQUENCE);
 
       // (3006,0039)[i] / (0x3006, 0x0040)[0] / (0x3006, 0x0016)[0] / (0x0008, 0x1155)
-      OrthancPlugins::DicomPath referencedInstancePath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, i,
-                                                       DICOM_TAG_CONTOUR_SEQUENCE, 0,
-                                                       DICOM_TAG_CONTOUR_IMAGE_SEQUENCE, 0,
-                                                       DICOM_TAG_REFERENCED_SOP_INSTANCE_UID);
+      OrthancPlugins::DicomPath referencedInstancePath(
+        DICOM_TAG_ROI_CONTOUR_SEQUENCE, i,
+        DICOM_TAG_CONTOUR_SEQUENCE, 0,
+        DICOM_TAG_CONTOUR_IMAGE_SEQUENCE, 0,
+        DICOM_TAG_REFERENCED_SOP_INSTANCE_UID);
 
-      OrthancPlugins::DicomPath contourDataPath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, i,
-                                                DICOM_TAG_CONTOUR_SEQUENCE, 0,
-                                                DICOM_TAG_CONTOUR_DATA);
+      OrthancPlugins::DicomPath contourDataPath(
+        DICOM_TAG_ROI_CONTOUR_SEQUENCE, i,
+        DICOM_TAG_CONTOUR_SEQUENCE, 0,
+        DICOM_TAG_CONTOUR_DATA);
 
       for (size_t j = 0; j < countSlices; j++)
       {
-        unsigned int countPoints;
+        unsigned int countPoints = 0;
 
         countPointsPath.SetPrefixIndex(1, j);
         if (!reader.GetUnsignedIntegerValue(countPoints, countPointsPath))
         {
+          LOG(ERROR) << "Dicom path " << countPointsPath << " is not valid (should contain an unsigned integer)";
           throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
         }
-            
+
         //LOG(INFO) << "Parsing slice containing " << countPoints << " vertices";
 
         geometricTypePath.SetPrefixIndex(1, j);
         std::string type = reader.GetMandatoryStringValue(geometricTypePath);
         if (type != "CLOSED_PLANAR")
         {
+          // TODO: support points!!
           LOG(WARNING) << "Ignoring contour with geometry type: " << type;
           continue;
         }
 
-        size_t size;
+        size_t size = 0;
 
         imageSequencePath.SetPrefixIndex(1, j);
         if (!tags.GetSequenceSize(size, imageSequencePath) || size != 1)
         {
           LOG(ERROR) << "The ContourImageSequence sequence (tag 3006,0016) must be present and contain one entry.";
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);          
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
         }
 
         referencedInstancePath.SetPrefixIndex(1, j);
         std::string sopInstanceUid = reader.GetMandatoryStringValue(referencedInstancePath);
 
-        contourDataPath.SetPrefixIndex(1, j);        
+        contourDataPath.SetPrefixIndex(1, j);
         std::string slicesData = reader.GetMandatoryStringValue(contourDataPath);
 
         Vector points;
         if (!LinearAlgebra::ParseVector(points, slicesData) ||
-            points.size() != 3 * countPoints)
+          points.size() != 3 * countPoints)
         {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);          
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
         }
 
         // seen in real world
-        if(Orthanc::Toolbox::StripSpaces(sopInstanceUid) == "") 
+        if (Orthanc::Toolbox::StripSpaces(sopInstanceUid) == "")
         {
           LOG(ERROR) << "WARNING. The following Dicom tag (Referenced SOP Instance UID) contains an empty value : // (3006,0039)[" << i << "] / (0x3006, 0x0040)[0] / (0x3006, 0x0016)[0] / (0x0008, 0x1155)";
         }
 
-        Polygon polygon(sopInstanceUid);
+        DicomStructurePolygon2 polygon(sopInstanceUid,type);
         polygon.Reserve(countPoints);
 
         for (size_t k = 0; k < countPoints; k++)
@@ -540,297 +270,15 @@
           v[2] = points[3 * k + 2];
           polygon.AddPoint(v);
         }
-
-        structures_[i].polygons_.push_back(polygon);
-      }
-    }
-  }
-
-
-  Vector DicomStructureSet2::GetStructureCenter(size_t index) const
-  {
-    const Structure& structure = GetStructure(index);
-
-    Vector center;
-    LinearAlgebra::AssignVector(center, 0, 0, 0);
-    if (structure.polygons_.empty())
-    {
-      return center;
-    }
-
-    double n = static_cast<double>(structure.polygons_.size());
-
-    for (Polygons::const_iterator polygon = structure.polygons_.begin();
-         polygon != structure.polygons_.end(); ++polygon)
-    {
-      if (!polygon->GetPoints().empty())
-      {
-        center += polygon->GetPoints().front() / n;
-      }
-    }
-
-    return center;
-  }
-
-
-  const std::string& DicomStructureSet2::GetStructureName(size_t index) const
-  {
-    return GetStructure(index).name_;
-  }
-
-
-  const std::string& DicomStructureSet2::GetStructureInterpretation(size_t index) const
-  {
-    return GetStructure(index).interpretation_;
-  }
-
-
-  Color DicomStructureSet2::GetStructureColor(size_t index) const
-  {
-    const Structure& s = GetStructure(index);
-    return Color(s.red_, s.green_, s.blue_);
-  }
-  
-    
-  void DicomStructureSet2::GetStructureColor(uint8_t& red,
-                                            uint8_t& green,
-                                            uint8_t& blue,
-                                            size_t index) const
-  {
-    const Structure& s = GetStructure(index);
-    red = s.red_;
-    green = s.green_;
-    blue = s.blue_;
-  }
-
-
-  void DicomStructureSet2::GetReferencedInstances(std::set<std::string>& instances)
-  {
-    for (Structures::const_iterator structure = structures_.begin();
-         structure != structures_.end(); ++structure)
-    {
-      for (Polygons::const_iterator polygon = structure->polygons_.begin();
-           polygon != structure->polygons_.end(); ++polygon)
-      {
-        instances.insert(polygon->GetSopInstanceUid());
-      }
-    }
-  }
-
-
-  void DicomStructureSet2::AddReferencedSlice(const std::string& sopInstanceUid,
-                                             const std::string& seriesInstanceUid,
-                                             const CoordinateSystem3D& geometry,
-                                             double thickness)
-  {
-    if (referencedSlices_.find(sopInstanceUid) != referencedSlices_.end())
-    {
-      // This geometry is already known
-      LOG(ERROR) << "DicomStructureSet2::AddReferencedSlice(): (referencedSlices_.find(sopInstanceUid) != referencedSlices_.end()). sopInstanceUid = " << sopInstanceUid;
-     
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-    }
-    else
-    {
-      if (thickness < 0)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-      }
-        
-      if (!referencedSlices_.empty())
-      {
-        const ReferencedSlice& reference = referencedSlices_.begin()->second;
-
-        if (reference.seriesInstanceUid_ != seriesInstanceUid)
-        {
-          LOG(ERROR) << "This RT-STRUCT refers to several different series";
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
-        }
-
-        if (!GeometryToolbox::IsParallel(reference.geometry_.GetNormal(), geometry.GetNormal()))
-        {
-          LOG(ERROR) << "The slices in this RT-STRUCT are not parallel";
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
-        }
-      }
-        
-      referencedSlices_[sopInstanceUid] = ReferencedSlice(seriesInstanceUid, geometry, thickness);
-
-      for (Structures::iterator structure = structures_.begin();
-           structure != structures_.end(); ++structure)
-      {
-        for (Polygons::iterator polygon = structure->polygons_.begin();
-             polygon != structure->polygons_.end(); ++polygon)
-        {
-          polygon->UpdateReferencedSlice(referencedSlices_);
-        }
+        structures_[i].AddPolygon(polygon);
       }
     }
   }
 
 
-  void DicomStructureSet2::AddReferencedSlice(const Orthanc::DicomMap& dataset)
-  {
-    CoordinateSystem3D slice(dataset);
-
-    double thickness = 1;  // 1 mm by default
-
-    std::string s;
-    Vector v;
-    if (dataset.LookupStringValue(s, Orthanc::DICOM_TAG_SLICE_THICKNESS, false) &&
-        LinearAlgebra::ParseVector(v, s) &&
-        v.size() > 0)
-    {
-      thickness = v[0];
-    }
-
-    std::string instance, series;
-    if (dataset.LookupStringValue(instance, Orthanc::DICOM_TAG_SOP_INSTANCE_UID, false) &&
-        dataset.LookupStringValue(series, Orthanc::DICOM_TAG_SERIES_INSTANCE_UID, false))
-    {
-      AddReferencedSlice(instance, series, slice, thickness);
-    }
-    else
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
-    }
-  }
-
-
-  void DicomStructureSet2::CheckReferencedSlices()
+  void DicomStructureSet2::Clear()
   {
-    for (Structures::iterator structure = structures_.begin();
-         structure != structures_.end(); ++structure)
-    {
-      for (Polygons::iterator polygon = structure->polygons_.begin();
-           polygon != structure->polygons_.end(); ++polygon)
-      {
-        if (!polygon->UpdateReferencedSlice(referencedSlices_))
-        {
-          std::string sopInstanceUid = polygon->GetSopInstanceUid();
-          if (Orthanc::Toolbox::StripSpaces(sopInstanceUid) == "")
-          {
-            LOG(ERROR) << "DicomStructureSet2::CheckReferencedSlices(): "
-              << " missing information about referenced instance "
-              << "(sopInstanceUid is empty!)";
-          }
-          else
-          {
-            LOG(ERROR) << "DicomStructureSet2::CheckReferencedSlices(): "
-              << " missing information about referenced instance "
-              << "(sopInstanceUid = " << sopInstanceUid << ")";
-          }
-          //throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-        }
-      }
-    }
-  }
-
-
-  Vector DicomStructureSet2::GetNormal() const
-  {
-    if (referencedSlices_.empty())
-    {
-      Vector v;
-      LinearAlgebra::AssignVector(v, 0, 0, 1);
-      return v;
-    }
-    else
-    {
-      return referencedSlices_.begin()->second.geometry_.GetNormal();
-    }
+    structures_.clear();
   }
 
-  
-  bool DicomStructureSet2::ProjectStructure(std::vector< std::vector<PolygonPoint2D> >& polygons,
-                                           const Structure& structure,
-                                           const CoordinateSystem3D& slice) const
-  {
-    polygons.clear();
-
-    Vector normal = GetNormal();
-    
-    bool isOpposite;    
-    if (GeometryToolbox::IsParallelOrOpposite(isOpposite, normal, slice.GetNormal()))
-    {
-      // This is an axial projection
-
-      for (Polygons::const_iterator polygon = structure.polygons_.begin();
-           polygon != structure.polygons_.end(); ++polygon)
-      {
-        if (polygon->IsOnSlice(slice))
-        {
-          polygons.push_back(std::vector<PolygonPoint2D>());
-          
-          for (Points::const_iterator p = polygon->GetPoints().begin();
-               p != polygon->GetPoints().end(); ++p)
-          {
-            double x, y;
-            slice.ProjectPoint(x, y, *p);
-            polygons.back().push_back(std::make_pair(x, y));
-          }
-        }
-      }
-
-      return true;
-    }
-    else if (GeometryToolbox::IsParallelOrOpposite(isOpposite, normal, slice.GetAxisX()) ||
-             GeometryToolbox::IsParallelOrOpposite(isOpposite, normal, slice.GetAxisY()))
-    {
-#if 1
-      // Sagittal or coronal projection
-      std::vector<BoostPolygon> projected;
-  
-      for (Polygons::const_iterator polygon = structure.polygons_.begin();
-           polygon != structure.polygons_.end(); ++polygon)
-      {
-        double x1, y1, x2, y2;
-        if (polygon->Project(x1, y1, x2, y2, slice))
-        {
-          projected.push_back(CreateRectangle(
-            static_cast<float>(x1), 
-            static_cast<float>(y1), 
-            static_cast<float>(x2), 
-            static_cast<float>(y2)));
-        }
-      }
-
-      BoostMultiPolygon merged;
-      Union(merged, projected);
-
-      polygons.resize(merged.size());
-      for (size_t i = 0; i < merged.size(); i++)
-      {
-        const std::vector<BoostPoint>& outer = merged[i].outer();
-
-        polygons[i].resize(outer.size());
-        for (size_t j = 0; j < outer.size(); j++)
-        {
-          polygons[i][j] = std::make_pair(outer[j].x(), outer[j].y());
-        }
-      }  
-#else
-      for (Polygons::iterator polygon = structure.polygons_.begin();
-           polygon != structure.polygons_.end(); ++polygon)
-      {
-        double x1, y1, x2, y2;
-        if (polygon->Project(x1, y1, x2, y2, slice))
-        {
-          std::vector<PolygonPoint2D> p(4);
-          p[0] = std::make_pair(x1, y1);
-          p[1] = std::make_pair(x2, y1);
-          p[2] = std::make_pair(x2, y2);
-          p[3] = std::make_pair(x1, y2);
-          polygons.push_back(p);
-        }
-      }
-#endif
-      
-      return true;
-    }
-    else
-    {
-      return false;
-    }
-  }
 }
--- a/Framework/Toolbox/DicomStructureSet2.h	Sat Sep 14 17:27:41 2019 +0200
+++ b/Framework/Toolbox/DicomStructureSet2.h	Fri Sep 20 11:58:00 2019 +0200
@@ -18,9 +18,9 @@
  * along with this program. If not, see <http://www.gnu.org/licenses/>.
  **/
 
-
 #pragma once
 
+#include "DicomStructure2.h"
 #include "CoordinateSystem3D.h"
 #include "Extent2D.h"
 #include "../Scene2D/Color.h"
@@ -31,155 +31,34 @@
 
 namespace OrthancStone
 {
-   class DicomStructureSet2 : public boost::noncopyable
+  class DicomStructureSet2 : public boost::noncopyable
   {
   public:
-    typedef std::pair<double, double> PolygonPoint2D;
-    
-  private:
-    struct ReferencedSlice
-    {
-      std::string          seriesInstanceUid_;
-      CoordinateSystem3D   geometry_;
-      double               thickness_;
-
-      ReferencedSlice()
-      {
-      }
-      
-      ReferencedSlice(const std::string& seriesInstanceUid,
-                      const CoordinateSystem3D& geometry,
-                      double thickness) :
-        seriesInstanceUid_(seriesInstanceUid),
-        geometry_(geometry),
-        thickness_(thickness)
-      {
-      }
-    };
-
-    typedef std::map<std::string, ReferencedSlice>  ReferencedSlices;
-    
-    typedef std::vector<Vector>  Points;
-
-    class Polygon
-    {
-    private:
-      std::string         sopInstanceUid_;
-      bool                hasSlice_;
-      CoordinateSystem3D  geometry_;
-      double              projectionAlongNormal_;
-      double              sliceThickness_;  // In millimeters
-      Points              points_;
-      Extent2D            extent_;
-
-      void CheckPointIsOnSlice(const Vector& v) const;
-      bool IsPointOnSliceIfAny(const Vector& v) const;
-
-    public:
-      Polygon(const std::string& sopInstanceUid) :
-        sopInstanceUid_(sopInstanceUid),
-        hasSlice_(false)
-      {
-      }
-
-      void Reserve(size_t n)
-      {
-        points_.reserve(n);
-      }
-
-      void AddPoint(const Vector& v);
-
-      bool UpdateReferencedSlice(const ReferencedSlices& slices);
-
-      bool IsOnSlice(const CoordinateSystem3D& geometry) const;
-
-      const std::string& GetSopInstanceUid() const
-      {
-        return sopInstanceUid_;
-      }
-
-      const Points& GetPoints() const
-      {
-        return points_;
-      }
-
-      double GetSliceThickness() const
-      {
-        return sliceThickness_;
-      }
-
-      bool Project(double& x1,
-                   double& y1,
-                   double& x2,
-                   double& y2,
-                   const CoordinateSystem3D& slice) const;
-    };
-
-    typedef std::list<Polygon>  Polygons;
-
-    struct Structure
-    {
-      std::string   name_;
-      std::string   interpretation_;
-      Polygons      polygons_;
-      uint8_t       red_;
-      uint8_t       green_;
-      uint8_t       blue_;
-    };
-
-    typedef std::vector<Structure>  Structures;
-
-    Structures        structures_;
-    ReferencedSlices  referencedSlices_;
-
-    const Structure& GetStructure(size_t index) const;
-
-    Structure& GetStructure(size_t index);
-  
-    bool ProjectStructure(std::vector< std::vector<PolygonPoint2D> >& polygons,
-                          const Structure& structure,
-                          const CoordinateSystem3D& slice) const;
-
-  public:
-    DicomStructureSet2(const OrthancPlugins::FullOrthancDataset& instance);
+    DicomStructureSet2();
+    ~DicomStructureSet2();
+   
+    void SetContents(const OrthancPlugins::FullOrthancDataset& tags);
 
     size_t GetStructuresCount() const
     {
       return structures_.size();
     }
 
-    Vector GetStructureCenter(size_t index) const;
-
-    const std::string& GetStructureName(size_t index) const;
-
-    const std::string& GetStructureInterpretation(size_t index) const;
-
-    Color GetStructureColor(size_t index) const;
+    void Clear();
 
-    // TODO - remove
-    void GetStructureColor(uint8_t& red,
-                           uint8_t& green,
-                           uint8_t& blue,
-                           size_t index) const;
-
-    void GetReferencedInstances(std::set<std::string>& instances);
+    const DicomStructure2& GetStructure(size_t i) const
+    {
+      // at() is like []() but with range check
+      return structures_.at(i);
+    }
 
-    void AddReferencedSlice(const std::string& sopInstanceUid,
-                            const std::string& seriesInstanceUid,
-                            const CoordinateSystem3D& geometry,
-                            double thickness);
-
-    void AddReferencedSlice(const Orthanc::DicomMap& dataset);
-
-    void CheckReferencedSlices();
+    /** Internal use only */
+    void FillStructuresFromDataset(const OrthancPlugins::FullOrthancDataset& tags);
 
-    Vector GetNormal() const;
+    /** Internal use only */
+    void ComputeDependentProperties();
 
-    bool ProjectStructure(std::vector< std::vector<PolygonPoint2D> >& polygons,
-                          size_t index,
-                          const CoordinateSystem3D& slice) const
-    {
-      return ProjectStructure(polygons, GetStructure(index), slice);
-    }
+    /** Internal use only */
+    std::vector<DicomStructure2> structures_;
   };
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Toolbox/DicomStructureSetUtils.cpp	Fri Sep 20 11:58:00 2019 +0200
@@ -0,0 +1,276 @@
+/**
+ * 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 "DicomStructureSetUtils.h"
+
+namespace OrthancStone
+{
+
+#if 0
+  void DicomStructure2::PartitionRectangleList(std::vector< std::vector<size_t> > & sets, const std::vector<RtStructRectanglesInSlab> slabCuts)
+  {
+    // map position ( <slabIndex,rectIndex> )--> disjoint set index
+    std::map<std::pair<size_t, size_t>, size_t> posToIndex;
+
+    // disjoint set index --> position
+    std::map<size_t, std::pair<size_t, size_t> > indexToPos;
+
+    size_t nextIndex = 0;
+    for (size_t i = 0; i < slabCuts.size(); ++i)
+    {
+      for (size_t j = 0; j < slabCuts[i].size(); ++j)
+      {
+        std::pair<size_t, size_t> pos(i, j);
+        posToIndex<pos> = nextIndex;
+        indexToPos<nextIndex> = pos;
+      }
+    }
+    // nextIndex is now the total rectangle count
+    DisjointDataSet ds(nextIndex);
+
+    // we loop on all slabs (except the last one) and we connect all rectangles
+    if (slabCuts.size() < 2)
+    {
+#error write special case
+    }
+    else
+    {
+      for (size_t i = 0; i < slabCuts.size() - 1; ++i)
+      {
+        for (size_t j = 0; j < slabCuts[i].size(); ++j)
+        {
+          const RtStructRectangleInSlab& r1 = slabCuts[i][j];
+          const size_t r1i = posToIndex(std::pair<size_t, size_t>(i, j));
+          for (size_t k = 0; k < slabCuts[i + 1].size(); ++k)
+          {
+            const RtStructRectangleInSlab& r2 = slabCuts[i + 1][k];
+            const size_t r2i = posToIndex(std::pair<size_t, size_t>(i, j));
+            // rect.xmin <= rectBottom.xmax && rectBottom.xmin <= rect.xmax
+            if ((r1.xmin <= r2.xmax) && (r2.xmin <= r1.xmax))
+            {
+#error now go!
+            }
+
+          }
+        }
+      }
+    }
+#endif
+
+    /*
+
+      compute list of segments :
+
+      numberOfRectsFromHereOn = 0
+      possibleNext = {in_k,in_kplus1}
+
+      for all boundaries:
+        - we create a vertical segment and we push it
+        - if boundary is a start, numberOfRectsFromHereOn += 1.
+          - if we switch from 0 to 1, we start a segment
+          - if we switch from 1 to 2, we end the current segment and we record it
+        - if boundary is an end, numberOfRectsFromHereOn -= 1.
+          - if we switch from 1 to 0, we end the current segment and we record it
+          - if we switch from 2 to 1, we start a segment
+    */
+
+    // static
+    void AddSlabBoundaries(
+      std::vector<std::pair<double, RectangleBoundaryKind> > & boundaries,
+      const std::vector<RtStructRectanglesInSlab> & slabCuts, size_t iSlab)
+    {
+      if (iSlab < slabCuts.size())
+      {
+        const RtStructRectanglesInSlab& slab = slabCuts[iSlab];
+        for (size_t iRect = 0; iRect < slab.size(); ++iRect)
+        {
+          const RtStructRectangleInSlab& rect = slab[iRect];
+          {
+            std::pair<double, RectangleBoundaryKind> boundary(rect.xmin, RectangleBoundaryKind_Start);
+            boundaries.insert(std::lower_bound(boundaries.begin(), boundaries.end(), boundary), boundary);
+          }
+          {
+            std::pair<double, RectangleBoundaryKind> boundary(rect.xmax, RectangleBoundaryKind_End);
+            boundaries.insert(std::lower_bound(boundaries.begin(), boundaries.end(), boundary), boundary);
+          }
+        }
+      }
+    }
+
+    // static
+    void ProcessBoundaryList(
+      std::vector< std::pair<Point2D, Point2D> > & segments,
+      const std::vector<std::pair<double, RectangleBoundaryKind> > & boundaries,
+      double y)
+    {
+      Point2D start;
+      Point2D end;
+      int curNumberOfSegments = 0; // we count the number of segments. we only draw if it is 1 (not 0 or 2)
+      for (size_t i = 0; i < boundaries.size(); ++i)
+      {
+        switch (boundaries[i].second)
+        {
+        case RectangleBoundaryKind_Start:
+          curNumberOfSegments += 1;
+          switch (curNumberOfSegments)
+          {
+          case 0:
+            assert(false);
+            break;
+          case 1:
+            // a new segment has begun!
+            start.x = boundaries[i].first;
+            start.y = y;
+            break;
+          case 2:
+            // an extra segment has begun : stop the current one (we don't draw overlaps)
+            end.x = boundaries[i].first;
+            end.y = y;
+            segments.push_back(std::pair<Point2D, Point2D>(start, end));
+            break;
+          default:
+            //assert(false); // seen IRL ! 
+            break;
+          }
+          break;
+        case RectangleBoundaryKind_End:
+          curNumberOfSegments -= 1;
+          switch (curNumberOfSegments)
+          {
+          case 0:
+            // a lone (thus active) segment has ended.
+            end.x = boundaries[i].first;
+            end.y = y;
+            segments.push_back(std::pair<Point2D, Point2D>(start, end));
+            break;
+          case 1:
+            // an extra segment has ended : start a new one one
+            start.x = boundaries[i].first;
+            start.y = y;
+            break;
+          default:
+            // this should not happen!
+            //assert(false);
+            break;
+          }
+          break;
+        default:
+          assert(false);
+          break;
+        }
+      }
+    }
+
+#if 0
+    void ConvertListOfSlabsToSegments(
+      std::vector< std::pair<Point2D, Point2D> >& segments,
+      const std::vector<RtStructRectanglesInSlab>& slabCuts,
+      const size_t totalRectCount)
+    {
+#error to delete 
+    }
+#else
+    // See https://www.dropbox.com/s/bllco6q8aazxk44/2019-09-18-rtstruct-cut-algorithm-rect-merge.png
+    void ConvertListOfSlabsToSegments(
+      std::vector< std::pair<Point2D, Point2D> > & segments,
+      const std::vector<RtStructRectanglesInSlab> & slabCuts,
+      const size_t totalRectCount)
+    {
+      if (slabCuts.size() == 0)
+        return;
+
+      if (totalRectCount > 0)
+        segments.reserve(4 * totalRectCount); // worst case, but common.
+
+      /*
+      VERTICAL
+      */
+      for (size_t iSlab = 0; iSlab < slabCuts.size(); ++iSlab)
+      {
+        for (size_t iRect = 0; iRect < slabCuts[iSlab].size(); ++iRect)
+        {
+          const RtStructRectangleInSlab& rect = slabCuts[iSlab][iRect];
+          {
+            Point2D p1(rect.xmin, rect.ymin);
+            Point2D p2(rect.xmin, rect.ymax);
+            segments.push_back(std::pair<Point2D, Point2D>(p1, p2));
+          }
+          {
+            Point2D p1(rect.xmax, rect.ymin);
+            Point2D p2(rect.xmax, rect.ymax);
+            segments.push_back(std::pair<Point2D, Point2D>(p1, p2));
+          }
+        }
+      }
+
+      /*
+      HORIZONTAL
+      */
+
+      // if we have N slabs, we have N+1 potential vertical positions for horizontal segments
+      // - one for top of slab 0
+      // - N-1 for all positions between two slabs
+      // - one for bottom of slab N-1
+
+      // this adds all the horizontal segments for the tops of 3the rectangles
+      // in row 0
+      if (slabCuts[0].size() > 0)
+      {
+        std::vector<std::pair<double, RectangleBoundaryKind> > boundaries;
+        AddSlabBoundaries(boundaries, slabCuts, 0);
+
+        ProcessBoundaryList(segments, boundaries, slabCuts[0][0].ymin);
+      }
+
+      // this adds all the horizontal segments belonging to two slabs
+      for (size_t iSlab = 0; iSlab < slabCuts.size() - 1; ++iSlab)
+      {
+        std::vector<std::pair<double, RectangleBoundaryKind> > boundaries;
+        AddSlabBoundaries(boundaries, slabCuts, iSlab);
+        AddSlabBoundaries(boundaries, slabCuts, iSlab + 1);
+        double curY = 0;
+        if (slabCuts[iSlab].size() > 0)
+        {
+          curY = slabCuts[iSlab][0].ymax;
+          ProcessBoundaryList(segments, boundaries, curY);
+        }
+        else if (slabCuts[iSlab + 1].size() > 0)
+        {
+          curY = slabCuts[iSlab + 1][0].ymin;
+          ProcessBoundaryList(segments, boundaries, curY);
+        }
+        else
+        {
+          // nothing to do!! : both slab lists are empty!
+        }
+      }
+
+      // this adds all the horizontal segments for the BOTTOM of the rectangles
+      // on last row 
+      if (slabCuts[slabCuts.size() - 1].size() > 0)
+      {
+        std::vector<std::pair<double, RectangleBoundaryKind> > boundaries;
+        AddSlabBoundaries(boundaries, slabCuts, slabCuts.size() - 1);
+
+        ProcessBoundaryList(segments, boundaries, slabCuts[slabCuts.size() - 1][0].ymax);
+      }
+    }
+#endif
+  }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Toolbox/DicomStructureSetUtils.h	Fri Sep 20 11:58:00 2019 +0200
@@ -0,0 +1,84 @@
+/**
+ * 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 <vector>
+#include <utility>
+
+#include "../Toolbox/LinearAlgebra.h"
+
+namespace OrthancStone
+{
+#if 0
+  struct Point3D
+  {
+    Point3D(double x, double y, double z) : x(x), y(y), z(z) {}
+    Point3D() : x(0), y(0), z(0) {}
+    double x, y, z;
+  };
+
+  struct Vector3D
+  {
+    Vector3D(double x, double y, double z) : x(x), y(y), z(z) {}
+    Vector3D() : x(0), y(0), z(0) {}
+    double x, y, z;
+  };
+#else
+  typedef Vector Vector3D;
+  typedef Vector Point3D;
+#endif
+
+  struct Point2D
+  {
+    Point2D(double x, double y) : x(x), y(y) {}
+    Point2D() : x(0), y(0) {}
+    double x, y;
+  };
+
+
+  /** Internal */
+  struct RtStructRectangleInSlab
+  {
+    double xmin, xmax, ymin, ymax;
+  };
+  typedef std::vector<RtStructRectangleInSlab> RtStructRectanglesInSlab;
+
+  enum RectangleBoundaryKind
+  {
+    RectangleBoundaryKind_Start,
+    RectangleBoundaryKind_End
+  };
+
+#if 0
+  /** Internal */
+  void PartitionRectangleList(std::vector< std::vector<size_t> > & sets, const std::vector<RtStructRectanglesInSlab>);
+#endif
+
+  /** Internal */
+  void ConvertListOfSlabsToSegments(std::vector< std::pair<Point2D, Point2D> >& segments, const std::vector<RtStructRectanglesInSlab>& slabCuts, const size_t totalRectCount);
+
+  /** Internal */
+  void AddSlabBoundaries(std::vector<std::pair<double, RectangleBoundaryKind> >& boundaries, const std::vector<RtStructRectanglesInSlab>& slabCuts, size_t iSlab);
+
+  /** Internal */
+  void ProcessBoundaryList(std::vector< std::pair<Point2D, Point2D> >& segments, const std::vector<std::pair<double, RectangleBoundaryKind> >& boundaries, double y);
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Volumes/DicomStructureSetSlicer2.cpp	Fri Sep 20 11:58:00 2019 +0200
@@ -0,0 +1,109 @@
+/**
+ * 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 "DicomStructureSetSlicer2.h"
+
+#include "../Toolbox/GeometryToolbox.h"
+#include "../Volumes/IVolumeSlicer.h"
+#include "../Scene2D/PolylineSceneLayer.h"
+
+namespace OrthancStone
+{
+  DicomStructureSetSlicer2::DicomStructureSetSlicer2(boost::shared_ptr<DicomStructureSet2> structureSet)
+    : structureSet_(structureSet)
+  {}
+
+  IVolumeSlicer::IExtractedSlice* DicomStructureSetSlicer2::ExtractSlice(const CoordinateSystem3D& cuttingPlane)
+  {
+    // revision is always the same, hence 0
+    return new DicomStructureSetSlice2(structureSet_, 0, cuttingPlane);
+  }
+
+  DicomStructureSetSlice2::DicomStructureSetSlice2(
+    boost::weak_ptr<DicomStructureSet2> structureSet, 
+    uint64_t revision, 
+    const CoordinateSystem3D& cuttingPlane) 
+    : structureSet_(structureSet.lock())
+    , isValid_(false)
+  {
+    bool opposite = false;
+
+    if (structureSet_->GetStructuresCount() == 0)
+    {
+      isValid_ = false;
+    }
+    else
+    {
+      // some structures seen in real life have no polygons. We must be 
+      // careful
+      bool found = false;
+      size_t curStructure = 0;
+      while (!found && curStructure < structureSet_->GetStructuresCount())
+      {
+        if (structureSet_->GetStructure(curStructure).IsValid())
+        {
+          found = true;
+          const Vector normal = structureSet_->GetStructure(0).GetNormal();
+          isValid_ = (
+            GeometryToolbox::IsParallelOrOpposite(opposite, normal, cuttingPlane.GetNormal()) ||
+            GeometryToolbox::IsParallelOrOpposite(opposite, normal, cuttingPlane.GetAxisX()) ||
+            GeometryToolbox::IsParallelOrOpposite(opposite, normal, cuttingPlane.GetAxisY()));
+        }
+      }
+    }
+  }
+
+  ISceneLayer* DicomStructureSetSlice2::CreateSceneLayer(
+    const ILayerStyleConfigurator* configurator,
+    const CoordinateSystem3D& cuttingPlane)
+  {
+    assert(isValid_);
+
+    std::auto_ptr<PolylineSceneLayer> layer(new PolylineSceneLayer);
+    layer->SetThickness(2); // thickness of the on-screen line
+
+    for (size_t i = 0; i < structureSet_->GetStructuresCount(); i++)
+    {
+      const DicomStructure2& structure = structureSet_->GetStructure(i);
+      if (structure.IsValid())
+      {
+        const Color& color = structure.GetColor();
+
+        std::vector< std::pair<Point2D, Point2D> > segments;
+
+        if (structure.Project(segments, cuttingPlane))
+        {
+          for (size_t j = 0; j < segments.size(); j++)
+          {
+            PolylineSceneLayer::Chain chain;
+            chain.resize(2);
+
+            chain[0] = ScenePoint2D(segments[j].first.x, segments[j].first.y);
+            chain[1] = ScenePoint2D(segments[j].second.x, segments[j].second.y);
+
+            layer->AddChain(chain, false /* NOT closed */, color);
+          }
+        }
+      }
+    }
+    return layer.release();
+  }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Volumes/DicomStructureSetSlicer2.h	Fri Sep 20 11:58:00 2019 +0200
@@ -0,0 +1,70 @@
+/**
+ * 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 "../Toolbox/DicomStructureSet2.h"
+#include "../Volumes/IVolumeSlicer.h"
+
+#include <boost/shared_ptr.hpp>
+#include <boost/weak_ptr.hpp>
+
+namespace OrthancStone
+{
+  class DicomStructureSetSlice2 : public IVolumeSlicer::IExtractedSlice
+  {
+  public:
+    DicomStructureSetSlice2(
+      boost::weak_ptr<DicomStructureSet2> structureSet,
+      uint64_t revision,
+      const CoordinateSystem3D& cuttingPlane);
+
+    virtual bool IsValid() ORTHANC_OVERRIDE
+    {
+      return isValid_;
+    }
+
+    virtual uint64_t GetRevision() ORTHANC_OVERRIDE
+    {
+      return revision_;
+    }
+
+    virtual ISceneLayer* CreateSceneLayer(
+      const ILayerStyleConfigurator* configurator,  // possibly absent
+      const CoordinateSystem3D& cuttingPlane) ORTHANC_OVERRIDE;
+
+  private:
+    boost::shared_ptr<DicomStructureSet2> structureSet_;
+    bool isValid_;
+    uint64_t revision_;
+  };
+
+  class DicomStructureSetSlicer2 : public IVolumeSlicer
+  {
+  public:
+    DicomStructureSetSlicer2(boost::shared_ptr<DicomStructureSet2> structureSet);
+
+    /** IVolumeSlicer impl */
+    virtual IExtractedSlice* ExtractSlice(const CoordinateSystem3D& cuttingPlane) ORTHANC_OVERRIDE;
+  private:
+    boost::weak_ptr<DicomStructureSet2> structureSet_;
+  };
+}