changeset 742:fa5febe0f0c2

moved OrientedBoundingBox in the Volumes folder
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 22 May 2019 09:41:03 +0200
parents c1d6a566dfd3
children bcd3ea868bcd
files Framework/Toolbox/FiniteProjectiveCamera.h Framework/Toolbox/OrientedBoundingBox.cpp Framework/Toolbox/OrientedBoundingBox.h Framework/Toolbox/ParallelSlices.h Framework/Toolbox/VolumeImageGeometry.cpp Framework/Toolbox/VolumeImageGeometry.h Framework/Volumes/OrientedVolumeBoundingBox.cpp Framework/Volumes/OrientedVolumeBoundingBox.h Framework/Volumes/VolumeImageGeometry.cpp Framework/Volumes/VolumeImageGeometry.h Framework/Volumes/VolumeReslicer.cpp Framework/Volumes/VolumeReslicer.h Resources/CMake/OrthancStoneConfiguration.cmake Samples/Sdl/Loader.cpp
diffstat 14 files changed, 790 insertions(+), 791 deletions(-) [+]
line wrap: on
line diff
--- a/Framework/Toolbox/FiniteProjectiveCamera.h	Wed May 22 09:27:21 2019 +0200
+++ b/Framework/Toolbox/FiniteProjectiveCamera.h	Wed May 22 09:41:03 2019 +0200
@@ -23,7 +23,7 @@
 
 #include "LinearAlgebra.h"
 #include "../Volumes/ImageBuffer3D.h"
