changeset 1908:affde38b84de

moved tentative bgo reimplementation of rt-struct into graveyard
author Sebastien Jodogne <s.jodogne@gmail.com>
date Tue, 01 Feb 2022 08:38:32 +0100
parents 0208f99b8bde
children 782ba9eb6f22
files OrthancStone/Resources/CMake/OrthancStoneConfiguration.cmake OrthancStone/Resources/Graveyard/RTStructTentativeReimplementation-BGO/DicomStructure2.cpp OrthancStone/Resources/Graveyard/RTStructTentativeReimplementation-BGO/DicomStructure2.h OrthancStone/Resources/Graveyard/RTStructTentativeReimplementation-BGO/DicomStructurePolygon2.cpp OrthancStone/Resources/Graveyard/RTStructTentativeReimplementation-BGO/DicomStructurePolygon2.h OrthancStone/Resources/Graveyard/RTStructTentativeReimplementation-BGO/DicomStructureSet-BGO.cpp OrthancStone/Resources/Graveyard/RTStructTentativeReimplementation-BGO/DicomStructureSet2.cpp OrthancStone/Resources/Graveyard/RTStructTentativeReimplementation-BGO/DicomStructureSet2.h OrthancStone/Resources/Graveyard/RTStructTentativeReimplementation-BGO/DicomStructureSetSlicer2.cpp OrthancStone/Resources/Graveyard/RTStructTentativeReimplementation-BGO/DicomStructureSetSlicer2.h OrthancStone/Resources/Graveyard/RTStructTentativeReimplementation-BGO/DicomStructureSetUtils.cpp OrthancStone/Resources/Graveyard/RTStructTentativeReimplementation-BGO/DicomStructureSetUtils.h OrthancStone/Resources/Graveyard/RTStructTentativeReimplementation-BGO/DisjointDataSet.h OrthancStone/Resources/Graveyard/RTStructTentativeReimplementation-BGO/TestStructureSet.cpp OrthancStone/Sources/Toolbox/DicomStructure2.cpp OrthancStone/Sources/Toolbox/DicomStructure2.h OrthancStone/Sources/Toolbox/DicomStructurePolygon2.cpp OrthancStone/Sources/Toolbox/DicomStructurePolygon2.h OrthancStone/Sources/Toolbox/DicomStructureSet.cpp OrthancStone/Sources/Toolbox/DicomStructureSet2.cpp OrthancStone/Sources/Toolbox/DicomStructureSet2.h OrthancStone/Sources/Toolbox/DicomStructureSetUtils.cpp OrthancStone/Sources/Toolbox/DicomStructureSetUtils.h OrthancStone/Sources/Toolbox/DisjointDataSet.h OrthancStone/Sources/Volumes/DicomStructureSetSlicer2.cpp OrthancStone/Sources/Volumes/DicomStructureSetSlicer2.h OrthancStone/UnitTestsSources/TestStructureSet.cpp OrthancStone/UnitTestsSources/UnitTestsSources.cmake
diffstat 28 files changed, 3282 insertions(+), 3281 deletions(-) [+]
line wrap: on
line diff
--- a/OrthancStone/Resources/CMake/OrthancStoneConfiguration.cmake	Tue Feb 01 07:19:15 2022 +0100
+++ b/OrthancStone/Resources/CMake/OrthancStoneConfiguration.cmake	Tue Feb 01 08:38:32 2022 +0100
@@ -403,17 +403,8 @@
   ${ORTHANC_STONE_ROOT}/Toolbox/CoordinateSystem3D.h
   ${ORTHANC_STONE_ROOT}/Toolbox/DicomInstanceParameters.cpp
   ${ORTHANC_STONE_ROOT}/Toolbox/DicomInstanceParameters.h
-  ${ORTHANC_STONE_ROOT}/Toolbox/DicomStructure2.cpp
-  ${ORTHANC_STONE_ROOT}/Toolbox/DicomStructure2.h
-  ${ORTHANC_STONE_ROOT}/Toolbox/DicomStructurePolygon2.cpp
-  ${ORTHANC_STONE_ROOT}/Toolbox/DicomStructurePolygon2.h
   ${ORTHANC_STONE_ROOT}/Toolbox/DicomStructureSet.cpp
   ${ORTHANC_STONE_ROOT}/Toolbox/DicomStructureSet.h
-  ${ORTHANC_STONE_ROOT}/Toolbox/DicomStructureSet2.cpp
-  ${ORTHANC_STONE_ROOT}/Toolbox/DicomStructureSet2.h
-  ${ORTHANC_STONE_ROOT}/Toolbox/DicomStructureSetUtils.cpp
-  ${ORTHANC_STONE_ROOT}/Toolbox/DicomStructureSetUtils.h
-  ${ORTHANC_STONE_ROOT}/Toolbox/DisjointDataSet.h
   ${ORTHANC_STONE_ROOT}/Toolbox/DynamicBitmap.cpp
   ${ORTHANC_STONE_ROOT}/Toolbox/DynamicBitmap.h
   ${ORTHANC_STONE_ROOT}/Toolbox/Extent2D.cpp
@@ -469,8 +460,6 @@
   ${ORTHANC_STONE_ROOT}/Volumes/VolumeReslicer.h
   ${ORTHANC_STONE_ROOT}/Volumes/VolumeSceneLayerSource.cpp
   ${ORTHANC_STONE_ROOT}/Volumes/VolumeSceneLayerSource.h
-  ${ORTHANC_STONE_ROOT}/Volumes/DicomStructureSetSlicer2.cpp
-  ${ORTHANC_STONE_ROOT}/Volumes/DicomStructureSetSlicer2.h
   ${ORTHANC_STONE_ROOT}/Volumes/DicomVolumeImage.h
   ${ORTHANC_STONE_ROOT}/Volumes/DicomVolumeImage.cpp
   ${ORTHANC_STONE_ROOT}/Volumes/DicomVolumeImage.h
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancStone/Resources/Graveyard/RTStructTentativeReimplementation-BGO/DicomStructure2.cpp	Tue Feb 01 08:38:32 2022 +0100
@@ -0,0 +1,297 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2022 Osimis S.A., Belgium
+ * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program. If not, see
+ * <http://www.gnu.org/licenses/>.
+ **/
+
+#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2
+
+#include "DicomStructure2.h"
+
+#include "GeometryToolbox.h"
+#include "DisjointDataSet.h"
+
+#include <Logging.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 (structures)
+      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
+  }
+
+  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<ScenePoint2D, ScenePoint2D> > & 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<ScenePoint2D> 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].GetY(), intersections[2 * iRect + 1].GetY()));
+              ORTHANC_ASSERT((2 * iRect + 1) < intersections.size());
+              double x1 = intersections[2 * iRect].GetX();
+              double x2 = intersections[2 * iRect + 1].GetX();
+              double y1 = intersections[2 * iRect].GetY() - sliceThickness_ * 0.5;
+              double y2 = intersections[2 * iRect].GetY() + 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 structures, but dot product is: "
+            << dot << " and (180/pi)*acos(dot) = " << acDot;
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+        }
+      }
+      return segments.size() != 0;
+    }
+}
+
+#endif 
+// BGO_ENABLE_DICOMSTRUCTURESETLOADER2
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancStone/Resources/Graveyard/RTStructTentativeReimplementation-BGO/DicomStructure2.h	Tue Feb 01 08:38:32 2022 +0100
@@ -0,0 +1,163 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2022 Osimis S.A., Belgium
+ * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program. If not, see
+ * <http://www.gnu.org/licenses/>.
+ **/
+
+#pragma once
+
+#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2
+
+#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 structures, 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<ScenePoint2D, ScenePoint2D> >& 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_;
+    Vector                            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_;
+  };
+}
+
+#endif 
+// BGO_ENABLE_DICOMSTRUCTURESETLOADER2
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancStone/Resources/Graveyard/RTStructTentativeReimplementation-BGO/DicomStructurePolygon2.cpp	Tue Feb 01 08:38:32 2022 +0100
@@ -0,0 +1,307 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2022 Osimis S.A., Belgium
+ * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program. If not, see
+ * <http://www.gnu.org/licenses/>.
+ **/
+
+#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2
+
+#include "DicomStructurePolygon2.h"
+
+#include "../Toolbox/LinearAlgebra.h"
+
+#include <Logging.h>
+
+namespace OrthancStone
+{
+  void DicomStructurePolygon2::ComputeDependentProperties()
+  {
+    ORTHANC_ASSERT(state_ == Building);
+
+    for (size_t j = 0; j < points_.size(); ++j)
+    {
+      // TODO: move to AddPoint!
+      const Vector& 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<ScenePoint2D>& 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;
+
+    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;
+      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;
+      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(ScenePoint2D(x, y));
+
+            plane.ProjectPoint(x, y, points_[iPoint + 1]);
+            intersections.push_back(ScenePoint2D(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(ScenePoint2D(x, minZ_));
+      }
+    } // end of if (pointCount >= 3)
+    else
+    {
+      LOG(ERROR) << "This polygon has " << pointCount << " vertices, which is less than 3 --> skipping";
+    }
+  } 
+
+  void DicomStructurePolygon2::ProjectOnParallelPlane(
+    std::vector< std::pair<ScenePoint2D, ScenePoint2D> >& 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));
+
+    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 Vector& point0 = GetPoint(j);
+      // subtract plane origin x and y
+      ScenePoint2D p0(point0[0] - planeOriginX, point0[1] - planeOriginY);
+    
+      const Vector& point1 = GetPoint(j+1);
+      // subtract plane origin x and y
+      ScenePoint2D p1(point1[0] - planeOriginX, point1[1] - planeOriginY);
+
+      segments.push_back(std::pair<ScenePoint2D, ScenePoint2D>(p0,p1));
+    }
+
+
+    // final segment 
+
+    const Vector& point0 = GetPoint(points_.size() - 1);
+    // subtract plane origin x and y
+    ScenePoint2D p0(point0[0] - planeOriginX, point0[1] - planeOriginY);
+
+    const Vector& point1 = GetPoint(0);
+    // subtract plane origin x and y
+    ScenePoint2D p1(point1[0] - planeOriginX, point1[1] - planeOriginY);
+
+    segments.push_back(std::pair<ScenePoint2D, ScenePoint2D>(p0, p1));
+  }
+
+  double 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_;
+  }
+}
+
+#endif 
+// BGO_ENABLE_DICOMSTRUCTURESETLOADER2
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancStone/Resources/Graveyard/RTStructTentativeReimplementation-BGO/DicomStructurePolygon2.h	Tue Feb 01 08:38:32 2022 +0100
@@ -0,0 +1,163 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2022 Osimis S.A., Belgium
+ * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program. If not, see
+ * <http://www.gnu.org/licenses/>.
+ **/
+
+#pragma once
+
+#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2
+
+#include "CoordinateSystem3D.h"
+#include "DicomStructureSetUtils.h"
+#include "Extent2D.h"
+
+#include "../Scene2D/Color.h"
+#include "../StoneException.h"
+
+#include "OrthancDatasets/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 Vector& GetPoint(size_t i) const
+    {
+      ORTHANC_ASSERT(state_ == Valid);
+      return points_.at(i);
+    }
+
+    void AddPoint(const Vector& 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<ScenePoint2D, ScenePoint2D> >& 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<ScenePoint2D>& 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
+    */
+    Vector 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<Vector>  points_;
+    Vector              normal_; // sign is irrelevant for now
+    State                 state_;
+    double                minX_, maxX_, minY_, maxY_, minZ_, maxZ_;
+    Type                  type_;
+  };
+}
+
+#endif
+// BGO_ENABLE_DICOMSTRUCTURESETLOADER2
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancStone/Resources/Graveyard/RTStructTentativeReimplementation-BGO/DicomStructureSet-BGO.cpp	Tue Feb 01 08:38:32 2022 +0100
@@ -0,0 +1,124 @@
+namespace OrthancStone
+{
+  static RtStructRectangleInSlab CreateRectangle(float x1, float y1,
+                                                 float x2, float y2)
+  {
+    RtStructRectangleInSlab rect;
+    rect.xmin = std::min(x1, x2);
+    rect.xmax = std::max(x1, x2);
+    rect.ymin = std::min(y1, y2);
+    rect.ymax = std::max(y1, y2);
+    return rect;
+  }
+
+  bool CompareRectanglesForProjection(const std::pair<RtStructRectangleInSlab,double>& r1,
+                                      const std::pair<RtStructRectangleInSlab, double>& r2)
+  {
+    return r1.second < r2.second;
+  }
+
+  bool CompareSlabsY(const RtStructRectanglesInSlab& r1,
+                     const RtStructRectanglesInSlab& r2)
+  {
+    if ((r1.size() == 0) || (r2.size() == 0))
+      return false;
+
+    return r1[0].ymax < r2[0].ymax;
+  }
+}
+
+
+  bool DicomStructureSet::ProjectStructure(std::vector< std::vector<ScenePoint2D> >& chains,
+                                           const Structure& structure,
+                                           const CoordinateSystem3D& sourceSlice) const
+  {
+
+    // FOR SAGITTAL AND CORONAL
+
+
+          // this will contain the intersection of the polygon slab with
+      // the cutting plane, projected on the cutting plane coord system 
+      // (that yields a rectangle) + the Z coordinate of the polygon 
+      // (this is required to group polygons with the same Z later)
+      std::vector<std::pair<RtStructRectangleInSlab, double> > 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, GetEstimatedNormal(), GetEstimatedSliceThickness()))
+        {
+          double curZ = polygon->GetGeometryOrigin()[2];
+
+          // x1,y1 and x2,y2 are in "slice" coordinates (the cutting plane 
+          // geometry)
+          projected.push_back(std::make_pair(CreateRectangle(
+                                               static_cast<float>(x1), 
+                                               static_cast<float>(y1), 
+                                               static_cast<float>(x2), 
+                                               static_cast<float>(y2)),curZ));
+        }
+      }
+
+      // projected contains a set of rectangles specified by two opposite
+      // corners (x1,y1,x2,y2)
+      // we need to merge them 
+      // each slab yields ONE polygon!
+
+      // we need to sorted all the rectangles that originate from the same Z
+      // into lanes. To make sure they are grouped together in the array, we
+      // sort it.
+      std::sort(projected.begin(), projected.end(), CompareRectanglesForProjection);
+
+      std::vector<RtStructRectanglesInSlab> rectanglesForEachSlab;
+      rectanglesForEachSlab.reserve(projected.size());
+
+      double curZ = 0;
+      for (size_t i = 0; i < projected.size(); ++i)
+      {
+#if 0
+        rectanglesForEachSlab.push_back(RtStructRectanglesInSlab());
+#else
+        if (i == 0)
+        {
+          curZ = projected[i].second;
+          rectanglesForEachSlab.push_back(RtStructRectanglesInSlab());
+        }
+        else
+        {
+          // this check is needed to prevent creating a new slab if 
+          // the new polygon is at the same Z coord than last one
+          if (!LinearAlgebra::IsNear(curZ, projected[i].second))
+          {
+            rectanglesForEachSlab.push_back(RtStructRectanglesInSlab());
+            curZ = projected[i].second;
+          }
+        }
+#endif
+
+        rectanglesForEachSlab.back().push_back(projected[i].first);
+
+        // as long as they have the same y, we should put them into the same lane
+        // BUT in Sebastien's code, there is only one polygon per lane.
+
+        //std::cout << "rect: xmin = " << rect.xmin << " xmax = " << rect.xmax << " ymin = " << rect.ymin << " ymax = " << rect.ymax << std::endl;
+      }
+      
+      // now we need to sort the slabs in increasing Y order (see ConvertListOfSlabsToSegments)
+      std::sort(rectanglesForEachSlab.begin(), rectanglesForEachSlab.end(), CompareSlabsY);
+
+      std::vector< std::pair<ScenePoint2D, ScenePoint2D> > segments;
+      ConvertListOfSlabsToSegments(segments, rectanglesForEachSlab, projected.size());
+
+      chains.resize(segments.size());
+      for (size_t i = 0; i < segments.size(); i++)
+      {
+        chains[i].resize(2);
+        chains[i][0] = segments[i].first;
+        chains[i][1] = segments[i].second;
+      }
+#endif
+      
+      return true;
+  }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancStone/Resources/Graveyard/RTStructTentativeReimplementation-BGO/DicomStructureSet2.cpp	Tue Feb 01 08:38:32 2022 +0100
@@ -0,0 +1,313 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2022 Osimis S.A., Belgium
+ * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program. If not, see
+ * <http://www.gnu.org/licenses/>.
+ **/
+
+#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2
+
+#include "DicomStructureSet2.h"
+
+#include "../Toolbox/LinearAlgebra.h"
+#include "../StoneException.h"
+
+#include <Logging.h>
+#include <OrthancException.h>
+#include <Toolbox.h>
+#include <DicomFormat/DicomTag.h>
+
+#include "DicomStructure2.h"
+#include "GenericToolbox.h"
+#include "OrthancDatasets/DicomDatasetReader.h"
+
+namespace OrthancStone
+{
+  static const Orthanc::DicomTag DICOM_TAG_CONTOUR_GEOMETRIC_TYPE(0x3006, 0x0042);
+  static const Orthanc::DicomTag DICOM_TAG_CONTOUR_IMAGE_SEQUENCE(0x3006, 0x0016);
+  static const Orthanc::DicomTag DICOM_TAG_CONTOUR_SEQUENCE(0x3006, 0x0040);
+  static const Orthanc::DicomTag DICOM_TAG_CONTOUR_DATA(0x3006, 0x0050);
+  static const Orthanc::DicomTag DICOM_TAG_NUMBER_OF_CONTOUR_POINTS(0x3006, 0x0046);
+  static const Orthanc::DicomTag DICOM_TAG_REFERENCED_SOP_INSTANCE_UID(0x0008, 0x1155);
+  static const Orthanc::DicomTag DICOM_TAG_ROI_CONTOUR_SEQUENCE(0x3006, 0x0039);
+  static const Orthanc::DicomTag DICOM_TAG_ROI_DISPLAY_COLOR(0x3006, 0x002a);
+  static const Orthanc::DicomTag DICOM_TAG_ROI_NAME(0x3006, 0x0026);
+  static const Orthanc::DicomTag DICOM_TAG_RT_ROI_INTERPRETED_TYPE(0x3006, 0x00a4);
+  static const Orthanc::DicomTag DICOM_TAG_RT_ROI_OBSERVATIONS_SEQUENCE(0x3006, 0x0080);
+  static const Orthanc::DicomTag DICOM_TAG_STRUCTURE_SET_ROI_SEQUENCE(0x3006, 0x0020);
+
+  static inline uint8_t ConvertAndClipToByte(double v)
+  {
+    if (v < 0)
+    {
+      return 0;
+    }
+    else if (v >= 255)
+    {
+      return 255;
+    }
+    else
+    {
+      return static_cast<uint8_t>(v);
+    }
+  }
+
+  static bool ReadDicomToVector(Vector& target,
+                                const IDicomDataset& dataset,
+                                const Orthanc::DicomPath& tag)
+  {
+    std::string value;
+    return (dataset.GetStringValue(value, tag) &&
+            GenericToolbox::FastParseVector(target, value));
+  }
+
+
+  void DicomPathToString(std::string& s, const Orthanc::DicomPath& dicomPath)
+  {
+    std::stringstream tmp;
+    for (size_t i = 0; i < dicomPath.GetPrefixLength(); ++i)
+    {
+      Orthanc::DicomTag tag = dicomPath.GetPrefixTag(i);
+
+      // We use this other object to be able to use Format
+      Orthanc::DicomTag tag2(tag.GetGroup(), tag.GetElement());
+      size_t index = dicomPath.GetPrefixIndex(i);
+      tmp << " (" << tag2.Format() << ") [" << index << "] / ";
+    }
+    const Orthanc::DicomTag& tag = dicomPath.GetFinalTag();
+    Orthanc::DicomTag tag2(tag.GetGroup(), tag.GetElement());
+    tmp << " (" << tag2.Format() << ")";
+    s = tmp.str();
+  }
+
+  std::ostream& operator<<(std::ostream& s, const Orthanc::DicomPath& dicomPath)
+  {
+    std::string tmp;
+    DicomPathToString(tmp, dicomPath);
+    s << tmp;
+    return s;
+  }
+
+
+  DicomStructureSet2::DicomStructureSet2()
+  {
+
+  }
+
+
+  DicomStructureSet2::~DicomStructureSet2()
+  {
+
+  }
+
+  void DicomStructureSet2::SetContents(const FullOrthancDataset& tags)
+  {
+    FillStructuresFromDataset(tags);
+    ComputeDependentProperties();
+  }
+
+  void DicomStructureSet2::ComputeDependentProperties()
+  {
+    for (size_t i = 0; i < structures_.size(); ++i)
+    {
+      structures_[i].ComputeDependentProperties();
+    }
+  }
+
+  void DicomStructureSet2::FillStructuresFromDataset(const FullOrthancDataset& tags)
+  {
+    DicomDatasetReader reader(tags);
+
+    // 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, Orthanc::DicomPath(DICOM_TAG_RT_ROI_OBSERVATIONS_SEQUENCE)) ||
+        !tags.GetSequenceSize(tmp, Orthanc::DicomPath(DICOM_TAG_ROI_CONTOUR_SEQUENCE)) ||
+        tmp != count ||
+        !tags.GetSequenceSize(tmp, Orthanc::DicomPath(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
+        (Orthanc::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
+        (Orthanc::DicomPath(DICOM_TAG_STRUCTURE_SET_ROI_SEQUENCE, i,
+                            DICOM_TAG_ROI_NAME),
+         "No name");
+
+      Vector color;
+      // (0x3006, 0x0039)[i]/(0x3006, 0x002a)
+      if (ReadDicomToVector(color, tags, Orthanc::DicomPath(
+                              DICOM_TAG_ROI_CONTOUR_SEQUENCE, i, DICOM_TAG_ROI_DISPLAY_COLOR)) 
+          && color.size() == 3)
+      {
+        structures_[i].red_   = ConvertAndClipToByte(color[0]);
+        structures_[i].green_ = ConvertAndClipToByte(color[1]);
+        structures_[i].blue_  = ConvertAndClipToByte(color[2]);
+      }
+      else
+      {
+        structures_[i].red_ = 255;
+        structures_[i].green_ = 0;
+        structures_[i].blue_ = 0;
+      }
+
+      size_t countSlices;
+      //  DICOM_TAG_ROI_CONTOUR_SEQUENCE          (0x3006, 0x0039);
+      //  DICOM_TAG_CONTOUR_SEQUENCE              (0x3006, 0x0040);
+      if (!tags.GetSequenceSize(countSlices, Orthanc::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_) << ")";
+
+      // These temporary variables avoid allocating many vectors in the loop below
+      
+      // (0x3006, 0x0039)[i]/(0x3006, 0x0040)[0]/(0x3006, 0x0046)
+      Orthanc::DicomPath countPointsPath(
+        DICOM_TAG_ROI_CONTOUR_SEQUENCE, i,
+        DICOM_TAG_CONTOUR_SEQUENCE, 0,
+        DICOM_TAG_NUMBER_OF_CONTOUR_POINTS);
+
+      Orthanc::DicomPath geometricTypePath(
+        DICOM_TAG_ROI_CONTOUR_SEQUENCE, i,
+        DICOM_TAG_CONTOUR_SEQUENCE, 0,
+        DICOM_TAG_CONTOUR_GEOMETRIC_TYPE);
+
+      Orthanc::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)
+      Orthanc::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);
+
+      Orthanc::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 = 0;
+
+        countPointsPath.SetPrefixIndex(1, j);
+        if (!reader.GetUnsignedIntegerValue(countPoints, countPointsPath))
+        {
+          std::string s;
+          DicomPathToString(s, countPointsPath);
+          LOG(ERROR) << "Dicom path " << s << " 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 = 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);
+        }
+
+        referencedInstancePath.SetPrefixIndex(1, j);
+        std::string sopInstanceUid = reader.GetMandatoryStringValue(referencedInstancePath);
+
+        contourDataPath.SetPrefixIndex(1, j);
+        std::string slicesData = reader.GetMandatoryStringValue(contourDataPath);
+
+        Vector points;
+        if (!GenericToolbox::FastParseVector(points, slicesData) ||
+            points.size() != 3 * countPoints)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+        }
+
+        // seen in real world
+        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)";
+        }
+
+        DicomStructurePolygon2 polygon(sopInstanceUid,type);
+        polygon.Reserve(countPoints);
+
+        for (size_t k = 0; k < countPoints; k++)
+        {
+          Vector v(3);
+          v[0] = points[3 * k];
+          v[1] = points[3 * k + 1];
+          v[2] = points[3 * k + 2];
+          polygon.AddPoint(v);
+        }
+        structures_[i].AddPolygon(polygon);
+      }
+    }
+  }
+
+
+  void DicomStructureSet2::Clear()
+  {
+    structures_.clear();
+  }
+
+}
+
+#endif 
+// BGO_ENABLE_DICOMSTRUCTURESETLOADER2
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancStone/Resources/Graveyard/RTStructTentativeReimplementation-BGO/DicomStructureSet2.h	Tue Feb 01 08:38:32 2022 +0100
@@ -0,0 +1,72 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2022 Osimis S.A., Belgium
+ * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program. If not, see
+ * <http://www.gnu.org/licenses/>.
+ **/
+
+#pragma once
+
+#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2
+
+#include "../Scene2D/Color.h"
+#include "CoordinateSystem3D.h"
+#include "DicomStructure2.h"
+#include "Extent2D.h"
+#include "OrthancDatasets/FullOrthancDataset.h"
+
+#include <list>
+
+namespace OrthancStone
+{
+  class DicomStructureSet2 : public boost::noncopyable
+  {
+  public:
+    DicomStructureSet2();
+    ~DicomStructureSet2();
+   
+    void SetContents(const FullOrthancDataset& tags);
+
+    size_t GetStructuresCount() const
+    {
+      return structures_.size();
+    }
+
+    void Clear();
+
+    const DicomStructure2& GetStructure(size_t i) const
+    {
+      // at() is like []() but with range check
+      return structures_.at(i);
+    }
+
+    /** Internal use only */
+    void FillStructuresFromDataset(const FullOrthancDataset& tags);
+
+    /** Internal use only */
+    void ComputeDependentProperties();
+
+    /** Internal use only */
+    std::vector<DicomStructure2> structures_;
+  };
+}
+
+#endif 
+// BGO_ENABLE_DICOMSTRUCTURESETLOADER2
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancStone/Resources/Graveyard/RTStructTentativeReimplementation-BGO/DicomStructureSetSlicer2.cpp	Tue Feb 01 08:38:32 2022 +0100
@@ -0,0 +1,118 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2022 Osimis S.A., Belgium
+ * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program. If not, see
+ * <http://www.gnu.org/licenses/>.
+ **/
+
+#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2
+
+#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::unique_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<ScenePoint2D, ScenePoint2D> > 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.GetX(), segments[j].first.GetY());
+            chain[1] = ScenePoint2D(segments[j].second.GetX(), segments[j].second.GetY());
+
+            layer->AddChain(chain, false /* NOT closed */, color);
+          }
+        }
+      }
+    }
+    return layer.release();
+  }
+}
+
+
+#endif 
+// BGO_ENABLE_DICOMSTRUCTURESETLOADER2
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancStone/Resources/Graveyard/RTStructTentativeReimplementation-BGO/DicomStructureSetSlicer2.h	Tue Feb 01 08:38:32 2022 +0100
@@ -0,0 +1,78 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2022 Osimis S.A., Belgium
+ * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program. If not, see
+ * <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2
+
+#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_;
+  };
+}
+
+#endif 
+// BGO_ENABLE_DICOMSTRUCTURESETLOADER2
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancStone/Resources/Graveyard/RTStructTentativeReimplementation-BGO/DicomStructureSetUtils.cpp	Tue Feb 01 08:38:32 2022 +0100
@@ -0,0 +1,274 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2022 Osimis S.A., Belgium
+ * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser 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<ScenePoint2D, ScenePoint2D> > & segments,
+      const std::vector<std::pair<double, RectangleBoundaryKind> > & boundaries,
+      double y)
+    {
+      ScenePoint2D start;
+      ScenePoint2D 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 = ScenePoint2D(boundaries[i].first, y);
+            break;
+          case 2:
+            // an extra segment has begun : stop the current one (we don't draw overlaps)
+            end = ScenePoint2D(boundaries[i].first, y);
+            segments.push_back(std::pair<ScenePoint2D, ScenePoint2D>(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 = ScenePoint2D(boundaries[i].first, y);
+            segments.push_back(std::pair<ScenePoint2D, ScenePoint2D>(start, end));
+            break;
+          case 1:
+            // an extra segment has ended : start a new one one
+            start = ScenePoint2D(boundaries[i].first, y);
+            break;
+          default:
+            // this should not happen!
+            //assert(false);
+            break;
+          }
+          break;
+        default:
+          assert(false);
+          break;
+        }
+      }
+    }
+
+#if 0
+    void ConvertListOfSlabsToSegments(
+      std::vector< std::pair<ScenePoint2D, ScenePoint2D> >& 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<ScenePoint2D, ScenePoint2D> > & 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];
+          {
+            ScenePoint2D p1(rect.xmin, rect.ymin);
+            ScenePoint2D p2(rect.xmin, rect.ymax);
+            segments.push_back(std::pair<ScenePoint2D, ScenePoint2D>(p1, p2));
+          }
+          {
+            ScenePoint2D p1(rect.xmax, rect.ymin);
+            ScenePoint2D p2(rect.xmax, rect.ymax);
+            segments.push_back(std::pair<ScenePoint2D, ScenePoint2D>(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/OrthancStone/Resources/Graveyard/RTStructTentativeReimplementation-BGO/DicomStructureSetUtils.h	Tue Feb 01 08:38:32 2022 +0100
@@ -0,0 +1,66 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2022 Osimis S.A., Belgium
+ * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program. If not, see
+ * <http://www.gnu.org/licenses/>.
+ **/
+
+#pragma once
+
+#include <vector>
+#include <utility>
+
+#include "../Scene2D/ScenePoint2D.h"
+#include "../Toolbox/LinearAlgebra.h"
+
+namespace OrthancStone
+{
+  /** 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<ScenePoint2D, ScenePoint2D> >& 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<ScenePoint2D, ScenePoint2D> >& segments,
+                           const std::vector<std::pair<double, RectangleBoundaryKind> >& boundaries,
+                           double y);
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancStone/Resources/Graveyard/RTStructTentativeReimplementation-BGO/DisjointDataSet.h	Tue Feb 01 08:38:32 2022 +0100
@@ -0,0 +1,146 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2022 Osimis S.A., Belgium
+ * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program. If not, see
+ * <http://www.gnu.org/licenses/>.
+ **/
+
+#pragma once
+
+#include <vector> 
+
+#include "../StoneException.h"
+
+namespace OrthancStone
+{
+  class DisjointDataSet
+  {
+  public:
+    DisjointDataSet(size_t itemCount) :
+      parents_(itemCount),
+      ranks_(itemCount)
+    {
+      for (size_t index = 0; index < parents_.size(); index++)
+      {
+        SetParent(index,index);
+        ranks_[index] = 1;
+      }
+    }
+
+    size_t Find(size_t item)
+    {
+      /*
+      If parents_[i] == i, it means i is representative of a set.
+      Otherwise, we go up the tree...
+      */
+      if (GetParent(item) != item)
+      {
+        // if item is not a top item (representative of its set),
+        // we use path compression to improve future lookups 
+        // see: https://en.wikipedia.org/wiki/Disjoint-set_data_structure#Path_compression
+        SetParent(item, Find(parents_[item]));
+      }
+
+      // now that paths have been compressed, we are positively certain
+      // that item's parent is a set ("X is a set" means that X is the 
+      // representative of a set)
+      return GetParent(item);
+    }
+
+    /*
+    This merge the two sets that contains itemA and itemB
+    */
+    void Union(size_t itemA, size_t itemB)
+    {
+      // Find current sets of x and y 
+      size_t setA = Find(itemA);
+      size_t setB = Find(itemB);
+
+      // if setA == setB, it means they are already in the same set and 
+      // do not need to be merged!
+      if (setA != setB)
+      {
+        // we need to merge the sets, which means that the trees representing
+        // the sets needs to be merged (there must be a single top parent to 
+        // all the items originally belonging to setA and setB must be the same)
+
+        // since the algorithm speed is inversely proportional to the tree
+        // height (the rank), we need to combine trees in a way that 
+        // minimizes this rank. See "Union by rank" at 
+        // https://en.wikipedia.org/wiki/Disjoint-set_data_structure#by_rank
+        if (GetRank(setA) < GetRank(setB))
+        {
+          SetParent(setA, setB);
+        }
+        else if (GetRank(setA) > GetRank(setB))
+        {
+          SetParent(setB, setA);
+        }
+        else
+        {
+          SetParent(setB, setA);
+          BumpRank(setA);
+          // the trees had the same height but we attached the whole of setB
+          // under setA (under its parent), so the resulting tree is now 
+          // 1 higher. setB is NOT representative of a set anymore.
+        }
+      }
+    }
+
+  private:
+    size_t GetRank(size_t i) const
+    {
+      ORTHANC_ASSERT(i < ranks_.size());
+      ORTHANC_ASSERT(ranks_.size() == parents_.size());
+      return ranks_[i];
+    }
+
+    size_t GetParent(size_t i) const
+    {
+      ORTHANC_ASSERT(i < parents_.size());
+      ORTHANC_ASSERT(ranks_.size() == parents_.size());
+      return parents_[i];
+    }
+
+    void SetParent(size_t i, size_t parent)
+    {
+      ORTHANC_ASSERT(i < parents_.size());
+      ORTHANC_ASSERT(ranks_.size() == parents_.size());
+      parents_[i] = parent;
+    }
+
+    void BumpRank(size_t i)
+    {
+      ORTHANC_ASSERT(i < ranks_.size());
+      ORTHANC_ASSERT(ranks_.size() == parents_.size());
+      ranks_[i] = ranks_[i] + 1u;
+    }
+
+    /*
+    This vector contains the direct parent of each item
+    */
+    std::vector<size_t> parents_;
+
+    /*
+    This vector contains the tree height of each set. The values in the
+    vector for non-representative items is UNDEFINED!
+    */
+    std::vector<size_t> ranks_;
+  };
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancStone/Resources/Graveyard/RTStructTentativeReimplementation-BGO/TestStructureSet.cpp	Tue Feb 01 08:38:32 2022 +0100
@@ -0,0 +1,1155 @@
+
+#define BGO_ENABLE_DICOMSTRUCTURESETLOADER2
+
+// working around a bug where the Visual C++ compiler would get 
+// stuck trying to compile this cpp file in release mode
+// (versions: https://en.wikipedia.org/wiki/Microsoft_Visual_C%2B%2B)
+#ifdef _MSC_VER
+#  pragma optimize("", off)
+// warning C4748: /GS can not protect parameters and local variables from
+// local buffer overrun because optimizations are disabled in function
+#  pragma warning(disable: 4748)
+#endif
+
+
+
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <boost/make_shared.hpp>
+#include <string>
+
+
+
+using namespace OrthancStone;
+
+static const double DELTA_MAX = 10.0 * std::numeric_limits<float>::epsilon();
+
+
+
+#define STONE_ARRAY_SIZE(arr)     (sizeof(arr) / sizeof((arr)[0]))
+
+static double pointsCoord1[] = { 2, 2, 3,  3,  6,  8, 8, 7, 8, 8, 6 };
+static double pointsCoord2[] = { 2, 6, 8, 10, 12, 10, 8, 6, 4, 2, 4 };
+static const size_t pointsCoord1Count = STONE_ARRAY_SIZE(pointsCoord1);
+static const size_t pointsCoord2Count = STONE_ARRAY_SIZE(pointsCoord2);
+const size_t POLYGON_POINT_COUNT = pointsCoord1Count;
+
+
+#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2
+
+static void CheckGroundTruth(
+  const std::vector<DicomStructure2>& structures,
+  const size_t structureIndex,
+  const size_t sliceIndex,
+  std::vector<double> groundTruth)
+{
+  const std::vector<DicomStructurePolygon2>& polygonsForThisStruct = structures.at(structureIndex).GetPolygons();
+  const DicomStructurePolygon2& polygon = polygonsForThisStruct.at(sliceIndex);
+
+  //double groundTruth[] = { 7.657838, 108.2725, 304.01, 6.826687, 107.4413, 304.01, 6.152492, 106.4785, 304.01, 5.655735, 105.4132, 304.01, 5.351513, 104.2778, 304.01, 5.249068, 103.1069, 304.01, 5.351513, 101.9359, 304.01, 5.655735, 100.8005, 304.01, 6.152492, 99.73524, 304.01, 6.826687, 98.77239, 304.01, 7.657838, 97.94124, 304.01, 8.620689, 97.26704, 304.01, 9.685987, 96.77029, 304.01, 10.82136, 96.46606, 304.01, 11.99231, 96.36362, 304.01, 13.16326, 96.46606, 304.01, 14.29864, 96.77029, 304.01, 15.36393, 97.26704, 304.01, 16.32678, 97.94124, 304.01, 17.15794, 98.77239, 304.01, 17.83213, 99.73524, 304.01, 18.32889, 100.8005, 304.01, 18.63311, 101.9359, 304.01, 18.73555, 103.1069, 304.01, 18.63311, 104.2778, 304.01, 18.32889, 105.4132, 304.01, 17.83213, 106.4785, 304.01, 17.15794, 107.4413, 304.01, 16.32678, 108.2725, 304.01, 15.36393, 108.9467, 304.01, 14.29864, 109.4434, 304.01, 13.16326, 109.7477, 304.01, 11.99231, 109.8501, 304.01, 10.82136, 109.7477, 304.01, 9.685987, 109.4434, 304.01, 8.620689, 108.9467, 304.01 };
+  size_t groundTruthItems = groundTruth.size();
+
+  size_t pointCount = 3 * polygon.GetPointCount();
+
+  EXPECT_EQ(groundTruthItems, pointCount);
+
+  for (size_t i = 0; i < polygon.GetPointCount(); ++i)
+  {
+    const Vector& point = polygon.GetPoint(i);
+
+    // loop over X, Y then Z.
+    for (size_t j = 0; j < 3; ++j)
+    {
+      size_t index = 3 * i + j;
+      ASSERT_LT(index, groundTruthItems);
+      bool isNear = LinearAlgebra::IsNear(groundTruth[index], point[j]);
+      EXPECT_TRUE(isNear);
+    }
+  }
+}
+
+
+#include <Toolbox.h>
+
+TEST(StructureSet2, ReadFromJsonThatsAll)
+{
+  /*
+    The "RT_STRUCT_00" string is the reply to the following Orthanc request:
+    
+    http://localhost:8042/instances/1aa5f84b-c32a03b4-3c1857da-da2e69f3-3ef6e2b3/tags?ignore-length=3006-0050
+    
+    The tag hierarchy can be found here: https://dicom.innolitics.com/ciods/rt-dose
+  */
+
+  DicomStructureSet2 structureSet;
+
+  FullOrthancDataset dicom(Orthanc::EmbeddedResources::GetFileResourceBuffer(Orthanc::EmbeddedResources::RT_STRUCT_00),
+                           Orthanc::EmbeddedResources::GetFileResourceSize(Orthanc::EmbeddedResources::RT_STRUCT_00));
+  structureSet.Clear();
+
+  structureSet.FillStructuresFromDataset(dicom);
+  structureSet.ComputeDependentProperties();
+
+  const std::vector<DicomStructure2>& structures = structureSet.structures_;
+
+  /*
+
+  ██████╗  █████╗ ███████╗██╗ ██████╗     ██████╗██╗  ██╗███████╗ ██████╗██╗  ██╗███████╗
+  ██╔══██╗██╔══██╗██╔════╝██║██╔════╝    ██╔════╝██║  ██║██╔════╝██╔════╝██║ ██╔╝██╔════╝
+  ██████╔╝███████║███████╗██║██║         ██║     ███████║█████╗  ██║     █████╔╝ ███████╗
+  ██╔══██╗██╔══██║╚════██║██║██║         ██║     ██╔══██║██╔══╝  ██║     ██╔═██╗ ╚════██║
+  ██████╔╝██║  ██║███████║██║╚██████╗    ╚██████╗██║  ██║███████╗╚██████╗██║  ██╗███████║
+  ╚═════╝ ╚═╝  ╚═╝╚══════╝╚═╝ ╚═════╝     ╚═════╝╚═╝  ╚═╝╚══════╝ ╚═════╝╚═╝  ╚═╝╚══════╝
+  http://patorjk.com/software/taag/#p=display&f=ANSI%20Shadow&t=BASIC%20CHECKS
+  */
+
+  // (0x3006, 0x0080) seq. size
+  EXPECT_EQ(7u, structures.size());
+
+  // (0x3006, 0x0080)[i]/(0x3006, 0x00a4)
+  for (size_t i = 0; i < 5; ++i)
+  {
+    EXPECT_EQ(std::string("ORGAN"), structures[i].interpretation_);
+  }
+  EXPECT_EQ(std::string("EXTERNAL"), structures[5].interpretation_);
+  EXPECT_EQ(std::string("PTV"), structures[6].interpretation_);
+
+  // (0x3006, 0x0020)[i]/(0x3006, 0x0026)
+  EXPECT_EQ(std::string("LN300"), structures[0].name_);
+  EXPECT_EQ(std::string("Cortical Bone"), structures[1].name_);
+  EXPECT_EQ(std::string("Adipose"), structures[2].name_);
+  EXPECT_EQ(std::string("CB2-50%"), structures[3].name_);
+  EXPECT_EQ(std::string("Water"), structures[4].name_);
+  EXPECT_EQ(std::string("External"), structures[5].name_);
+  EXPECT_EQ(std::string("PTV"), structures[6].name_);
+
+  // (0x3006, 0x0039)[i]/(0x3006, 0x002a)
+  EXPECT_EQ(0xff, structures[0].red_);
+  EXPECT_EQ(0x00, structures[0].green_);
+  EXPECT_EQ(0x00, structures[0].blue_);
+
+  EXPECT_EQ(0x00, structures[1].red_);
+  EXPECT_EQ(0xff, structures[1].green_);
+  EXPECT_EQ(0xff, structures[1].blue_);
+
+  // ...
+
+  EXPECT_EQ(0x00, structures[5].red_);
+  EXPECT_EQ(0x80, structures[5].green_);
+  EXPECT_EQ(0x00, structures[5].blue_);
+
+  EXPECT_EQ(0xff, structures[6].red_);
+  EXPECT_EQ(0x00, structures[6].green_);
+  EXPECT_EQ(0xff, structures[6].blue_);
+
+  /*
+
+   ██████╗ ███████╗ ██████╗ ███╗   ███╗███████╗████████╗██████╗ ██╗   ██╗
+  ██╔════╝ ██╔════╝██╔═══██╗████╗ ████║██╔════╝╚══██╔══╝██╔══██╗╚██╗ ██╔╝
+  ██║  ███╗█████╗  ██║   ██║██╔████╔██║█████╗     ██║   ██████╔╝ ╚████╔╝
+  ██║   ██║██╔══╝  ██║   ██║██║╚██╔╝██║██╔══╝     ██║   ██╔══██╗  ╚██╔╝
+  ╚██████╔╝███████╗╚██████╔╝██║ ╚═╝ ██║███████╗   ██║   ██║  ██║   ██║
+   ╚═════╝ ╚══════╝ ╚═════╝ ╚═╝     ╚═╝╚══════╝   ╚═╝   ╚═╝  ╚═╝   ╚═╝
+  http://patorjk.com/software/taag/#p=display&f=ANSI%20Shadow&t=BASIC%20CHECKS
+  */
+
+
+  {
+    double groundTruthRaw[] = { 7.657838, 108.2725, 304.01, 6.826687, 107.4413, 304.01, 6.152492, 106.4785, 304.01, 5.655735, 105.4132, 304.01, 5.351513, 104.2778, 304.01, 5.249068, 103.1069, 304.01, 5.351513, 101.9359, 304.01, 5.655735, 100.8005, 304.01, 6.152492, 99.73524, 304.01, 6.826687, 98.77239, 304.01, 7.657838, 97.94124, 304.01, 8.620689, 97.26704, 304.01, 9.685987, 96.77029, 304.01, 10.82136, 96.46606, 304.01, 11.99231, 96.36362, 304.01, 13.16326, 96.46606, 304.01, 14.29864, 96.77029, 304.01, 15.36393, 97.26704, 304.01, 16.32678, 97.94124, 304.01, 17.15794, 98.77239, 304.01, 17.83213, 99.73524, 304.01, 18.32889, 100.8005, 304.01, 18.63311, 101.9359, 304.01, 18.73555, 103.1069, 304.01, 18.63311, 104.2778, 304.01, 18.32889, 105.4132, 304.01, 17.83213, 106.4785, 304.01, 17.15794, 107.4413, 304.01, 16.32678, 108.2725, 304.01, 15.36393, 108.9467, 304.01, 14.29864, 109.4434, 304.01, 13.16326, 109.7477, 304.01, 11.99231, 109.8501, 304.01, 10.82136, 109.7477, 304.01, 9.685987, 109.4434, 304.01, 8.620689, 108.9467, 304.01 };
+    size_t n = sizeof(groundTruthRaw) / sizeof(groundTruthRaw[0]); 
+    std::vector<double> groundTruth(groundTruthRaw, groundTruthRaw+n);
+    CheckGroundTruth(structures, 0, 0, groundTruth);
+  }
+  {
+    double groundTruthRaw[] = { 7.657838, 108.2725, 310.01, 6.826687, 107.4413, 310.01, 6.152492, 106.4785, 310.01, 5.655735, 105.4132, 310.01, 5.351513, 104.2778, 310.01, 5.249068, 103.1069, 310.01, 5.351513, 101.9359, 310.01, 5.655735, 100.8005, 310.01, 6.152492, 99.73524, 310.01, 6.826687, 98.77239, 310.01, 7.657838, 97.94124, 310.01, 8.620689, 97.26704, 310.01, 9.685987, 96.77029, 310.01, 10.82136, 96.46606, 310.01, 11.99231, 96.36362, 310.01, 13.16326, 96.46606, 310.01, 14.29864, 96.77029, 310.01, 15.36393, 97.26704, 310.01, 16.32678, 97.94124, 310.01, 17.15794, 98.77239, 310.01, 17.83213, 99.73524, 310.01, 18.32889, 100.8005, 310.01, 18.63311, 101.9359, 310.01, 18.73555, 103.1069, 310.01, 18.63311, 104.2778, 310.01, 18.32889, 105.4132, 310.01, 17.83213, 106.4785, 310.01, 17.15794, 107.4413, 310.01, 16.32678, 108.2725, 310.01, 15.36393, 108.9467, 310.01, 14.29864, 109.4434, 310.01, 13.16326, 109.7477, 310.01, 11.99231, 109.8501, 310.01, 10.82136, 109.7477, 310.01, 9.685987, 109.4434, 310.01, 8.620689, 108.9467, 310.01 };
+    size_t n = sizeof(groundTruthRaw) / sizeof(groundTruthRaw[0]); 
+    std::vector<double> groundTruth(groundTruthRaw, groundTruthRaw+n);
+    CheckGroundTruth(structures, 0, 2, groundTruth);
+  }
+  {
+    double groundTruthRaw[] = { -37.967, 161.9664, 304.01, -39.10237, 161.6622, 304.01, -40.16767, 161.1655, 304.01, -41.13052, 160.4913, 304.01, -41.96167, 159.6601, 304.01, -42.63587, 158.6973, 304.01, -43.13263, 157.632, 304.01, -43.43685, 156.4966, 304.01, -43.53929, 155.3257, 304.01, -43.43685, 154.1547, 304.01, -43.13263, 153.0193, 304.01, -42.63587, 151.954, 304.01, -41.96167, 150.9912, 304.01, -41.13052, 150.16, 304.01, -40.16767, 149.4858, 304.01, -39.10237, 148.9891, 304.01, -37.967, 148.6849, 304.01, -36.79605, 148.5824, 304.01, -35.6251, 148.6849, 304.01, -34.48972, 148.9891, 304.01, -33.42443, 149.4858, 304.01, -32.46157, 150.16, 304.01, -31.63042, 150.9912, 304.01, -30.95623, 151.954, 304.01, -30.45947, 153.0193, 304.01, -30.15525, 154.1547, 304.01, -30.0528, 155.3257, 304.01, -30.15525, 156.4966, 304.01, -30.45947, 157.632, 304.01, -30.95623, 158.6973, 304.01, -31.63042, 159.6601, 304.01, -32.46157, 160.4913, 304.01, -33.42443, 161.1655, 304.01, -34.48972, 161.6622, 304.01, -35.6251, 161.9664, 304.01, -36.79605, 162.0689, 304.01 };
+    size_t n = sizeof(groundTruthRaw) / sizeof(groundTruthRaw[0]); 
+    std::vector<double> groundTruth(groundTruthRaw, groundTruthRaw+n);
+    CheckGroundTruth(structures, 1, 0, groundTruth);
+  }
+  {
+    double groundTruthRaw[] = { 69.4042, 150.7324, 307.01, 69.70842, 151.8678, 307.01, 69.81087, 153.0387, 307.01, 69.70842, 154.2097, 307.01, 69.4042, 155.345, 307.01, 68.90745, 156.4103, 307.01, 68.23325, 157.3732, 307.01, 67.4021, 158.2043, 307.01, 66.43925, 158.8785, 307.01, 65.37395, 159.3753, 307.01, 64.23858, 159.6795, 307.01, 63.06762, 159.7819, 307.01, 61.89667, 159.6795, 307.01, 60.7613, 159.3753, 307.01, 59.696, 158.8785, 307.01, 58.73315, 158.2043, 307.01, 57.902, 157.3732, 307.01, 57.22781, 156.4103, 307.01, 56.73105, 155.345, 307.01, 56.42683, 154.2097, 307.01, 56.32438, 153.0387, 307.01, 56.42683, 151.8678, 307.01, 56.73105, 150.7324, 307.01, 57.22781, 149.6671, 307.01, 57.902, 148.7042, 307.01, 58.73315, 147.8731, 307.01, 59.696, 147.1989, 307.01, 60.7613, 146.7021, 307.01, 61.89667, 146.3979, 307.01, 63.06762, 146.2955, 307.01, 64.23858, 146.3979, 307.01, 65.37395, 146.7021, 307.01, 66.43925, 147.1989, 307.01, 67.4021, 147.8731, 307.01, 68.23325, 148.7042, 307.01, 68.90745, 149.6671, 307.01 };
+    size_t n = sizeof(groundTruthRaw) / sizeof(groundTruthRaw[0]); 
+    std::vector<double> groundTruth(groundTruthRaw, groundTruthRaw+n);
+    CheckGroundTruth(structures, 2, 1, groundTruth);
+  }
+
+  {
+    double groundTruthRaw[] = { 108.3984, 232.7406, 274.01, 106.0547, 231.7948, 274.01, 103.7109, 232.8407, 274.01, 96.67969, 232.8757, 274.01, 77.92969, 232.887, 274.01, 47.46094, 232.8902, 274.01, 38.08594, 232.7537, 274.01, 37.6668, 232.3734, 274.01, 38.08594, 231.9774, 274.01, 40.42969, 231.8475, 274.01, 41.76413, 230.0297, 274.01, 42.77344, 229.1388, 274.01, 45.11719, 228.5069, 274.01, 47.46094, 227.1533, 274.01, 49.80469, 226.3505, 274.01, 52.14844, 224.6564, 274.01, 54.49219, 223.923, 274.01, 56.83594, 222.0692, 274.01, 59.17969, 220.3438, 274.01, 61.52344, 219.3888, 274.01, 63.86719, 217.1287, 274.01, 65.83488, 215.9672, 274.01, 68.55469, 213.2383, 274.01, 70.89844, 211.2328, 274.01, 72.8125, 208.9359, 274.01, 75.58594, 206.3615, 274.01, 76.91445, 204.2484, 274.01, 78.89509, 201.9047, 274.01, 80.51276, 199.5609, 274.01, 81.51955, 197.2172, 274.01, 83.67448, 194.8734, 274.01, 84.60938, 192.5297, 274.01, 85.86986, 190.1859, 274.01, 86.57623, 187.8422, 274.01, 88.30051, 185.4984, 274.01, 88.94002, 183.1547, 274.01, 89.23261, 180.8109, 274.01, 89.64844, 180.3263, 274.01, 90.71885, 178.4672, 274.01, 90.97656, 176.1234, 274.01, 91.99219, 174.4794, 274.01, 92.56773, 173.7797, 274.01, 92.80016, 171.4359, 274.01, 93.23473, 169.0922, 274.01, 93.37606, 166.7484, 274.01, 93.60748, 157.3734, 274.01, 93.6341, 152.6859, 274.01, 93.35742, 140.9672, 274.01, 92.89317, 138.6234, 274.01, 92.7069, 136.2797, 274.01, 92.03726, 133.9359, 274.01, 90.84009, 131.5922, 274.01, 90.3769, 129.2484, 274.01, 89.09074, 126.9047, 274.01, 88.13225, 122.2172, 274.01, 86.17828, 119.8734, 274.01, 84.96094, 117.4163, 274.01, 83.99619, 115.1859, 274.01, 83.13079, 112.8422, 274.01, 82.61719, 112.2984, 274.01, 80.27344, 108.8454, 274.01, 79.64514, 108.1547, 274.01, 77.21497, 105.8109, 274.01, 76.47787, 103.4672, 274.01, 75.58594, 102.6177, 274.01, 73.24219, 100.0077, 274.01, 69.54492, 96.43594, 274.01, 67.34096, 94.09219, 274.01, 64.66306, 91.74844, 274.01, 63.86719, 90.92619, 274.01, 61.52344, 90.20454, 274.01, 59.17969, 87.78574, 274.01, 56.83594, 86.48566, 274.01, 54.49219, 84.31388, 274.01, 52.14844, 83.44438, 274.01, 49.80469, 82.75121, 274.01, 49.37617, 82.37344, 274.01, 47.46094, 81.26244, 274.01, 45.71391, 80.02969, 274.01, 45.11719, 79.45415, 274.01, 42.77344, 79.08185, 274.01, 40.42969, 78.51941, 274.01, 38.08594, 78.27534, 274.01, 37.36932, 77.68594, 274.01, 35.74219, 76.67624, 274.01, 33.39844, 76.49941, 274.01, 31.05469, 76.03495, 274.01, 28.71094, 74.83174, 274.01, 26.36719, 74.62859, 274.01, 24.02344, 74.55463, 274.01, 21.67969, 74.22861, 274.01, 19.33594, 74.05312, 274.01, 12.30469, 73.99397, 274.01, 5.273438, 74.0736, 274.01, 2.929688, 74.55463, 274.01, 0.5859375, 74.68513, 274.01, -1.757813, 74.914, 274.01, -2.319131, 75.34219, 274.01, -4.101563, 76.31516, 274.01, -8.789063, 76.74514, 274.01, -11.13281, 78.39038, 274.01, -13.47656, 78.6124, 274.01, -15.82031, 79.19784, 274.01, -18.16406, 81.11024, 274.01, -20.50781, 82.03296, 274.01, -22.85156, 83.13991, 274.01, -25.19531, 83.70732, 274.01, -27.53906, 85.85863, 274.01, -29.88281, 87.03368, 274.01, -32.22656, 88.3274, 274.01, -34.57031, 90.53674, 274.01, -36.91406, 92.5602, 274.01, -39.25781, 93.55952, 274.01, -41.60156, 95.74537, 274.01, -43.94531, 98.26609, 274.01, -46.28906, 100.3701, 274.01, -47.02621, 101.1234, 274.01, -47.86611, 103.4672, 274.01, -49.83594, 105.8109, 274.01, -51.98182, 108.1547, 274.01, -53.06448, 110.4984, 274.01, -53.32031, 110.7675, 274.01, -54.53804, 112.8422, 274.01, -55.66406, 114.273, 274.01, -56.55722, 115.1859, 274.01, -57.13953, 117.5297, 274.01, -58.29264, 119.8734, 274.01, -59.26869, 122.2172, 274.01, -60.35156, 124.0119, 274.01, -60.84229, 124.5609, 274.01, -61.54484, 126.9047, 274.01, -61.71691, 129.2484, 274.01, -63.62281, 131.5922, 274.01, -63.81256, 133.9359, 274.01, -64.12511, 136.2797, 274.01, -64.84515, 138.6234, 274.01, -65.13599, 140.9672, 274.01, -65.33604, 143.3109, 274.01, -65.87358, 145.6547, 274.01, -66.10577, 147.9984, 274.01, -66.17618, 155.0297, 274.01, -66.09933, 162.0609, 274.01, -65.40382, 164.4047, 274.01, -65.24833, 166.7484, 274.01, -64.71442, 171.4359, 274.01, -63.88171, 173.7797, 274.01, -63.69299, 176.1234, 274.01, -61.79081, 178.4672, 274.01, -61.59269, 180.8109, 274.01, -61.19405, 183.1547, 274.01, -60.35156, 185.2055, 274.01, -59.08288, 187.8422, 274.01, -58.00781, 189.3499, 274.01, -57.25858, 190.1859, 274.01, -56.64558, 192.5297, 274.01, -55.29191, 194.8734, 274.01, -54.28698, 197.2172, 274.01, -52.28595, 199.5609, 274.01, -51.47569, 201.9047, 274.01, -48.63281, 204.6417, 274.01, -47.10181, 206.5922, 274.01, -44.64154, 208.9359, 274.01, -42.38504, 211.2797, 274.01, -39.25781, 214.4025, 274.01, -37.42723, 215.9672, 274.01, -34.57031, 218.9107, 274.01, -32.22656, 219.7277, 274.01, -29.88281, 221.6934, 274.01, -27.53906, 222.852, 274.01, -25.19531, 224.5168, 274.01, -22.85156, 225.9419, 274.01, -20.50781, 226.7359, 274.01, -18.16406, 228.3332, 274.01, -15.82031, 229.065, 274.01, -13.47656, 229.267, 274.01, -12.63854, 230.0297, 274.01, -11.13281, 231.9201, 274.01, -10.65505, 232.3734, 274.01, -11.13281, 232.7794, 274.01, -15.82031, 232.792, 274.01, -18.16406, 232.8902, 274.01, -36.91406, 232.9015, 274.01, -39.25781, 232.8902, 274.01, -50.97656, 232.9236, 274.01, -60.35156, 232.9126, 274.01, -67.38281, 232.8407, 274.01, -72.07031, 232.8642, 274.01, -79.10156, 232.8555, 274.01, -83.78906, 232.8788, 274.01, -95.50781, 232.8902, 274.01, -97.85156, 233.4886, 274.01, -100.1953, 233.647, 274.01, -102.5391, 232.9858, 274.01, -104.8828, 233.6969, 274.01, -109.5703, 233.722, 274.01, -125.9766, 233.7086, 274.01, -128.3203, 233.2849, 274.01, -130.6641, 233.702, 274.01, -135.3516, 233.727, 274.01, -149.4141, 233.7135, 274.01, -156.4453, 233.727, 274.01, -163.4766, 233.7119, 274.01, -168.1641, 233.7643, 274.01, -191.6016, 233.7809, 274.01, -210.3516, 233.7716, 274.01, -224.4141, 233.7998, 274.01, -233.7891, 233.7647, 274.01, -243.1641, 233.7785, 274.01, -247.8516, 233.7378, 274.01, -254.8828, 233.8578, 274.01, -257.2266, 235.2519, 274.01, -259.5703, 236.0817, 274.01, -260.7617, 237.0609, 274.01, -261.9141, 238.2262, 274.01, -262.8989, 239.4047, 274.01, -262.9743, 241.7484, 274.01, -262.5977, 244.0922, 274.01, -260.6675, 246.4359, 274.01, -259.6161, 248.7797, 274.01, -257.2266, 251.0035, 274.01, -255.0361, 253.4672, 274.01, -252.5391, 256.0995, 274.01, -251.2277, 258.1547, 274.01, -246.7444, 262.8422, 274.01, -243.1641, 266.3515, 274.01, -239.7411, 269.8734, 274.01, -238.4766, 270.9495, 274.01, -237.2269, 272.2172, 274.01, -236.1328, 273.5215, 274.01, -235.0934, 274.5609, 274.01, -233.7891, 275.6655, 274.01, -232.5319, 276.9047, 274.01, -231.4453, 278.1693, 274.01, -227.917, 281.5922, 274.01, -224.4141, 285.1802, 274.01, -222.0703, 287.4025, 274.01, -218.6841, 290.9672, 274.01, -217.3828, 291.9709, 274.01, -215.0391, 293.1788, 274.01, -212.6953, 294.5138, 274.01, -210.3516, 295.2614, 274.01, -209.8994, 295.6547, 274.01, -208.0078, 296.7083, 274.01, -203.3203, 296.9372, 274.01, -196.2891, 296.9317, 274.01, -193.9453, 296.8988, 274.01, -172.8516, 296.8482, 274.01, -161.1328, 296.843, 274.01, -137.6953, 296.8542, 274.01, -130.6641, 296.8378, 274.01, -107.2266, 296.8379, 274.01, -93.16406, 296.8208, 274.01, -74.41406, 296.838, 274.01, -65.03906, 296.8609, 274.01, -50.97656, 296.8556, 274.01, -46.28906, 296.9051, 274.01, -41.60156, 298.5331, 274.01, -39.25781, 298.5624, 274.01, -36.91406, 297.1455, 274.01, -34.57031, 297.0498, 274.01, -32.22656, 298.5589, 274.01, -25.19531, 298.5624, 274.01, -22.85156, 297.2842, 274.01, -20.50781, 298.5624, 274.01, -1.757813, 298.5624, 274.01, 0.5859375, 297.2104, 274.01, 2.929688, 298.5624, 274.01, 5.273438, 297.6946, 274.01, 7.617188, 298.5168, 274.01, 9.960938, 298.5512, 274.01, 12.30469, 296.937, 274.01, 14.64844, 298.5478, 274.01, 16.99219, 298.5478, 274.01, 19.33594, 297.0782, 274.01, 21.67969, 296.844, 274.01, 23.54531, 297.9984, 274.01, 24.02344, 298.4023, 274.01, 24.50156, 297.9984, 274.01, 26.36719, 296.844, 274.01, 38.08594, 296.8381, 274.01, 52.14844, 296.8033, 274.01, 59.17969, 296.8033, 274.01, 73.24219, 296.7682, 274.01, 99.02344, 296.7566, 274.01, 117.7734, 296.7216, 274.01, 129.4922, 296.7152, 274.01, 131.8359, 295.9083, 274.01, 134.1797, 295.5245, 274.01, 138.8672, 295.4763, 274.01, 155.2734, 295.4763, 274.01, 176.3672, 295.3861, 274.01, 190.4297, 295.3718, 274.01, 197.4609, 295.4763, 274.01, 202.1484, 295.4454, 274.01, 204.4922, 295.3438, 274.01, 206.8359, 295.0757, 274.01, 209.1797, 294.4124, 274.01, 211.5234, 292.3133, 274.01, 213.8672, 291.0809, 274.01, 216.2109, 289.6743, 274.01, 217.3081, 288.6234, 274.01, 219.3558, 286.2797, 274.01, 221.8608, 283.9359, 274.01, 225.5859, 280.045, 274.01, 227.9297, 277.8885, 274.01, 230.2734, 275.2857, 274.01, 232.6172, 273.2225, 274.01, 233.6225, 272.2172, 274.01, 234.9609, 270.5822, 274.01, 238.2254, 267.5297, 274.01, 240.3691, 265.1859, 274.01, 244.3359, 261.3326, 274.01, 246.6797, 258.8034, 274.01, 249.0234, 256.7196, 274.01, 251.3672, 254.0746, 274.01, 254.5313, 251.1234, 274.01, 255.333, 248.7797, 274.01, 257.3723, 246.4359, 274.01, 259.7201, 244.0922, 274.01, 260.106, 241.7484, 274.01, 261.6423, 239.4047, 274.01, 261.0804, 237.0609, 274.01, 259.3552, 234.7172, 274.01, 258.3984, 233.7696, 274.01, 256.0547, 232.8757, 274.01, 253.7109, 232.792, 274.01, 251.3672, 232.8161, 274.01, 246.6797, 232.6981, 274.01, 244.3359, 232.725, 274.01, 239.6484, 232.9137, 274.01, 234.9609, 232.8525, 274.01, 225.5859, 232.8757, 274.01, 209.1797, 232.8757, 274.01, 204.4922, 232.7537, 274.01, 195.1172, 232.7794, 274.01, 171.6797, 232.792, 274.01, 164.6484, 232.7666, 274.01, 152.9297, 232.7666, 274.01, 148.2422, 232.792, 274.01, 138.8672, 232.7406, 274.01 };
+    size_t n = sizeof(groundTruthRaw) / sizeof(groundTruthRaw[0]); 
+    std::vector<double> groundTruth(groundTruthRaw, groundTruthRaw+n);
+    EXPECT_EQ(340u * 3, groundTruth.size());
+    CheckGroundTruth(structures, 5, 0, groundTruth);
+  }
+
+  {
+    double groundTruthRaw[] = { -18.16406, 233.0632, 298.01, -27.53906, 233.1042, 298.01, -29.88281, 233.0819, 298.01, -34.57031, 233.131, 298.01, -43.94531, 233.1221, 298.01, -50.97656, 233.1736, 298.01, -62.69531, 233.1397, 298.01, -65.03906, 232.8376, 298.01, -69.72656, 232.9839, 298.01, -79.10156, 233.0245, 298.01, -90.82031, 233.0382, 298.01, -93.16406, 233.0859, 298.01, -109.5703, 233.1132, 298.01, -111.9141, 233.1791, 298.01, -114.2578, 233.7139, 298.01, -118.9453, 233.9793, 298.01, -128.3203, 234.0284, 298.01, -130.6641, 233.9793, 298.01, -135.3516, 234.0591, 298.01, -137.6953, 234.0284, 298.01, -142.3828, 234.0855, 298.01, -144.7266, 234.0284, 298.01, -151.7578, 234.002, 298.01, -158.7891, 234.0263, 298.01, -163.4766, 233.9784, 298.01, -165.8203, 234.0072, 298.01, -168.1641, 234.1756, 298.01, -170.5078, 234.2214, 298.01, -179.8828, 234.1934, 298.01, -186.9141, 234.2721, 298.01, -189.2578, 234.2289, 298.01, -193.9453, 234.2431, 298.01, -198.6328, 234.1692, 298.01, -200.9766, 234.2326, 298.01, -205.6641, 234.1271, 298.01, -212.6953, 234.2224, 298.01, -215.0391, 234.1992, 298.01, -222.0703, 234.3115, 298.01, -224.4141, 234.2224, 298.01, -226.7578, 234.2502, 298.01, -233.7891, 234.0906, 298.01, -238.4766, 234.0329, 298.01, -243.1641, 234.0283, 298.01, -247.8516, 233.7949, 298.01, -250.1953, 233.8681, 298.01, -252.5391, 234.7626, 298.01, -254.3469, 237.0609, 298.01, -255.6034, 239.4047, 298.01, -254.5181, 241.7484, 298.01, -254.2274, 244.0922, 298.01, -254.181, 248.7797, 298.01, -253.9355, 251.1234, 298.01, -253.5926, 253.4672, 298.01, -252.7483, 255.8109, 298.01, -250.8092, 258.1547, 298.01, -248.713, 260.4984, 298.01, -246.263, 262.8422, 298.01, -244.1406, 265.1859, 298.01, -241.6671, 267.5297, 298.01, -239.4754, 269.8734, 298.01, -237.0156, 272.2172, 298.01, -233.7891, 275.382, 298.01, -231.4453, 277.8249, 298.01, -229.1016, 279.9981, 298.01, -226.7578, 282.5281, 298.01, -224.4141, 284.6784, 298.01, -222.0703, 287.2355, 298.01, -220.5414, 288.6234, 298.01, -218.2745, 290.9672, 298.01, -217.3828, 291.6508, 298.01, -212.6953, 294.5949, 298.01, -210.3516, 295.3142, 298.01, -208.0078, 296.4674, 298.01, -205.6641, 296.8852, 298.01, -203.3203, 297.1563, 298.01, -196.2891, 297.1488, 298.01, -193.9453, 297.0597, 298.01, -182.2266, 296.9529, 298.01, -168.1641, 296.8576, 298.01, -154.1016, 296.9249, 298.01, -149.4141, 296.8921, 298.01, -128.3203, 296.9228, 298.01, -121.2891, 296.8623, 298.01, -111.9141, 296.8549, 298.01, -107.2266, 296.8266, 298.01, -102.5391, 296.8731, 298.01, -95.50781, 296.8453, 298.01, -88.47656, 296.9218, 298.01, -83.78906, 296.9016, 298.01, -69.72656, 296.979, 298.01, -67.38281, 296.9514, 298.01, -65.03906, 297.2199, 298.01, -62.69531, 296.9622, 298.01, -55.66406, 296.9926, 298.01, -50.97656, 296.9467, 298.01, -48.63281, 297.3652, 298.01, -46.28906, 297.0439, 298.01, -43.94531, 297.2875, 298.01, -39.25781, 297.0121, 298.01, -34.57031, 297.1564, 298.01, -32.22656, 297.3612, 298.01, -29.88281, 297.4229, 298.01, -27.53906, 297.1687, 298.01, -25.19531, 297.4334, 298.01, -18.16406, 297.3612, 298.01, -15.82031, 297.4441, 298.01, -13.47656, 297.4125, 298.01, -11.13281, 297.2468, 298.01, -8.789063, 297.4125, 298.01, -6.445313, 297.373, 298.01, -4.101563, 297.4195, 298.01, -1.757813, 297.077, 298.01, 0.5859375, 297.4229, 298.01, 2.929688, 297.4125, 298.01, 5.273438, 296.9489, 298.01, 7.617188, 297.3168, 298.01, 9.960938, 296.9377, 298.01, 12.30469, 296.8998, 298.01, 14.64844, 297.1975, 298.01, 16.99219, 296.8579, 298.01, 28.71094, 296.878, 298.01, 40.42969, 296.8163, 298.01, 42.77344, 296.8369, 298.01, 49.80469, 296.734, 298.01, 59.17969, 296.6906, 298.01, 61.52344, 296.6365, 298.01, 68.55469, 296.6278, 298.01, 73.24219, 296.5777, 298.01, 75.58594, 296.6191, 298.01, 84.96094, 296.5284, 298.01, 96.67969, 296.5538, 298.01, 103.7109, 296.479, 298.01, 115.4297, 296.4259, 298.01, 122.4609, 296.3434, 298.01, 129.4922, 296.3495, 298.01, 131.8359, 295.9141, 298.01, 136.5234, 296.2256, 298.01, 138.8672, 295.833, 298.01, 143.5547, 295.9857, 298.01, 145.8984, 295.8791, 298.01, 152.9297, 295.833, 298.01, 164.6484, 295.6819, 298.01, 171.6797, 295.6819, 298.01, 181.0547, 295.5401, 298.01, 185.7422, 295.5742, 298.01, 192.7734, 295.557, 298.01, 197.4609, 295.8012, 298.01, 202.1484, 295.6819, 298.01, 204.4922, 295.3698, 298.01, 206.8359, 294.803, 298.01, 209.1797, 294.3656, 298.01, 211.5234, 292.4764, 298.01, 213.8672, 291.1765, 298.01, 216.2109, 289.5873, 298.01, 217.229, 288.6234, 298.01, 218.5547, 287.0752, 298.01, 221.7097, 283.9359, 298.01, 225.5859, 279.8775, 298.01, 227.9297, 277.5633, 298.01, 230.2734, 275.0808, 298.01, 233.1989, 272.2172, 298.01, 234.9609, 270.2887, 298.01, 237.7384, 267.5297, 298.01, 241.9922, 263.0843, 298.01, 244.3359, 260.7643, 298.01, 246.788, 258.1547, 298.01, 249.0234, 255.451, 298.01, 250.3651, 253.4672, 298.01, 251.5297, 251.1234, 298.01, 252.1947, 248.7797, 298.01, 252.4915, 246.4359, 298.01, 252.5755, 241.7484, 298.01, 252.8592, 239.4047, 298.01, 252.9236, 237.0609, 298.01, 252.2924, 234.7172, 298.01, 251.3672, 233.4697, 298.01, 249.0234, 232.882, 298.01, 244.3359, 232.9048, 298.01, 241.9922, 233.0145, 298.01, 232.6172, 232.9048, 298.01, 227.9297, 233.0007, 298.01, 216.2109, 233.0632, 298.01, 211.5234, 233.0537, 298.01, 206.8359, 232.9699, 298.01, 204.4922, 232.7322, 298.01, 199.8047, 232.7186, 298.01, 190.4297, 232.7719, 298.01, 183.3984, 232.7719, 298.01, 181.0547, 232.7322, 298.01, 174.0234, 232.7048, 298.01, 171.6797, 232.7322, 298.01, 166.9922, 232.6908, 298.01, 157.6172, 232.7975, 298.01, 155.2734, 232.7588, 298.01, 148.2422, 232.7875, 298.01, 143.5547, 232.7614, 298.01, 138.8672, 232.6477, 298.01, 124.8047, 232.6179, 298.01, 122.4609, 232.6477, 298.01, 113.0859, 232.6027, 298.01, 110.7422, 232.4552, 298.01, 108.3984, 232.2192, 298.01, 106.0547, 231.6764, 298.01, 103.7109, 231.8559, 298.01, 102.8237, 232.3734, 298.01, 101.3672, 232.9839, 298.01, 99.02344, 233.0951, 298.01, 87.30469, 233.0819, 298.01, 84.96094, 233.1091, 298.01, 80.27344, 233.0726, 298.01, 77.92969, 233.1132, 298.01, 70.89844, 233.1397, 298.01, 68.55469, 233.1132, 298.01, 52.14844, 233.131, 298.01, 45.11719, 233.0859, 298.01, 44.16726, 232.3734, 298.01, 42.77344, 231.0206, 298.01, 42.04498, 230.0297, 298.01, 42.77344, 229.2462, 298.01, 45.11719, 228.5664, 298.01, 47.46094, 227.0695, 298.01, 49.80469, 226.0552, 298.01, 52.14844, 224.5723, 298.01, 54.49219, 223.6857, 298.01, 56.83594, 221.8519, 298.01, 59.17969, 220.2086, 298.01, 61.52344, 218.8854, 298.01, 64.94469, 215.9672, 298.01, 66.21094, 215.0191, 298.01, 67.72036, 213.6234, 298.01, 68.55469, 212.6986, 298.01, 70.89844, 210.5055, 298.01, 74.53191, 206.5922, 298.01, 76.54903, 204.2484, 298.01, 78.26105, 201.9047, 298.01, 80.27344, 198.9262, 298.01, 82.61719, 195.2822, 298.01, 82.98087, 194.8734, 298.01, 84.96094, 190.9255, 298.01, 85.43701, 190.1859, 298.01, 86.33423, 187.8422, 298.01, 87.78722, 185.4984, 298.01, 88.60233, 183.1547, 298.01, 89.10253, 180.8109, 298.01, 90.17504, 178.4672, 298.01, 90.88959, 176.1234, 298.01, 91.43783, 173.7797, 298.01, 92.39601, 171.4359, 298.01, 92.95762, 169.0922, 298.01, 93.55695, 159.7172, 298.01, 93.65527, 157.3734, 298.01, 93.67542, 152.6859, 298.01, 93.61213, 150.3422, 298.01, 93.22542, 143.3109, 298.01, 93.06345, 140.9672, 298.01, 92.77563, 138.6234, 298.01, 91.21714, 133.9359, 298.01, 90.67235, 131.5922, 298.01, 89.88776, 129.2484, 298.01, 88.8737, 126.9047, 298.01, 88.44087, 124.5609, 298.01, 86.09712, 119.8734, 298.01, 85.05786, 117.5297, 298.01, 83.87151, 115.1859, 298.01, 82.22388, 112.8422, 298.01, 81.09117, 110.4984, 298.01, 77.92969, 106.4052, 298.01, 77.3894, 105.8109, 298.01, 75.94332, 103.4672, 298.01, 71.71799, 98.77969, 298.01, 68.55469, 95.65721, 298.01, 63.86719, 91.54878, 298.01, 61.52344, 90.1121, 298.01, 59.17969, 88.15762, 298.01, 56.83594, 86.51503, 298.01, 54.49219, 85.42721, 298.01, 52.14844, 83.64907, 298.01, 49.80469, 82.89023, 298.01, 47.46094, 81.50237, 298.01, 45.11719, 80.62591, 298.01, 42.77344, 79.18153, 298.01, 40.42969, 78.7203, 298.01, 38.08594, 78.1349, 298.01, 35.74219, 77.11755, 298.01, 33.39844, 76.51949, 298.01, 31.05469, 76.07934, 298.01, 26.36719, 74.67744, 298.01, 24.02344, 74.42056, 298.01, 14.64844, 74.07317, 298.01, 9.960938, 74.11538, 298.01, 2.929688, 74.40105, 298.01, 0.5859375, 74.67952, 298.01, -1.757813, 75.31406, 298.01, -4.101563, 76.07065, 298.01, -6.445313, 76.49051, 298.01, -8.789063, 77.17276, 298.01, -11.13281, 78.20097, 298.01, -15.82031, 79.31967, 298.01, -18.16406, 80.76948, 298.01, -20.50781, 81.64266, 298.01, -22.85156, 83.0305, 298.01, -25.19531, 83.7937, 298.01, -27.53906, 85.63515, 298.01, -29.88281, 86.7363, 298.01, -32.22656, 88.36089, 298.01, -34.57031, 90.3302, 298.01, -36.56719, 91.74844, 298.01, -41.60156, 95.93605, 298.01, -46.58845, 101.1234, 298.01, -50.17995, 105.8109, 298.01, -52.10386, 108.1547, 298.01, -53.63992, 110.4984, 298.01, -54.95532, 112.8422, 298.01, -56.64794, 115.1859, 298.01, -57.4403, 117.5297, 298.01, -58.91927, 119.8734, 298.01, -59.78655, 122.2172, 298.01, -61.11754, 124.5609, 298.01, -61.58921, 126.9047, 298.01, -62.38012, 129.2484, 298.01, -63.49118, 131.5922, 298.01, -64.02599, 133.9359, 298.01, -64.3932, 136.2797, 298.01, -65.11897, 138.6234, 298.01, -65.64544, 140.9672, 298.01, -66.23938, 147.9984, 298.01, -66.46289, 152.6859, 298.01, -66.48911, 155.0297, 298.01, -66.34437, 159.7172, 298.01, -65.99894, 164.4047, 298.01, -65.49149, 169.0922, 298.01, -64.6875, 171.4359, 298.01, -63.7739, 176.1234, 298.01, -62.9398, 178.4672, 298.01, -61.86011, 180.8109, 298.01, -61.33423, 183.1547, 298.01, -60.43332, 185.4984, 298.01, -58.00781, 190.0632, 298.01, -56.85406, 192.5297, 298.01, -55.66406, 194.7283, 298.01, -54.11692, 197.2172, 298.01, -50.97656, 201.8369, 298.01, -47.36435, 206.5922, 298.01, -45.04395, 208.9359, 298.01, -42.83026, 211.2797, 298.01, -39.25781, 214.7435, 298.01, -34.57031, 218.4974, 298.01, -32.22656, 219.9595, 298.01, -28.02053, 222.9984, 298.01, -27.53906, 223.4238, 298.01, -25.19531, 224.4187, 298.01, -22.85156, 225.8252, 298.01, -20.50781, 226.9067, 298.01, -18.16406, 228.4286, 298.01, -15.82031, 229.1235, 298.01, -14.9447, 230.0297, 298.01, -15.82031, 231.3969, 298.01, -16.94484, 232.3734, 298.01 };
+    size_t n = sizeof(groundTruthRaw) / sizeof(groundTruthRaw[0]); 
+    std::vector<double> groundTruth(groundTruthRaw, groundTruthRaw+n);
+    EXPECT_EQ(358u * 3, groundTruth.size());
+    CheckGroundTruth(structures, 5, 8, groundTruth);
+  }
+}
+
+#endif 
+// BGO_ENABLE_DICOMSTRUCTURESETLOADER2
+
+#if 0
+
+TEST(StructureSet2, ReadFromJsonAndCompute1)
+{
+  DicomStructureSet2 structureSet;
+
+  OrthancPlugins::FullOrthancDataset dicom(GetTestJson());
+  structureSet.Clear();
+
+  structureSet.FillStructuresFromDataset(dicom);
+
+  structureSet.ComputeDependentProperties();
+}
+
+TEST(StructureSet2, ReadFromJsonAndCompute2)
+{
+  DicomStructureSet2 structureSet;
+
+  OrthancPlugins::FullOrthancDataset dicom(GetTestJson());
+  structureSet.Clear();
+
+  structureSet.SetContents(dicom);
+}
+#endif
+
+#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2
+
+static bool CutStructureWithPlane(
+  std::vector< std::pair<ScenePoint2D, ScenePoint2D> >& segments,
+  const DicomStructure2& structure,
+  const double originX, const double originY, const double originZ,
+  const double axisX_X, const double axisX_Y, const double axisX_Z,
+  const double axisY_X, const double axisY_Y, const double axisY_Z
+)
+{
+  // create an AXIAL cutting plane, too far away from the volume 
+  // (> sliceThickness/2)
+  Vector origin, axisX, axisY;
+  LinearAlgebra::AssignVector(origin, originX, originY, originZ);
+  LinearAlgebra::AssignVector(axisX, axisX_X, axisX_Y, axisX_Z);
+  LinearAlgebra::AssignVector(axisY, axisY_X, axisY_Y, axisY_Z);
+  CoordinateSystem3D cuttingPlane(origin, axisX, axisY);
+
+  // compute intersection
+  bool ok = structure.Project(segments, cuttingPlane);
+  return ok;
+}
+
+#endif 
+// BGO_ENABLE_DICOMSTRUCTURESETLOADER2
+
+#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2
+
+static void CreateBasicStructure(DicomStructure2& structure)
+{
+  // see https://www.dropbox.com/s/1o1vg53hsbvx4cc/test-rtstruct-polygons.jpg?dl=0
+  EXPECT_EQ(pointsCoord1Count, pointsCoord2Count);
+  EXPECT_EQ(11u, pointsCoord2Count);
+
+  for (size_t slice = 0; slice < 3; ++slice)
+  {
+    DicomStructurePolygon2 polygon("Oblomptu", "CLOSED_PLANAR");
+    for (size_t ip = 0; ip < pointsCoord1Count; ++ip)
+    {
+      Vector pt;
+      double pt0 = pointsCoord1[ip];
+      double pt1 = pointsCoord2[ip];
+      double pt2 = 4 * (static_cast<double>(slice) - 1); // -4, 0, 4 
+      LinearAlgebra::AssignVector(pt, pt0, pt1, pt2);
+      polygon.AddPoint(pt);
+    }
+    structure.AddPolygon(polygon);
+  }
+  structure.ComputeDependentProperties();
+}
+
+
+TEST(StructureSet2, CutAxialOutsideTop)
+{
+  DicomStructure2 structure;
+  CreateBasicStructure(structure);
+  std::vector< std::pair<ScenePoint2D, ScenePoint2D> > segments;
+
+  // create an AXIAL cutting plane, too far away from the volume 
+  // (> sliceThickness/2)
+  bool ok = CutStructureWithPlane(segments, structure,
+    0, 0, 7,
+    1, 0, 0,
+    0, 1, 0);
+  EXPECT_FALSE(ok);
+}
+
+
+TEST(StructureSet2, CutAxialOutsideBottom)
+{
+  DicomStructure2 structure;
+  CreateBasicStructure(structure);
+  std::vector< std::pair<ScenePoint2D, ScenePoint2D> > segments;
+
+  // create an AXIAL cutting plane, too far away from the volume 
+  // (> sliceThickness/2)
+  bool ok = CutStructureWithPlane(segments, structure,
+    0, 0, -6.66,
+    1, 0, 0,
+    0, 1, 0);
+  EXPECT_FALSE(ok);
+}
+
+TEST(StructureSet2, CutAxialInsideClose)
+{
+  DicomStructure2 structure;
+  CreateBasicStructure(structure);
+  std::vector< std::pair<ScenePoint2D, ScenePoint2D> > segments;
+
+  // create an AXIAL cutting plane in the volume
+  bool ok = CutStructureWithPlane(segments, structure,
+    0, 0, 1.1,
+    1, 0, 0,
+    0, 1, 0);
+  EXPECT_TRUE(ok);
+  EXPECT_EQ(POLYGON_POINT_COUNT, segments.size());
+
+  for (size_t i = 0; i < segments.size(); ++i)
+  {
+    EXPECT_LT(i, POLYGON_POINT_COUNT);
+    EXPECT_LT(i, POLYGON_POINT_COUNT);
+
+    const ScenePoint2D& pt = segments[i].first;
+
+    // ...should be at the same location as the 3D coords since the plane 
+    // is rooted at 0,0,0 with normal 0,0,1
+    EXPECT_NEAR(pt.GetX(), pointsCoord1[i], DELTA_MAX);
+    EXPECT_NEAR(pt.GetY(), pointsCoord2[i], DELTA_MAX);
+  }
+}
+
+
+TEST(StructureSet2, CutAxialInsideFar)
+{
+  DicomStructure2 structure;
+  CreateBasicStructure(structure);
+  std::vector< std::pair<ScenePoint2D, ScenePoint2D> > segments;
+
+  // create an AXIAL cutting plane
+  Vector origin, axisX, axisY;
+  LinearAlgebra::AssignVector(origin, 0, 0, 0);
+  LinearAlgebra::AssignVector(axisX, 1, 0, 0);
+  LinearAlgebra::AssignVector(axisY, 0, 1, 0);
+  CoordinateSystem3D cuttingPlane(origin, axisX, axisY);
+
+  // compute intersection
+  bool ok = structure.Project(segments, cuttingPlane);
+  EXPECT_TRUE(ok);
+
+  EXPECT_EQ(11u, segments.size());
+  for (size_t i = 0; i < segments.size(); ++i)
+  {
+    EXPECT_LT(i, pointsCoord1Count);
+    EXPECT_LT(i, pointsCoord2Count);
+
+    // the 2D points of the projected polygon
+    const ScenePoint2D& pt = segments[i].first;
+
+    // ...should be at the same location as the 3D coords since the plane 
+    // is rooted at 0,0,0 with normal 0,0,1
+    EXPECT_NEAR(pt.GetX(), pointsCoord1[i], DELTA_MAX);
+    EXPECT_NEAR(pt.GetY(), pointsCoord2[i], DELTA_MAX);
+  }
+}
+
+TEST(StructureSet2, CutCoronalOutsideClose)
+{
+  DicomStructure2 structure;
+  CreateBasicStructure(structure);
+  std::vector< std::pair<ScenePoint2D, ScenePoint2D> > segments;
+
+  // create an X,Z cutting plane, outside of the volume
+  Vector origin, axisX, axisY;
+  LinearAlgebra::AssignVector(origin, 0, 0, 0);
+  LinearAlgebra::AssignVector(axisX, 1, 0, 0);
+  LinearAlgebra::AssignVector(axisY, 0, 0, 1);
+  CoordinateSystem3D cuttingPlane(origin, axisX, axisY);
+
+  // compute intersection
+  bool ok = structure.Project(segments, cuttingPlane);
+  EXPECT_FALSE(ok);
+}
+
+TEST(StructureSet2, CutCoronalInsideClose1DTest)
+{
+  DicomStructure2 structure;
+  CreateBasicStructure(structure);
+
+  // create an X,Z cutting plane, outside of the volume
+  Vector origin, axisX, axisY;
+  LinearAlgebra::AssignVector(origin, 0, 3, 0);
+  LinearAlgebra::AssignVector(axisX, 1, 0, 0);
+  LinearAlgebra::AssignVector(axisY, 0, 0, 1);
+  CoordinateSystem3D cuttingPlane(origin, axisX, axisY);
+
+  ASSERT_EQ(3u, structure.GetPolygons().size());
+
+  for (int i = 0; i < 3; ++i)
+  {
+    double polygonZ = static_cast<double>(i - 1) * 4.0;
+
+    const DicomStructurePolygon2& topSlab = structure.GetPolygons()[i];
+
+    // let's compute the intersection between the polygon and the plane
+    // intersections are in plane coords
+    std::vector<ScenePoint2D> intersects;
+    topSlab.ProjectOnConstantPlane(intersects, cuttingPlane);
+
+    ASSERT_EQ(4u, intersects.size());
+
+    EXPECT_NEAR(2, intersects[0].GetX(), DELTA_MAX);
+    EXPECT_NEAR(4, intersects[1].GetX(), DELTA_MAX);
+    EXPECT_NEAR(7, intersects[2].GetX(), DELTA_MAX);
+    EXPECT_NEAR(8, intersects[3].GetX(), DELTA_MAX);
+
+    for (size_t i = 0; i < 4u; ++i)
+    {
+      EXPECT_NEAR(polygonZ, intersects[i].GetY(), DELTA_MAX);
+    }
+  }
+}
+
+TEST(StructureSet2, CutCoronalInsideClose)
+{
+  DicomStructure2 structure;
+  CreateBasicStructure(structure);
+  std::vector< std::pair<ScenePoint2D, ScenePoint2D> > segments;
+
+  // create an X,Z cutting plane, outside of the volume
+  Vector origin, axisX, axisY;
+  LinearAlgebra::AssignVector(origin, 0, 3, 0);
+  LinearAlgebra::AssignVector(axisX, 1, 0, 0);
+  LinearAlgebra::AssignVector(axisY, 0, 0, 1);
+  CoordinateSystem3D cuttingPlane(origin, axisX, axisY);
+
+  // compute intersection
+  ASSERT_TRUE(structure.Project(segments, cuttingPlane));
+  EXPECT_EQ(24u, segments.size());
+
+  size_t numberOfVeryShortSegments = 0;
+  for (size_t iSegment = 0; iSegment < segments.size(); ++iSegment)
+  {
+    // count the NON vertical very short segments 
+    if (LinearAlgebra::IsNear(segments[iSegment].first.GetX(), segments[iSegment].second.GetX()))
+    {
+      if (LinearAlgebra::IsNear(segments[iSegment].first.GetY(), segments[iSegment].second.GetY()))
+      {
+        numberOfVeryShortSegments++;
+      }
+    }
+  }
+  EXPECT_EQ(8u, numberOfVeryShortSegments);
+}
+
+#endif 
+// BGO_ENABLE_DICOMSTRUCTURESETLOADER2
+
+TEST(DisjointDataSet, BasicTest)
+{
+  const size_t ITEM_COUNT = 10;
+  DisjointDataSet ds(ITEM_COUNT);
+
+  for (size_t i = 0; i < ITEM_COUNT; ++i)
+  {
+    EXPECT_EQ(i, ds.Find(i));
+  }
+
+  ds.Union(0, 4);
+  EXPECT_EQ(0u, ds.Find(0));
+  EXPECT_EQ(0u, ds.Find(4));
+
+  ds.Union(4, 6);
+  ds.Union(8, 9);
+  ds.Union(0, 8);
+
+  for (size_t i = 0; i < ITEM_COUNT; ++i)
+  {
+    size_t parent = ds.Find(i);
+    EXPECT_TRUE(0 == parent || 1 == parent || 2 == parent || 3 == parent || 5 == parent || 7 == parent);
+  }
+
+  ds.Union(1, 2);
+  ds.Union(1, 7);
+  for (size_t i = 0; i < ITEM_COUNT; ++i)
+  {
+    size_t parent = ds.Find(i);
+    EXPECT_TRUE(0 == parent || 1 == parent || 3 == parent || 5 == parent);
+  }
+
+  ds.Union(3, 5);
+  for (size_t i = 0; i < ITEM_COUNT; ++i)
+  {
+    size_t parent = ds.Find(i);
+    EXPECT_TRUE(0 == parent || 1 == parent || 3 == parent);
+  }
+
+  EXPECT_EQ(ds.Find(0), ds.Find(0));
+  EXPECT_EQ(ds.Find(0), ds.Find(4));
+  EXPECT_EQ(ds.Find(0), ds.Find(6));
+  EXPECT_EQ(ds.Find(0), ds.Find(8));
+  EXPECT_EQ(ds.Find(0), ds.Find(8));
+
+  EXPECT_EQ(ds.Find(1), ds.Find(7));
+  EXPECT_EQ(ds.Find(2), ds.Find(1));
+  EXPECT_EQ(ds.Find(7), ds.Find(2));
+
+  EXPECT_EQ(ds.Find(3), ds.Find(5));
+  EXPECT_EQ(ds.Find(5), ds.Find(3));
+
+  ds.Union(0, 1);
+  ds.Union(3, 1);
+  for (size_t i = 0; i < ITEM_COUNT; ++i)
+  {
+    EXPECT_EQ(ds.Find(0), ds.Find(i));
+  }
+}
+
+#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2
+
+TEST(StructureSet2, CutSagittalInsideClose)
+{
+  DicomStructure2 structure;
+  CreateBasicStructure(structure);
+  std::vector< std::pair<ScenePoint2D, ScenePoint2D> > segments;
+
+  // create an X,Z cutting plane, inside of the volume
+  Vector origin, axisX, axisY;
+  LinearAlgebra::AssignVector(origin, 0, 3, 0);
+  LinearAlgebra::AssignVector(axisX, 1, 0, 0);
+  LinearAlgebra::AssignVector(axisY, 0, 0, 1);
+  CoordinateSystem3D cuttingPlane(origin, axisX, axisY);
+
+  // compute intersection
+  bool ok = structure.Project(segments, cuttingPlane);
+  EXPECT_TRUE(ok);
+}
+
+#endif 
+// BGO_ENABLE_DICOMSTRUCTURESETLOADER2
+
+
+static size_t ConvertListOfSlabsToSegments_Add(RtStructRectanglesInSlab& rectangles, int row, double xmin, double xmax)
+{
+  double ymin = static_cast<double>(row) * 5.0;
+  double ymax = static_cast<double>(row + 1) * 5.0;
+
+  RtStructRectangleInSlab rectangle;
+  rectangle.xmin = xmin;
+  rectangle.xmax = xmax;
+  rectangle.ymin = ymin;
+  rectangle.ymax = ymax;
+
+  rectangles.push_back(rectangle);
+  
+  return 1u;
+}
+
+static size_t FillTestRectangleList(std::vector< RtStructRectanglesInSlab >& rectanglesForEachSlab)
+{
+  // ConvertListOfSlabsToSegments
+  size_t rectCount = 0;
+
+  rectanglesForEachSlab.push_back(RtStructRectanglesInSlab());
+  rectCount += ConvertListOfSlabsToSegments_Add(rectanglesForEachSlab.back(), 0, 5, 31);
+  rectCount += ConvertListOfSlabsToSegments_Add(rectanglesForEachSlab.back(), 0, 36, 50);
+
+  rectanglesForEachSlab.push_back(RtStructRectanglesInSlab());
+  rectCount += ConvertListOfSlabsToSegments_Add(rectanglesForEachSlab.back(), 1, 20, 45);
+  rectCount += ConvertListOfSlabsToSegments_Add(rectanglesForEachSlab.back(), 1, 52, 70);
+
+  rectanglesForEachSlab.push_back(RtStructRectanglesInSlab());
+  rectCount += ConvertListOfSlabsToSegments_Add(rectanglesForEachSlab.back(), 2, 0, 32);
+  rectCount += ConvertListOfSlabsToSegments_Add(rectanglesForEachSlab.back(), 2, 35, 44);
+  rectCount += ConvertListOfSlabsToSegments_Add(rectanglesForEachSlab.back(), 2, 60, 75);
+
+  rectanglesForEachSlab.push_back(RtStructRectanglesInSlab());
+  rectCount += ConvertListOfSlabsToSegments_Add(rectanglesForEachSlab.back(), 3, 10, 41);
+  rectCount += ConvertListOfSlabsToSegments_Add(rectanglesForEachSlab.back(), 3, 46, 80);
+
+  rectanglesForEachSlab.push_back(RtStructRectanglesInSlab());
+  rectCount += ConvertListOfSlabsToSegments_Add(rectanglesForEachSlab.back(), 4, 34, 42);
+  rectCount += ConvertListOfSlabsToSegments_Add(rectanglesForEachSlab.back(), 4, 90, 96);
+
+  rectanglesForEachSlab.push_back(RtStructRectanglesInSlab());
+  rectCount += ConvertListOfSlabsToSegments_Add(rectanglesForEachSlab.back(), 5, 1, 33);
+  rectCount += ConvertListOfSlabsToSegments_Add(rectanglesForEachSlab.back(), 5, 40, 43);
+  rectCount += ConvertListOfSlabsToSegments_Add(rectanglesForEachSlab.back(), 5, 51, 61);
+  rectCount += ConvertListOfSlabsToSegments_Add(rectanglesForEachSlab.back(), 5, 76, 95);
+
+  return rectCount;
+}
+
+/*
+void AddSlabBoundaries(
+  std::vector<std::pair<double, RectangleBoundaryKind> >& boundaries,
+  const std::vector<RtStructRectanglesInSlab>& slabCuts, size_t iSlab)
+*/
+
+
+/*
+void ProcessBoundaryList(
+  std::vector< std::pair<ScenePoint2D, ScenePoint2D> >& segments,
+  const std::vector<std::pair<double, RectangleBoundaryKind> >& boundaries,
+  double y)
+*/
+
+
+TEST(StructureSet2, ProcessBoundaryList_Empty)
+{
+  std::vector< RtStructRectanglesInSlab > slabCuts;
+  std::vector<std::pair<double, RectangleBoundaryKind> > boundaries;
+
+  boundaries.clear();
+  EXPECT_NO_THROW(AddSlabBoundaries(boundaries, slabCuts, 0));
+  ASSERT_EQ(0u, boundaries.size());
+}
+
+TEST(StructureSet2, ProcessBoundaryListTopRow)
+{
+  std::vector< RtStructRectanglesInSlab > slabCuts;
+  std::vector<std::pair<double, RectangleBoundaryKind> > boundaries;
+  FillTestRectangleList(slabCuts);
+
+  boundaries.clear();
+  AddSlabBoundaries(boundaries, slabCuts, 0);
+
+  {
+    size_t i = 0;
+    ASSERT_EQ(4u, boundaries.size());
+
+    ASSERT_EQ(RectangleBoundaryKind_Start, boundaries[i].second);
+    ASSERT_NEAR(5, boundaries[i].first, DELTA_MAX);
+    i++;
+
+    ASSERT_EQ(RectangleBoundaryKind_End, boundaries[i].second);
+    ASSERT_NEAR(31, boundaries[i].first, DELTA_MAX);
+    i++;
+
+    ASSERT_EQ(RectangleBoundaryKind_Start, boundaries[i].second);
+    ASSERT_NEAR(36, boundaries[i].first, DELTA_MAX);
+    i++;
+
+    ASSERT_EQ(RectangleBoundaryKind_End, boundaries[i].second);
+    ASSERT_NEAR(50, boundaries[i].first, DELTA_MAX);
+    i++;
+  }
+}
+
+TEST(StructureSet2, ProcessBoundaryListRows_0_and_1)
+{
+  std::vector< RtStructRectanglesInSlab > slabCuts;
+  std::vector<std::pair<double, RectangleBoundaryKind> > boundaries;
+  FillTestRectangleList(slabCuts);
+
+  boundaries.clear();
+  AddSlabBoundaries(boundaries, slabCuts, 0);
+  AddSlabBoundaries(boundaries, slabCuts, 1);
+
+  ASSERT_EQ(8u, boundaries.size());
+
+  {
+    size_t i = 0;
+
+    ASSERT_EQ(RectangleBoundaryKind_Start, boundaries[i].second);
+    ASSERT_NEAR(5, boundaries[i].first, DELTA_MAX);
+    i++;
+
+    ASSERT_EQ(RectangleBoundaryKind_Start, boundaries[i].second);
+    ASSERT_NEAR(20, boundaries[i].first, DELTA_MAX);
+    i++;
+
+    ASSERT_EQ(RectangleBoundaryKind_End, boundaries[i].second);
+    ASSERT_NEAR(31, boundaries[i].first, DELTA_MAX);
+    i++;
+
+    ASSERT_EQ(RectangleBoundaryKind_Start, boundaries[i].second);
+    ASSERT_NEAR(36, boundaries[i].first, DELTA_MAX);
+    i++;
+
+    ASSERT_EQ(RectangleBoundaryKind_End, boundaries[i].second);
+    ASSERT_NEAR(45, boundaries[i].first, DELTA_MAX);
+    i++;
+
+    ASSERT_EQ(RectangleBoundaryKind_End, boundaries[i].second);
+    ASSERT_NEAR(50, boundaries[i].first, DELTA_MAX);
+    i++;
+
+    ASSERT_EQ(RectangleBoundaryKind_Start, boundaries[i].second);
+    ASSERT_NEAR(52, boundaries[i].first, DELTA_MAX);
+    i++;
+
+    ASSERT_EQ(RectangleBoundaryKind_End, boundaries[i].second);
+    ASSERT_NEAR(70, boundaries[i].first, DELTA_MAX);
+    i++;
+  }
+}
+
+TEST(StructureSet2, ConvertListOfSlabsToSegments_EmptyBoundaries)
+{
+  std::vector< RtStructRectanglesInSlab > slabCuts;
+  std::vector<std::pair<double, RectangleBoundaryKind> > boundaries;
+  FillTestRectangleList(slabCuts);
+  boundaries.clear();
+  std::vector< std::pair<ScenePoint2D, ScenePoint2D> > segments;
+  ASSERT_NO_THROW(ProcessBoundaryList(segments, boundaries, 42.0));
+  ASSERT_EQ(0u, segments.size());
+}
+
+TEST(StructureSet2, ConvertListOfSlabsToSegments_TopRow_Horizontal)
+{
+  std::vector< RtStructRectanglesInSlab > slabCuts;
+  FillTestRectangleList(slabCuts);
+
+  // top row
+  {
+    std::vector< std::pair<ScenePoint2D, ScenePoint2D> > segments;
+    std::vector<std::pair<double, RectangleBoundaryKind> > boundaries;
+    AddSlabBoundaries(boundaries, slabCuts, 0);
+    ProcessBoundaryList(segments, boundaries, slabCuts[0][0].ymin);
+
+    ASSERT_EQ(2u, segments.size());
+
+    ASSERT_NEAR( 5.0, segments[0].first.GetX(), DELTA_MAX);
+    ASSERT_NEAR(31.0, segments[0].second.GetX(), DELTA_MAX);
+    ASSERT_NEAR( 0.0, segments[0].first.GetY(), DELTA_MAX);
+    ASSERT_NEAR( 0.0, segments[0].second.GetY(), DELTA_MAX);
+
+    ASSERT_NEAR(36.0, segments[1].first.GetX(), DELTA_MAX);
+    ASSERT_NEAR(50.0, segments[1].second.GetX(), DELTA_MAX);
+    ASSERT_NEAR( 0.0, segments[1].first.GetY(), DELTA_MAX);
+    ASSERT_NEAR( 0.0, segments[1].second.GetY(), DELTA_MAX);
+  }
+}
+
+TEST(StructureSet2, ConvertListOfSlabsToSegments_All_Horizontal)
+{
+  std::vector< RtStructRectanglesInSlab > slabCuts;
+  std::vector<std::pair<double, RectangleBoundaryKind> > boundaries;
+  FillTestRectangleList(slabCuts);
+
+  // top row
+  {
+    std::vector<std::pair<double, RectangleBoundaryKind> > boundaries;
+    AddSlabBoundaries(boundaries, slabCuts, 0);
+    std::vector< std::pair<ScenePoint2D, ScenePoint2D> > segments;
+    ProcessBoundaryList(segments, boundaries, slabCuts[0][0].ymin);
+  }
+
+  // mids
+  {
+    std::vector<std::pair<double, RectangleBoundaryKind> > boundaries;
+    AddSlabBoundaries(boundaries, slabCuts, 0);
+    AddSlabBoundaries(boundaries, slabCuts, 1);
+    std::vector< std::pair<ScenePoint2D, ScenePoint2D> > segments;
+    ProcessBoundaryList(segments, boundaries, slabCuts[0][0].ymax);
+
+    ASSERT_EQ(4u, segments.size());
+
+    ASSERT_NEAR(05.0, segments[0].first.GetX(), DELTA_MAX);
+    ASSERT_NEAR(20.0, segments[0].second.GetX(), DELTA_MAX);
+    ASSERT_NEAR(05.0, segments[0].first.GetY(), DELTA_MAX);
+    ASSERT_NEAR(05.0, segments[0].second.GetY(), DELTA_MAX);
+
+    ASSERT_NEAR(31.0, segments[1].first.GetX(), DELTA_MAX);
+    ASSERT_NEAR(36.0, segments[1].second.GetX(), DELTA_MAX);
+    ASSERT_NEAR(05.0, segments[1].first.GetY(), DELTA_MAX);
+    ASSERT_NEAR(05.0, segments[1].second.GetY(), DELTA_MAX);
+
+    ASSERT_NEAR(45.0, segments[2].first.GetX(), DELTA_MAX);
+    ASSERT_NEAR(50.0, segments[2].second.GetX(), DELTA_MAX);
+    ASSERT_NEAR(05.0, segments[2].first.GetY(), DELTA_MAX);
+    ASSERT_NEAR(05.0, segments[2].second.GetY(), DELTA_MAX);
+
+    ASSERT_NEAR(52.0, segments[3].first.GetX(), DELTA_MAX);
+    ASSERT_NEAR(70.0, segments[3].second.GetX(), DELTA_MAX);
+    ASSERT_NEAR(05.0, segments[3].first.GetY(), DELTA_MAX);
+    ASSERT_NEAR(05.0, segments[3].second.GetY(), DELTA_MAX);
+  }
+
+  // bottom row
+  {
+    std::vector<std::pair<double, RectangleBoundaryKind> > boundaries;
+    AddSlabBoundaries(boundaries, slabCuts, 1);
+    std::vector< std::pair<ScenePoint2D, ScenePoint2D> > segments;
+    ProcessBoundaryList(segments, boundaries, slabCuts[1][0].ymax);
+
+    ASSERT_EQ(2u, segments.size());
+
+    ASSERT_NEAR(20.0, segments[0].first.GetX(), DELTA_MAX);
+    ASSERT_NEAR(45.0, segments[0].second.GetX(), DELTA_MAX);
+    ASSERT_NEAR(10.0, segments[0].first.GetY(), DELTA_MAX);
+    ASSERT_NEAR(10.0, segments[0].second.GetY(), DELTA_MAX);
+
+    ASSERT_NEAR(52.0, segments[1].first.GetX(), DELTA_MAX);
+    ASSERT_NEAR(70.0, segments[1].second.GetX(), DELTA_MAX);
+    ASSERT_NEAR(10.0, segments[1].first.GetY(), DELTA_MAX);
+    ASSERT_NEAR(10.0, segments[1].second.GetY(), DELTA_MAX);
+  }
+
+}
+
+TEST(StructureSet2, ConvertListOfSlabsToSegments_Complete_Empty)
+{
+  std::vector< RtStructRectanglesInSlab > slabCuts;
+  std::vector<std::pair<double, RectangleBoundaryKind> > boundaries;
+
+  std::vector< std::pair<ScenePoint2D, ScenePoint2D> > segments;
+
+  ASSERT_NO_THROW(ConvertListOfSlabsToSegments(segments, slabCuts, 0));
+  ASSERT_EQ(0u, segments.size());
+}
+
+TEST(StructureSet2, ConvertListOfSlabsToSegments_Complete_Regular)
+{
+  std::vector< RtStructRectanglesInSlab > slabCuts;
+  std::vector<std::pair<double, RectangleBoundaryKind> > boundaries;
+  size_t totalRectCount = FillTestRectangleList(slabCuts);
+
+  std::vector< std::pair<ScenePoint2D, ScenePoint2D> > segments;
+
+  ASSERT_NO_THROW(ConvertListOfSlabsToSegments(segments, slabCuts, totalRectCount));
+  ASSERT_EQ(60u, segments.size());
+
+  size_t i = 0;
+
+  ASSERT_NEAR(segments[i].first.GetX(), 5.0000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].first.GetY(), 0.0000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetX(), 5.0000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetY(), 5.0000000000000000, DELTA_MAX);
+  i++;
+  ASSERT_NEAR(segments[i].first.GetX(), 31.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].first.GetY(), 0.0000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetX(), 31.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetY(), 5.0000000000000000, DELTA_MAX);
+  i++;
+  ASSERT_NEAR(segments[i].first.GetX(), 36.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].first.GetY(), 0.0000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetX(), 36.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetY(), 5.0000000000000000, DELTA_MAX);
+  i++;
+  ASSERT_NEAR(segments[i].first.GetX(), 50.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].first.GetY(), 0.0000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetX(), 50.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetY(), 5.0000000000000000, DELTA_MAX);
+  i++;
+  ASSERT_NEAR(segments[i].first.GetX(), 20.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].first.GetY(), 5.0000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetX(), 20.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetY(), 10.000000000000000, DELTA_MAX);
+  i++;
+  ASSERT_NEAR(segments[i].first.GetX(), 45.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].first.GetY(), 5.0000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetX(), 45.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetY(), 10.000000000000000, DELTA_MAX);
+  i++;
+  ASSERT_NEAR(segments[i].first.GetX(), 52.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].first.GetY(), 5.0000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetX(), 52.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetY(), 10.000000000000000, DELTA_MAX);
+  i++;
+  ASSERT_NEAR(segments[i].first.GetX(), 70.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].first.GetY(), 5.0000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetX(), 70.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetY(), 10.000000000000000, DELTA_MAX);
+  i++;
+  ASSERT_NEAR(segments[i].first.GetX(), 0.0000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].first.GetY(), 10.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetX(), 0.0000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetY(), 15.000000000000000, DELTA_MAX);
+  i++;
+  ASSERT_NEAR(segments[i].first.GetX(), 32.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].first.GetY(), 10.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetX(), 32.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetY(), 15.000000000000000, DELTA_MAX);
+  i++;
+  ASSERT_NEAR(segments[i].first.GetX(), 35.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].first.GetY(), 10.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetX(), 35.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetY(), 15.000000000000000, DELTA_MAX);
+  i++;
+  ASSERT_NEAR(segments[i].first.GetX(), 44.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].first.GetY(), 10.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetX(), 44.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetY(), 15.000000000000000, DELTA_MAX);
+  i++;
+  ASSERT_NEAR(segments[i].first.GetX(), 60.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].first.GetY(), 10.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetX(), 60.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetY(), 15.000000000000000, DELTA_MAX);
+  i++;
+  ASSERT_NEAR(segments[i].first.GetX(), 75.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].first.GetY(), 10.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetX(), 75.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetY(), 15.000000000000000, DELTA_MAX);
+  i++;
+  ASSERT_NEAR(segments[i].first.GetX(), 10.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].first.GetY(), 15.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetX(), 10.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetY(), 20.000000000000000, DELTA_MAX);
+  i++;
+  ASSERT_NEAR(segments[i].first.GetX(), 41.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].first.GetY(), 15.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetX(), 41.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetY(), 20.000000000000000, DELTA_MAX);
+  i++;
+  ASSERT_NEAR(segments[i].first.GetX(), 46.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].first.GetY(), 15.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetX(), 46.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetY(), 20.000000000000000, DELTA_MAX);
+  i++;
+  ASSERT_NEAR(segments[i].first.GetX(), 80.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].first.GetY(), 15.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetX(), 80.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetY(), 20.000000000000000, DELTA_MAX);
+  i++;
+  ASSERT_NEAR(segments[i].first.GetX(), 34.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].first.GetY(), 20.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetX(), 34.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetY(), 25.000000000000000, DELTA_MAX);
+  i++;
+  ASSERT_NEAR(segments[i].first.GetX(), 42.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].first.GetY(), 20.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetX(), 42.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetY(), 25.000000000000000, DELTA_MAX);
+  i++;
+  ASSERT_NEAR(segments[i].first.GetX(), 90.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].first.GetY(), 20.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetX(), 90.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetY(), 25.000000000000000, DELTA_MAX);
+  i++;
+  ASSERT_NEAR(segments[i].first.GetX(), 96.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].first.GetY(), 20.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetX(), 96.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetY(), 25.000000000000000, DELTA_MAX);
+  i++;
+  ASSERT_NEAR(segments[i].first.GetX(), 1.0000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].first.GetY(), 25.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetX(), 1.0000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetY(), 30.000000000000000, DELTA_MAX);
+  i++;
+  ASSERT_NEAR(segments[i].first.GetX(), 33.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].first.GetY(), 25.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetX(), 33.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetY(), 30.000000000000000, DELTA_MAX);
+  i++;
+  ASSERT_NEAR(segments[i].first.GetX(), 40.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].first.GetY(), 25.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetX(), 40.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetY(), 30.000000000000000, DELTA_MAX);
+  i++;
+  ASSERT_NEAR(segments[i].first.GetX(), 43.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].first.GetY(), 25.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetX(), 43.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetY(), 30.000000000000000, DELTA_MAX);
+  i++;
+  ASSERT_NEAR(segments[i].first.GetX(), 51.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].first.GetY(), 25.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetX(), 51.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetY(), 30.000000000000000, DELTA_MAX);
+  i++;
+  ASSERT_NEAR(segments[i].first.GetX(), 61.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].first.GetY(), 25.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetX(), 61.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetY(), 30.000000000000000, DELTA_MAX);
+  i++;
+  ASSERT_NEAR(segments[i].first.GetX(), 76.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].first.GetY(), 25.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetX(), 76.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetY(), 30.000000000000000, DELTA_MAX);
+  i++;
+  ASSERT_NEAR(segments[i].first.GetX(), 95.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].first.GetY(), 25.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetX(), 95.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetY(), 30.000000000000000, DELTA_MAX);
+  i++;
+  ASSERT_NEAR(segments[i].first.GetX(), 5.0000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].first.GetY(), 0.0000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetX(), 31.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetY(), 0.0000000000000000, DELTA_MAX);
+  i++;
+  ASSERT_NEAR(segments[i].first.GetX(), 36.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].first.GetY(), 0.0000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetX(), 50.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetY(), 0.0000000000000000, DELTA_MAX);
+  i++;
+  ASSERT_NEAR(segments[i].first.GetX(), 5.0000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].first.GetY(), 5.0000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetX(), 20.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetY(), 5.0000000000000000, DELTA_MAX);
+  i++;
+  ASSERT_NEAR(segments[i].first.GetX(), 31.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].first.GetY(), 5.0000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetX(), 36.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetY(), 5.0000000000000000, DELTA_MAX);
+  i++;
+  ASSERT_NEAR(segments[i].first.GetX(), 45.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].first.GetY(), 5.0000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetX(), 50.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetY(), 5.0000000000000000, DELTA_MAX);
+  i++;
+  ASSERT_NEAR(segments[i].first.GetX(), 52.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].first.GetY(), 5.0000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetX(), 70.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetY(), 5.0000000000000000, DELTA_MAX);
+  i++;
+  ASSERT_NEAR(segments[i].first.GetX(), 0.0000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].first.GetY(), 10.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetX(), 20.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetY(), 10.000000000000000, DELTA_MAX);
+  i++;
+  ASSERT_NEAR(segments[i].first.GetX(), 32.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].first.GetY(), 10.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetX(), 35.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetY(), 10.000000000000000, DELTA_MAX);
+  i++;
+  ASSERT_NEAR(segments[i].first.GetX(), 44.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].first.GetY(), 10.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetX(), 45.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetY(), 10.000000000000000, DELTA_MAX);
+  i++;
+  ASSERT_NEAR(segments[i].first.GetX(), 52.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].first.GetY(), 10.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetX(), 60.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetY(), 10.000000000000000, DELTA_MAX);
+  i++;
+  ASSERT_NEAR(segments[i].first.GetX(), 70.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].first.GetY(), 10.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetX(), 75.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetY(), 10.000000000000000, DELTA_MAX);
+  i++;
+  ASSERT_NEAR(segments[i].first.GetX(), 0.0000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].first.GetY(), 15.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetX(), 10.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetY(), 15.000000000000000, DELTA_MAX);
+  i++;
+  ASSERT_NEAR(segments[i].first.GetX(), 32.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].first.GetY(), 15.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetX(), 35.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetY(), 15.000000000000000, DELTA_MAX);
+  i++;
+  ASSERT_NEAR(segments[i].first.GetX(), 41.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].first.GetY(), 15.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetX(), 44.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetY(), 15.000000000000000, DELTA_MAX);
+  i++;
+  ASSERT_NEAR(segments[i].first.GetX(), 46.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].first.GetY(), 15.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetX(), 60.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetY(), 15.000000000000000, DELTA_MAX);
+  i++;
+  ASSERT_NEAR(segments[i].first.GetX(), 75.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].first.GetY(), 15.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetX(), 80.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetY(), 15.000000000000000, DELTA_MAX);
+  i++;
+  ASSERT_NEAR(segments[i].first.GetX(), 10.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].first.GetY(), 20.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetX(), 34.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetY(), 20.000000000000000, DELTA_MAX);
+  i++;
+  ASSERT_NEAR(segments[i].first.GetX(), 41.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].first.GetY(), 20.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetX(), 42.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetY(), 20.000000000000000, DELTA_MAX);
+  i++;
+  ASSERT_NEAR(segments[i].first.GetX(), 46.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].first.GetY(), 20.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetX(), 80.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetY(), 20.000000000000000, DELTA_MAX);
+  i++;
+  ASSERT_NEAR(segments[i].first.GetX(), 90.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].first.GetY(), 20.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetX(), 96.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetY(), 20.000000000000000, DELTA_MAX);
+  i++;
+  ASSERT_NEAR(segments[i].first.GetX(), 1.0000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].first.GetY(), 25.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetX(), 33.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetY(), 25.000000000000000, DELTA_MAX);
+  i++;
+  ASSERT_NEAR(segments[i].first.GetX(), 34.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].first.GetY(), 25.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetX(), 40.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetY(), 25.000000000000000, DELTA_MAX);
+  i++;
+  ASSERT_NEAR(segments[i].first.GetX(), 42.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].first.GetY(), 25.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetX(), 43.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetY(), 25.000000000000000, DELTA_MAX);
+  i++;
+  ASSERT_NEAR(segments[i].first.GetX(), 51.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].first.GetY(), 25.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetX(), 61.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetY(), 25.000000000000000, DELTA_MAX);
+  i++;
+  ASSERT_NEAR(segments[i].first.GetX(), 76.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].first.GetY(), 25.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetX(), 90.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetY(), 25.000000000000000, DELTA_MAX);
+  i++;
+  ASSERT_NEAR(segments[i].first.GetX(), 95.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].first.GetY(), 25.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetX(), 96.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetY(), 25.000000000000000, DELTA_MAX);
+  i++;
+  ASSERT_NEAR(segments[i].first.GetX(), 1.0000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].first.GetY(), 30.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetX(), 33.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetY(), 30.000000000000000, DELTA_MAX);
+  i++;
+  ASSERT_NEAR(segments[i].first.GetX(), 40.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].first.GetY(), 30.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetX(), 43.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetY(), 30.000000000000000, DELTA_MAX);
+  i++;
+  ASSERT_NEAR(segments[i].first.GetX(), 51.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].first.GetY(), 30.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetX(), 61.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetY(), 30.000000000000000, DELTA_MAX);
+  i++;
+  ASSERT_NEAR(segments[i].first.GetX(), 76.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].first.GetY(), 30.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetX(), 95.000000000000000, DELTA_MAX);
+  ASSERT_NEAR(segments[i].second.GetY(), 30.000000000000000, DELTA_MAX);
+}
+
+#if defined(BGO_ENABLE_DICOMSTRUCTURESETLOADER2) && (ORTHANC_SANDBOXED != 1)
+
+#include <SystemToolbox.h>
+
+TEST(StructureSet2, ReadFromJsonPart2)
+{
+  DicomStructureSet2 structureSet;
+  std::string jsonText;
+
+  Orthanc::SystemToolbox::ReadFile(jsonText, "72c773ac-5059f2c4-2e6a9120-4fd4bca1-45701661.json");
+
+  FullOrthancDataset dicom(jsonText);
+  structureSet.Clear();
+
+  structureSet.FillStructuresFromDataset(dicom);
+  structureSet.ComputeDependentProperties();
+
+  //const std::vector<DicomStructure2>& structures = structureSet.structures_;
+}
+
+#endif 
+// BGO_ENABLE_DICOMSTRUCTURESETLOADER2
+
+
--- a/OrthancStone/Sources/Toolbox/DicomStructure2.cpp	Tue Feb 01 07:19:15 2022 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,297 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2022 Osimis S.A., Belgium
- * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Lesser 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
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this program. If not, see
- * <http://www.gnu.org/licenses/>.
- **/
-
-#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2
-
-#include "DicomStructure2.h"
-
-#include "GeometryToolbox.h"
-#include "DisjointDataSet.h"
-
-#include <Logging.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 (structures)
-      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
-  }
-
-  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<ScenePoint2D, ScenePoint2D> > & 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<ScenePoint2D> 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].GetY(), intersections[2 * iRect + 1].GetY()));
-              ORTHANC_ASSERT((2 * iRect + 1) < intersections.size());
-              double x1 = intersections[2 * iRect].GetX();
-              double x2 = intersections[2 * iRect + 1].GetX();
-              double y1 = intersections[2 * iRect].GetY() - sliceThickness_ * 0.5;
-              double y2 = intersections[2 * iRect].GetY() + 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 structures, but dot product is: "
-            << dot << " and (180/pi)*acos(dot) = " << acDot;
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
-        }
-      }
-      return segments.size() != 0;
-    }
-}
-
-#endif 
-// BGO_ENABLE_DICOMSTRUCTURESETLOADER2
-
--- a/OrthancStone/Sources/Toolbox/DicomStructure2.h	Tue Feb 01 07:19:15 2022 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,163 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2022 Osimis S.A., Belgium
- * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Lesser 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
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this program. If not, see
- * <http://www.gnu.org/licenses/>.
- **/
-
-#pragma once
-
-#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2
-
-#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 structures, 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<ScenePoint2D, ScenePoint2D> >& 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_;
-    Vector                            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_;
-  };
-}
-
-#endif 
-// BGO_ENABLE_DICOMSTRUCTURESETLOADER2
-
--- a/OrthancStone/Sources/Toolbox/DicomStructurePolygon2.cpp	Tue Feb 01 07:19:15 2022 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,307 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2022 Osimis S.A., Belgium
- * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Lesser 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
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this program. If not, see
- * <http://www.gnu.org/licenses/>.
- **/
-
-#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2
-
-#include "DicomStructurePolygon2.h"
-
-#include "../Toolbox/LinearAlgebra.h"
-
-#include <Logging.h>
-
-namespace OrthancStone
-{
-  void DicomStructurePolygon2::ComputeDependentProperties()
-  {
-    ORTHANC_ASSERT(state_ == Building);
-
-    for (size_t j = 0; j < points_.size(); ++j)
-    {
-      // TODO: move to AddPoint!
-      const Vector& 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<ScenePoint2D>& 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;
-
-    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;
-      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;
-      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(ScenePoint2D(x, y));
-
-            plane.ProjectPoint(x, y, points_[iPoint + 1]);
-            intersections.push_back(ScenePoint2D(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(ScenePoint2D(x, minZ_));
-      }
-    } // end of if (pointCount >= 3)
-    else
-    {
-      LOG(ERROR) << "This polygon has " << pointCount << " vertices, which is less than 3 --> skipping";
-    }
-  } 
-
-  void DicomStructurePolygon2::ProjectOnParallelPlane(
-    std::vector< std::pair<ScenePoint2D, ScenePoint2D> >& 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));
-
-    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 Vector& point0 = GetPoint(j);
-      // subtract plane origin x and y
-      ScenePoint2D p0(point0[0] - planeOriginX, point0[1] - planeOriginY);
-    
-      const Vector& point1 = GetPoint(j+1);
-      // subtract plane origin x and y
-      ScenePoint2D p1(point1[0] - planeOriginX, point1[1] - planeOriginY);
-
-      segments.push_back(std::pair<ScenePoint2D, ScenePoint2D>(p0,p1));
-    }
-
-
-    // final segment 
-
-    const Vector& point0 = GetPoint(points_.size() - 1);
-    // subtract plane origin x and y
-    ScenePoint2D p0(point0[0] - planeOriginX, point0[1] - planeOriginY);
-
-    const Vector& point1 = GetPoint(0);
-    // subtract plane origin x and y
-    ScenePoint2D p1(point1[0] - planeOriginX, point1[1] - planeOriginY);
-
-    segments.push_back(std::pair<ScenePoint2D, ScenePoint2D>(p0, p1));
-  }
-
-  double 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_;
-  }
-}
-
-#endif 
-// BGO_ENABLE_DICOMSTRUCTURESETLOADER2
-
--- a/OrthancStone/Sources/Toolbox/DicomStructurePolygon2.h	Tue Feb 01 07:19:15 2022 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,163 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2022 Osimis S.A., Belgium
- * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Lesser 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
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this program. If not, see
- * <http://www.gnu.org/licenses/>.
- **/
-
-#pragma once
-
-#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2
-
-#include "CoordinateSystem3D.h"
-#include "DicomStructureSetUtils.h"
-#include "Extent2D.h"
-
-#include "../Scene2D/Color.h"
-#include "../StoneException.h"
-
-#include "OrthancDatasets/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 Vector& GetPoint(size_t i) const
-    {
-      ORTHANC_ASSERT(state_ == Valid);
-      return points_.at(i);
-    }
-
-    void AddPoint(const Vector& 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<ScenePoint2D, ScenePoint2D> >& 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<ScenePoint2D>& 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
-    */
-    Vector 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<Vector>  points_;
-    Vector              normal_; // sign is irrelevant for now
-    State                 state_;
-    double                minX_, maxX_, minY_, maxY_, minZ_, maxZ_;
-    Type                  type_;
-  };
-}
-
-#endif
-// BGO_ENABLE_DICOMSTRUCTURESETLOADER2
-
-
--- a/OrthancStone/Sources/Toolbox/DicomStructureSet.cpp	Tue Feb 01 07:19:15 2022 +0100
+++ b/OrthancStone/Sources/Toolbox/DicomStructureSet.cpp	Tue Feb 01 08:38:32 2022 +0100
@@ -57,7 +57,6 @@
 #  include <boost/geometry/geometries/polygon.hpp>
 #  include <boost/geometry/multi/geometries/multi_polygon.hpp>
 #else