-#include "VolumeImageGeometry.h"
+#include "../Volumes/VolumeImageGeometry.h"
 
 namespace OrthancStone
 {
--- a/Framework/Toolbox/OrientedBoundingBox.cpp	Wed May 22 09:27:21 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,268 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "OrientedBoundingBox.h"
-
-#include "GeometryToolbox.h"
-#include "../Volumes/ImageBuffer3D.h"
-
-#include <Core/OrthancException.h>
-
-#include <cassert>
-
-namespace OrthancStone
-{
-  OrientedBoundingBox::OrientedBoundingBox(const VolumeImageGeometry& geometry)
-  {
-    unsigned int n = geometry.GetDepth();
-    if (n < 1)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageSize);      
-    }
-
-    Vector dim = geometry.GetVoxelDimensions(VolumeProjection_Axial);
-
-    u_ = geometry.GetAxialGeometry().GetAxisX();
-    v_ = geometry.GetAxialGeometry().GetAxisY();
-    w_ = geometry.GetAxialGeometry().GetNormal();
-
-    hu_ = static_cast<double>(geometry.GetWidth() * dim[0] / 2.0);
-    hv_ = static_cast<double>(geometry.GetHeight() * dim[1] / 2.0);
-    hw_ = static_cast<double>(geometry.GetDepth() * dim[2] / 2.0);
-      
-    c_ = (geometry.GetAxialGeometry().GetOrigin() + 
-          (hu_ - dim[0] / 2.0) * u_ +
-          (hv_ - dim[1] / 2.0) * v_ +
-          (hw_ - dim[2] / 2.0) * w_);
-  }
-
-
-  bool OrientedBoundingBox::HasIntersectionWithPlane(std::vector<Vector>& points,
-                                                     const Vector& normal,
-                                                     double d) const
-  {
-    assert(normal.size() == 3);
-
-    double r = (hu_ * fabs(boost::numeric::ublas::inner_prod(normal, u_)) +
-                hv_ * fabs(boost::numeric::ublas::inner_prod(normal, v_)) +
-                hw_ * fabs(boost::numeric::ublas::inner_prod(normal, w_)));
-
-    double s = boost::numeric::ublas::inner_prod(normal, c_) + d;
-
-    if (fabs(s) >= r)
-    {
-      // No intersection, or intersection is reduced to a single point
-      return false;
-    }
-    else
-    {
-      Vector p;
-
-      // Loop over all the 12 edges (segments) of the oriented
-      // bounding box, and check whether they intersect the plane
-        
-      // X-aligned edges
-      if (GeometryToolbox::IntersectPlaneAndSegment
-          (p, normal, d,
-           c_ - u_ * hu_ - v_ * hv_ - w_ * hw_,
-           c_ + u_ * hu_ - v_ * hv_ - w_ * hw_))
-      {
-        points.push_back(p);
-      }
-
-      if (GeometryToolbox::IntersectPlaneAndSegment
-          (p, normal, d,
-           c_ - u_ * hu_ + v_ * hv_ - w_ * hw_,
-           c_ + u_ * hu_ + v_ * hv_ - w_ * hw_))
-      {
-        points.push_back(p);
-      }
-
-      if (GeometryToolbox::IntersectPlaneAndSegment
-          (p, normal, d,
-           c_ - u_ * hu_ - v_ * hv_ + w_ * hw_,
-           c_ + u_ * hu_ - v_ * hv_ + w_ * hw_))
-      {
-        points.push_back(p);
-      }
-
-      if (GeometryToolbox::IntersectPlaneAndSegment
-          (p, normal, d,
-           c_ - u_ * hu_ + v_ * hv_ + w_ * hw_,
-           c_ + u_ * hu_ + v_ * hv_ + w_ * hw_))
-      {
-        points.push_back(p);
-      }
-
-      // Y-aligned edges
-      if (GeometryToolbox::IntersectPlaneAndSegment
-          (p, normal, d,
-           c_ - u_ * hu_ - v_ * hv_ - w_ * hw_,
-           c_ - u_ * hu_ + v_ * hv_ - w_ * hw_))
-      {
-        points.push_back(p);
-      }
-
-      if (GeometryToolbox::IntersectPlaneAndSegment
-          (p, normal, d,
-           c_ + u_ * hu_ - v_ * hv_ - w_ * hw_,
-           c_ + u_ * hu_ + v_ * hv_ - w_ * hw_))
-      {
-        points.push_back(p);
-      }
-
-      if (GeometryToolbox::IntersectPlaneAndSegment
-          (p, normal, d,
-           c_ - u_ * hu_ - v_ * hv_ + w_ * hw_,
-           c_ - u_ * hu_ + v_ * hv_ + w_ * hw_))
-      {
-        points.push_back(p);
-      }
-
-      if (GeometryToolbox::IntersectPlaneAndSegment
-          (p, normal, d,
-           c_ + u_ * hu_ - v_ * hv_ + w_ * hw_,
-           c_ + u_ * hu_ + v_ * hv_ + w_ * hw_))
-      {
-        points.push_back(p);
-      }
-
-      // Z-aligned edges
-      if (GeometryToolbox::IntersectPlaneAndSegment
-          (p, normal, d,
-           c_ - u_ * hu_ - v_ * hv_ - w_ * hw_,
-           c_ - u_ * hu_ - v_ * hv_ + w_ * hw_))
-      {
-        points.push_back(p);
-      }
-
-      if (GeometryToolbox::IntersectPlaneAndSegment
-          (p, normal, d,
-           c_ + u_ * hu_ - v_ * hv_ - w_ * hw_,
-           c_ + u_ * hu_ - v_ * hv_ + w_ * hw_))
-      {
-        points.push_back(p);
-      }
-
-      if (GeometryToolbox::IntersectPlaneAndSegment
-          (p, normal, d,
-           c_ - u_ * hu_ + v_ * hv_ - w_ * hw_,
-           c_ - u_ * hu_ + v_ * hv_ + w_ * hw_))
-      {
-        points.push_back(p);
-      }
-
-      if (GeometryToolbox::IntersectPlaneAndSegment
-          (p, normal, d,
-           c_ + u_ * hu_ + v_ * hv_ - w_ * hw_,
-           c_ + u_ * hu_ + v_ * hv_ + w_ * hw_))
-      {
-        points.push_back(p);
-      }
-
-      return true;
-    }
-  }
-
-
-  bool OrientedBoundingBox::HasIntersection(std::vector<Vector>& points,
-                                            const CoordinateSystem3D& plane) const
-  {
-    // From the vector equation of a 3D plane (specified by origin
-    // and normal), to the general equation of a 3D plane (which
-    // looses information about the origin of the coordinate system)
-    const Vector& normal = plane.GetNormal();
-    const Vector& origin = plane.GetOrigin();
-    double d = -(normal[0] * origin[0] + normal[1] * origin[1] + normal[2] * origin[2]);
-
-    return HasIntersectionWithPlane(points, normal, d);
-  }
-  
-
-  bool OrientedBoundingBox::Contains(const Vector& p) const
-  {
-    assert(p.size() == 3);
-
-    const Vector q = p - c_;
-
-    return (fabs(boost::numeric::ublas::inner_prod(q, u_)) <= hu_ &&
-            fabs(boost::numeric::ublas::inner_prod(q, v_)) <= hv_ &&
-            fabs(boost::numeric::ublas::inner_prod(q, w_)) <= hw_);
-  }
-
-  
-  void OrientedBoundingBox::FromInternalCoordinates(Vector& target,
-                                                    double x,
-                                                    double y,
-                                                    double z) const
-  {
-    target = (c_ +
-              u_ * 2.0 * hu_ * (x - 0.5) +
-              v_ * 2.0 * hv_ * (y - 0.5) +
-              w_ * 2.0 * hw_ * (z - 0.5));
-  }
-
-  
-  void OrientedBoundingBox::FromInternalCoordinates(Vector& target,
-                                                    const Vector& source) const
-  {
-    assert(source.size() == 3);
-    FromInternalCoordinates(target, source[0], source[1], source[2]);
-  }
-
-  
-  void OrientedBoundingBox::ToInternalCoordinates(Vector& target,
-                                                  const Vector& source) const
-  {
-    assert(source.size() == 3);
-    const Vector q = source - c_;
-
-    double x = boost::numeric::ublas::inner_prod(q, u_) / (2.0 * hu_) + 0.5;
-    double y = boost::numeric::ublas::inner_prod(q, v_) / (2.0 * hv_) + 0.5;
-    double z = boost::numeric::ublas::inner_prod(q, w_) / (2.0 * hw_) + 0.5;
-
-    LinearAlgebra::AssignVector(target, x, y, z);
-  }
-
-
-  bool OrientedBoundingBox::ComputeExtent(Extent2D& extent,
-                                          const CoordinateSystem3D& plane) const
-  {
-    extent.Reset();
-    
-    std::vector<Vector> points;
-    if (HasIntersection(points, plane))
-    {
-      for (size_t i = 0; i < points.size(); i++)
-      {
-        double x, y;
-        plane.ProjectPoint(x, y, points[i]);
-        extent.AddPoint(x, y);
-      }
-      
-      return true;
-    }
-    else
-    {
-      return false;
-    }
-  }
-}
--- a/Framework/Toolbox/OrientedBoundingBox.h	Wed May 22 09:27:21 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,74 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "CoordinateSystem3D.h"
-#include "Extent2D.h"
-#include "LinearAlgebra.h"
-#include "VolumeImageGeometry.h"
-
-namespace OrthancStone
-{
-  class OrientedBoundingBox : public boost::noncopyable
-  {
-  private:
-    Vector  c_;   // center
-    Vector  u_;   // normalized width vector
-    Vector  v_;   // normalized height vector
-    Vector  w_;   // normalized depth vector
-    double  hu_;  // half width
-    double  hv_;  // half height
-    double  hw_;  // half depth
-
-  public:
-    OrientedBoundingBox(const VolumeImageGeometry& geometry);
-
-    const Vector& GetCenter() const
-    {
-      return c_;
-    }
-
-    bool HasIntersectionWithPlane(std::vector<Vector>& points,
-                                  const Vector& normal,
-                                  double d) const;
-
-    bool HasIntersection(std::vector<Vector>& points,
-                         const CoordinateSystem3D& plane) const;
-
-    bool Contains(const Vector& p) const;
-
-    void FromInternalCoordinates(Vector& target,
-                                 double x,
-                                 double y,
-                                 double z) const;
-
-    void FromInternalCoordinates(Vector& target,
-                                 const Vector& source) const;
-
-    void ToInternalCoordinates(Vector& target,
-                               const Vector& source) const;
-
-    bool ComputeExtent(Extent2D& extent,
-                       const CoordinateSystem3D& plane) const;
-  };
-}
-
--- a/Framework/Toolbox/ParallelSlices.h	Wed May 22 09:27:21 2019 +0200
+++ b/Framework/Toolbox/ParallelSlices.h	Wed May 22 09:41:03 2019 +0200
@@ -22,7 +22,7 @@
 #pragma once
 
 #include "CoordinateSystem3D.h"