-#  include "DicomStructureSetUtils.h"  // TODO REMOVE
 #  include "UnionOfRectangles.h"
 #endif
 
@@ -117,38 +116,8 @@
   return r;
 }
 
-#else
-
-namespace OrthancStone
-{
-  static RtStructRectangleInSlab CreateRectangle(float x1, float y1,
-                                                 float x2, float y2)
-  {
-    RtStructRectangleInSlab rect;
-    rect.xmin = std::min(x1, x2);
-    rect.xmax = std::max(x1, x2);
-    rect.ymin = std::min(y1, y2);
-    rect.ymax = std::max(y1, y2);
-    return rect;
-  }
+#endif
 
-  bool CompareRectanglesForProjection(const std::pair<RtStructRectangleInSlab,double>& r1,
-                                      const std::pair<RtStructRectangleInSlab, double>& r2)
-  {
-    return r1.second < r2.second;
-  }
-
-  bool CompareSlabsY(const RtStructRectanglesInSlab& r1,
-                     const RtStructRectanglesInSlab& r2)
-  {
-    if ((r1.size() == 0) || (r2.size() == 0))
-      return false;
-
-    return r1[0].ymax < r2[0].ymax;
-  }
-}
-
-#endif
 
 namespace OrthancStone
 {
@@ -921,7 +890,7 @@
         }
       }  
 
-#elif 1
+#else
 
       std::list<Extent2D> rectangles;
       
@@ -948,91 +917,8 @@
         chains.push_back(*it);
       }
       
-#else
-      // this will contain the intersection of the polygon slab with
-      // the cutting plane, projected on the cutting plane coord system 
-      // (that yields a rectangle) + the Z coordinate of the polygon 
-      // (this is required to group polygons with the same Z later)
-      std::vector<std::pair<RtStructRectangleInSlab, double> > 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, GetEstimatedNormal(), GetEstimatedSliceThickness()))
-        {
-          double curZ = polygon->GetGeometryOrigin()[2];
-
-          // x1,y1 and x2,y2 are in "slice" coordinates (the cutting plane 
-          // geometry)
-          projected.push_back(std::make_pair(CreateRectangle(
-                                               static_cast<float>(x1), 
-                                               static_cast<float>(y1), 
-                                               static_cast<float>(x2), 
-                                               static_cast<float>(y2)),curZ));
-        }
-      }
-
-      // projected contains a set of rectangles specified by two opposite
-      // corners (x1,y1,x2,y2)
-      // we need to merge them 
-      // each slab yields ONE polygon!
-
-      // we need to sorted all the rectangles that originate from the same Z
-      // into lanes. To make sure they are grouped together in the array, we
-      // sort it.
-      std::sort(projected.begin(), projected.end(), CompareRectanglesForProjection);
-
-      std::vector<RtStructRectanglesInSlab> rectanglesForEachSlab;
-      rectanglesForEachSlab.reserve(projected.size());
-
-      double curZ = 0;
-      for (size_t i = 0; i < projected.size(); ++i)
-      {
-#if 0
-        rectanglesForEachSlab.push_back(RtStructRectanglesInSlab());
-#else
-        if (i == 0)
-        {
-          curZ = projected[i].second;
-          rectanglesForEachSlab.push_back(RtStructRectanglesInSlab());
-        }
-        else
-        {
-          // this check is needed to prevent creating a new slab if 
-          // the new polygon is at the same Z coord than last one
-          if (!LinearAlgebra::IsNear(curZ, projected[i].second))
-          {
-            rectanglesForEachSlab.push_back(RtStructRectanglesInSlab());
-            curZ = projected[i].second;
-          }
-        }
 #endif
 
-        rectanglesForEachSlab.back().push_back(projected[i].first);
-
-        // as long as they have the same y, we should put them into the same lane
-        // BUT in Sebastien's code, there is only one polygon per lane.
-
-        //std::cout << "rect: xmin = " << rect.xmin << " xmax = " << rect.xmax << " ymin = " << rect.ymin << " ymax = " << rect.ymax << std::endl;
-      }
-      
-      // now we need to sort the slabs in increasing Y order (see ConvertListOfSlabsToSegments)
-      std::sort(rectanglesForEachSlab.begin(), rectanglesForEachSlab.end(), CompareSlabsY);
-
-      std::vector< std::pair<ScenePoint2D, ScenePoint2D> > segments;
-      ConvertListOfSlabsToSegments(segments, rectanglesForEachSlab, projected.size());
-
-      chains.resize(segments.size());
-      for (size_t i = 0; i < segments.size(); i++)
-      {
-        chains[i].resize(2);
-        chains[i][0] = segments[i].first;
-        chains[i][1] = segments[i].second;
-      }
-#endif
-      
       return true;
     }
     else