-#include "VolumeImageGeometry.h"
+#include "../Volumes/VolumeImageGeometry.h"
 
 namespace OrthancStone
 {
--- a/Framework/Toolbox/VolumeImageGeometry.cpp	Wed May 22 09:27:21 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,309 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "VolumeImageGeometry.h"
-
-#include "../Toolbox/GeometryToolbox.h"
-
-#include <Core/OrthancException.h>
-
-
-namespace OrthancStone
-{
-  void VolumeImageGeometry::Invalidate()
-  {
-    Vector p = (axialGeometry_.GetOrigin() +
-                static_cast<double>(depth_ - 1) * voxelDimensions_[2] * axialGeometry_.GetNormal());
-        
-    coronalGeometry_ = CoordinateSystem3D(p,
-                                          axialGeometry_.GetAxisX(),
-                                          -axialGeometry_.GetNormal());
-    
-    sagittalGeometry_ = CoordinateSystem3D(p,
-                                           axialGeometry_.GetAxisY(),
-                                           axialGeometry_.GetNormal());
-
-    Vector origin = (
-      axialGeometry_.MapSliceToWorldCoordinates(-0.5 * voxelDimensions_[0],
-                                                -0.5 * voxelDimensions_[1]) -
-      0.5 * voxelDimensions_[2] * axialGeometry_.GetNormal());
-
-    Vector scaling;
-    
-    if (width_ == 0 ||
-        height_ == 0 ||
-        depth_ == 0)
-    {
-      LinearAlgebra::AssignVector(scaling, 1, 1, 1);
-    }
-    else
-    {
-      scaling = (
-        axialGeometry_.GetAxisX() * voxelDimensions_[0] * static_cast<double>(width_) +
-        axialGeometry_.GetAxisY() * voxelDimensions_[1] * static_cast<double>(height_) +
-        axialGeometry_.GetNormal() * voxelDimensions_[2] * static_cast<double>(depth_));
-    }
-
-    transform_ = LinearAlgebra::Product(
-      GeometryToolbox::CreateTranslationMatrix(origin[0], origin[1], origin[2]),
-      GeometryToolbox::CreateScalingMatrix(scaling[0], scaling[1], scaling[2]));
-
-    LinearAlgebra::InvertMatrix(transformInverse_, transform_);
-  }
-
-  
-  VolumeImageGeometry::VolumeImageGeometry() :
-    width_(0),
-    height_(0),
-    depth_(0)
-  {
-    LinearAlgebra::AssignVector(voxelDimensions_, 1, 1, 1);
-    Invalidate();
-  }
-
-
-  void VolumeImageGeometry::SetSize(unsigned int width,
-                                    unsigned int height,
-                                    unsigned int depth)
-  {
-    width_ = width;
-    height_ = height;
-    depth_ = depth;
-    Invalidate();
-  }
-
-  
-  void VolumeImageGeometry::SetAxialGeometry(const CoordinateSystem3D& geometry)
-  {
-    axialGeometry_ = geometry;
-    Invalidate();
-  }
-
-
-  void VolumeImageGeometry::SetVoxelDimensions(double x,
-                                               double y,
-                                               double z)
-  {
-    if (x <= 0 ||
-        y <= 0 ||
-        z <= 0)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-    }
-    else
-    {
-      LinearAlgebra::AssignVector(voxelDimensions_, x, y, z);
-      Invalidate();
-    }
-  }
-
-
-  const CoordinateSystem3D& VolumeImageGeometry::GetProjectionGeometry(VolumeProjection projection) const
-  {
-    switch (projection)
-    {
-      case VolumeProjection_Axial:
-        return axialGeometry_;
-
-      case VolumeProjection_Coronal:
-        return coronalGeometry_;
-
-      case VolumeProjection_Sagittal:
-        return sagittalGeometry_;
-
-      default:
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-    }
-  }
-
-  
-  Vector VolumeImageGeometry::GetVoxelDimensions(VolumeProjection projection) const
-  {
-    switch (projection)
-    {
-      case VolumeProjection_Axial:
-        return voxelDimensions_;
-
-      case VolumeProjection_Coronal:
-        return LinearAlgebra::CreateVector(voxelDimensions_[0], voxelDimensions_[2], voxelDimensions_[1]);
-
-      case VolumeProjection_Sagittal:
-        return LinearAlgebra::CreateVector(voxelDimensions_[1], voxelDimensions_[2], voxelDimensions_[0]);
-
-      default:
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-    }
-  }
-
-
-  unsigned int VolumeImageGeometry::GetProjectionWidth(VolumeProjection projection) const
-  {
-    switch (projection)
-    {
-      case VolumeProjection_Axial:
-        return width_;
-
-      case VolumeProjection_Coronal:
-        return width_;
-
-      case VolumeProjection_Sagittal:
-        return height_;
-
-      default:
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-    }
-  }
-
-
-  unsigned int VolumeImageGeometry::GetProjectionHeight(VolumeProjection projection) const
-  {
-    switch (projection)
-    {
-      case VolumeProjection_Axial:
-        return height_;
-
-      case VolumeProjection_Coronal:
-        return depth_;
-
-      case VolumeProjection_Sagittal:
-        return depth_;
-
-      default:
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-    }
-  }
-
-
-  unsigned int VolumeImageGeometry::GetProjectionDepth(VolumeProjection projection) const
-  {
-    switch (projection)
-    {
-      case VolumeProjection_Axial:
-        return depth_;
-
-      case VolumeProjection_Coronal:
-        return height_;
-
-      case VolumeProjection_Sagittal:
-        return width_;
-
-      default:
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-    }    
-  }
-
-
-  Vector VolumeImageGeometry::GetCoordinates(float x,
-                                             float y,
-                                             float z) const
-  {
-    Vector p = LinearAlgebra::Product(transform_, LinearAlgebra::CreateVector(x, y, z, 1));
-
-    assert(LinearAlgebra::IsNear(p[3], 1));  // Affine transform, no perspective effect
-
-    // Back to non-homogeneous coordinates
-    return LinearAlgebra::CreateVector(p[0], p[1], p[2]);
-  }
-
-
-  bool VolumeImageGeometry::DetectProjection(VolumeProjection& projection,
-                                             const Vector& planeNormal) const
-  {
-    if (GeometryToolbox::IsParallel(planeNormal, axialGeometry_.GetNormal()))
-    {
-      projection = VolumeProjection_Axial;
-      return true;
-    }
-    else if (GeometryToolbox::IsParallel(planeNormal, coronalGeometry_.GetNormal()))
-    {
-      projection = VolumeProjection_Coronal;
-      return true;
-    }
-    else if (GeometryToolbox::IsParallel(planeNormal, sagittalGeometry_.GetNormal()))
-    {
-      projection = VolumeProjection_Sagittal;
-      return true;
-    }
-    else
-    {
-      return false;
-    }
-  }
-
-  
-  bool VolumeImageGeometry::DetectSlice(VolumeProjection& projection,
-                                        unsigned int& slice,
-                                        const CoordinateSystem3D& plane) const
-  {
-    if (!DetectProjection(projection, plane.GetNormal()))
-    {
-      return false;
-    }
-
-    // Transforms the coordinates of the origin of the plane, into the
-    // coordinates of the axial geometry
-    const Vector& origin = plane.GetOrigin();
-    Vector p = LinearAlgebra::Product(
-      transformInverse_,
-      LinearAlgebra::CreateVector(origin[0], origin[1], origin[2], 1));
-
-    assert(LinearAlgebra::IsNear(p[3], 1));
-
-    double z;
-
-    switch (projection)
-    {
-      case VolumeProjection_Axial:
-        z = p[2];
-        break;
-
-      case VolumeProjection_Coronal:
-        z = p[1];
-        break;
-
-      case VolumeProjection_Sagittal:
-        z = p[0];
-        break;
-
-      default:
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-    }
-
-    const unsigned int projectionDepth = GetProjectionDepth(projection);
-    
-    z *= static_cast<double>(projectionDepth);
-    if (z < 0)
-    {
-      return false;
-    }
-        
-    unsigned int d = static_cast<unsigned int>(std::floor(z));
-    if (d >= projectionDepth)
-    {
-      return false;
-    }
-    else
-    {
-      slice = d;
-      return true;
-    }
-  }
-}
--- a/Framework/Toolbox/VolumeImageGeometry.h	Wed May 22 09:27:21 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,122 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "../StoneEnumerations.h"
-#include "CoordinateSystem3D.h"
-
-namespace OrthancStone
-{
-  class VolumeImageGeometry
-  {
-  private:
-    unsigned int           width_;
-    unsigned int           height_;
-    unsigned int           depth_;
-    CoordinateSystem3D     axialGeometry_;
-    CoordinateSystem3D     coronalGeometry_;
-    CoordinateSystem3D     sagittalGeometry_;
-    Vector                 voxelDimensions_;
-    Matrix                 transform_;
-    Matrix                 transformInverse_;
-
-    void Invalidate();
-
-  public:
-    VolumeImageGeometry();
-
-    unsigned int GetWidth() const
-    {
-      return width_;
-    }
-
-    unsigned int GetHeight() const
-    {
-      return height_;
-    }
-
-    unsigned int GetDepth() const
-    {
-      return depth_;
-    }
-
-    const CoordinateSystem3D& GetAxialGeometry() const
-    {
-      return axialGeometry_;
-    }
-
-    const CoordinateSystem3D& GetCoronalGeometry() const
-    {
-      return coronalGeometry_;
-    }
-
-    const CoordinateSystem3D& GetSagittalGeometry() const
-    {
-      return sagittalGeometry_;
-    }
-
-    const CoordinateSystem3D& GetProjectionGeometry(VolumeProjection projection) const;
-    
-    const Matrix& GetTransform() const
-    {
-      return transform_;
-    }
-
-    const Matrix& GetTransformInverse() const
-    {
-      return transformInverse_;
-    }
-
-    void SetSize(unsigned int width,
-                 unsigned int height,
-                 unsigned int depth);
-
-    // Set the geometry of the first axial slice (i.e. the one whose
-    // depth == 0)
-    void SetAxialGeometry(const CoordinateSystem3D& geometry);
-
-    void SetVoxelDimensions(double x,
-                            double y,
-                            double z);
-
-    Vector GetVoxelDimensions(VolumeProjection projection) const;
-
-    unsigned int GetProjectionWidth(VolumeProjection projection) const;
-
-    unsigned int GetProjectionHeight(VolumeProjection projection) const;
-
-    unsigned int GetProjectionDepth(VolumeProjection projection) const;
-
-    // Get the 3D position of a point in the volume, where x, y and z
-    // lie in the [0;1] range
-    Vector GetCoordinates(float x,
-                          float y,
-                          float z) const;
-
-    bool DetectProjection(VolumeProjection& projection,
-                          const Vector& planeNormal) const;
-
-    bool DetectSlice(VolumeProjection& projection,
-                     unsigned int& slice,
-                     const CoordinateSystem3D& plane) const;
-  };
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Volumes/OrientedVolumeBoundingBox.cpp	Wed May 22 09:41:03 2019 +0200
@@ -0,0 +1,268 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "OrientedVolumeBoundingBox.h"
+
+#include "../Toolbox/GeometryToolbox.h"
+#include "ImageBuffer3D.h"
+
+#include <Core/OrthancException.h>
+
+#include <cassert>
+
+namespace OrthancStone
+{
+  OrientedVolumeBoundingBox::OrientedVolumeBoundingBox(const VolumeImageGeometry& geometry)
+  {
+    unsigned int n = geometry.GetDepth();
+    if (n < 1)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageSize);      
+    }
+
+    Vector dim = geometry.GetVoxelDimensions(VolumeProjection_Axial);
+
+    u_ = geometry.GetAxialGeometry().GetAxisX();
+    v_ = geometry.GetAxialGeometry().GetAxisY();
+    w_ = geometry.GetAxialGeometry().GetNormal();
+
+    hu_ = static_cast<double>(geometry.GetWidth() * dim[0] / 2.0);
+    hv_ = static_cast<double>(geometry.GetHeight() * dim[1] / 2.0);
+    hw_ = static_cast<double>(geometry.GetDepth() * dim[2] / 2.0);
+      
+    c_ = (geometry.GetAxialGeometry().GetOrigin() + 
+          (hu_ - dim[0] / 2.0) * u_ +
+          (hv_ - dim[1] / 2.0) * v_ +
+          (hw_ - dim[2] / 2.0) * w_);
+  }
+
+
+  bool OrientedVolumeBoundingBox::HasIntersectionWithPlane(std::vector<Vector>& points,
+                                                           const Vector& normal,
+                                                           double d) const
+  {
+    assert(normal.size() == 3);
+
+    double r = (hu_ * fabs(boost::numeric::ublas::inner_prod(normal, u_)) +
+                hv_ * fabs(boost::numeric::ublas::inner_prod(normal, v_)) +
+                hw_ * fabs(boost::numeric::ublas::inner_prod(normal, w_)));
+
+    double s = boost::numeric::ublas::inner_prod(normal, c_) + d;
+
+    if (fabs(s) >= r)
+    {
+      // No intersection, or intersection is reduced to a single point
+      return false;
+    }
+    else
+    {
+      Vector p;
+
+      // Loop over all the 12 edges (segments) of the oriented
+      // bounding box, and check whether they intersect the plane
+        
+      // X-aligned edges
+      if (GeometryToolbox::IntersectPlaneAndSegment
+          (p, normal, d,
+           c_ - u_ * hu_ - v_ * hv_ - w_ * hw_,
+           c_ + u_ * hu_ - v_ * hv_ - w_ * hw_))
+      {
+        points.push_back(p);
+      }
+
+      if (GeometryToolbox::IntersectPlaneAndSegment
+          (p, normal, d,
+           c_ - u_ * hu_ + v_ * hv_ - w_ * hw_,
+           c_ + u_ * hu_ + v_ * hv_ - w_ * hw_))
+      {
+        points.push_back(p);
+      }
+
+      if (GeometryToolbox::IntersectPlaneAndSegment
+          (p, normal, d,
+           c_ - u_ * hu_ - v_ * hv_ + w_ * hw_,
+           c_ + u_ * hu_ - v_ * hv_ + w_ * hw_))
+      {
+        points.push_back(p);
+      }
+
+      if (GeometryToolbox::IntersectPlaneAndSegment
+          (p, normal, d,
+           c_ - u_ * hu_ + v_ * hv_ + w_ * hw_,
+           c_ + u_ * hu_ + v_ * hv_ + w_ * hw_))
+      {
+        points.push_back(p);
+      }
+
+      // Y-aligned edges
+      if (GeometryToolbox::IntersectPlaneAndSegment
+          (p, normal, d,
+           c_ - u_ * hu_ - v_ * hv_ - w_ * hw_,
+           c_ - u_ * hu_ + v_ * hv_ - w_ * hw_))
+      {
+        points.push_back(p);
+      }
+
+      if (GeometryToolbox::IntersectPlaneAndSegment
+          (p, normal, d,
+           c_ + u_ * hu_ - v_ * hv_ - w_ * hw_,
+           c_ + u_ * hu_ + v_ * hv_ - w_ * hw_))
+      {
+        points.push_back(p);
+      }
+
+      if (GeometryToolbox::IntersectPlaneAndSegment
+          (p, normal, d,
+           c_ - u_ * hu_ - v_ * hv_ + w_ * hw_,
+           c_ - u_ * hu_ + v_ * hv_ + w_ * hw_))
+      {
+        points.push_back(p);
+      }
+
+      if (GeometryToolbox::IntersectPlaneAndSegment
+          (p, normal, d,
+           c_ + u_ * hu_ - v_ * hv_ + w_ * hw_,
+           c_ + u_ * hu_ + v_ * hv_ + w_ * hw_))
+      {
+        points.push_back(p);
+      }
+
+      // Z-aligned edges
+      if (GeometryToolbox::IntersectPlaneAndSegment
+          (p, normal, d,
+           c_ - u_ * hu_ - v_ * hv_ - w_ * hw_,
+           c_ - u_ * hu_ - v_ * hv_ + w_ * hw_))
+      {
+        points.push_back(p);
+      }
+
+      if (GeometryToolbox::IntersectPlaneAndSegment
+          (p, normal, d,
+           c_ + u_ * hu_ - v_ * hv_ - w_ * hw_,
+           c_ + u_ * hu_ - v_ * hv_ + w_ * hw_))
+      {
+        points.push_back(p);
+      }
+
+      if (GeometryToolbox::IntersectPlaneAndSegment
+          (p, normal, d,
+           c_ - u_ * hu_ + v_ * hv_ - w_ * hw_,
+           c_ - u_ * hu_ + v_ * hv_ + w_ * hw_))
+      {
+        points.push_back(p);
+      }
+
+      if (GeometryToolbox::IntersectPlaneAndSegment
+          (p, normal, d,
+           c_ + u_ * hu_ + v_ * hv_ - w_ * hw_,
+           c_ + u_ * hu_ + v_ * hv_ + w_ * hw_))
+      {
+        points.push_back(p);
+      }
+
+      return true;
+    }
+  }
+
+
+  bool OrientedVolumeBoundingBox::HasIntersection(std::vector<Vector>& points,
+                                                  const CoordinateSystem3D& plane) const
+  {
+    // From the vector equation of a 3D plane (specified by origin
+    // and normal), to the general equation of a 3D plane (which
+    // looses information about the origin of the coordinate system)
+    const Vector& normal = plane.GetNormal();
+    const Vector& origin = plane.GetOrigin();
+    double d = -(normal[0] * origin[0] + normal[1] * origin[1] + normal[2] * origin[2]);
+
+    return HasIntersectionWithPlane(points, normal, d);
+  }
+  
+
+  bool OrientedVolumeBoundingBox::Contains(const Vector& p) const
+  {
+    assert(p.size() == 3);
+
+    const Vector q = p - c_;
+
+    return (fabs(boost::numeric::ublas::inner_prod(q, u_)) <= hu_ &&
+            fabs(boost::numeric::ublas::inner_prod(q, v_)) <= hv_ &&
+            fabs(boost::numeric::ublas::inner_prod(q, w_)) <= hw_);
+  }
+
+  
+  void OrientedVolumeBoundingBox::FromInternalCoordinates(Vector& target,
+                                                          double x,
+                                                          double y,
+                                                          double z) const
+  {
+    target = (c_ +
+              u_ * 2.0 * hu_ * (x - 0.5) +
+              v_ * 2.0 * hv_ * (y - 0.5) +
+              w_ * 2.0 * hw_ * (z - 0.5));
+  }
+
+  
+  void OrientedVolumeBoundingBox::FromInternalCoordinates(Vector& target,
+                                                          const Vector& source) const
+  {
+    assert(source.size() == 3);
+    FromInternalCoordinates(target, source[0], source[1], source[2]);
+  }
+
+  
+  void OrientedVolumeBoundingBox::ToInternalCoordinates(Vector& target,
+                                                        const Vector& source) const
+  {
+    assert(source.size() == 3);
+    const Vector q = source - c_;
+
+    double x = boost::numeric::ublas::inner_prod(q, u_) / (2.0 * hu_) + 0.5;
+    double y = boost::numeric::ublas::inner_prod(q, v_) / (2.0 * hv_) + 0.5;
+    double z = boost::numeric::ublas::inner_prod(q, w_) / (2.0 * hw_) + 0.5;
+
+    LinearAlgebra::AssignVector(target, x, y, z);
+  }
+
+
+  bool OrientedVolumeBoundingBox::ComputeExtent(Extent2D& extent,
+                                                const CoordinateSystem3D& plane) const
+  {
+    extent.Reset();
+    
+    std::vector<Vector> points;
+    if (HasIntersection(points, plane))
+    {
+      for (size_t i = 0; i < points.size(); i++)
+      {
+        double x, y;
+        plane.ProjectPoint(x, y, points[i]);
+        extent.AddPoint(x, y);
+      }
+      
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Volumes/OrientedVolumeBoundingBox.h	Wed May 22 09:41:03 2019 +0200
@@ -0,0 +1,74 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../Toolbox/CoordinateSystem3D.h"
+#include "../Toolbox/Extent2D.h"
+#include "../Toolbox/LinearAlgebra.h"
+#include "VolumeImageGeometry.h"
+
+namespace OrthancStone
+{
+  class OrientedVolumeBoundingBox : public boost::noncopyable
+  {
+  private:
+    Vector  c_;   // center
+    Vector  u_;   // normalized width vector
+    Vector  v_;   // normalized height vector
+    Vector  w_;   // normalized depth vector
+    double  hu_;  // half width
+    double  hv_;  // half height
+    double  hw_;  // half depth
+
+  public:
+    OrientedVolumeBoundingBox(const VolumeImageGeometry& geometry);
+
+    const Vector& GetCenter() const
+    {
+      return c_;
+    }
+
+    bool HasIntersectionWithPlane(std::vector<Vector>& points,
+                                  const Vector& normal,
+                                  double d) const;
+
+    bool HasIntersection(std::vector<Vector>& points,
+                         const CoordinateSystem3D& plane) const;
+
+    bool Contains(const Vector& p) const;
+
+    void FromInternalCoordinates(Vector& target,
+                                 double x,
+                                 double y,
+                                 double z) const;
+
+    void FromInternalCoordinates(Vector& target,
+                                 const Vector& source) const;
+
+    void ToInternalCoordinates(Vector& target,
+                               const Vector& source) const;
+
+    bool ComputeExtent(Extent2D& extent,
+                       const CoordinateSystem3D& plane) const;
+  };
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Volumes/VolumeImageGeometry.cpp	Wed May 22 09:41:03 2019 +0200
@@ -0,0 +1,309 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "VolumeImageGeometry.h"
+
+#include "../Toolbox/GeometryToolbox.h"
+
+#include <Core/OrthancException.h>
+
+
+namespace OrthancStone
+{
+  void VolumeImageGeometry::Invalidate()
+  {
+    Vector p = (axialGeometry_.GetOrigin() +
+                static_cast<double>(depth_ - 1) * voxelDimensions_[2] * axialGeometry_.GetNormal());
+        
+    coronalGeometry_ = CoordinateSystem3D(p,
+                                          axialGeometry_.GetAxisX(),
+                                          -axialGeometry_.GetNormal());
+    
+    sagittalGeometry_ = CoordinateSystem3D(p,
+                                           axialGeometry_.GetAxisY(),
+                                           axialGeometry_.GetNormal());
+
+    Vector origin = (
+      axialGeometry_.MapSliceToWorldCoordinates(-0.5 * voxelDimensions_[0],
+                                                -0.5 * voxelDimensions_[1]) -
+      0.5 * voxelDimensions_[2] * axialGeometry_.GetNormal());
+
+    Vector scaling;
+    
+    if (width_ == 0 ||
+        height_ == 0 ||
+        depth_ == 0)
+    {
+      LinearAlgebra::AssignVector(scaling, 1, 1, 1);
+    }
+    else
+    {
+      scaling = (
+        axialGeometry_.GetAxisX() * voxelDimensions_[0] * static_cast<double>(width_) +
+        axialGeometry_.GetAxisY() * voxelDimensions_[1] * static_cast<double>(height_) +
+        axialGeometry_.GetNormal() * voxelDimensions_[2] * static_cast<double>(depth_));
+    }
+
+    transform_ = LinearAlgebra::Product(
+      GeometryToolbox::CreateTranslationMatrix(origin[0], origin[1], origin[2]),
+      GeometryToolbox::CreateScalingMatrix(scaling[0], scaling[1], scaling[2]));
+
+    LinearAlgebra::InvertMatrix(transformInverse_, transform_);
+  }
+
+  
+  VolumeImageGeometry::VolumeImageGeometry() :
+    width_(0),
+    height_(0),
+    depth_(0)
+  {
+    LinearAlgebra::AssignVector(voxelDimensions_, 1, 1, 1);
+    Invalidate();
+  }
+
+
+  void VolumeImageGeometry::SetSize(unsigned int width,
+                                    unsigned int height,
+                                    unsigned int depth)
+  {
+    width_ = width;
+    height_ = height;
+    depth_ = depth;
+    Invalidate();
+  }
+
+  
+  void VolumeImageGeometry::SetAxialGeometry(const CoordinateSystem3D& geometry)
+  {
+    axialGeometry_ = geometry;
+    Invalidate();
+  }
+
+
+  void VolumeImageGeometry::SetVoxelDimensions(double x,
+                                               double y,
+                                               double z)
+  {
+    if (x <= 0 ||
+        y <= 0 ||
+        z <= 0)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+    else
+    {
+      LinearAlgebra::AssignVector(voxelDimensions_, x, y, z);
+      Invalidate();
+    }
+  }
+
+
+  const CoordinateSystem3D& VolumeImageGeometry::GetProjectionGeometry(VolumeProjection projection) const
+  {
+    switch (projection)
+    {
+      case VolumeProjection_Axial:
+        return axialGeometry_;
+
+      case VolumeProjection_Coronal:
+        return coronalGeometry_;
+
+      case VolumeProjection_Sagittal:
+        return sagittalGeometry_;
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+  
+  Vector VolumeImageGeometry::GetVoxelDimensions(VolumeProjection projection) const
+  {
+    switch (projection)
+    {
+      case VolumeProjection_Axial:
+        return voxelDimensions_;
+
+      case VolumeProjection_Coronal:
+        return LinearAlgebra::CreateVector(voxelDimensions_[0], voxelDimensions_[2], voxelDimensions_[1]);
+
+      case VolumeProjection_Sagittal:
+        return LinearAlgebra::CreateVector(voxelDimensions_[1], voxelDimensions_[2], voxelDimensions_[0]);
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  unsigned int VolumeImageGeometry::GetProjectionWidth(VolumeProjection projection) const
+  {
+    switch (projection)
+    {
+      case VolumeProjection_Axial:
+        return width_;
+
+      case VolumeProjection_Coronal:
+        return width_;
+
+      case VolumeProjection_Sagittal:
+        return height_;
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  unsigned int VolumeImageGeometry::GetProjectionHeight(VolumeProjection projection) const
+  {
+    switch (projection)
+    {
+      case VolumeProjection_Axial:
+        return height_;
+
+      case VolumeProjection_Coronal:
+        return depth_;
+
+      case VolumeProjection_Sagittal:
+        return depth_;
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  unsigned int VolumeImageGeometry::GetProjectionDepth(VolumeProjection projection) const
+  {
+    switch (projection)
+    {
+      case VolumeProjection_Axial:
+        return depth_;
+
+      case VolumeProjection_Coronal:
+        return height_;
+
+      case VolumeProjection_Sagittal:
+        return width_;
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }    
+  }
+
+
+  Vector VolumeImageGeometry::GetCoordinates(float x,
+                                             float y,
+                                             float z) const
+  {
+    Vector p = LinearAlgebra::Product(transform_, LinearAlgebra::CreateVector(x, y, z, 1));
+
+    assert(LinearAlgebra::IsNear(p[3], 1));  // Affine transform, no perspective effect
+
+    // Back to non-homogeneous coordinates
+    return LinearAlgebra::CreateVector(p[0], p[1], p[2]);
+  }
+
+
+  bool VolumeImageGeometry::DetectProjection(VolumeProjection& projection,
+                                             const Vector& planeNormal) const
+  {
+    if (GeometryToolbox::IsParallel(planeNormal, axialGeometry_.GetNormal()))
+    {
+      projection = VolumeProjection_Axial;
+      return true;
+    }
+    else if (GeometryToolbox::IsParallel(planeNormal, coronalGeometry_.GetNormal()))
+    {
+      projection = VolumeProjection_Coronal;
+      return true;
+    }
+    else if (GeometryToolbox::IsParallel(planeNormal, sagittalGeometry_.GetNormal()))
+    {
+      projection = VolumeProjection_Sagittal;
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+  
+  bool VolumeImageGeometry::DetectSlice(VolumeProjection& projection,
+                                        unsigned int& slice,
+                                        const CoordinateSystem3D& plane) const
+  {
+    if (!DetectProjection(projection, plane.GetNormal()))
+    {
+      return false;
+    }
+
+    // Transforms the coordinates of the origin of the plane, into the
+    // coordinates of the axial geometry
+    const Vector& origin = plane.GetOrigin();
+    Vector p = LinearAlgebra::Product(
+      transformInverse_,
+      LinearAlgebra::CreateVector(origin[0], origin[1], origin[2], 1));
+
+    assert(LinearAlgebra::IsNear(p[3], 1));
+
+    double z;
+
+    switch (projection)
+    {
+      case VolumeProjection_Axial:
+        z = p[2];
+        break;
+
+      case VolumeProjection_Coronal:
+        z = p[1];
+        break;
+
+      case VolumeProjection_Sagittal:
+        z = p[0];
+        break;
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    const unsigned int projectionDepth = GetProjectionDepth(projection);
+    
+    z *= static_cast<double>(projectionDepth);
+    if (z < 0)
+    {
+      return false;
+    }
+        
+    unsigned int d = static_cast<unsigned int>(std::floor(z));
+    if (d >= projectionDepth)
+    {
+      return false;
+    }
+    else
+    {
+      slice = d;
+      return true;
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Volumes/VolumeImageGeometry.h	Wed May 22 09:41:03 2019 +0200
@@ -0,0 +1,122 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../StoneEnumerations.h"
+#include "../Toolbox/CoordinateSystem3D.h"
+
+namespace OrthancStone
+{
+  class VolumeImageGeometry
+  {
+  private:
+    unsigned int           width_;
+    unsigned int           height_;
+    unsigned int           depth_;
+    CoordinateSystem3D     axialGeometry_;
+    CoordinateSystem3D     coronalGeometry_;
+    CoordinateSystem3D     sagittalGeometry_;
+    Vector                 voxelDimensions_;
+    Matrix                 transform_;
+    Matrix                 transformInverse_;
+
+    void Invalidate();
+
+  public:
+    VolumeImageGeometry();
+
+    unsigned int GetWidth() const
+    {
+      return width_;
+    }
+
+    unsigned int GetHeight() const
+    {
+      return height_;
+    }
+
+    unsigned int GetDepth() const
+    {
+      return depth_;
+    }
+
+    const CoordinateSystem3D& GetAxialGeometry() const
+    {
+      return axialGeometry_;
+    }
+
+    const CoordinateSystem3D& GetCoronalGeometry() const
+    {
+      return coronalGeometry_;
+    }
+
+    const CoordinateSystem3D& GetSagittalGeometry() const
+    {
+      return sagittalGeometry_;
+    }
+
+    const CoordinateSystem3D& GetProjectionGeometry(VolumeProjection projection) const;
+    
+    const Matrix& GetTransform() const
+    {
+      return transform_;
+    }
+
+    const Matrix& GetTransformInverse() const
+    {
+      return transformInverse_;
+    }
+
+    void SetSize(unsigned int width,
+                 unsigned int height,
+                 unsigned int depth);
+
+    // Set the geometry of the first axial slice (i.e. the one whose
+    // depth == 0)
+    void SetAxialGeometry(const CoordinateSystem3D& geometry);
+
+    void SetVoxelDimensions(double x,
+                            double y,
+                            double z);
+
+    Vector GetVoxelDimensions(VolumeProjection projection) const;
+
+    unsigned int GetProjectionWidth(VolumeProjection projection) const;
+
+    unsigned int GetProjectionHeight(VolumeProjection projection) const;
+
+    unsigned int GetProjectionDepth(VolumeProjection projection) const;
+
+    // Get the 3D position of a point in the volume, where x, y and z
+    // lie in the [0;1] range
+    Vector GetCoordinates(float x,
+                          float y,
+                          float z) const;
+
+    bool DetectProjection(VolumeProjection& projection,
+                          const Vector& planeNormal) const;
+
+    bool DetectSlice(VolumeProjection& projection,
+                     unsigned int& slice,
+                     const CoordinateSystem3D& plane) const;
+  };
+}
--- a/Framework/Volumes/VolumeReslicer.cpp	Wed May 22 09:27:21 2019 +0200
+++ b/Framework/Volumes/VolumeReslicer.cpp	Wed May 22 09:41:03 2019 +0200
@@ -230,7 +230,7 @@
       FastRowIterator(const Orthanc::ImageAccessor& slice,
                       const Extent2D& extent,
                       const CoordinateSystem3D& plane,
-                      const OrientedBoundingBox& box,
+                      const OrientedVolumeBoundingBox& box,
                       unsigned int y)
       {
         const double width = static_cast<double>(slice.GetWidth());
@@ -285,7 +285,7 @@
       const Orthanc::ImageAccessor&  slice_;
       const Extent2D&                extent_;
       const CoordinateSystem3D&      plane_;
-      const OrientedBoundingBox&     box_;
+      const OrientedVolumeBoundingBox&     box_;
       unsigned int                   x_;
       unsigned int                   y_;
       
@@ -293,7 +293,7 @@
       SlowRowIterator(const Orthanc::ImageAccessor& slice,
                       const Extent2D& extent,
                       const CoordinateSystem3D& plane,
-                      const OrientedBoundingBox& box,
+                      const OrientedVolumeBoundingBox& box,
                       unsigned int y) :
         slice_(slice),
         extent_(extent),
@@ -342,7 +342,7 @@
                              const Extent2D& extent,
                              const ImageBuffer3D& source,
                              const CoordinateSystem3D& plane,
-                             const OrientedBoundingBox& box,
+                             const OrientedVolumeBoundingBox& box,
                              float scaling,
                              float offset)
     {
@@ -386,7 +386,7 @@
                              const Extent2D& extent,
                              const ImageBuffer3D& source,
                              const CoordinateSystem3D& plane,
-                             const OrientedBoundingBox& box,
+                             const OrientedVolumeBoundingBox& box,
                              ImageInterpolation interpolation,
                              bool hasLinearFunction,
                              float scaling,
@@ -452,7 +452,7 @@
                              const Extent2D& extent,
                              const ImageBuffer3D& source,
                              const CoordinateSystem3D& plane,
-                             const OrientedBoundingBox& box,
+                             const OrientedVolumeBoundingBox& box,
                              ImageInterpolation interpolation,
                              bool hasLinearFunction,
                              float scaling,
@@ -501,7 +501,7 @@
 
   void VolumeReslicer::CheckIterators(const ImageBuffer3D& source,
                                       const CoordinateSystem3D& plane,
-                                      const OrientedBoundingBox& box) const
+                                      const OrientedVolumeBoundingBox& box) const
   {
     for (unsigned int y = 0; y < slice_->GetHeight(); y++)
     {
@@ -785,7 +785,7 @@
     // to 6 vertices. We compute the extent of the intersection
     // polygon, with respect to the coordinate system of the reslicing
     // plane.
-    OrientedBoundingBox box(geometry);
+    OrientedVolumeBoundingBox box(geometry);
 
     if (!box.ComputeExtent(extent_, plane))
     {
--- a/Framework/Volumes/VolumeReslicer.h	Wed May 22 09:27:21 2019 +0200
+++ b/Framework/Volumes/VolumeReslicer.h	Wed May 22 09:41:03 2019 +0200
@@ -22,7 +22,7 @@
 #pragma once
 
 #include "../Toolbox/Extent2D.h"
-#include "../Toolbox/OrientedBoundingBox.h"
+#include "OrientedVolumeBoundingBox.h"
 #include "ImageBuffer3D.h"
 
 namespace OrthancStone
@@ -46,7 +46,7 @@
 
     void CheckIterators(const ImageBuffer3D& source,
                         const CoordinateSystem3D& plane,
-                        const OrientedBoundingBox& box) const;
+                        const OrientedVolumeBoundingBox& box) const;
 
     void Reset();
 
--- a/Resources/CMake/OrthancStoneConfiguration.cmake	Wed May 22 09:27:21 2019 +0200
+++ b/Resources/CMake/OrthancStoneConfiguration.cmake	Wed May 22 09:41:03 2019 +0200
@@ -409,8 +409,8 @@
   ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/OneGesturePointerTracker.h
   ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/PointerTypes.h
   ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/ViewportController.cpp
-  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/ViewportController.h
-  
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/ViewportController.h 
+
   ${ORTHANC_STONE_ROOT}/Framework/Fonts/FontRenderer.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Fonts/Glyph.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Fonts/GlyphAlphabet.cpp
@@ -448,17 +448,16 @@
   ${ORTHANC_STONE_ROOT}/Framework/Toolbox/ImageGeometry.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Toolbox/LinearAlgebra.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Toolbox/MessagingToolbox.cpp
-  ${ORTHANC_STONE_ROOT}/Framework/Toolbox/OrientedBoundingBox.cpp
-
   ${ORTHANC_STONE_ROOT}/Framework/Toolbox/ParallelSlices.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Toolbox/ParallelSlicesCursor.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Toolbox/ShearWarpProjectiveTransform.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Toolbox/SlicesSorter.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Toolbox/UndoRedoStack.cpp
-  ${ORTHANC_STONE_ROOT}/Framework/Toolbox/VolumeImageGeometry.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Viewport/CairoContext.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Viewport/CairoSurface.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Volumes/ImageBuffer3D.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Volumes/OrientedVolumeBoundingBox.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Volumes/VolumeImageGeometry.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Volumes/VolumeReslicer.cpp
 
   ${ORTHANC_STONE_ROOT}/Framework/Messages/ICallable.h
--- a/Samples/Sdl/Loader.cpp	Wed May 22 09:27:21 2019 +0200
+++ b/Samples/Sdl/Loader.cpp	Wed May 22 09:41:03 2019 +0200
@@ -31,8 +31,8 @@
 #include "../../Framework/StoneInitialization.h"
 #include "../../Framework/Toolbox/GeometryToolbox.h"
 #include "../../Framework/Toolbox/SlicesSorter.h"
-#include "../../Framework/Toolbox/VolumeImageGeometry.h"
 #include "../../Framework/Volumes/ImageBuffer3D.h"
+#include "../../Framework/Volumes/VolumeImageGeometry.h"
 
 // From Orthanc framework
 #include <Core/Compression/GzipCompressor.h>