--- a/OrthancStone/Sources/Toolbox/DicomStructureSet2.cpp	Tue Feb 01 07:19:15 2022 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,313 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2022 Osimis S.A., Belgium
- * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Lesser 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
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this program. If not, see
- * <http://www.gnu.org/licenses/>.
- **/
-
-#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2
-
-#include "DicomStructureSet2.h"
-
-#include "../Toolbox/LinearAlgebra.h"
-#include "../StoneException.h"
-
-#include <Logging.h>
-#include <OrthancException.h>
-#include <Toolbox.h>
-#include <DicomFormat/DicomTag.h>
-
-#include "DicomStructure2.h"
-#include "GenericToolbox.h"
-#include "OrthancDatasets/DicomDatasetReader.h"
-
-namespace OrthancStone
-{
-  static const Orthanc::DicomTag DICOM_TAG_CONTOUR_GEOMETRIC_TYPE(0x3006, 0x0042);
-  static const Orthanc::DicomTag DICOM_TAG_CONTOUR_IMAGE_SEQUENCE(0x3006, 0x0016);
-  static const Orthanc::DicomTag DICOM_TAG_CONTOUR_SEQUENCE(0x3006, 0x0040);
-  static const Orthanc::DicomTag DICOM_TAG_CONTOUR_DATA(0x3006, 0x0050);
-  static const Orthanc::DicomTag DICOM_TAG_NUMBER_OF_CONTOUR_POINTS(0x3006, 0x0046);
-  static const Orthanc::DicomTag DICOM_TAG_REFERENCED_SOP_INSTANCE_UID(0x0008, 0x1155);
-  static const Orthanc::DicomTag DICOM_TAG_ROI_CONTOUR_SEQUENCE(0x3006, 0x0039);
-  static const Orthanc::DicomTag DICOM_TAG_ROI_DISPLAY_COLOR(0x3006, 0x002a);
-  static const Orthanc::DicomTag DICOM_TAG_ROI_NAME(0x3006, 0x0026);
-  static const Orthanc::DicomTag DICOM_TAG_RT_ROI_INTERPRETED_TYPE(0x3006, 0x00a4);
-  static const Orthanc::DicomTag DICOM_TAG_RT_ROI_OBSERVATIONS_SEQUENCE(0x3006, 0x0080);
-  static const Orthanc::DicomTag DICOM_TAG_STRUCTURE_SET_ROI_SEQUENCE(0x3006, 0x0020);
-
-  static inline uint8_t ConvertAndClipToByte(double v)
-  {
-    if (v < 0)
-    {
-      return 0;
-    }
-    else if (v >= 255)
-    {
-      return 255;
-    }
-    else
-    {
-      return static_cast<uint8_t>(v);
-    }
-  }
-
-  static bool ReadDicomToVector(Vector& target,
-                                const IDicomDataset& dataset,
-                                const Orthanc::DicomPath& tag)
-  {
-    std::string value;
-    return (dataset.GetStringValue(value, tag) &&
-            GenericToolbox::FastParseVector(target, value));
-  }
-
-
-  void DicomPathToString(std::string& s, const Orthanc::DicomPath& dicomPath)
-  {
-    std::stringstream tmp;
-    for (size_t i = 0; i < dicomPath.GetPrefixLength(); ++i)
-    {
-      Orthanc::DicomTag tag = dicomPath.GetPrefixTag(i);
-
-      // We use this other object to be able to use Format
-      Orthanc::DicomTag tag2(tag.GetGroup(), tag.GetElement());
-      size_t index = dicomPath.GetPrefixIndex(i);
-      tmp << " (" << tag2.Format() << ") [" << index << "] / ";
-    }
-    const Orthanc::DicomTag& tag = dicomPath.GetFinalTag();
-    Orthanc::DicomTag tag2(tag.GetGroup(), tag.GetElement());
-    tmp << " (" << tag2.Format() << ")";
-    s = tmp.str();
-  }
-
-  std::ostream& operator<<(std::ostream& s, const Orthanc::DicomPath& dicomPath)
-  {
-    std::string tmp;
-    DicomPathToString(tmp, dicomPath);
-    s << tmp;
-    return s;
-  }
-
-
-  DicomStructureSet2::DicomStructureSet2()
-  {
-
-  }
-
-
-  DicomStructureSet2::~DicomStructureSet2()
-  {
-
-  }
-
-  void DicomStructureSet2::SetContents(const FullOrthancDataset& tags)
-  {
-    FillStructuresFromDataset(tags);
-    ComputeDependentProperties();
-  }
-
-  void DicomStructureSet2::ComputeDependentProperties()
-  {
-    for (size_t i = 0; i < structures_.size(); ++i)
-    {
-      structures_[i].ComputeDependentProperties();
-    }
-  }
-
-  void DicomStructureSet2::FillStructuresFromDataset(const FullOrthancDataset& tags)
-  {
-    DicomDatasetReader reader(tags);
-
-    // 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, Orthanc::DicomPath(DICOM_TAG_RT_ROI_OBSERVATIONS_SEQUENCE)) ||
-        !tags.GetSequenceSize(tmp, Orthanc::DicomPath(DICOM_TAG_ROI_CONTOUR_SEQUENCE)) ||
-        tmp != count ||
-        !tags.GetSequenceSize(tmp, Orthanc::DicomPath(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
-        (Orthanc::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
-        (Orthanc::DicomPath(DICOM_TAG_STRUCTURE_SET_ROI_SEQUENCE, i,
-                            DICOM_TAG_ROI_NAME),
-         "No name");
-
-      Vector color;
-      // (0x3006, 0x0039)[i]/(0x3006, 0x002a)
-      if (ReadDicomToVector(color, tags, Orthanc::DicomPath(
-                              DICOM_TAG_ROI_CONTOUR_SEQUENCE, i, DICOM_TAG_ROI_DISPLAY_COLOR)) 
-          && color.size() == 3)
-      {
-        structures_[i].red_   = ConvertAndClipToByte(color[0]);
-        structures_[i].green_ = ConvertAndClipToByte(color[1]);
-        structures_[i].blue_  = ConvertAndClipToByte(color[2]);
-      }
-      else
-      {
-        structures_[i].red_ = 255;
-        structures_[i].green_ = 0;
-        structures_[i].blue_ = 0;
-      }
-
-      size_t countSlices;
-      //  DICOM_TAG_ROI_CONTOUR_SEQUENCE          (0x3006, 0x0039);
-      //  DICOM_TAG_CONTOUR_SEQUENCE              (0x3006, 0x0040);
-      if (!tags.GetSequenceSize(countSlices, Orthanc::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_) << ")";
-
-      // These temporary variables avoid allocating many vectors in the loop below
-      
-      // (0x3006, 0x0039)[i]/(0x3006, 0x0040)[0]/(0x3006, 0x0046)
-      Orthanc::DicomPath countPointsPath(
-        DICOM_TAG_ROI_CONTOUR_SEQUENCE, i,
-        DICOM_TAG_CONTOUR_SEQUENCE, 0,
-        DICOM_TAG_NUMBER_OF_CONTOUR_POINTS);
-
-      Orthanc::DicomPath geometricTypePath(
-        DICOM_TAG_ROI_CONTOUR_SEQUENCE, i,
-        DICOM_TAG_CONTOUR_SEQUENCE, 0,
-        DICOM_TAG_CONTOUR_GEOMETRIC_TYPE);
-
-      Orthanc::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)
-      Orthanc::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);
-
-      Orthanc::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 = 0;
-
-        countPointsPath.SetPrefixIndex(1, j);
-        if (!reader.GetUnsignedIntegerValue(countPoints, countPointsPath))
-        {
-          std::string s;
-          DicomPathToString(s, countPointsPath);
-          LOG(ERROR) << "Dicom path " << s << " 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 = 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);
-        }
-
-        referencedInstancePath.SetPrefixIndex(1, j);
-        std::string sopInstanceUid = reader.GetMandatoryStringValue(referencedInstancePath);
-
-        contourDataPath.SetPrefixIndex(1, j);
-        std::string slicesData = reader.GetMandatoryStringValue(contourDataPath);
-
-        Vector points;
-        if (!GenericToolbox::FastParseVector(points, slicesData) ||
-            points.size() != 3 * countPoints)
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
-        }
-
-        // seen in real world
-        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)";
-        }
-
-        DicomStructurePolygon2 polygon(sopInstanceUid,type);
-        polygon.Reserve(countPoints);
-
-        for (size_t k = 0; k < countPoints; k++)
-        {
-          Vector v(3);
-          v[0] = points[3 * k];
-          v[1] = points[3 * k + 1];
-          v[2] = points[3 * k + 2];
-          polygon.AddPoint(v);
-        }
-        structures_[i].AddPolygon(polygon);
-      }
-    }
-  }
-
-
-  void DicomStructureSet2::Clear()
-  {
-    structures_.clear();
-  }
-
-}
-
-#endif 
-// BGO_ENABLE_DICOMSTRUCTURESETLOADER2
-
--- a/OrthancStone/Sources/Toolbox/DicomStructureSet2.h	Tue Feb 01 07:19:15 2022 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,72 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2022 Osimis S.A., Belgium
- * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Lesser 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
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this program. If not, see
- * <http://www.gnu.org/licenses/>.
- **/
-
-#pragma once
-
-#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2
-
-#include "../Scene2D/Color.h"
-#include "CoordinateSystem3D.h"
-#include "DicomStructure2.h"
-#include "Extent2D.h"
-#include "OrthancDatasets/FullOrthancDataset.h"
-
-#include <list>
-
-namespace OrthancStone
-{
-  class DicomStructureSet2 : public boost::noncopyable
-  {
-  public:
-    DicomStructureSet2();
-    ~DicomStructureSet2();
-   
-    void SetContents(const FullOrthancDataset& tags);
-
-    size_t GetStructuresCount() const
-    {
-      return structures_.size();
-    }
-
-    void Clear();
-
-    const DicomStructure2& GetStructure(size_t i) const
-    {
-      // at() is like []() but with range check
-      return structures_.at(i);
-    }
-
-    /** Internal use only */
-    void FillStructuresFromDataset(const FullOrthancDataset& tags);
-
-    /** Internal use only */
-    void ComputeDependentProperties();
-
-    /** Internal use only */
-    std::vector<DicomStructure2> structures_;
-  };
-}
-
-#endif 
-// BGO_ENABLE_DICOMSTRUCTURESETLOADER2
-
-
--- a/OrthancStone/Sources/Toolbox/DicomStructureSetUtils.cpp	Tue Feb 01 07:19:15 2022 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,274 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2022 Osimis S.A., Belgium
- * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Lesser 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
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser 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<ScenePoint2D, ScenePoint2D> > & segments,
-      const std::vector<std::pair<double, RectangleBoundaryKind> > & boundaries,
-      double y)
-    {
-      ScenePoint2D start;
-      ScenePoint2D 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 = ScenePoint2D(boundaries[i].first, y);
-            break;
-          case 2:
-            // an extra segment has begun : stop the current one (we don't draw overlaps)
-            end = ScenePoint2D(boundaries[i].first, y);
-            segments.push_back(std::pair<ScenePoint2D, ScenePoint2D>(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 = ScenePoint2D(boundaries[i].first, y);
-            segments.push_back(std::pair<ScenePoint2D, ScenePoint2D>(start, end));
-            break;
-          case 1:
-            // an extra segment has ended : start a new one one
-            start = ScenePoint2D(boundaries[i].first, y);
-            break;
-          default:
-            // this should not happen!
-            //assert(false);
-            break;
-          }
-          break;
-        default:
-          assert(false);
-          break;
-        }
-      }
-    }
-
-#if 0
-    void ConvertListOfSlabsToSegments(
-      std::vector< std::pair<ScenePoint2D, ScenePoint2D> >& 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<ScenePoint2D, ScenePoint2D> > & 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];
-          {
-            ScenePoint2D p1(rect.xmin, rect.ymin);
-            ScenePoint2D p2(rect.xmin, rect.ymax);
-            segments.push_back(std::pair<ScenePoint2D, ScenePoint2D>(p1, p2));
-          }
-          {
-            ScenePoint2D p1(rect.xmax, rect.ymin);
-            ScenePoint2D p2(rect.xmax, rect.ymax);
-            segments.push_back(std::pair<ScenePoint2D, ScenePoint2D>(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
-  }
--- a/OrthancStone/Sources/Toolbox/DicomStructureSetUtils.h	Tue Feb 01 07:19:15 2022 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,66 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2022 Osimis S.A., Belgium
- * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Lesser 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
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this program. If not, see
- * <http://www.gnu.org/licenses/>.
- **/
-
-#pragma once
-
-#include <vector>
-#include <utility>
-
-#include "../Scene2D/ScenePoint2D.h"
-#include "../Toolbox/LinearAlgebra.h"
-
-namespace OrthancStone
-{
-  /** 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<ScenePoint2D, ScenePoint2D> >& 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<ScenePoint2D, ScenePoint2D> >& segments,
-                           const std::vector<std::pair<double, RectangleBoundaryKind> >& boundaries,
-                           double y);
-
-}
--- a/OrthancStone/Sources/Toolbox/DisjointDataSet.h	Tue Feb 01 07:19:15 2022 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,146 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2022 Osimis S.A., Belgium
- * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Lesser 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
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this program. If not, see
- * <http://www.gnu.org/licenses/>.
- **/
-
-#pragma once
-
-#include <vector> 
-
-#include "../StoneException.h"
-
-namespace OrthancStone
-{
-  class DisjointDataSet
-  {
-  public:
-    DisjointDataSet(size_t itemCount) :
-      parents_(itemCount),
-      ranks_(itemCount)
-    {
-      for (size_t index = 0; index < parents_.size(); index++)
-      {
-        SetParent(index,index);
-        ranks_[index] = 1;
-      }
-    }
-
-    size_t Find(size_t item)
-    {
-      /*
-      If parents_[i] == i, it means i is representative of a set.
-      Otherwise, we go up the tree...
-      */
-      if (GetParent(item) != item)
-      {
-        // if item is not a top item (representative of its set),
-        // we use path compression to improve future lookups 
-        // see: https://en.wikipedia.org/wiki/Disjoint-set_data_structure#Path_compression
-        SetParent(item, Find(parents_[item]));
-      }
-
-      // now that paths have been compressed, we are positively certain
-      // that item's parent is a set ("X is a set" means that X is the 
-      // representative of a set)
-      return GetParent(item);
-    }
-
-    /*
-    This merge the two sets that contains itemA and itemB
-    */
-    void Union(size_t itemA, size_t itemB)
-    {
-      // Find current sets of x and y 
-      size_t setA = Find(itemA);
-      size_t setB = Find(itemB);
-
-      // if setA == setB, it means they are already in the same set and 
-      // do not need to be merged!
-      if (setA != setB)
-      {
-        // we need to merge the sets, which means that the trees representing
-        // the sets needs to be merged (there must be a single top parent to 
-        // all the items originally belonging to setA and setB must be the same)
-
-        // since the algorithm speed is inversely proportional to the tree
-        // height (the rank), we need to combine trees in a way that 
-        // minimizes this rank. See "Union by rank" at 
-        // https://en.wikipedia.org/wiki/Disjoint-set_data_structure#by_rank
-        if (GetRank(setA) < GetRank(setB))
-        {
-          SetParent(setA, setB);
-        }
-        else if (GetRank(setA) > GetRank(setB))
-        {
-          SetParent(setB, setA);
-        }
-        else
-        {
-          SetParent(setB, setA);
-          BumpRank(setA);
-          // the trees had the same height but we attached the whole of setB
-          // under setA (under its parent), so the resulting tree is now 
-          // 1 higher. setB is NOT representative of a set anymore.
-        }
-      }
-    }
-
-  private:
-    size_t GetRank(size_t i) const
-    {
-      ORTHANC_ASSERT(i < ranks_.size());
-      ORTHANC_ASSERT(ranks_.size() == parents_.size());
-      return ranks_[i];
-    }
-
-    size_t GetParent(size_t i) const
-    {
-      ORTHANC_ASSERT(i < parents_.size());
-      ORTHANC_ASSERT(ranks_.size() == parents_.size());
-      return parents_[i];
-    }
-
-    void SetParent(size_t i, size_t parent)
-    {
-      ORTHANC_ASSERT(i < parents_.size());
-      ORTHANC_ASSERT(ranks_.size() == parents_.size());
-      parents_[i] = parent;
-    }
-
-    void BumpRank(size_t i)
-    {
-      ORTHANC_ASSERT(i < ranks_.size());
-      ORTHANC_ASSERT(ranks_.size() == parents_.size());
-      ranks_[i] = ranks_[i] + 1u;
-    }
-
-    /*
-    This vector contains the direct parent of each item
-    */
-    std::vector<size_t> parents_;
-
-    /*
-    This vector contains the tree height of each set. The values in the
-    vector for non-representative items is UNDEFINED!
-    */
-    std::vector<size_t> ranks_;
-  };
-
-}
--- a/OrthancStone/Sources/Volumes/DicomStructureSetSlicer2.cpp	Tue Feb 01 07:19:15 2022 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,118 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2022 Osimis S.A., Belgium
- * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Lesser 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
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this program. If not, see
- * <http://www.gnu.org/licenses/>.
- **/
-
-#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2
-
-#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::unique_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<ScenePoint2D, ScenePoint2D> > 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.GetX(), segments[j].first.GetY());
-            chain[1] = ScenePoint2D(segments[j].second.GetX(), segments[j].second.GetY());
-
-            layer->AddChain(chain, false /* NOT closed */, color);
-          }
-        }
-      }
-    }
-    return layer.release();
-  }
-}
-
-
-#endif 
-// BGO_ENABLE_DICOMSTRUCTURESETLOADER2
-
-
--- a/OrthancStone/Sources/Volumes/DicomStructureSetSlicer2.h	Tue Feb 01 07:19:15 2022 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,78 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2022 Osimis S.A., Belgium
- * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Lesser 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
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this program. If not, see
- * <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2
-
-#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_;
-  };
-}
-
-#endif 
-// BGO_ENABLE_DICOMSTRUCTURESETLOADER2
-
--- a/OrthancStone/UnitTestsSources/TestStructureSet.cpp	Tue Feb 01 07:19:15 2022 +0100
+++ b/OrthancStone/UnitTestsSources/TestStructureSet.cpp	Tue Feb 01 08:38:32 2022 +0100
@@ -21,1169 +21,20 @@
  **/
 
 
-// working around a bug where the Visual C++ compiler would get 
-// stuck trying to compile this cpp file in release mode
-// (versions: https://en.wikipedia.org/wiki/Microsoft_Visual_C%2B%2B)
-#ifdef _MSC_VER
-#  pragma optimize("", off)
-// warning C4748: /GS can not protect parameters and local variables from
-// local buffer overrun because optimizations are disabled in function
-#  pragma warning(disable: 4748)
-#endif
-
 #include "../Sources/Toolbox/DicomStructureSet.h"
-#include "../Sources/Toolbox/DicomStructureSet2.h"
-#include "../Sources/Toolbox/DicomStructureSetUtils.h"
-#include "../Sources/Toolbox/DisjointDataSet.h"
 
 #include <EmbeddedResources.h>
 
-#include <boost/date_time/posix_time/posix_time.hpp>
-#include <boost/make_shared.hpp>
-
 #include <gtest/gtest.h>
-#include <string>
-
-using namespace OrthancStone;
-
-static const double DELTA_MAX = 10.0 * std::numeric_limits<float>::epsilon();
-
-
-
-#define STONE_ARRAY_SIZE(arr)     (sizeof(arr) / sizeof((arr)[0]))
-
-#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2
-
-static void CheckGroundTruth(
-  const std::vector<DicomStructure2>& structures,
-  const size_t structureIndex,
-  const size_t sliceIndex,
-  std::vector<double> groundTruth)
-{
-  const std::vector<DicomStructurePolygon2>& polygonsForThisStruct = structures.at(structureIndex).GetPolygons();
-  const DicomStructurePolygon2& polygon = polygonsForThisStruct.at(sliceIndex);
-
-  //double groundTruth[] = { 7.657838, 108.2725, 304.01, 6.826687, 107.4413, 304.01, 6.152492, 106.4785, 304.01, 5.655735, 105.4132, 304.01, 5.351513, 104.2778, 304.01, 5.249068, 103.1069, 304.01, 5.351513, 101.9359, 304.01, 5.655735, 100.8005, 304.01, 6.152492, 99.73524, 304.01, 6.826687, 98.77239, 304.01, 7.657838, 97.94124, 304.01, 8.620689, 97.26704, 304.01, 9.685987, 96.77029, 304.01, 10.82136, 96.46606, 304.01, 11.99231, 96.36362, 304.01, 13.16326, 96.46606, 304.01, 14.29864, 96.77029, 304.01, 15.36393, 97.26704, 304.01, 16.32678, 97.94124, 304.01, 17.15794, 98.77239, 304.01, 17.83213, 99.73524, 304.01, 18.32889, 100.8005, 304.01, 18.63311, 101.9359, 304.01, 18.73555, 103.1069, 304.01, 18.63311, 104.2778, 304.01, 18.32889, 105.4132, 304.01, 17.83213, 106.4785, 304.01, 17.15794, 107.4413, 304.01, 16.32678, 108.2725, 304.01, 15.36393, 108.9467, 304.01, 14.29864, 109.4434, 304.01, 13.16326, 109.7477, 304.01, 11.99231, 109.8501, 304.01, 10.82136, 109.7477, 304.01, 9.685987, 109.4434, 304.01, 8.620689, 108.9467, 304.01 };
-  size_t groundTruthItems = groundTruth.size();
-
-  size_t pointCount = 3 * polygon.GetPointCount();
-
-  EXPECT_EQ(groundTruthItems, pointCount);
-
-  for (size_t i = 0; i < polygon.GetPointCount(); ++i)
-  {
-    const Vector& point = polygon.GetPoint(i);
-
-    // loop over X, Y then Z.
-    for (size_t j = 0; j < 3; ++j)
-    {
-      size_t index = 3 * i + j;
-      ASSERT_LT(index, groundTruthItems);
-      bool isNear = LinearAlgebra::IsNear(groundTruth[index], point[j]);
-      EXPECT_TRUE(isNear);
-    }
-  }
-}
-
-
-#include <Toolbox.h>
-
-TEST(StructureSet2, ReadFromJsonThatsAll)
-{
-  /*
-    The "RT_STRUCT_00" string is the reply to the following Orthanc request:
-    
-    http://localhost:8042/instances/1aa5f84b-c32a03b4-3c1857da-da2e69f3-3ef6e2b3/tags?ignore-length=3006-0050
-    
-    The tag hierarchy can be found here: https://dicom.innolitics.com/ciods/rt-dose
-  */
-
-  DicomStructureSet2 structureSet;
-
-  FullOrthancDataset dicom(Orthanc::EmbeddedResources::GetFileResourceBuffer(Orthanc::EmbeddedResources::RT_STRUCT_00),
-                           Orthanc::EmbeddedResources::GetFileResourceSize(Orthanc::EmbeddedResources::RT_STRUCT_00));
-  structureSet.Clear();
-
-  structureSet.FillStructuresFromDataset(dicom);
-  structureSet.ComputeDependentProperties();
-
-  const std::vector<DicomStructure2>& structures = structureSet.structures_;
-
-  /*
-
-  ██████╗  █████╗ ███████╗██╗ ██████╗     ██████╗██╗  ██╗███████╗ ██████╗██╗  ██╗███████╗
-  ██╔══██╗██╔══██╗██╔════╝██║██╔════╝    ██╔════╝██║  ██║██╔════╝██╔════╝██║ ██╔╝██╔════╝
-  ██████╔╝███████║███████╗██║██║         ██║     ███████║█████╗  ██║     █████╔╝ ███████╗
-  ██╔══██╗██╔══██║╚════██║██║██║         ██║     ██╔══██║██╔══╝  ██║     ██╔═██╗ ╚════██║
-  ██████╔╝██║  ██║███████║██║╚██████╗    ╚██████╗██║  ██║███████╗╚██████╗██║  ██╗███████║
-  ╚═════╝ ╚═╝  ╚═╝╚══════╝╚═╝ ╚═════╝     ╚═════╝╚═╝  ╚═╝╚══════╝ ╚═════╝╚═╝  ╚═╝╚══════╝
-  http://patorjk.com/software/taag/#p=display&f=ANSI%20Shadow&t=BASIC%20CHECKS
-  */
-
-  // (0x3006, 0x0080) seq. size
-  EXPECT_EQ(7u, structures.size());
-
-  // (0x3006, 0x0080)[i]/(0x3006, 0x00a4)
-  for (size_t i = 0; i < 5; ++i)
-  {
-    EXPECT_EQ(std::string("ORGAN"), structures[i].interpretation_);
-  }
-  EXPECT_EQ(std::string("EXTERNAL"), structures[5].interpretation_);
-  EXPECT_EQ(std::string("PTV"), structures[6].interpretation_);
-
-  // (0x3006, 0x0020)[i]/(0x3006, 0x0026)
-  EXPECT_EQ(std::string("LN300"), structures[0].name_);
-  EXPECT_EQ(std::string("Cortical Bone"), structures[1].name_);
-  EXPECT_EQ(std::string("Adipose"), structures[2].name_);
-  EXPECT_EQ(std::string("CB2-50%"), structures[3].name_);
-  EXPECT_EQ(std::string("Water"), structures[4].name_);
-  EXPECT_EQ(std::string("External"), structures[5].name_);
-  EXPECT_EQ(std::string("PTV"), structures[6].name_);
-
-  // (0x3006, 0x0039)[i]/(0x3006, 0x002a)
-  EXPECT_EQ(0xff, structures[0].red_);
-  EXPECT_EQ(0x00, structures[0].green_);
-  EXPECT_EQ(0x00, structures[0].blue_);
-
-  EXPECT_EQ(0x00, structures[1].red_);
-  EXPECT_EQ(0xff, structures[1].green_);
-  EXPECT_EQ(0xff, structures[1].blue_);
-
-  // ...
-
-  EXPECT_EQ(0x00, structures[5].red_);
-  EXPECT_EQ(0x80, structures[5].green_);
-  EXPECT_EQ(0x00, structures[5].blue_);
-
-  EXPECT_EQ(0xff, structures[6].red_);
-  EXPECT_EQ(0x00, structures[6].green_);
-  EXPECT_EQ(0xff, structures[6].blue_);
-
-  /*
-
-   ██████╗ ███████╗ ██████╗ ███╗   ███╗███████╗████████╗██████╗ ██╗   ██╗
-  ██╔════╝ ██╔════╝██╔═══██╗████╗ ████║██╔════╝╚══██╔══╝██╔══██╗╚██╗ ██╔╝
-  ██║  ███╗█████╗  ██║   ██║██╔████╔██║█████╗     ██║   ██████╔╝ ╚████╔╝
-  ██║   ██║██╔══╝  ██║   ██║██║╚██╔╝██║██╔══╝     ██║   ██╔══██╗  ╚██╔╝
-  ╚██████╔╝███████╗╚██████╔╝██║ ╚═╝ ██║███████╗   ██║   ██║  ██║   ██║
-   ╚═════╝ ╚══════╝ ╚═════╝ ╚═╝     ╚═╝╚══════╝   ╚═╝   ╚═╝  ╚═╝   ╚═╝
-  http://patorjk.com/software/taag/#p=display&f=ANSI%20Shadow&t=BASIC%20CHECKS
-  */
-
-
-  {
-    double groundTruthRaw[] = { 7.657838, 108.2725, 304.01, 6.826687, 107.4413, 304.01, 6.152492, 106.4785, 304.01, 5.655735, 105.4132, 304.01, 5.351513, 104.2778, 304.01, 5.249068, 103.1069, 304.01, 5.351513, 101.9359, 304.01, 5.655735, 100.8005, 304.01, 6.152492, 99.73524, 304.01, 6.826687, 98.77239, 304.01, 7.657838, 97.94124, 304.01, 8.620689, 97.26704, 304.01, 9.685987, 96.77029, 304.01, 10.82136, 96.46606, 304.01, 11.99231, 96.36362, 304.01, 13.16326, 96.46606, 304.01, 14.29864, 96.77029, 304.01, 15.36393, 97.26704, 304.01, 16.32678, 97.94124, 304.01, 17.15794, 98.77239, 304.01, 17.83213, 99.73524, 304.01, 18.32889, 100.8005, 304.01, 18.63311, 101.9359, 304.01, 18.73555, 103.1069, 304.01, 18.63311, 104.2778, 304.01, 18.32889, 105.4132, 304.01, 17.83213, 106.4785, 304.01, 17.15794, 107.4413, 304.01, 16.32678, 108.2725, 304.01, 15.36393, 108.9467, 304.01, 14.29864, 109.4434, 304.01, 13.16326, 109.7477, 304.01, 11.99231, 109.8501, 304.01, 10.82136, 109.7477, 304.01, 9.685987, 109.4434, 304.01, 8.620689, 108.9467, 304.01 };
-    size_t n = sizeof(groundTruthRaw) / sizeof(groundTruthRaw[0]); 
-    std::vector<double> groundTruth(groundTruthRaw, groundTruthRaw+n);
-    CheckGroundTruth(structures, 0, 0, groundTruth);
-  }
-  {
-    double groundTruthRaw[] = { 7.657838, 108.2725, 310.01, 6.826687, 107.4413, 310.01, 6.152492, 106.4785, 310.01, 5.655735, 105.4132, 310.01, 5.351513, 104.2778, 310.01, 5.249068, 103.1069, 310.01, 5.351513, 101.9359, 310.01, 5.655735, 100.8005, 310.01, 6.152492, 99.73524, 310.01, 6.826687, 98.77239, 310.01, 7.657838, 97.94124, 310.01, 8.620689, 97.26704, 310.01, 9.685987, 96.77029, 310.01, 10.82136, 96.46606, 310.01, 11.99231, 96.36362, 310.01, 13.16326, 96.46606, 310.01, 14.29864, 96.77029, 310.01, 15.36393, 97.26704, 310.01, 16.32678, 97.94124, 310.01, 17.15794, 98.77239, 310.01, 17.83213, 99.73524, 310.01, 18.32889, 100.8005, 310.01, 18.63311, 101.9359, 310.01, 18.73555, 103.1069, 310.01, 18.63311, 104.2778, 310.01, 18.32889, 105.4132, 310.01, 17.83213, 106.4785, 310.01, 17.15794, 107.4413, 310.01, 16.32678, 108.2725, 310.01, 15.36393, 108.9467, 310.01, 14.29864, 109.4434, 310.01, 13.16326, 109.7477, 310.01, 11.99231, 109.8501, 310.01, 10.82136, 109.7477, 310.01, 9.685987, 109.4434, 310.01, 8.620689, 108.9467, 310.01 };
-    size_t n = sizeof(groundTruthRaw) / sizeof(groundTruthRaw[0]); 
-    std::vector<double> groundTruth(groundTruthRaw, groundTruthRaw+n);
-    CheckGroundTruth(structures, 0, 2, groundTruth);
-  }
-  {
-    double groundTruthRaw[] = { -37.967, 161.9664, 304.01, -39.10237, 161.6622, 304.01, -40.16767, 161.1655, 304.01, -41.13052, 160.4913, 304.01, -41.96167, 159.6601, 304.01, -42.63587, 158.6973, 304.01, -43.13263, 157.632, 304.01, -43.43685, 156.4966, 304.01, -43.53929, 155.3257, 304.01, -43.43685, 154.1547, 304.01, -43.13263, 153.0193, 304.01, -42.63587, 151.954, 304.01, -41.96167, 150.9912, 304.01, -41.13052, 150.16, 304.01, -40.16767, 149.4858, 304.01, -39.10237, 148.9891, 304.01, -37.967, 148.6849, 304.01, -36.79605, 148.5824, 304.01, -35.6251, 148.6849, 304.01, -34.48972, 148.9891, 304.01, -33.42443, 149.4858, 304.01, -32.46157, 150.16, 304.01, -31.63042, 150.9912, 304.01, -30.95623, 151.954, 304.01, -30.45947, 153.0193, 304.01, -30.15525, 154.1547, 304.01, -30.0528, 155.3257, 304.01, -30.15525, 156.4966, 304.01, -30.45947, 157.632, 304.01, -30.95623, 158.6973, 304.01, -31.63042, 159.6601, 304.01, -32.46157, 160.4913, 304.01, -33.42443, 161.1655, 304.01, -34.48972, 161.6622, 304.01, -35.6251, 161.9664, 304.01, -36.79605, 162.0689, 304.01 };
-    size_t n = sizeof(groundTruthRaw) / sizeof(groundTruthRaw[0]); 
-    std::vector<double> groundTruth(groundTruthRaw, groundTruthRaw+n);
-    CheckGroundTruth(structures, 1, 0, groundTruth);
-  }
-  {
-    double groundTruthRaw[] = { 69.4042, 150.7324, 307.01, 69.70842, 151.8678, 307.01, 69.81087, 153.0387, 307.01, 69.70842, 154.2097, 307.01, 69.4042, 155.345, 307.01, 68.90745, 156.4103, 307.01, 68.23325, 157.3732, 307.01, 67.4021, 158.2043, 307.01, 66.43925, 158.8785, 307.01, 65.37395, 159.3753, 307.01, 64.23858, 159.6795, 307.01, 63.06762, 159.7819, 307.01, 61.89667, 159.6795, 307.01, 60.7613, 159.3753, 307.01, 59.696, 158.8785, 307.01, 58.73315, 158.2043, 307.01, 57.902, 157.3732, 307.01, 57.22781, 156.4103, 307.01, 56.73105, 155.345, 307.01, 56.42683, 154.2097, 307.01, 56.32438, 153.0387, 307.01, 56.42683, 151.8678, 307.01, 56.73105, 150.7324, 307.01, 57.22781, 149.6671, 307.01, 57.902, 148.7042, 307.01, 58.73315, 147.8731, 307.01, 59.696, 147.1989, 307.01, 60.7613, 146.7021, 307.01, 61.89667, 146.3979, 307.01, 63.06762, 146.2955, 307.01, 64.23858, 146.3979, 307.01, 65.37395, 146.7021, 307.01, 66.43925, 147.1989, 307.01, 67.4021, 147.8731, 307.01, 68.23325, 148.7042, 307.01, 68.90745, 149.6671, 307.01 };
-    size_t n = sizeof(groundTruthRaw) / sizeof(groundTruthRaw[0]); 
-    std::vector<double> groundTruth(groundTruthRaw, groundTruthRaw+n);
-    CheckGroundTruth(structures, 2, 1, groundTruth);
-  }
-
-  {
-    double groundTruthRaw[] = { 108.3984, 232.7406, 274.01, 106.0547, 231.7948, 274.01, 103.7109, 232.8407, 274.01, 96.67969, 232.8757, 274.01, 77.92969, 232.887, 274.01, 47.46094, 232.8902, 274.01, 38.08594, 232.7537, 274.01, 37.6668, 232.3734, 274.01, 38.08594, 231.9774, 274.01, 40.42969, 231.8475, 274.01, 41.76413, 230.0297, 274.01, 42.77344, 229.1388, 274.01, 45.11719, 228.5069, 274.01, 47.46094, 227.1533, 274.01, 49.80469, 226.3505, 274.01, 52.14844, 224.6564, 274.01, 54.49219, 223.923, 274.01, 56.83594, 222.0692, 274.01, 59.17969, 220.3438, 274.01, 61.52344, 219.3888, 274.01, 63.86719, 217.1287, 274.01, 65.83488, 215.9672, 274.01, 68.55469, 213.2383, 274.01, 70.89844, 211.2328, 274.01, 72.8125, 208.9359, 274.01, 75.58594, 206.3615, 274.01, 76.91445, 204.2484, 274.01, 78.89509, 201.9047, 274.01, 80.51276, 199.5609, 274.01, 81.51955, 197.2172, 274.01, 83.67448, 194.8734, 274.01, 84.60938, 192.5297, 274.01, 85.86986, 190.1859, 274.01, 86.57623, 187.8422, 274.01, 88.30051, 185.4984, 274.01, 88.94002, 183.1547, 274.01, 89.23261, 180.8109, 274.01, 89.64844, 180.3263, 274.01, 90.71885, 178.4672, 274.01, 90.97656, 176.1234, 274.01, 91.99219, 174.4794, 274.01, 92.56773, 173.7797, 274.01, 92.80016, 171.4359, 274.01, 93.23473, 169.0922, 274.01, 93.37606, 166.7484, 274.01, 93.60748, 157.3734, 274.01, 93.6341, 152.6859, 274.01, 93.35742, 140.9672, 274.01, 92.89317, 138.6234, 274.01, 92.7069, 136.2797, 274.01, 92.03726, 133.9359, 274.01, 90.84009, 131.5922, 274.01, 90.3769, 129.2484, 274.01, 89.09074, 126.9047, 274.01, 88.13225, 122.2172, 274.01, 86.17828, 119.8734, 274.01, 84.96094, 117.4163, 274.01, 83.99619, 115.1859, 274.01, 83.13079, 112.8422, 274.01, 82.61719, 112.2984, 274.01, 80.27344, 108.8454, 274.01, 79.64514, 108.1547, 274.01, 77.21497, 105.8109, 274.01, 76.47787, 103.4672, 274.01, 75.58594, 102.6177, 274.01, 73.24219, 100.0077, 274.01, 69.54492, 96.43594, 274.01, 67.34096, 94.09219, 274.01, 64.66306, 91.74844, 274.01, 63.86719, 90.92619, 274.01, 61.52344, 90.20454, 274.01, 59.17969, 87.78574, 274.01, 56.83594, 86.48566, 274.01, 54.49219, 84.31388, 274.01, 52.14844, 83.44438, 274.01, 49.80469, 82.75121, 274.01, 49.37617, 82.37344, 274.01, 47.46094, 81.26244, 274.01, 45.71391, 80.02969, 274.01, 45.11719, 79.45415, 274.01, 42.77344, 79.08185, 274.01, 40.42969, 78.51941, 274.01, 38.08594, 78.27534, 274.01, 37.36932, 77.68594, 274.01, 35.74219, 76.67624, 274.01, 33.39844, 76.49941, 274.01, 31.05469, 76.03495, 274.01, 28.71094, 74.83174, 274.01, 26.36719, 74.62859, 274.01, 24.02344, 74.55463, 274.01, 21.67969, 74.22861, 274.01, 19.33594, 74.05312, 274.01, 12.30469, 73.99397, 274.01, 5.273438, 74.0736, 274.01, 2.929688, 74.55463, 274.01, 0.5859375, 74.68513, 274.01, -1.757813, 74.914, 274.01, -2.319131, 75.34219, 274.01, -4.101563, 76.31516, 274.01, -8.789063, 76.74514, 274.01, -11.13281, 78.39038, 274.01, -13.47656, 78.6124, 274.01, -15.82031, 79.19784, 274.01, -18.16406, 81.11024, 274.01, -20.50781, 82.03296, 274.01, -22.85156, 83.13991, 274.01, -25.19531, 83.70732, 274.01, -27.53906, 85.85863, 274.01, -29.88281, 87.03368, 274.01, -32.22656, 88.3274, 274.01, -34.57031, 90.53674, 274.01, -36.91406, 92.5602, 274.01, -39.25781, 93.55952, 274.01, -41.60156, 95.74537, 274.01, -43.94531, 98.26609, 274.01, -46.28906, 100.3701, 274.01, -47.02621, 101.1234, 274.01, -47.86611, 103.4672, 274.01, -49.83594, 105.8109, 274.01, -51.98182, 108.1547, 274.01, -53.06448, 110.4984, 274.01, -53.32031, 110.7675, 274.01, -54.53804, 112.8422, 274.01, -55.66406, 114.273, 274.01, -56.55722, 115.1859, 274.01, -57.13953, 117.5297, 274.01, -58.29264, 119.8734, 274.01, -59.26869, 122.2172, 274.01, -60.35156, 124.0119, 274.01, -60.84229, 124.5609, 274.01, -61.54484, 126.9047, 274.01, -61.71691, 129.2484, 274.01, -63.62281, 131.5922, 274.01, -63.81256, 133.9359, 274.01, -64.12511, 136.2797, 274.01, -64.84515, 138.6234, 274.01, -65.13599, 140.9672, 274.01, -65.33604, 143.3109, 274.01, -65.87358, 145.6547, 274.01, -66.10577, 147.9984, 274.01, -66.17618, 155.0297, 274.01, -66.09933, 162.0609, 274.01, -65.40382, 164.4047, 274.01, -65.24833, 166.7484, 274.01, -64.71442, 171.4359, 274.01, -63.88171, 173.7797, 274.01, -63.69299, 176.1234, 274.01, -61.79081, 178.4672, 274.01, -61.59269, 180.8109, 274.01, -61.19405, 183.1547, 274.01, -60.35156, 185.2055, 274.01, -59.08288, 187.8422, 274.01, -58.00781, 189.3499, 274.01, -57.25858, 190.1859, 274.01, -56.64558, 192.5297, 274.01, -55.29191, 194.8734, 274.01, -54.28698, 197.2172, 274.01, -52.28595, 199.5609, 274.01, -51.47569, 201.9047, 274.01, -48.63281, 204.6417, 274.01, -47.10181, 206.5922, 274.01, -44.64154, 208.9359, 274.01, -42.38504, 211.2797, 274.01, -39.25781, 214.4025, 274.01, -37.42723, 215.9672, 274.01, -34.57031, 218.9107, 274.01, -32.22656, 219.7277, 274.01, -29.88281, 221.6934, 274.01, -27.53906, 222.852, 274.01, -25.19531, 224.5168, 274.01, -22.85156, 225.9419, 274.01, -20.50781, 226.7359, 274.01, -18.16406, 228.3332, 274.01, -15.82031, 229.065, 274.01, -13.47656, 229.267, 274.01, -12.63854, 230.0297, 274.01, -11.13281, 231.9201, 274.01, -10.65505, 232.3734, 274.01, -11.13281, 232.7794, 274.01, -15.82031, 232.792, 274.01, -18.16406, 232.8902, 274.01, -36.91406, 232.9015, 274.01, -39.25781, 232.8902, 274.01, -50.97656, 232.9236, 274.01, -60.35156, 232.9126, 274.01, -67.38281, 232.8407, 274.01, -72.07031, 232.8642, 274.01, -79.10156, 232.8555, 274.01, -83.78906, 232.8788, 274.01, -95.50781, 232.8902, 274.01, -97.85156, 233.4886, 274.01, -100.1953, 233.647, 274.01, -102.5391, 232.9858, 274.01, -104.8828, 233.6969, 274.01, -109.5703, 233.722, 274.01, -125.9766, 233.7086, 274.01, -128.3203, 233.2849, 274.01, -130.6641, 233.702, 274.01, -135.3516, 233.727, 274.01, -149.4141, 233.7135, 274.01, -156.4453, 233.727, 274.01, -163.4766, 233.7119, 274.01, -168.1641, 233.7643, 274.01, -191.6016, 233.7809, 274.01, -210.3516, 233.7716, 274.01, -224.4141, 233.7998, 274.01, -233.7891, 233.7647, 274.01, -243.1641, 233.7785, 274.01, -247.8516, 233.7378, 274.01, -254.8828, 233.8578, 274.01, -257.2266, 235.2519, 274.01, -259.5703, 236.0817, 274.01, -260.7617, 237.0609, 274.01, -261.9141, 238.2262, 274.01, -262.8989, 239.4047, 274.01, -262.9743, 241.7484, 274.01, -262.5977, 244.0922, 274.01, -260.6675, 246.4359, 274.01, -259.6161, 248.7797, 274.01, -257.2266, 251.0035, 274.01, -255.0361, 253.4672, 274.01, -252.5391, 256.0995, 274.01, -251.2277, 258.1547, 274.01, -246.7444, 262.8422, 274.01, -243.1641, 266.3515, 274.01, -239.7411, 269.8734, 274.01, -238.4766, 270.9495, 274.01, -237.2269, 272.2172, 274.01, -236.1328, 273.5215, 274.01, -235.0934, 274.5609, 274.01, -233.7891, 275.6655, 274.01, -232.5319, 276.9047, 274.01, -231.4453, 278.1693, 274.01, -227.917, 281.5922, 274.01, -224.4141, 285.1802, 274.01, -222.0703, 287.4025, 274.01, -218.6841, 290.9672, 274.01, -217.3828, 291.9709, 274.01, -215.0391, 293.1788, 274.01, -212.6953, 294.5138, 274.01, -210.3516, 295.2614, 274.01, -209.8994, 295.6547, 274.01, -208.0078, 296.7083, 274.01, -203.3203, 296.9372, 274.01, -196.2891, 296.9317, 274.01, -193.9453, 296.8988, 274.01, -172.8516, 296.8482, 274.01, -161.1328, 296.843, 274.01, -137.6953, 296.8542, 274.01, -130.6641, 296.8378, 274.01, -107.2266, 296.8379, 274.01, -93.16406, 296.8208, 274.01, -74.41406, 296.838, 274.01, -65.03906, 296.8609, 274.01, -50.97656, 296.8556, 274.01, -46.28906, 296.9051, 274.01, -41.60156, 298.5331, 274.01, -39.25781, 298.5624, 274.01, -36.91406, 297.1455, 274.01, -34.57031, 297.0498, 274.01, -32.22656, 298.5589, 274.01, -25.19531, 298.5624, 274.01, -22.85156, 297.2842, 274.01, -20.50781, 298.5624, 274.01, -1.757813, 298.5624, 274.01, 0.5859375, 297.2104, 274.01, 2.929688, 298.5624, 274.01, 5.273438, 297.6946, 274.01, 7.617188, 298.5168, 274.01, 9.960938, 298.5512, 274.01, 12.30469, 296.937, 274.01, 14.64844, 298.5478, 274.01, 16.99219, 298.5478, 274.01, 19.33594, 297.0782, 274.01, 21.67969, 296.844, 274.01, 23.54531, 297.9984, 274.01, 24.02344, 298.4023, 274.01, 24.50156, 297.9984, 274.01, 26.36719, 296.844, 274.01, 38.08594, 296.8381, 274.01, 52.14844, 296.8033, 274.01, 59.17969, 296.8033, 274.01, 73.24219, 296.7682, 274.01, 99.02344, 296.7566, 274.01, 117.7734, 296.7216, 274.01, 129.4922, 296.7152, 274.01, 131.8359, 295.9083, 274.01, 134.1797, 295.5245, 274.01, 138.8672, 295.4763, 274.01, 155.2734, 295.4763, 274.01, 176.3672, 295.3861, 274.01, 190.4297, 295.3718, 274.01, 197.4609, 295.4763, 274.01, 202.1484, 295.4454, 274.01, 204.4922, 295.3438, 274.01, 206.8359, 295.0757, 274.01, 209.1797, 294.4124, 274.01, 211.5234, 292.3133, 274.01, 213.8672, 291.0809, 274.01, 216.2109, 289.6743, 274.01, 217.3081, 288.6234, 274.01, 219.3558, 286.2797, 274.01, 221.8608, 283.9359, 274.01, 225.5859, 280.045, 274.01, 227.9297, 277.8885, 274.01, 230.2734, 275.2857, 274.01, 232.6172, 273.2225, 274.01, 233.6225, 272.2172, 274.01, 234.9609, 270.5822, 274.01, 238.2254, 267.5297, 274.01, 240.3691, 265.1859, 274.01, 244.3359, 261.3326, 274.01, 246.6797, 258.8034, 274.01, 249.0234, 256.7196, 274.01, 251.3672, 254.0746, 274.01, 254.5313, 251.1234, 274.01, 255.333, 248.7797, 274.01, 257.3723, 246.4359, 274.01, 259.7201, 244.0922, 274.01, 260.106, 241.7484, 274.01, 261.6423, 239.4047, 274.01, 261.0804, 237.0609, 274.01, 259.3552, 234.7172, 274.01, 258.3984, 233.7696, 274.01, 256.0547, 232.8757, 274.01, 253.7109, 232.792, 274.01, 251.3672, 232.8161, 274.01, 246.6797, 232.6981, 274.01, 244.3359, 232.725, 274.01, 239.6484, 232.9137, 274.01, 234.9609, 232.8525, 274.01, 225.5859, 232.8757, 274.01, 209.1797, 232.8757, 274.01, 204.4922, 232.7537, 274.01, 195.1172, 232.7794, 274.01, 171.6797, 232.792, 274.01, 164.6484, 232.7666, 274.01, 152.9297, 232.7666, 274.01, 148.2422, 232.792, 274.01, 138.8672, 232.7406, 274.01 };
-    size_t n = sizeof(groundTruthRaw) / sizeof(groundTruthRaw[0]); 
-    std::vector<double> groundTruth(groundTruthRaw, groundTruthRaw+n);
-    EXPECT_EQ(340u * 3, groundTruth.size());
-    CheckGroundTruth(structures, 5, 0, groundTruth);
-  }
-
-  {
-    double groundTruthRaw[] = { -18.16406, 233.0632, 298.01, -27.53906, 233.1042, 298.01, -29.88281, 233.0819, 298.01, -34.57031, 233.131, 298.01, -43.94531, 233.1221, 298.01, -50.97656, 233.1736, 298.01, -62.69531, 233.1397, 298.01, -65.03906, 232.8376, 298.01, -69.72656, 232.9839, 298.01, -79.10156, 233.0245, 298.01, -90.82031, 233.0382, 298.01, -93.16406, 233.0859, 298.01, -109.5703, 233.1132, 298.01, -111.9141, 233.1791, 298.01, -114.2578, 233.7139, 298.01, -118.9453, 233.9793, 298.01, -128.3203, 234.0284, 298.01, -130.6641, 233.9793, 298.01, -135.3516, 234.0591, 298.01, -137.6953, 234.0284, 298.01, -142.3828, 234.0855, 298.01, -144.7266, 234.0284, 298.01, -151.7578, 234.002, 298.01, -158.7891, 234.0263, 298.01, -163.4766, 233.9784, 298.01, -165.8203, 234.0072, 298.01, -168.1641, 234.1756, 298.01, -170.5078, 234.2214, 298.01, -179.8828, 234.1934, 298.01, -186.9141, 234.2721, 298.01, -189.2578, 234.2289, 298.01, -193.9453, 234.2431, 298.01, -198.6328, 234.1692, 298.01, -200.9766, 234.2326, 298.01, -205.6641, 234.1271, 298.01, -212.6953, 234.2224, 298.01, -215.0391, 234.1992, 298.01, -222.0703, 234.3115, 298.01, -224.4141, 234.2224, 298.01, -226.7578, 234.2502, 298.01, -233.7891, 234.0906, 298.01, -238.4766, 234.0329, 298.01, -243.1641, 234.0283, 298.01, -247.8516, 233.7949, 298.01, -250.1953, 233.8681, 298.01, -252.5391, 234.7626, 298.01, -254.3469, 237.0609, 298.01, -255.6034, 239.4047, 298.01, -254.5181, 241.7484, 298.01, -254.2274, 244.0922, 298.01, -254.181, 248.7797, 298.01, -253.9355, 251.1234, 298.01, -253.5926, 253.4672, 298.01, -252.7483, 255.8109, 298.01, -250.8092, 258.1547, 298.01, -248.713, 260.4984, 298.01, -246.263, 262.8422, 298.01, -244.1406, 265.1859, 298.01, -241.6671, 267.5297, 298.01, -239.4754, 269.8734, 298.01, -237.0156, 272.2172, 298.01, -233.7891, 275.382, 298.01, -231.4453, 277.8249, 298.01, -229.1016, 279.9981, 298.01, -226.7578, 282.5281, 298.01, -224.4141, 284.6784, 298.01, -222.0703, 287.2355, 298.01, -220.5414, 288.6234, 298.01, -218.2745, 290.9672, 298.01, -217.3828, 291.6508, 298.01, -212.6953, 294.5949, 298.01, -210.3516, 295.3142, 298.01, -208.0078, 296.4674, 298.01, -205.6641, 296.8852, 298.01, -203.3203, 297.1563, 298.01, -196.2891, 297.1488, 298.01, -193.9453, 297.0597, 298.01, -182.2266, 296.9529, 298.01, -168.1641, 296.8576, 298.01, -154.1016, 296.9249, 298.01, -149.4141, 296.8921, 298.01, -128.3203, 296.9228, 298.01, -121.2891, 296.8623, 298.01, -111.9141, 296.8549, 298.01, -107.2266, 296.8266, 298.01, -102.5391, 296.8731, 298.01, -95.50781, 296.8453, 298.01, -88.47656, 296.9218, 298.01, -83.78906, 296.9016, 298.01, -69.72656, 296.979, 298.01, -67.38281, 296.9514, 298.01, -65.03906, 297.2199, 298.01, -62.69531, 296.9622, 298.01, -55.66406, 296.9926, 298.01, -50.97656, 296.9467, 298.01, -48.63281, 297.3652, 298.01, -46.28906, 297.0439, 298.01, -43.94531, 297.2875, 298.01, -39.25781, 297.0121, 298.01, -34.57031, 297.1564, 298.01, -32.22656, 297.3612, 298.01, -29.88281, 297.4229, 298.01, -27.53906, 297.1687, 298.01, -25.19531, 297.4334, 298.01, -18.16406, 297.3612, 298.01, -15.82031, 297.4441, 298.01, -13.47656, 297.4125, 298.01, -11.13281, 297.2468, 298.01, -8.789063, 297.4125, 298.01, -6.445313, 297.373, 298.01, -4.101563, 297.4195, 298.01, -1.757813, 297.077, 298.01, 0.5859375, 297.4229, 298.01, 2.929688, 297.4125, 298.01, 5.273438, 296.9489, 298.01, 7.617188, 297.3168, 298.01, 9.960938, 296.9377, 298.01, 12.30469, 296.8998, 298.01, 14.64844, 297.1975, 298.01, 16.99219, 296.8579, 298.01, 28.71094, 296.878, 298.01, 40.42969, 296.8163, 298.01, 42.77344, 296.8369, 298.01, 49.80469, 296.734, 298.01, 59.17969, 296.6906, 298.01, 61.52344, 296.6365, 298.01, 68.55469, 296.6278, 298.01, 73.24219, 296.5777, 298.01, 75.58594, 296.6191, 298.01, 84.96094, 296.5284, 298.01, 96.67969, 296.5538, 298.01, 103.7109, 296.479, 298.01, 115.4297, 296.4259, 298.01, 122.4609, 296.3434, 298.01, 129.4922, 296.3495, 298.01, 131.8359, 295.9141, 298.01, 136.5234, 296.2256, 298.01, 138.8672, 295.833, 298.01, 143.5547, 295.9857, 298.01, 145.8984, 295.8791, 298.01, 152.9297, 295.833, 298.01, 164.6484, 295.6819, 298.01, 171.6797, 295.6819, 298.01, 181.0547, 295.5401, 298.01, 185.7422, 295.5742, 298.01, 192.7734, 295.557, 298.01, 197.4609, 295.8012, 298.01, 202.1484, 295.6819, 298.01, 204.4922, 295.3698, 298.01, 206.8359, 294.803, 298.01, 209.1797, 294.3656, 298.01, 211.5234, 292.4764, 298.01, 213.8672, 291.1765, 298.01, 216.2109, 289.5873, 298.01, 217.229, 288.6234, 298.01, 218.5547, 287.0752, 298.01, 221.7097, 283.9359, 298.01, 225.5859, 279.8775, 298.01, 227.9297, 277.5633, 298.01, 230.2734, 275.0808, 298.01, 233.1989, 272.2172, 298.01, 234.9609, 270.2887, 298.01, 237.7384, 267.5297, 298.01, 241.9922, 263.0843, 298.01, 244.3359, 260.7643, 298.01, 246.788, 258.1547, 298.01, 249.0234, 255.451, 298.01, 250.3651, 253.4672, 298.01, 251.5297, 251.1234, 298.01, 252.1947, 248.7797, 298.01, 252.4915, 246.4359, 298.01, 252.5755, 241.7484, 298.01, 252.8592, 239.4047, 298.01, 252.9236, 237.0609, 298.01, 252.2924, 234.7172, 298.01, 251.3672, 233.4697, 298.01, 249.0234, 232.882, 298.01, 244.3359, 232.9048, 298.01, 241.9922, 233.0145, 298.01, 232.6172, 232.9048, 298.01, 227.9297, 233.0007, 298.01, 216.2109, 233.0632, 298.01, 211.5234, 233.0537, 298.01, 206.8359, 232.9699, 298.01, 204.4922, 232.7322, 298.01, 199.8047, 232.7186, 298.01, 190.4297, 232.7719, 298.01, 183.3984, 232.7719, 298.01, 181.0547, 232.7322, 298.01, 174.0234, 232.7048, 298.01, 171.6797, 232.7322, 298.01, 166.9922, 232.6908, 298.01, 157.6172, 232.7975, 298.01, 155.2734, 232.7588, 298.01, 148.2422, 232.7875, 298.01, 143.5547, 232.7614, 298.01, 138.8672, 232.6477, 298.01, 124.8047, 232.6179, 298.01, 122.4609, 232.6477, 298.01, 113.0859, 232.6027, 298.01, 110.7422, 232.4552, 298.01, 108.3984, 232.2192, 298.01, 106.0547, 231.6764, 298.01, 103.7109, 231.8559, 298.01, 102.8237, 232.3734, 298.01, 101.3672, 232.9839, 298.01, 99.02344, 233.0951, 298.01, 87.30469, 233.0819, 298.01, 84.96094, 233.1091, 298.01, 80.27344, 233.0726, 298.01, 77.92969, 233.1132, 298.01, 70.89844, 233.1397, 298.01, 68.55469, 233.1132, 298.01, 52.14844, 233.131, 298.01, 45.11719, 233.0859, 298.01, 44.16726, 232.3734, 298.01, 42.77344, 231.0206, 298.01, 42.04498, 230.0297, 298.01, 42.77344, 229.2462, 298.01, 45.11719, 228.5664, 298.01, 47.46094, 227.0695, 298.01, 49.80469, 226.0552, 298.01, 52.14844, 224.5723, 298.01, 54.49219, 223.6857, 298.01, 56.83594, 221.8519, 298.01, 59.17969, 220.2086, 298.01, 61.52344, 218.8854, 298.01, 64.94469, 215.9672, 298.01, 66.21094, 215.0191, 298.01, 67.72036, 213.6234, 298.01, 68.55469, 212.6986, 298.01, 70.89844, 210.5055, 298.01, 74.53191, 206.5922, 298.01, 76.54903, 204.2484, 298.01, 78.26105, 201.9047, 298.01, 80.27344, 198.9262, 298.01, 82.61719, 195.2822, 298.01, 82.98087, 194.8734, 298.01, 84.96094, 190.9255, 298.01, 85.43701, 190.1859, 298.01, 86.33423, 187.8422, 298.01, 87.78722, 185.4984, 298.01, 88.60233, 183.1547, 298.01, 89.10253, 180.8109, 298.01, 90.17504, 178.4672, 298.01, 90.88959, 176.1234, 298.01, 91.43783, 173.7797, 298.01, 92.39601, 171.4359, 298.01, 92.95762, 169.0922, 298.01, 93.55695, 159.7172, 298.01, 93.65527, 157.3734, 298.01, 93.67542, 152.6859, 298.01, 93.61213, 150.3422, 298.01, 93.22542, 143.3109, 298.01, 93.06345, 140.9672, 298.01, 92.77563, 138.6234, 298.01, 91.21714, 133.9359, 298.01, 90.67235, 131.5922, 298.01, 89.88776, 129.2484, 298.01, 88.8737, 126.9047, 298.01, 88.44087, 124.5609, 298.01, 86.09712, 119.8734, 298.01, 85.05786, 117.5297, 298.01, 83.87151, 115.1859, 298.01, 82.22388, 112.8422, 298.01, 81.09117, 110.4984, 298.01, 77.92969, 106.4052, 298.01, 77.3894, 105.8109, 298.01, 75.94332, 103.4672, 298.01, 71.71799, 98.77969, 298.01, 68.55469, 95.65721, 298.01, 63.86719, 91.54878, 298.01, 61.52344, 90.1121, 298.01, 59.17969, 88.15762, 298.01, 56.83594, 86.51503, 298.01, 54.49219, 85.42721, 298.01, 52.14844, 83.64907, 298.01, 49.80469, 82.89023, 298.01, 47.46094, 81.50237, 298.01, 45.11719, 80.62591, 298.01, 42.77344, 79.18153, 298.01, 40.42969, 78.7203, 298.01, 38.08594, 78.1349, 298.01, 35.74219, 77.11755, 298.01, 33.39844, 76.51949, 298.01, 31.05469, 76.07934, 298.01, 26.36719, 74.67744, 298.01, 24.02344, 74.42056, 298.01, 14.64844, 74.07317, 298.01, 9.960938, 74.11538, 298.01, 2.929688, 74.40105, 298.01, 0.5859375, 74.67952, 298.01, -1.757813, 75.31406, 298.01, -4.101563, 76.07065, 298.01, -6.445313, 76.49051, 298.01, -8.789063, 77.17276, 298.01, -11.13281, 78.20097, 298.01, -15.82031, 79.31967, 298.01, -18.16406, 80.76948, 298.01, -20.50781, 81.64266, 298.01, -22.85156, 83.0305, 298.01, -25.19531, 83.7937, 298.01, -27.53906, 85.63515, 298.01, -29.88281, 86.7363, 298.01, -32.22656, 88.36089, 298.01, -34.57031, 90.3302, 298.01, -36.56719, 91.74844, 298.01, -41.60156, 95.93605, 298.01, -46.58845, 101.1234, 298.01, -50.17995, 105.8109, 298.01, -52.10386, 108.1547, 298.01, -53.63992, 110.4984, 298.01, -54.95532, 112.8422, 298.01, -56.64794, 115.1859, 298.01, -57.4403, 117.5297, 298.01, -58.91927, 119.8734, 298.01, -59.78655, 122.2172, 298.01, -61.11754, 124.5609, 298.01, -61.58921, 126.9047, 298.01, -62.38012, 129.2484, 298.01, -63.49118, 131.5922, 298.01, -64.02599, 133.9359, 298.01, -64.3932, 136.2797, 298.01, -65.11897, 138.6234, 298.01, -65.64544, 140.9672, 298.01, -66.23938, 147.9984, 298.01, -66.46289, 152.6859, 298.01, -66.48911, 155.0297, 298.01, -66.34437, 159.7172, 298.01, -65.99894, 164.4047, 298.01, -65.49149, 169.0922, 298.01, -64.6875, 171.4359, 298.01, -63.7739, 176.1234, 298.01, -62.9398, 178.4672, 298.01, -61.86011, 180.8109, 298.01, -61.33423, 183.1547, 298.01, -60.43332, 185.4984, 298.01, -58.00781, 190.0632, 298.01, -56.85406, 192.5297, 298.01, -55.66406, 194.7283, 298.01, -54.11692, 197.2172, 298.01, -50.97656, 201.8369, 298.01, -47.36435, 206.5922, 298.01, -45.04395, 208.9359, 298.01, -42.83026, 211.2797, 298.01, -39.25781, 214.7435, 298.01, -34.57031, 218.4974, 298.01, -32.22656, 219.9595, 298.01, -28.02053, 222.9984, 298.01, -27.53906, 223.4238, 298.01, -25.19531, 224.4187, 298.01, -22.85156, 225.8252, 298.01, -20.50781, 226.9067, 298.01, -18.16406, 228.4286, 298.01, -15.82031, 229.1235, 298.01, -14.9447, 230.0297, 298.01, -15.82031, 231.3969, 298.01, -16.94484, 232.3734, 298.01 };
-    size_t n = sizeof(groundTruthRaw) / sizeof(groundTruthRaw[0]); 
-    std::vector<double> groundTruth(groundTruthRaw, groundTruthRaw+n);
-    EXPECT_EQ(358u * 3, groundTruth.size());
-    CheckGroundTruth(structures, 5, 8, groundTruth);
-  }
-}
-
-#endif 
-// BGO_ENABLE_DICOMSTRUCTURESETLOADER2
-
-#if 0
-
-TEST(StructureSet2, ReadFromJsonAndCompute1)
-{
-  DicomStructureSet2 structureSet;
-
-  OrthancPlugins::FullOrthancDataset dicom(GetTestJson());
-  structureSet.Clear();
-
-  structureSet.FillStructuresFromDataset(dicom);
-
-  structureSet.ComputeDependentProperties();
-}
-
-TEST(StructureSet2, ReadFromJsonAndCompute2)
-{
-  DicomStructureSet2 structureSet;
-
-  OrthancPlugins::FullOrthancDataset dicom(GetTestJson());
-  structureSet.Clear();
-
-  structureSet.SetContents(dicom);
-}
-#endif
-
-#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2
-
-static bool CutStructureWithPlane(
-  std::vector< std::pair<ScenePoint2D, ScenePoint2D> >& segments,
-  const DicomStructure2& structure,
-  const double originX, const double originY, const double originZ,
-  const double axisX_X, const double axisX_Y, const double axisX_Z,
-  const double axisY_X, const double axisY_Y, const double axisY_Z
-)
-{
-  // create an AXIAL cutting plane, too far away from the volume 
-  // (> sliceThickness/2)
-  Vector origin, axisX, axisY;
-  LinearAlgebra::AssignVector(origin, originX, originY, originZ);
-  LinearAlgebra::AssignVector(axisX, axisX_X, axisX_Y, axisX_Z);
-  LinearAlgebra::AssignVector(axisY, axisY_X, axisY_Y, axisY_Z);
-  CoordinateSystem3D cuttingPlane(origin, axisX, axisY);
-
-  // compute intersection
-  bool ok = structure.Project(segments, cuttingPlane);
-  return ok;
-}
-
-#endif 
-// BGO_ENABLE_DICOMSTRUCTURESETLOADER2
-
-static double pointsCoord1[] = { 2, 2, 3,  3,  6,  8, 8, 7, 8, 8, 6 };
-static double pointsCoord2[] = { 2, 6, 8, 10, 12, 10, 8, 6, 4, 2, 4 };
-static const size_t pointsCoord1Count = STONE_ARRAY_SIZE(pointsCoord1);
-static const size_t pointsCoord2Count = STONE_ARRAY_SIZE(pointsCoord2);
-const size_t POLYGON_POINT_COUNT = pointsCoord1Count;
-
-#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2
-
-static void CreateBasicStructure(DicomStructure2& structure)
-{
-  // see https://www.dropbox.com/s/1o1vg53hsbvx4cc/test-rtstruct-polygons.jpg?dl=0
-  EXPECT_EQ(pointsCoord1Count, pointsCoord2Count);
-  EXPECT_EQ(11u, pointsCoord2Count);
-
-  for (size_t slice = 0; slice < 3; ++slice)
-  {
-    DicomStructurePolygon2 polygon("Oblomptu", "CLOSED_PLANAR");
-    for (size_t ip = 0; ip < pointsCoord1Count; ++ip)
-    {
-      Vector pt;
-      double pt0 = pointsCoord1[ip];
-      double pt1 = pointsCoord2[ip];
-      double pt2 = 4 * (static_cast<double>(slice) - 1); // -4, 0, 4 
-      LinearAlgebra::AssignVector(pt, pt0, pt1, pt2);
-      polygon.AddPoint(pt);
-    }
-    structure.AddPolygon(polygon);
-  }
-  structure.ComputeDependentProperties();
-}
-
-
-TEST(StructureSet2, CutAxialOutsideTop)
-{
-  DicomStructure2 structure;
-  CreateBasicStructure(structure);
-  std::vector< std::pair<ScenePoint2D, ScenePoint2D> > segments;
-
-  // create an AXIAL cutting plane, too far away from the volume 
-  // (> sliceThickness/2)
-  bool ok = CutStructureWithPlane(segments, structure,
-    0, 0, 7,
-    1, 0, 0,
-    0, 1, 0);
-  EXPECT_FALSE(ok);
-}
-
-
-TEST(StructureSet2, CutAxialOutsideBottom)
-{
-  DicomStructure2 structure;
-  CreateBasicStructure(structure);
-  std::vector< std::pair<ScenePoint2D, ScenePoint2D> > segments;
-
-  // create an AXIAL cutting plane, too far away from the volume 
-  // (> sliceThickness/2)
-  bool ok = CutStructureWithPlane(segments, structure,
-    0, 0, -6.66,
-    1, 0, 0,
-    0, 1, 0);
-  EXPECT_FALSE(ok);
-}
-
-TEST(StructureSet2, CutAxialInsideClose)
-{
-  DicomStructure2 structure;
-  CreateBasicStructure(structure);
-  std::vector< std::pair<ScenePoint2D, ScenePoint2D> > segments;
-
-  // create an AXIAL cutting plane in the volume
-  bool ok = CutStructureWithPlane(segments, structure,
-    0, 0, 1.1,
-    1, 0, 0,
-    0, 1, 0);
-  EXPECT_TRUE(ok);
-  EXPECT_EQ(POLYGON_POINT_COUNT, segments.size());
-
-  for (size_t i = 0; i < segments.size(); ++i)
-  {
-    EXPECT_LT(i, POLYGON_POINT_COUNT);
-    EXPECT_LT(i, POLYGON_POINT_COUNT);
-
-    const ScenePoint2D& pt = segments[i].first;
-
-    // ...should be at the same location as the 3D coords since the plane 
-    // is rooted at 0,0,0 with normal 0,0,1
-    EXPECT_NEAR(pt.GetX(), pointsCoord1[i], DELTA_MAX);
-    EXPECT_NEAR(pt.GetY(), pointsCoord2[i], DELTA_MAX);
-  }
-}
-
-
-TEST(StructureSet2, CutAxialInsideFar)
-{
-  DicomStructure2 structure;
-  CreateBasicStructure(structure);
-  std::vector< std::pair<ScenePoint2D, ScenePoint2D> > segments;
-
-  // create an AXIAL cutting plane
-  Vector origin, axisX, axisY;
-  LinearAlgebra::AssignVector(origin, 0, 0, 0);
-  LinearAlgebra::AssignVector(axisX, 1, 0, 0);
-  LinearAlgebra::AssignVector(axisY, 0, 1, 0);
-  CoordinateSystem3D cuttingPlane(origin, axisX, axisY);
-
-  // compute intersection
-  bool ok = structure.Project(segments, cuttingPlane);
-  EXPECT_TRUE(ok);
-
-  EXPECT_EQ(11u, segments.size());
-  for (size_t i = 0; i < segments.size(); ++i)
-  {
-    EXPECT_LT(i, pointsCoord1Count);
-    EXPECT_LT(i, pointsCoord2Count);
-
-    // the 2D points of the projected polygon
-    const ScenePoint2D& pt = segments[i].first;
-
-    // ...should be at the same location as the 3D coords since the plane 
-    // is rooted at 0,0,0 with normal 0,0,1
-    EXPECT_NEAR(pt.GetX(), pointsCoord1[i], DELTA_MAX);
-    EXPECT_NEAR(pt.GetY(), pointsCoord2[i], DELTA_MAX);
-  }
-}
-
-TEST(StructureSet2, CutCoronalOutsideClose)
-{
-  DicomStructure2 structure;
-  CreateBasicStructure(structure);
-  std::vector< std::pair<ScenePoint2D, ScenePoint2D> > segments;
-
-  // create an X,Z cutting plane, outside of the volume
-  Vector origin, axisX, axisY;
-  LinearAlgebra::AssignVector(origin, 0, 0, 0);
-  LinearAlgebra::AssignVector(axisX, 1, 0, 0);
-  LinearAlgebra::AssignVector(axisY, 0, 0, 1);
-  CoordinateSystem3D cuttingPlane(origin, axisX, axisY);
-
-  // compute intersection
-  bool ok = structure.Project(segments, cuttingPlane);
-  EXPECT_FALSE(ok);
-}
-
-TEST(StructureSet2, CutCoronalInsideClose1DTest)
-{
-  DicomStructure2 structure;
-  CreateBasicStructure(structure);
-
-  // create an X,Z cutting plane, outside of the volume
-  Vector origin, axisX, axisY;
-  LinearAlgebra::AssignVector(origin, 0, 3, 0);
-  LinearAlgebra::AssignVector(axisX, 1, 0, 0);
-  LinearAlgebra::AssignVector(axisY, 0, 0, 1);
-  CoordinateSystem3D cuttingPlane(origin, axisX, axisY);
-
-  ASSERT_EQ(3u, structure.GetPolygons().size());
-
-  for (int i = 0; i < 3; ++i)
-  {
-    double polygonZ = static_cast<double>(i - 1) * 4.0;
-
-    const DicomStructurePolygon2& topSlab = structure.GetPolygons()[i];
-
-    // let's compute the intersection between the polygon and the plane
-    // intersections are in plane coords
-    std::vector<ScenePoint2D> intersects;
-    topSlab.ProjectOnConstantPlane(intersects, cuttingPlane);
-
-    ASSERT_EQ(4u, intersects.size());
-
-    EXPECT_NEAR(2, intersects[0].GetX(), DELTA_MAX);
-    EXPECT_NEAR(4, intersects[1].GetX(), DELTA_MAX);
-    EXPECT_NEAR(7, intersects[2].GetX(), DELTA_MAX);
-    EXPECT_NEAR(8, intersects[3].GetX(), DELTA_MAX);
-
-    for (size_t i = 0; i < 4u; ++i)
-    {
-      EXPECT_NEAR(polygonZ, intersects[i].GetY(), DELTA_MAX);
-    }
-  }
-}
-
-TEST(StructureSet2, CutCoronalInsideClose)
-{
-  DicomStructure2 structure;
-  CreateBasicStructure(structure);
-  std::vector< std::pair<ScenePoint2D, ScenePoint2D> > segments;
-
-  // create an X,Z cutting plane, outside of the volume
-  Vector origin, axisX, axisY;
-  LinearAlgebra::AssignVector(origin, 0, 3, 0);
-  LinearAlgebra::AssignVector(axisX, 1, 0, 0);
-  LinearAlgebra::AssignVector(axisY, 0, 0, 1);
-  CoordinateSystem3D cuttingPlane(origin, axisX, axisY);
-
-  // compute intersection
-  ASSERT_TRUE(structure.Project(segments, cuttingPlane));
-  EXPECT_EQ(24u, segments.size());
-
-  size_t numberOfVeryShortSegments = 0;
-  for (size_t iSegment = 0; iSegment < segments.size(); ++iSegment)
-  {
-    // count the NON vertical very short segments 
-    if (LinearAlgebra::IsNear(segments[iSegment].first.GetX(), segments[iSegment].second.GetX()))
-    {
-      if (LinearAlgebra::IsNear(segments[iSegment].first.GetY(), segments[iSegment].second.GetY()))
-      {
-        numberOfVeryShortSegments++;
-      }
-    }
-  }
-  EXPECT_EQ(8u, numberOfVeryShortSegments);
-}
-
-#endif 
-// BGO_ENABLE_DICOMSTRUCTURESETLOADER2
-
-
-TEST(DisjointDataSet, BasicTest)
-{
-  const size_t ITEM_COUNT = 10;
-  DisjointDataSet ds(ITEM_COUNT);
-
-  for (size_t i = 0; i < ITEM_COUNT; ++i)
-  {
-    EXPECT_EQ(i, ds.Find(i));
-  }
-
-  ds.Union(0, 4);
-  EXPECT_EQ(0u, ds.Find(0));
-  EXPECT_EQ(0u, ds.Find(4));
-
-  ds.Union(4, 6);
-  ds.Union(8, 9);
-  ds.Union(0, 8);
-
-  for (size_t i = 0; i < ITEM_COUNT; ++i)
-  {
-    size_t parent = ds.Find(i);
-    EXPECT_TRUE(0 == parent || 1 == parent || 2 == parent || 3 == parent || 5 == parent || 7 == parent);
-  }
-
-  ds.Union(1, 2);
-  ds.Union(1, 7);
-  for (size_t i = 0; i < ITEM_COUNT; ++i)
-  {
-    size_t parent = ds.Find(i);
-    EXPECT_TRUE(0 == parent || 1 == parent || 3 == parent || 5 == parent);
-  }
-
-  ds.Union(3, 5);
-  for (size_t i = 0; i < ITEM_COUNT; ++i)
-  {
-    size_t parent = ds.Find(i);
-    EXPECT_TRUE(0 == parent || 1 == parent || 3 == parent);
-  }
-
-  EXPECT_EQ(ds.Find(0), ds.Find(0));
-  EXPECT_EQ(ds.Find(0), ds.Find(4));
-  EXPECT_EQ(ds.Find(0), ds.Find(6));
-  EXPECT_EQ(ds.Find(0), ds.Find(8));
-  EXPECT_EQ(ds.Find(0), ds.Find(8));
-
-  EXPECT_EQ(ds.Find(1), ds.Find(7));
-  EXPECT_EQ(ds.Find(2), ds.Find(1));
-  EXPECT_EQ(ds.Find(7), ds.Find(2));
-
-  EXPECT_EQ(ds.Find(3), ds.Find(5));
-  EXPECT_EQ(ds.Find(5), ds.Find(3));
-
-  ds.Union(0, 1);
-  ds.Union(3, 1);
-  for (size_t i = 0; i < ITEM_COUNT; ++i)
-  {
-    EXPECT_EQ(ds.Find(0), ds.Find(i));
-  }
-}
-
-#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2
-
-TEST(StructureSet2, CutSagittalInsideClose)
-{
-  DicomStructure2 structure;
-  CreateBasicStructure(structure);
-  std::vector< std::pair<ScenePoint2D, ScenePoint2D> > segments;
-
-  // create an X,Z cutting plane, inside of the volume
-  Vector origin, axisX, axisY;
-  LinearAlgebra::AssignVector(origin, 0, 3, 0);
-  LinearAlgebra::AssignVector(axisX, 1, 0, 0);
-  LinearAlgebra::AssignVector(axisY, 0, 0, 1);
-  CoordinateSystem3D cuttingPlane(origin, axisX, axisY);
-
-  // compute intersection
-  bool ok = structure.Project(segments, cuttingPlane);
-  EXPECT_TRUE(ok);
-}
-
-#endif 
-// BGO_ENABLE_DICOMSTRUCTURESETLOADER2
-
-
-static size_t ConvertListOfSlabsToSegments_Add(RtStructRectanglesInSlab& rectangles, int row, double xmin, double xmax)
-{
-  double ymin = static_cast<double>(row) * 5.0;
-  double ymax = static_cast<double>(row + 1) * 5.0;
-
-  RtStructRectangleInSlab rectangle;
-  rectangle.xmin = xmin;
-  rectangle.xmax = xmax;
-  rectangle.ymin = ymin;
-  rectangle.ymax = ymax;
-
-  rectangles.push_back(rectangle);
-  
-  return 1u;
-}
-
-static size_t FillTestRectangleList(std::vector< RtStructRectanglesInSlab >& rectanglesForEachSlab)
-{
-  // ConvertListOfSlabsToSegments
-  size_t rectCount = 0;
-
-  rectanglesForEachSlab.push_back(RtStructRectanglesInSlab());
-  rectCount += ConvertListOfSlabsToSegments_Add(rectanglesForEachSlab.back(), 0, 5, 31);
-  rectCount += ConvertListOfSlabsToSegments_Add(rectanglesForEachSlab.back(), 0, 36, 50);
-
-  rectanglesForEachSlab.push_back(RtStructRectanglesInSlab());
-  rectCount += ConvertListOfSlabsToSegments_Add(rectanglesForEachSlab.back(), 1, 20, 45);
-  rectCount += ConvertListOfSlabsToSegments_Add(rectanglesForEachSlab.back(), 1, 52, 70);
-
-  rectanglesForEachSlab.push_back(RtStructRectanglesInSlab());
-  rectCount += ConvertListOfSlabsToSegments_Add(rectanglesForEachSlab.back(), 2, 0, 32);
-  rectCount += ConvertListOfSlabsToSegments_Add(rectanglesForEachSlab.back(), 2, 35, 44);
-  rectCount += ConvertListOfSlabsToSegments_Add(rectanglesForEachSlab.back(), 2, 60, 75);
-
-  rectanglesForEachSlab.push_back(RtStructRectanglesInSlab());
-  rectCount += ConvertListOfSlabsToSegments_Add(rectanglesForEachSlab.back(), 3, 10, 41);
-  rectCount += ConvertListOfSlabsToSegments_Add(rectanglesForEachSlab.back(), 3, 46, 80);
-
-  rectanglesForEachSlab.push_back(RtStructRectanglesInSlab());
-  rectCount += ConvertListOfSlabsToSegments_Add(rectanglesForEachSlab.back(), 4, 34, 42);
-  rectCount += ConvertListOfSlabsToSegments_Add(rectanglesForEachSlab.back(), 4, 90, 96);
-
-  rectanglesForEachSlab.push_back(RtStructRectanglesInSlab());
-  rectCount += ConvertListOfSlabsToSegments_Add(rectanglesForEachSlab.back(), 5, 1, 33);
-  rectCount += ConvertListOfSlabsToSegments_Add(rectanglesForEachSlab.back(), 5, 40, 43);
-  rectCount += ConvertListOfSlabsToSegments_Add(rectanglesForEachSlab.back(), 5, 51, 61);
-  rectCount += ConvertListOfSlabsToSegments_Add(rectanglesForEachSlab.back(), 5, 76, 95);
-
-  return rectCount;
-}
-
-/*
-void AddSlabBoundaries(
-  std::vector<std::pair<double, RectangleBoundaryKind> >& boundaries,
-  const std::vector<RtStructRectanglesInSlab>& slabCuts, size_t iSlab)
-*/
-
-
-/*
-void ProcessBoundaryList(
-  std::vector< std::pair<ScenePoint2D, ScenePoint2D> >& segments,
-  const std::vector<std::pair<double, RectangleBoundaryKind> >& boundaries,
-  double y)
-*/
-
-
-TEST(StructureSet2, ProcessBoundaryList_Empty)
-{
-  std::vector< RtStructRectanglesInSlab > slabCuts;
-  std::vector<std::pair<double, RectangleBoundaryKind> > boundaries;
-
-  boundaries.clear();
-  EXPECT_NO_THROW(AddSlabBoundaries(boundaries, slabCuts, 0));
-  ASSERT_EQ(0u, boundaries.size());
-}
-
-TEST(StructureSet2, ProcessBoundaryListTopRow)
-{
-  std::vector< RtStructRectanglesInSlab > slabCuts;
-  std::vector<std::pair<double, RectangleBoundaryKind> > boundaries;
-  FillTestRectangleList(slabCuts);
-
-  boundaries.clear();
-  AddSlabBoundaries(boundaries, slabCuts, 0);
-
-  {
-    size_t i = 0;
-    ASSERT_EQ(4u, boundaries.size());
-
-    ASSERT_EQ(RectangleBoundaryKind_Start, boundaries[i].second);
-    ASSERT_NEAR(5, boundaries[i].first, DELTA_MAX);
-    i++;
-
-    ASSERT_EQ(RectangleBoundaryKind_End, boundaries[i].second);
-    ASSERT_NEAR(31, boundaries[i].first, DELTA_MAX);
-    i++;
-
-    ASSERT_EQ(RectangleBoundaryKind_Start, boundaries[i].second);
-    ASSERT_NEAR(36, boundaries[i].first, DELTA_MAX);
-    i++;
-
-    ASSERT_EQ(RectangleBoundaryKind_End, boundaries[i].second);
-    ASSERT_NEAR(50, boundaries[i].first, DELTA_MAX);
-    i++;
-  }
-}
-
-TEST(StructureSet2, ProcessBoundaryListRows_0_and_1)
-{
-  std::vector< RtStructRectanglesInSlab > slabCuts;
-  std::vector<std::pair<double, RectangleBoundaryKind> > boundaries;
-  FillTestRectangleList(slabCuts);
-
-  boundaries.clear();
-  AddSlabBoundaries(boundaries, slabCuts, 0);
-  AddSlabBoundaries(boundaries, slabCuts, 1);
-
-  ASSERT_EQ(8u, boundaries.size());
-
-  {
-    size_t i = 0;
-
-    ASSERT_EQ(RectangleBoundaryKind_Start, boundaries[i].second);
-    ASSERT_NEAR(5, boundaries[i].first, DELTA_MAX);
-    i++;
-
-    ASSERT_EQ(RectangleBoundaryKind_Start, boundaries[i].second);
-    ASSERT_NEAR(20, boundaries[i].first, DELTA_MAX);
-    i++;
-
-    ASSERT_EQ(RectangleBoundaryKind_End, boundaries[i].second);
-    ASSERT_NEAR(31, boundaries[i].first, DELTA_MAX);
-    i++;
-
-    ASSERT_EQ(RectangleBoundaryKind_Start, boundaries[i].second);
-    ASSERT_NEAR(36, boundaries[i].first, DELTA_MAX);
-    i++;
-
-    ASSERT_EQ(RectangleBoundaryKind_End, boundaries[i].second);
-    ASSERT_NEAR(45, boundaries[i].first, DELTA_MAX);
-    i++;
-
-    ASSERT_EQ(RectangleBoundaryKind_End, boundaries[i].second);
-    ASSERT_NEAR(50, boundaries[i].first, DELTA_MAX);
-    i++;
-
-    ASSERT_EQ(RectangleBoundaryKind_Start, boundaries[i].second);
-    ASSERT_NEAR(52, boundaries[i].first, DELTA_MAX);
-    i++;
-
-    ASSERT_EQ(RectangleBoundaryKind_End, boundaries[i].second);
-    ASSERT_NEAR(70, boundaries[i].first, DELTA_MAX);
-    i++;
-  }
-}
-
-TEST(StructureSet2, ConvertListOfSlabsToSegments_EmptyBoundaries)
-{
-  std::vector< RtStructRectanglesInSlab > slabCuts;
-  std::vector<std::pair<double, RectangleBoundaryKind> > boundaries;
-  FillTestRectangleList(slabCuts);
-  boundaries.clear();
-  std::vector< std::pair<ScenePoint2D, ScenePoint2D> > segments;
-  ASSERT_NO_THROW(ProcessBoundaryList(segments, boundaries, 42.0));
-  ASSERT_EQ(0u, segments.size());
-}
-
-TEST(StructureSet2, ConvertListOfSlabsToSegments_TopRow_Horizontal)
-{
-  std::vector< RtStructRectanglesInSlab > slabCuts;
-  FillTestRectangleList(slabCuts);
-
-  // top row
-  {
-    std::vector< std::pair<ScenePoint2D, ScenePoint2D> > segments;
-    std::vector<std::pair<double, RectangleBoundaryKind> > boundaries;
-    AddSlabBoundaries(boundaries, slabCuts, 0);
-    ProcessBoundaryList(segments, boundaries, slabCuts[0][0].ymin);
-
-    ASSERT_EQ(2u, segments.size());
-
-    ASSERT_NEAR( 5.0, segments[0].first.GetX(), DELTA_MAX);
-    ASSERT_NEAR(31.0, segments[0].second.GetX(), DELTA_MAX);
-    ASSERT_NEAR( 0.0, segments[0].first.GetY(), DELTA_MAX);
-    ASSERT_NEAR( 0.0, segments[0].second.GetY(), DELTA_MAX);
-
-    ASSERT_NEAR(36.0, segments[1].first.GetX(), DELTA_MAX);
-    ASSERT_NEAR(50.0, segments[1].second.GetX(), DELTA_MAX);
-    ASSERT_NEAR( 0.0, segments[1].first.GetY(), DELTA_MAX);
-    ASSERT_NEAR( 0.0, segments[1].second.GetY(), DELTA_MAX);
-  }
-}
-
-TEST(StructureSet2, ConvertListOfSlabsToSegments_All_Horizontal)
-{
-  std::vector< RtStructRectanglesInSlab > slabCuts;
-  std::vector<std::pair<double, RectangleBoundaryKind> > boundaries;
-  FillTestRectangleList(slabCuts);
-
-  // top row
-  {
-    std::vector<std::pair<double, RectangleBoundaryKind> > boundaries;
-    AddSlabBoundaries(boundaries, slabCuts, 0);
-    std::vector< std::pair<ScenePoint2D, ScenePoint2D> > segments;
-    ProcessBoundaryList(segments, boundaries, slabCuts[0][0].ymin);
-  }
-
-  // mids
-  {
-    std::vector<std::pair<double, RectangleBoundaryKind> > boundaries;
-    AddSlabBoundaries(boundaries, slabCuts, 0);
-    AddSlabBoundaries(boundaries, slabCuts, 1);
-    std::vector< std::pair<ScenePoint2D, ScenePoint2D> > segments;
-    ProcessBoundaryList(segments, boundaries, slabCuts[0][0].ymax);
-
-    ASSERT_EQ(4u, segments.size());
-
-    ASSERT_NEAR(05.0, segments[0].first.GetX(), DELTA_MAX);
-    ASSERT_NEAR(20.0, segments[0].second.GetX(), DELTA_MAX);
-    ASSERT_NEAR(05.0, segments[0].first.GetY(), DELTA_MAX);
-    ASSERT_NEAR(05.0, segments[0].second.GetY(), DELTA_MAX);
-
-    ASSERT_NEAR(31.0, segments[1].first.GetX(), DELTA_MAX);
-    ASSERT_NEAR(36.0, segments[1].second.GetX(), DELTA_MAX);
-    ASSERT_NEAR(05.0, segments[1].first.GetY(), DELTA_MAX);
-    ASSERT_NEAR(05.0, segments[1].second.GetY(), DELTA_MAX);
-
-    ASSERT_NEAR(45.0, segments[2].first.GetX(), DELTA_MAX);
-    ASSERT_NEAR(50.0, segments[2].second.GetX(), DELTA_MAX);
-    ASSERT_NEAR(05.0, segments[2].first.GetY(), DELTA_MAX);
-    ASSERT_NEAR(05.0, segments[2].second.GetY(), DELTA_MAX);
-
-    ASSERT_NEAR(52.0, segments[3].first.GetX(), DELTA_MAX);
-    ASSERT_NEAR(70.0, segments[3].second.GetX(), DELTA_MAX);
-    ASSERT_NEAR(05.0, segments[3].first.GetY(), DELTA_MAX);
-    ASSERT_NEAR(05.0, segments[3].second.GetY(), DELTA_MAX);
-  }
-
-  // bottom row
-  {
-    std::vector<std::pair<double, RectangleBoundaryKind> > boundaries;
-    AddSlabBoundaries(boundaries, slabCuts, 1);
-    std::vector< std::pair<ScenePoint2D, ScenePoint2D> > segments;
-    ProcessBoundaryList(segments, boundaries, slabCuts[1][0].ymax);
-
-    ASSERT_EQ(2u, segments.size());
-
-    ASSERT_NEAR(20.0, segments[0].first.GetX(), DELTA_MAX);
-    ASSERT_NEAR(45.0, segments[0].second.GetX(), DELTA_MAX);
-    ASSERT_NEAR(10.0, segments[0].first.GetY(), DELTA_MAX);
-    ASSERT_NEAR(10.0, segments[0].second.GetY(), DELTA_MAX);
-
-    ASSERT_NEAR(52.0, segments[1].first.GetX(), DELTA_MAX);
-    ASSERT_NEAR(70.0, segments[1].second.GetX(), DELTA_MAX);
-    ASSERT_NEAR(10.0, segments[1].first.GetY(), DELTA_MAX);
-    ASSERT_NEAR(10.0, segments[1].second.GetY(), DELTA_MAX);
-  }
-
-}
-
-TEST(StructureSet2, ConvertListOfSlabsToSegments_Complete_Empty)
-{
-  std::vector< RtStructRectanglesInSlab > slabCuts;
-  std::vector<std::pair<double, RectangleBoundaryKind> > boundaries;
-
-  std::vector< std::pair<ScenePoint2D, ScenePoint2D> > segments;
-
-  ASSERT_NO_THROW(ConvertListOfSlabsToSegments(segments, slabCuts, 0));
-  ASSERT_EQ(0u, segments.size());
-}
-
-TEST(StructureSet2, ConvertListOfSlabsToSegments_Complete_Regular)
-{
-  std::vector< RtStructRectanglesInSlab > slabCuts;
-  std::vector<std::pair<double, RectangleBoundaryKind> > boundaries;
-  size_t totalRectCount = FillTestRectangleList(slabCuts);
-
-  std::vector< std::pair<ScenePoint2D, ScenePoint2D> > segments;
-
-  ASSERT_NO_THROW(ConvertListOfSlabsToSegments(segments, slabCuts, totalRectCount));
-  ASSERT_EQ(60u, segments.size());
-
-  size_t i = 0;
-
-  ASSERT_NEAR(segments[i].first.GetX(), 5.0000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].first.GetY(), 0.0000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetX(), 5.0000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetY(), 5.0000000000000000, DELTA_MAX);
-  i++;
-  ASSERT_NEAR(segments[i].first.GetX(), 31.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].first.GetY(), 0.0000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetX(), 31.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetY(), 5.0000000000000000, DELTA_MAX);
-  i++;
-  ASSERT_NEAR(segments[i].first.GetX(), 36.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].first.GetY(), 0.0000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetX(), 36.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetY(), 5.0000000000000000, DELTA_MAX);
-  i++;
-  ASSERT_NEAR(segments[i].first.GetX(), 50.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].first.GetY(), 0.0000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetX(), 50.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetY(), 5.0000000000000000, DELTA_MAX);
-  i++;
-  ASSERT_NEAR(segments[i].first.GetX(), 20.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].first.GetY(), 5.0000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetX(), 20.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetY(), 10.000000000000000, DELTA_MAX);
-  i++;
-  ASSERT_NEAR(segments[i].first.GetX(), 45.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].first.GetY(), 5.0000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetX(), 45.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetY(), 10.000000000000000, DELTA_MAX);
-  i++;
-  ASSERT_NEAR(segments[i].first.GetX(), 52.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].first.GetY(), 5.0000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetX(), 52.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetY(), 10.000000000000000, DELTA_MAX);
-  i++;
-  ASSERT_NEAR(segments[i].first.GetX(), 70.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].first.GetY(), 5.0000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetX(), 70.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetY(), 10.000000000000000, DELTA_MAX);
-  i++;
-  ASSERT_NEAR(segments[i].first.GetX(), 0.0000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].first.GetY(), 10.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetX(), 0.0000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetY(), 15.000000000000000, DELTA_MAX);
-  i++;
-  ASSERT_NEAR(segments[i].first.GetX(), 32.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].first.GetY(), 10.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetX(), 32.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetY(), 15.000000000000000, DELTA_MAX);
-  i++;
-  ASSERT_NEAR(segments[i].first.GetX(), 35.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].first.GetY(), 10.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetX(), 35.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetY(), 15.000000000000000, DELTA_MAX);
-  i++;
-  ASSERT_NEAR(segments[i].first.GetX(), 44.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].first.GetY(), 10.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetX(), 44.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetY(), 15.000000000000000, DELTA_MAX);
-  i++;
-  ASSERT_NEAR(segments[i].first.GetX(), 60.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].first.GetY(), 10.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetX(), 60.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetY(), 15.000000000000000, DELTA_MAX);
-  i++;
-  ASSERT_NEAR(segments[i].first.GetX(), 75.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].first.GetY(), 10.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetX(), 75.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetY(), 15.000000000000000, DELTA_MAX);
-  i++;
-  ASSERT_NEAR(segments[i].first.GetX(), 10.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].first.GetY(), 15.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetX(), 10.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetY(), 20.000000000000000, DELTA_MAX);
-  i++;
-  ASSERT_NEAR(segments[i].first.GetX(), 41.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].first.GetY(), 15.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetX(), 41.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetY(), 20.000000000000000, DELTA_MAX);
-  i++;
-  ASSERT_NEAR(segments[i].first.GetX(), 46.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].first.GetY(), 15.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetX(), 46.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetY(), 20.000000000000000, DELTA_MAX);
-  i++;
-  ASSERT_NEAR(segments[i].first.GetX(), 80.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].first.GetY(), 15.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetX(), 80.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetY(), 20.000000000000000, DELTA_MAX);
-  i++;
-  ASSERT_NEAR(segments[i].first.GetX(), 34.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].first.GetY(), 20.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetX(), 34.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetY(), 25.000000000000000, DELTA_MAX);
-  i++;
-  ASSERT_NEAR(segments[i].first.GetX(), 42.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].first.GetY(), 20.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetX(), 42.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetY(), 25.000000000000000, DELTA_MAX);
-  i++;
-  ASSERT_NEAR(segments[i].first.GetX(), 90.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].first.GetY(), 20.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetX(), 90.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetY(), 25.000000000000000, DELTA_MAX);
-  i++;
-  ASSERT_NEAR(segments[i].first.GetX(), 96.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].first.GetY(), 20.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetX(), 96.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetY(), 25.000000000000000, DELTA_MAX);
-  i++;
-  ASSERT_NEAR(segments[i].first.GetX(), 1.0000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].first.GetY(), 25.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetX(), 1.0000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetY(), 30.000000000000000, DELTA_MAX);
-  i++;
-  ASSERT_NEAR(segments[i].first.GetX(), 33.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].first.GetY(), 25.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetX(), 33.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetY(), 30.000000000000000, DELTA_MAX);
-  i++;
-  ASSERT_NEAR(segments[i].first.GetX(), 40.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].first.GetY(), 25.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetX(), 40.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetY(), 30.000000000000000, DELTA_MAX);
-  i++;
-  ASSERT_NEAR(segments[i].first.GetX(), 43.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].first.GetY(), 25.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetX(), 43.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetY(), 30.000000000000000, DELTA_MAX);
-  i++;
-  ASSERT_NEAR(segments[i].first.GetX(), 51.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].first.GetY(), 25.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetX(), 51.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetY(), 30.000000000000000, DELTA_MAX);
-  i++;
-  ASSERT_NEAR(segments[i].first.GetX(), 61.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].first.GetY(), 25.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetX(), 61.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetY(), 30.000000000000000, DELTA_MAX);
-  i++;
-  ASSERT_NEAR(segments[i].first.GetX(), 76.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].first.GetY(), 25.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetX(), 76.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetY(), 30.000000000000000, DELTA_MAX);
-  i++;
-  ASSERT_NEAR(segments[i].first.GetX(), 95.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].first.GetY(), 25.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetX(), 95.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetY(), 30.000000000000000, DELTA_MAX);
-  i++;
-  ASSERT_NEAR(segments[i].first.GetX(), 5.0000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].first.GetY(), 0.0000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetX(), 31.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetY(), 0.0000000000000000, DELTA_MAX);
-  i++;
-  ASSERT_NEAR(segments[i].first.GetX(), 36.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].first.GetY(), 0.0000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetX(), 50.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetY(), 0.0000000000000000, DELTA_MAX);
-  i++;
-  ASSERT_NEAR(segments[i].first.GetX(), 5.0000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].first.GetY(), 5.0000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetX(), 20.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetY(), 5.0000000000000000, DELTA_MAX);
-  i++;
-  ASSERT_NEAR(segments[i].first.GetX(), 31.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].first.GetY(), 5.0000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetX(), 36.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetY(), 5.0000000000000000, DELTA_MAX);
-  i++;
-  ASSERT_NEAR(segments[i].first.GetX(), 45.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].first.GetY(), 5.0000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetX(), 50.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetY(), 5.0000000000000000, DELTA_MAX);
-  i++;
-  ASSERT_NEAR(segments[i].first.GetX(), 52.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].first.GetY(), 5.0000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetX(), 70.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetY(), 5.0000000000000000, DELTA_MAX);
-  i++;
-  ASSERT_NEAR(segments[i].first.GetX(), 0.0000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].first.GetY(), 10.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetX(), 20.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetY(), 10.000000000000000, DELTA_MAX);
-  i++;
-  ASSERT_NEAR(segments[i].first.GetX(), 32.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].first.GetY(), 10.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetX(), 35.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetY(), 10.000000000000000, DELTA_MAX);
-  i++;
-  ASSERT_NEAR(segments[i].first.GetX(), 44.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].first.GetY(), 10.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetX(), 45.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetY(), 10.000000000000000, DELTA_MAX);
-  i++;
-  ASSERT_NEAR(segments[i].first.GetX(), 52.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].first.GetY(), 10.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetX(), 60.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetY(), 10.000000000000000, DELTA_MAX);
-  i++;
-  ASSERT_NEAR(segments[i].first.GetX(), 70.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].first.GetY(), 10.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetX(), 75.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetY(), 10.000000000000000, DELTA_MAX);
-  i++;
-  ASSERT_NEAR(segments[i].first.GetX(), 0.0000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].first.GetY(), 15.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetX(), 10.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetY(), 15.000000000000000, DELTA_MAX);
-  i++;
-  ASSERT_NEAR(segments[i].first.GetX(), 32.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].first.GetY(), 15.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetX(), 35.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetY(), 15.000000000000000, DELTA_MAX);
-  i++;
-  ASSERT_NEAR(segments[i].first.GetX(), 41.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].first.GetY(), 15.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetX(), 44.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetY(), 15.000000000000000, DELTA_MAX);
-  i++;
-  ASSERT_NEAR(segments[i].first.GetX(), 46.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].first.GetY(), 15.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetX(), 60.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetY(), 15.000000000000000, DELTA_MAX);
-  i++;
-  ASSERT_NEAR(segments[i].first.GetX(), 75.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].first.GetY(), 15.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetX(), 80.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetY(), 15.000000000000000, DELTA_MAX);
-  i++;
-  ASSERT_NEAR(segments[i].first.GetX(), 10.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].first.GetY(), 20.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetX(), 34.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetY(), 20.000000000000000, DELTA_MAX);
-  i++;
-  ASSERT_NEAR(segments[i].first.GetX(), 41.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].first.GetY(), 20.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetX(), 42.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetY(), 20.000000000000000, DELTA_MAX);
-  i++;
-  ASSERT_NEAR(segments[i].first.GetX(), 46.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].first.GetY(), 20.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetX(), 80.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetY(), 20.000000000000000, DELTA_MAX);
-  i++;
-  ASSERT_NEAR(segments[i].first.GetX(), 90.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].first.GetY(), 20.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetX(), 96.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetY(), 20.000000000000000, DELTA_MAX);
-  i++;
-  ASSERT_NEAR(segments[i].first.GetX(), 1.0000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].first.GetY(), 25.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetX(), 33.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetY(), 25.000000000000000, DELTA_MAX);
-  i++;
-  ASSERT_NEAR(segments[i].first.GetX(), 34.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].first.GetY(), 25.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetX(), 40.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetY(), 25.000000000000000, DELTA_MAX);
-  i++;
-  ASSERT_NEAR(segments[i].first.GetX(), 42.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].first.GetY(), 25.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetX(), 43.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetY(), 25.000000000000000, DELTA_MAX);
-  i++;
-  ASSERT_NEAR(segments[i].first.GetX(), 51.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].first.GetY(), 25.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetX(), 61.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetY(), 25.000000000000000, DELTA_MAX);
-  i++;
-  ASSERT_NEAR(segments[i].first.GetX(), 76.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].first.GetY(), 25.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetX(), 90.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetY(), 25.000000000000000, DELTA_MAX);
-  i++;
-  ASSERT_NEAR(segments[i].first.GetX(), 95.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].first.GetY(), 25.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetX(), 96.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetY(), 25.000000000000000, DELTA_MAX);
-  i++;
-  ASSERT_NEAR(segments[i].first.GetX(), 1.0000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].first.GetY(), 30.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetX(), 33.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetY(), 30.000000000000000, DELTA_MAX);
-  i++;
-  ASSERT_NEAR(segments[i].first.GetX(), 40.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].first.GetY(), 30.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetX(), 43.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetY(), 30.000000000000000, DELTA_MAX);
-  i++;
-  ASSERT_NEAR(segments[i].first.GetX(), 51.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].first.GetY(), 30.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetX(), 61.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetY(), 30.000000000000000, DELTA_MAX);
-  i++;
-  ASSERT_NEAR(segments[i].first.GetX(), 76.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].first.GetY(), 30.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetX(), 95.000000000000000, DELTA_MAX);
-  ASSERT_NEAR(segments[i].second.GetY(), 30.000000000000000, DELTA_MAX);
-}
-
-#if defined(BGO_ENABLE_DICOMSTRUCTURESETLOADER2) && (ORTHANC_SANDBOXED != 1)
-
-#include <SystemToolbox.h>
-
-TEST(StructureSet2, ReadFromJsonPart2)
-{
-  DicomStructureSet2 structureSet;
-  std::string jsonText;
-
-  Orthanc::SystemToolbox::ReadFile(jsonText, "72c773ac-5059f2c4-2e6a9120-4fd4bca1-45701661.json");
-
-  FullOrthancDataset dicom(jsonText);
-  structureSet.Clear();
-
-  structureSet.FillStructuresFromDataset(dicom);
-  structureSet.ComputeDependentProperties();
-
-  //const std::vector<DicomStructure2>& structures = structureSet.structures_;
-}
-
-#endif 
-// BGO_ENABLE_DICOMSTRUCTURESETLOADER2
 
 
 TEST(StructureSet, ReadFromJson)
 {
-  FullOrthancDataset dicom(Orthanc::EmbeddedResources::GetFileResourceBuffer(Orthanc::EmbeddedResources::RT_STRUCT_00),
-                           Orthanc::EmbeddedResources::GetFileResourceSize(Orthanc::EmbeddedResources::RT_STRUCT_00));
+  OrthancStone::FullOrthancDataset dicom(
+    Orthanc::EmbeddedResources::GetFileResourceBuffer(Orthanc::EmbeddedResources::RT_STRUCT_00),
+    Orthanc::EmbeddedResources::GetFileResourceSize(Orthanc::EmbeddedResources::RT_STRUCT_00));
 
-  DicomStructureSet rtstruct(dicom);
+  OrthancStone::DicomStructureSet rtstruct(dicom);
 
   ASSERT_DOUBLE_EQ(0.0, rtstruct.GetEstimatedNormal() [0]);
   ASSERT_DOUBLE_EQ(0.0, rtstruct.GetEstimatedNormal() [1]);
--- a/OrthancStone/UnitTestsSources/UnitTestsSources.cmake	Tue Feb 01 07:19:15 2022 +0100
+++ b/OrthancStone/UnitTestsSources/UnitTestsSources.cmake	Tue Feb 01 08:38:32 2022 +0100
@@ -33,7 +33,3 @@
   ${CMAKE_CURRENT_LIST_DIR}/UnitTestsMain.cpp
   ${CMAKE_CURRENT_LIST_DIR}/VolumeRenderingTests.cpp
   )
-
-add_definitions(
-  -DBGO_ENABLE_DICOMSTRUCTURESETLOADER2=1
-  )