changeset 158:a053ca7fa5c6 wasm

LinearAlgebra toolbox
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 14 Feb 2018 08:58:31 +0100
parents 2309e8d86efe
children 0a73d76333db
files Framework/Layers/CircleMeasureTracker.cpp Framework/Toolbox/CoordinateSystem3D.cpp Framework/Toolbox/DicomFrameConverter.cpp Framework/Toolbox/DicomStructureSet.cpp Framework/Toolbox/GeometryToolbox.cpp Framework/Toolbox/GeometryToolbox.h Framework/Toolbox/LinearAlgebra.cpp Framework/Toolbox/LinearAlgebra.h Framework/Toolbox/OrientedBoundingBox.cpp Framework/Toolbox/ParallelSlices.cpp Framework/Toolbox/Slice.cpp Framework/Volumes/ImageBuffer3D.cpp Framework/Volumes/VolumeReslicer.cpp Framework/dev.h Resources/CMake/OrthancStoneConfiguration.cmake
diffstat 15 files changed, 360 insertions(+), 297 deletions(-) [+]
line wrap: on
line diff
--- a/Framework/Layers/CircleMeasureTracker.cpp	Fri Feb 09 16:00:29 2018 +0100
+++ b/Framework/Layers/CircleMeasureTracker.cpp	Wed Feb 14 08:58:31 2018 +0100
@@ -59,7 +59,7 @@
     double y = (y1_ + y2_) / 2.0;
 
     Vector tmp;
-    GeometryToolbox::AssignVector(tmp, x2_ - x1_, y2_ - y1_);
+    LinearAlgebra::AssignVector(tmp, x2_ - x1_, y2_ - y1_);
     double r = boost::numeric::ublas::norm_2(tmp) / 2.0;
 
     context.SetSourceColor(color_[0], color_[1], color_[2]);
--- a/Framework/Toolbox/CoordinateSystem3D.cpp	Fri Feb 09 16:00:29 2018 +0100
+++ b/Framework/Toolbox/CoordinateSystem3D.cpp	Wed Feb 14 08:58:31 2018 +0100
@@ -21,6 +21,7 @@
 
 #include "CoordinateSystem3D.h"
 
+#include "LinearAlgebra.h"
 #include "GeometryToolbox.h"
 
 #include <Core/Logging.h>
@@ -36,8 +37,8 @@
     // product of each direction cosine vector with itself shall be
     // unity."
     // http://dicom.nema.org/medical/dicom/current/output/chtml/part03/sect_C.7.6.2.html
-    if (!GeometryToolbox::IsNear(boost::numeric::ublas::norm_2(axisX_), 1.0) ||
-        !GeometryToolbox::IsNear(boost::numeric::ublas::norm_2(axisY_), 1.0))
+    if (!LinearAlgebra::IsNear(boost::numeric::ublas::norm_2(axisX_), 1.0) ||
+        !LinearAlgebra::IsNear(boost::numeric::ublas::norm_2(axisY_), 1.0))
     {
       throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
     }
@@ -47,25 +48,25 @@
     // column direction cosine vectors shall be orthogonal, i.e.,
     // their dot product shall be zero."
     // http://dicom.nema.org/medical/dicom/current/output/chtml/part03/sect_C.7.6.2.html
-    if (!GeometryToolbox::IsCloseToZero(boost::numeric::ublas::inner_prod(axisX_, axisY_)))
+    if (!LinearAlgebra::IsCloseToZero(boost::numeric::ublas::inner_prod(axisX_, axisY_)))
     {
       throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
     }
 
-    GeometryToolbox::CrossProduct(normal_, axisX_, axisY_);
+    LinearAlgebra::CrossProduct(normal_, axisX_, axisY_);
 
     d_ = -(normal_[0] * origin_[0] + normal_[1] * origin_[1] + normal_[2] * origin_[2]);
 
     // Just a sanity check, it should be useless by construction
-    assert(GeometryToolbox::IsNear(boost::numeric::ublas::norm_2(normal_), 1.0));
+    assert(LinearAlgebra::IsNear(boost::numeric::ublas::norm_2(normal_), 1.0));
   }
 
 
   void CoordinateSystem3D::SetupCanonical()
   {
-    GeometryToolbox::AssignVector(origin_, 0, 0, 0);
-    GeometryToolbox::AssignVector(axisX_, 1, 0, 0);
-    GeometryToolbox::AssignVector(axisY_, 0, 1, 0);
+    LinearAlgebra::AssignVector(origin_, 0, 0, 0);
+    LinearAlgebra::AssignVector(axisX_, 1, 0, 0);
+    LinearAlgebra::AssignVector(axisY_, 0, 1, 0);
     CheckAndComputeNormal();
   }
 
@@ -88,8 +89,8 @@
     std::string tmpOrientation = Orthanc::Toolbox::StripSpaces(imageOrientationPatient);
 
     Vector orientation;
-    if (!GeometryToolbox::ParseVector(origin_, tmpPosition) ||
-        !GeometryToolbox::ParseVector(orientation, tmpOrientation) ||
+    if (!LinearAlgebra::ParseVector(origin_, tmpPosition) ||
+        !LinearAlgebra::ParseVector(orientation, tmpOrientation) ||
         origin_.size() != 3 ||
         orientation.size() != 6)
     {
--- a/Framework/Toolbox/DicomFrameConverter.cpp	Fri Feb 09 16:00:29 2018 +0100
+++ b/Framework/Toolbox/DicomFrameConverter.cpp	Wed Feb 14 08:58:31 2018 +0100
@@ -21,7 +21,7 @@
 
 #include "DicomFrameConverter.h"
 
-#include "GeometryToolbox.h"
+#include "LinearAlgebra.h"
 
 #include <Core/Images/Image.h>
 #include <Core/Images/ImageProcessing.h>
@@ -48,8 +48,8 @@
     SetDefaultParameters();
 
     Vector c, w;
-    if (GeometryToolbox::ParseVector(c, dicom, Orthanc::DICOM_TAG_WINDOW_CENTER) &&
-        GeometryToolbox::ParseVector(w, dicom, Orthanc::DICOM_TAG_WINDOW_WIDTH) &&
+    if (LinearAlgebra::ParseVector(c, dicom, Orthanc::DICOM_TAG_WINDOW_CENTER) &&
+        LinearAlgebra::ParseVector(w, dicom, Orthanc::DICOM_TAG_WINDOW_WIDTH) &&
         c.size() > 0 && 
         w.size() > 0)
     {
--- a/Framework/Toolbox/DicomStructureSet.cpp	Fri Feb 09 16:00:29 2018 +0100
+++ b/Framework/Toolbox/DicomStructureSet.cpp	Wed Feb 14 08:58:31 2018 +0100
@@ -124,7 +124,7 @@
   {
     std::string value;
     return (dataset.GetStringValue(value, tag) &&
-            GeometryToolbox::ParseVector(target, value));
+            LinearAlgebra::ParseVector(target, value));
   }
 
 
@@ -132,9 +132,9 @@
   {
     if (hasSlice_)
     {
-      if (!GeometryToolbox::IsNear(GeometryToolbox::ProjectAlongNormal(v, geometry_.GetNormal()),
-                                   projectionAlongNormal_, 
-                                   sliceThickness_ / 2.0 /* in mm */))
+      if (!LinearAlgebra::IsNear(GeometryToolbox::ProjectAlongNormal(v, geometry_.GetNormal()),
+                                 projectionAlongNormal_, 
+                                 sliceThickness_ / 2.0 /* in mm */))
       {
         LOG(ERROR) << "This RT-STRUCT contains a point that is off the slice of its instance";
         throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);          
@@ -203,8 +203,8 @@
     
     double d = GeometryToolbox::ProjectAlongNormal(slice.GetOrigin(), geometry_.GetNormal());
 
-    return (GeometryToolbox::IsNear(d, projectionAlongNormal_,
-                                    sliceThickness_ / 2.0));
+    return (LinearAlgebra::IsNear(d, projectionAlongNormal_,
+                                  sliceThickness_ / 2.0));
   }
 
   
@@ -468,7 +468,7 @@
                      DICOM_TAG_CONTOUR_DATA));
 
         Vector points;
-        if (!GeometryToolbox::ParseVector(points, slicesData) ||
+        if (!LinearAlgebra::ParseVector(points, slicesData) ||
             points.size() != 3 * countPoints)
         {
           throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);          
@@ -497,7 +497,7 @@
     const Structure& structure = GetStructure(index);
 
     Vector center;
-    GeometryToolbox::AssignVector(center, 0, 0, 0);
+    LinearAlgebra::AssignVector(center, 0, 0, 0);
     if (structure.polygons_.empty())
     {
       return center;
@@ -614,7 +614,7 @@
     std::string s;
     Vector v;
     if (dataset.CopyToString(s, Orthanc::DICOM_TAG_SLICE_THICKNESS, false) &&
-        GeometryToolbox::ParseVector(v, s) &&
+        LinearAlgebra::ParseVector(v, s) &&
         v.size() > 0)
     {
       thickness = v[0];
@@ -657,7 +657,7 @@
     if (referencedSlices_.empty())
     {
       Vector v;
-      GeometryToolbox::AssignVector(v, 0, 0, 1);
+      LinearAlgebra::AssignVector(v, 0, 0, 1);
       return v;
     }
     else
--- a/Framework/Toolbox/GeometryToolbox.cpp	Fri Feb 09 16:00:29 2018 +0100
+++ b/Framework/Toolbox/GeometryToolbox.cpp	Wed Feb 14 08:58:31 2018 +0100
@@ -23,142 +23,20 @@
 
 #include <Core/Logging.h>
 #include <Core/OrthancException.h>
-#include <Core/Toolbox.h>
 
-#include <stdio.h>
-#include <boost/lexical_cast.hpp>
+#include <cassert>
 
 namespace OrthancStone
 {
   namespace GeometryToolbox
   {
-    void Print(const Vector& v)
-    {
-      for (size_t i = 0; i < v.size(); i++)
-      {
-        printf("%g\n", v[i]);
-        //printf("%8.2f\n", v[i]);
-      }
-      printf("\n");
-    }
-
-
-    void Print(const Matrix& m)
-    {
-      for (size_t i = 0; i < m.size1(); i++)
-      {
-        for (size_t j = 0; j < m.size2(); j++)
-        {
-          printf("%g  ", m(i,j));
-          //printf("%8.2f  ", m(i,j));
-        }
-        printf("\n");        
-      }
-      printf("\n");        
-    }
-
-
-    bool ParseVector(Vector& target,
-                     const std::string& value)
-    {
-      std::vector<std::string> items;
-      Orthanc::Toolbox::TokenizeString(items, value, '\\');
-
-      target.resize(items.size());
-
-      for (size_t i = 0; i < items.size(); i++)
-      {
-        try
-        {
-          target[i] = boost::lexical_cast<double>(Orthanc::Toolbox::StripSpaces(items[i]));
-        }
-        catch (boost::bad_lexical_cast&)
-        {
-          target.clear();
-          return false;
-        }
-      }
-
-      return true;
-    }
-
-
-    bool ParseVector(Vector& target,
-                     const Orthanc::DicomMap& dataset,
-                     const Orthanc::DicomTag& tag)
-    {
-      std::string value;
-      return (dataset.CopyToString(value, tag, false) &&
-              ParseVector(target, value));
-    }
-
-
-    void AssignVector(Vector& v,
-                      double v1,
-                      double v2)
-    {
-      v.resize(2);
-      v[0] = v1;
-      v[1] = v2;
-    }
-
-
-    void AssignVector(Vector& v,
-                      double v1,
-                      double v2,
-                      double v3)
-    {
-      v.resize(3);
-      v[0] = v1;
-      v[1] = v2;
-      v[2] = v3;
-    }
-
-
-    bool IsNear(double x,
-                double y)
-    {
-      // As most input is read as single-precision numbers, we take the
-      // epsilon machine for float32 into consideration to compare numbers
-      return IsNear(x, y, 10.0 * std::numeric_limits<float>::epsilon());
-    }
-
-
-    void NormalizeVector(Vector& u)
-    {
-      double norm = boost::numeric::ublas::norm_2(u);
-      if (!IsCloseToZero(norm))
-      {
-        u = u / norm;
-      }
-    }
-
-
-    void CrossProduct(Vector& result,
-                      const Vector& u,
-                      const Vector& v)
-    {
-      if (u.size() != 3 ||
-          v.size() != 3)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-      }
-
-      result.resize(3);
-
-      result[0] = u[1] * v[2] - u[2] * v[1];
-      result[1] = u[2] * v[0] - u[0] * v[2];
-      result[2] = u[0] * v[1] - u[1] * v[0];
-    }
-
-
     void ProjectPointOntoPlane(Vector& result,
                                const Vector& point,
                                const Vector& planeNormal,
                                const Vector& planeOrigin)
     {
       double norm =  boost::numeric::ublas::norm_2(planeNormal);
-      if (IsCloseToZero(norm))
+      if (LinearAlgebra::IsCloseToZero(norm))
       {
         // Division by zero
         throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
@@ -187,8 +65,8 @@
       double normU = boost::numeric::ublas::norm_2(u);
       double normV = boost::numeric::ublas::norm_2(v);
 
-      if (IsCloseToZero(normU) ||
-          IsCloseToZero(normV))
+      if (LinearAlgebra::IsCloseToZero(normU) ||
+          LinearAlgebra::IsCloseToZero(normV))
       {
         return false;
       }
@@ -198,12 +76,12 @@
       // The angle must be zero, so the cosine must be almost equal to
       // cos(0) == 1 (or cos(180) == -1 if allowOppositeDirection == true)
 
-      if (IsCloseToZero(cosAngle - 1.0))
+      if (LinearAlgebra::IsCloseToZero(cosAngle - 1.0))
       {
         isOpposite = false;
         return true;
       }
-      else if (IsCloseToZero(fabs(cosAngle) - 1.0))
+      else if (LinearAlgebra::IsCloseToZero(fabs(cosAngle) - 1.0))
       {
         isOpposite = true;
         return true;
@@ -237,10 +115,10 @@
 
       // The direction of the line of intersection is orthogonal to the
       // normal of both planes
-      CrossProduct(direction, normal1, normal2);
+      LinearAlgebra::CrossProduct(direction, normal1, normal2);
 
       double norm = boost::numeric::ublas::norm_2(direction);
-      if (IsCloseToZero(norm))
+      if (LinearAlgebra::IsCloseToZero(norm))
       {
         // The two planes are parallel or coincident
         return false;
@@ -250,7 +128,7 @@
       double d2 = -boost::numeric::ublas::inner_prod(normal2, origin2);
       Vector tmp = d2 * normal1 - d1 * normal2;
 
-      CrossProduct(p, tmp, direction);
+      LinearAlgebra::CrossProduct(p, tmp, direction);
       p /= norm;
 
       return true;
@@ -312,19 +190,19 @@
 
       // Create the coordinates of the rectangle
       Vector x[4];
-      AssignVector(x[0], xmin, ymin, 1.0);
-      AssignVector(x[1], xmax, ymin, 1.0);
-      AssignVector(x[2], xmax, ymax, 1.0);
-      AssignVector(x[3], xmin, ymax, 1.0);
+      LinearAlgebra::AssignVector(x[0], xmin, ymin, 1.0);
+      LinearAlgebra::AssignVector(x[1], xmax, ymin, 1.0);
+      LinearAlgebra::AssignVector(x[2], xmax, ymax, 1.0);
+      LinearAlgebra::AssignVector(x[3], xmin, ymax, 1.0);
 
       // Move to homogoneous coordinates in 2D
       Vector p;
 
       {
         Vector a, b;
-        AssignVector(a, ax, ay, 1.0);
-        AssignVector(b, bx, by, 1.0);
-        CrossProduct(p, a, b);
+        LinearAlgebra::AssignVector(a, ax, ay, 1.0);
+        LinearAlgebra::AssignVector(b, bx, by, 1.0);
+        LinearAlgebra::CrossProduct(p, a, b);
       }
 
       uint8_t c = 0;
@@ -349,10 +227,10 @@
       else
       {
         Vector a, b, e;
-        CrossProduct(e, x[i], x[(i + 1) % 4]);
-        CrossProduct(a, p, e);
-        CrossProduct(e, x[j], x[(j + 1) % 4]);
-        CrossProduct(b, p, e);
+        LinearAlgebra::CrossProduct(e, x[i], x[(i + 1) % 4]);
+        LinearAlgebra::CrossProduct(a, p, e);
+        LinearAlgebra::CrossProduct(e, x[j], x[(j + 1) % 4]);
+        LinearAlgebra::CrossProduct(b, p, e);
 
         // Go back to non-homogeneous coordinates
         x1 = a[0] / a[2];
@@ -371,7 +249,7 @@
     {
       Vector v;
 
-      if (ParseVector(v, dicom, Orthanc::DICOM_TAG_PIXEL_SPACING))
+      if (LinearAlgebra::ParseVector(v, dicom, Orthanc::DICOM_TAG_PIXEL_SPACING))
       {
         if (v.size() != 2 ||
             v[0] <= 0 ||
@@ -513,51 +391,5 @@
         return true;
       }
     }
-
-
-    void FillMatrix(Matrix& target,
-                    size_t rows,
-                    size_t columns,
-                    const double values[])
-    {
-      target.resize(rows, columns);
-
-      size_t index = 0;
-
-      for (size_t y = 0; y < rows; y++)
-      {
-        for (size_t x = 0; x < columns; x++, index++)
-        {
-          target(y, x) = values[index];
-        }
-      }
-    }
-
-
-    void FillVector(Vector& target,
-                    size_t size,
-                    const double values[])
-    {
-      target.resize(size);
-
-      for (size_t i = 0; i < size; i++)
-      {
-        target[i] = values[i];
-      }
-    }
-
-
-    void Convert(Matrix& target,
-                 const Vector& source)
-    {
-      const size_t n = source.size();
-
-      target.resize(n, 1);
-
-      for (size_t i = 0; i < n; i++)
-      {
-        target(i, 0) = source[i];
-      }      
-    }
   }
 }
--- a/Framework/Toolbox/GeometryToolbox.h	Fri Feb 09 16:00:29 2018 +0100
+++ b/Framework/Toolbox/GeometryToolbox.h	Wed Feb 14 08:58:31 2018 +0100
@@ -21,67 +21,12 @@
 
 #pragma once
 
-// Patch for ublas in Boost 1.64.0
-// https://github.com/dealii/dealii/issues/4302
-#include <boost/version.hpp>
-#if BOOST_VERSION >= 106300  // or 64, need to check
-#  include <boost/serialization/array_wrapper.hpp>
-#endif
-
-#include <Core/DicomFormat/DicomMap.h>
-
-#include <boost/numeric/ublas/matrix.hpp>
-#include <boost/numeric/ublas/vector.hpp>
-#include <cassert>
+#include "LinearAlgebra.h"
 
 namespace OrthancStone
 {
-  typedef boost::numeric::ublas::matrix<double>   Matrix;
-  typedef boost::numeric::ublas::vector<double>   Vector;
-
   namespace GeometryToolbox
   {
-    void Print(const Vector& v);
-
-    void Print(const Matrix& m);
-
-    bool ParseVector(Vector& target,
-                     const std::string& s);
-
-    bool ParseVector(Vector& target,
-                     const Orthanc::DicomMap& dataset,
-                     const Orthanc::DicomTag& tag);
-
-    void AssignVector(Vector& v,
-                      double v1,
-                      double v2);
-
-    void AssignVector(Vector& v,
-                      double v1,
-                      double v2,
-                      double v3);
-
-    inline bool IsNear(double x,
-                       double y,
-                       double threshold)
-    {
-      return fabs(x - y) < threshold;
-    }
-
-    bool IsNear(double x,
-                double y);
-
-    inline bool IsCloseToZero(double x)
-    {
-      return IsNear(x, 0.0);
-    }
-
-    void NormalizeVector(Vector& u);
-
-    void CrossProduct(Vector& result,
-                      const Vector& u,
-                      const Vector& v);
-
     void ProjectPointOntoPlane(Vector& result,
                                const Vector& point,
                                const Vector& planeNormal,
@@ -142,18 +87,6 @@
                                const Vector& origin,
                                const Vector& direction);
 
-    void FillMatrix(Matrix& target,
-                    size_t rows,
-                    size_t columns,
-                    const double values[]);
-
-    void FillVector(Vector& target,
-                    size_t size,
-                    const double values[]);
-
-    void Convert(Matrix& target,
-                 const Vector& source);
-
     inline float ComputeBilinearInterpolationInternal(float x,
                                                       float y,
                                                       float f00,    // source(x, y)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Toolbox/LinearAlgebra.cpp	Wed Feb 14 08:58:31 2018 +0100
@@ -0,0 +1,200 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 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 "LinearAlgebra.h"
+
+#include <Core/Logging.h>
+#include <Core/OrthancException.h>
+#include <Core/Toolbox.h>
+
+#include <stdio.h>
+#include <boost/lexical_cast.hpp>
+
+namespace OrthancStone
+{
+  namespace LinearAlgebra
+  {
+    void Print(const Vector& v)
+    {
+      for (size_t i = 0; i < v.size(); i++)
+      {
+        printf("%g\n", v[i]);
+        //printf("%8.2f\n", v[i]);
+      }
+      printf("\n");
+    }
+
+
+    void Print(const Matrix& m)
+    {
+      for (size_t i = 0; i < m.size1(); i++)
+      {
+        for (size_t j = 0; j < m.size2(); j++)
+        {
+          printf("%g  ", m(i,j));
+          //printf("%8.2f  ", m(i,j));
+        }
+        printf("\n");        
+      }
+      printf("\n");        
+    }
+
+
+    bool ParseVector(Vector& target,
+                     const std::string& value)
+    {
+      std::vector<std::string> items;
+      Orthanc::Toolbox::TokenizeString(items, value, '\\');
+
+      target.resize(items.size());
+
+      for (size_t i = 0; i < items.size(); i++)
+      {
+        try
+        {
+          target[i] = boost::lexical_cast<double>(Orthanc::Toolbox::StripSpaces(items[i]));
+        }
+        catch (boost::bad_lexical_cast&)
+        {
+          target.clear();
+          return false;
+        }
+      }
+
+      return true;
+    }
+
+
+    bool ParseVector(Vector& target,
+                     const Orthanc::DicomMap& dataset,
+                     const Orthanc::DicomTag& tag)
+    {
+      std::string value;
+      return (dataset.CopyToString(value, tag, false) &&
+              ParseVector(target, value));
+    }
+
+
+    void AssignVector(Vector& v,
+                      double v1,
+                      double v2)
+    {
+      v.resize(2);
+      v[0] = v1;
+      v[1] = v2;
+    }
+
+
+    void AssignVector(Vector& v,
+                      double v1,
+                      double v2,
+                      double v3)
+    {
+      v.resize(3);
+      v[0] = v1;
+      v[1] = v2;
+      v[2] = v3;
+    }
+
+
+    bool IsNear(double x,
+                double y)
+    {
+      // As most input is read as single-precision numbers, we take the
+      // epsilon machine for float32 into consideration to compare numbers
+      return IsNear(x, y, 10.0 * std::numeric_limits<float>::epsilon());
+    }
+
+
+    void NormalizeVector(Vector& u)
+    {
+      double norm = boost::numeric::ublas::norm_2(u);
+      if (!IsCloseToZero(norm))
+      {
+        u = u / norm;
+      }
+    }
+
+
+    void CrossProduct(Vector& result,
+                      const Vector& u,
+                      const Vector& v)
+    {
+      if (u.size() != 3 ||
+          v.size() != 3)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+      }
+
+      result.resize(3);
+
+      result[0] = u[1] * v[2] - u[2] * v[1];
+      result[1] = u[2] * v[0] - u[0] * v[2];
+      result[2] = u[0] * v[1] - u[1] * v[0];
+    }
+
+
+    void FillMatrix(Matrix& target,
+                    size_t rows,
+                    size_t columns,
+                    const double values[])
+    {
+      target.resize(rows, columns);
+
+      size_t index = 0;
+
+      for (size_t y = 0; y < rows; y++)
+      {
+        for (size_t x = 0; x < columns; x++, index++)
+        {
+          target(y, x) = values[index];
+        }
+      }
+    }
+
+
+    void FillVector(Vector& target,
+                    size_t size,
+                    const double values[])
+    {
+      target.resize(size);
+
+      for (size_t i = 0; i < size; i++)
+      {
+        target[i] = values[i];
+      }
+    }
+
+
+    void Convert(Matrix& target,
+                 const Vector& source)
+    {
+      const size_t n = source.size();
+
+      target.resize(n, 1);
+
+      for (size_t i = 0; i < n; i++)
+      {
+        target(i, 0) = source[i];
+      }      
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Toolbox/LinearAlgebra.h	Wed Feb 14 08:58:31 2018 +0100
@@ -0,0 +1,96 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 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
+
+// Patch for ublas in Boost 1.64.0
+// https://github.com/dealii/dealii/issues/4302
+#include <boost/version.hpp>
+#if BOOST_VERSION >= 106300  // or 64, need to check
+#  include <boost/serialization/array_wrapper.hpp>
+#endif
+
+#include <Core/DicomFormat/DicomMap.h>
+
+#include <boost/numeric/ublas/matrix.hpp>
+#include <boost/numeric/ublas/vector.hpp>
+
+namespace OrthancStone
+{
+  typedef boost::numeric::ublas::matrix<double>   Matrix;
+  typedef boost::numeric::ublas::vector<double>   Vector;
+
+  namespace LinearAlgebra
+  {
+    void Print(const Vector& v);
+
+    void Print(const Matrix& m);
+
+    bool ParseVector(Vector& target,
+                     const std::string& s);
+
+    bool ParseVector(Vector& target,
+                     const Orthanc::DicomMap& dataset,
+                     const Orthanc::DicomTag& tag);
+
+    void AssignVector(Vector& v,
+                      double v1,
+                      double v2);
+
+    void AssignVector(Vector& v,
+                      double v1,
+                      double v2,
+                      double v3);
+
+    inline bool IsNear(double x,
+                       double y,
+                       double threshold)
+    {
+      return fabs(x - y) < threshold;
+    }
+
+    bool IsNear(double x,
+                double y);
+
+    inline bool IsCloseToZero(double x)
+    {
+      return IsNear(x, 0.0);
+    }
+
+    void NormalizeVector(Vector& u);
+
+    void CrossProduct(Vector& result,
+                      const Vector& u,
+                      const Vector& v);
+    
+    void FillMatrix(Matrix& target,
+                    size_t rows,
+                    size_t columns,
+                    const double values[]);
+
+    void FillVector(Vector& target,
+                    size_t size,
+                    const double values[]);
+
+    void Convert(Matrix& target,
+                 const Vector& source);
+  };
+}
--- a/Framework/Toolbox/OrientedBoundingBox.cpp	Fri Feb 09 16:00:29 2018 +0100
+++ b/Framework/Toolbox/OrientedBoundingBox.cpp	Wed Feb 14 08:58:31 2018 +0100
@@ -237,7 +237,7 @@
     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;
 
-    GeometryToolbox::AssignVector(target, x, y, z);
+    LinearAlgebra::AssignVector(target, x, y, z);
   }
 
 
--- a/Framework/Toolbox/ParallelSlices.cpp	Fri Feb 09 16:00:29 2018 +0100
+++ b/Framework/Toolbox/ParallelSlices.cpp	Wed Feb 14 08:58:31 2018 +0100
@@ -28,7 +28,7 @@
 {
   ParallelSlices::ParallelSlices()
   {
-    GeometryToolbox::AssignVector(normal_, 0, 0, 1);
+    LinearAlgebra::AssignVector(normal_, 0, 0, 1);
   }
 
 
--- a/Framework/Toolbox/Slice.cpp	Fri Feb 09 16:00:29 2018 +0100
+++ b/Framework/Toolbox/Slice.cpp	Wed Feb 14 08:58:31 2018 +0100
@@ -87,7 +87,7 @@
       return false;
     }
 
-    if (!GeometryToolbox::IsCloseToZero(offset0))
+    if (!LinearAlgebra::IsCloseToZero(offset0))
     {
       LOG(ERROR) << "Invalid syntax";
       return false;
@@ -304,9 +304,9 @@
     return (GeometryToolbox::IsParallelOrOpposite(opposite,
                                                   GetGeometry().GetNormal(),
                                                   plane.GetNormal()) &&
-            GeometryToolbox::IsNear(GetGeometry().ProjectAlongNormal(GetGeometry().GetOrigin()),
-                                    GetGeometry().ProjectAlongNormal(plane.GetOrigin()),
-                                    thickness_ / 2.0));
+            LinearAlgebra::IsNear(GetGeometry().ProjectAlongNormal(GetGeometry().GetOrigin()),
+                                  GetGeometry().ProjectAlongNormal(plane.GetOrigin()),
+                                  thickness_ / 2.0));
   }
 
   
--- a/Framework/Volumes/ImageBuffer3D.cpp	Fri Feb 09 16:00:29 2018 +0100
+++ b/Framework/Volumes/ImageBuffer3D.cpp	Wed Feb 14 08:58:31 2018 +0100
@@ -122,7 +122,7 @@
     computeRange_(computeRange),
     hasRange_(false)
   {
-    GeometryToolbox::AssignVector(voxelDimensions_, 1, 1, 1);
+    LinearAlgebra::AssignVector(voxelDimensions_, 1, 1, 1);
 
     LOG(INFO) << "Created an image of "
               << (GetEstimatedMemorySize() / (1024ll * 1024ll)) << "MB";
@@ -153,7 +153,7 @@
     }
 
     {
-      GeometryToolbox::AssignVector(voxelDimensions_, x, y, z);
+      LinearAlgebra::AssignVector(voxelDimensions_, x, y, z);
     }
   }
 
@@ -168,11 +168,11 @@
         break;
 
       case VolumeProjection_Coronal:
-        GeometryToolbox::AssignVector(result, voxelDimensions_[0], voxelDimensions_[2], voxelDimensions_[1]);
+        LinearAlgebra::AssignVector(result, voxelDimensions_[0], voxelDimensions_[2], voxelDimensions_[1]);
         break;
 
       case VolumeProjection_Sagittal:
-        GeometryToolbox::AssignVector(result, voxelDimensions_[1], voxelDimensions_[2], voxelDimensions_[0]);
+        LinearAlgebra::AssignVector(result, voxelDimensions_[1], voxelDimensions_[2], voxelDimensions_[0]);
         break;
 
       default:
--- a/Framework/Volumes/VolumeReslicer.cpp	Fri Feb 09 16:00:29 2018 +0100
+++ b/Framework/Volumes/VolumeReslicer.cpp	Wed Feb 14 08:58:31 2018 +0100
@@ -849,7 +849,7 @@
         slow.GetNearestCoordinates(qx, qy, qz);
 
         Vector d;
-        GeometryToolbox::AssignVector(d, px - qx, py - qy, pz - qz);
+        LinearAlgebra::AssignVector(d, px - qx, py - qy, pz - qz);
         double norm = boost::numeric::ublas::norm_2(d);
         if (norm > 0.0001)
         {
--- a/Framework/dev.h	Fri Feb 09 16:00:29 2018 +0100
+++ b/Framework/dev.h	Wed Feb 14 08:58:31 2018 +0100
@@ -86,8 +86,8 @@
         return false;
       }
 
-      if (!GeometryToolbox::IsNear(a.GetPixelSpacingX(), b.GetPixelSpacingX()) ||
-          !GeometryToolbox::IsNear(a.GetPixelSpacingY(), b.GetPixelSpacingY()))
+      if (!LinearAlgebra::IsNear(a.GetPixelSpacingX(), b.GetPixelSpacingX()) ||
+          !LinearAlgebra::IsNear(a.GetPixelSpacingY(), b.GetPixelSpacingY()))
       {
         LOG(ERROR) << "The pixel spacing of the slices change across the volume image";
         return false;
@@ -138,8 +138,8 @@
 
       for (size_t i = 1; i < loader.GetSliceCount(); i++)
       {
-        if (!GeometryToolbox::IsNear(spacingZ, GetDistance(loader.GetSlice(i - 1), loader.GetSlice(i)),
-                                     0.001 /* this is expressed in mm */))
+        if (!LinearAlgebra::IsNear(spacingZ, GetDistance(loader.GetSlice(i - 1), loader.GetSlice(i)),
+                                   0.001 /* this is expressed in mm */))
         {
           LOG(ERROR) << "The distance between successive slices is not constant in a volume image";
           SlicedVolumeBase::NotifyGeometryError();
--- a/Resources/CMake/OrthancStoneConfiguration.cmake	Fri Feb 09 16:00:29 2018 +0100
+++ b/Resources/CMake/OrthancStoneConfiguration.cmake	Wed Feb 14 08:58:31 2018 +0100
@@ -181,6 +181,7 @@
   ${ORTHANC_STONE_DIR}/Framework/Toolbox/DownloadStack.cpp
   ${ORTHANC_STONE_DIR}/Framework/Toolbox/Extent2D.cpp
   ${ORTHANC_STONE_DIR}/Framework/Toolbox/GeometryToolbox.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Toolbox/LinearAlgebra.cpp
   ${ORTHANC_STONE_DIR}/Framework/Toolbox/MessagingToolbox.cpp
   ${ORTHANC_STONE_DIR}/Framework/Toolbox/OrientedBoundingBox.cpp
   ${ORTHANC_STONE_DIR}/Framework/Toolbox/OrthancSlicesLoader.cpp
@@ -195,9 +196,9 @@
   ${ORTHANC_STONE_DIR}/Framework/Viewport/WidgetViewport.cpp
   ${ORTHANC_STONE_DIR}/Framework/Volumes/ImageBuffer3D.cpp
   ${ORTHANC_STONE_DIR}/Framework/Volumes/SlicedVolumeBase.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Volumes/StructureSetLoader.cpp
   ${ORTHANC_STONE_DIR}/Framework/Volumes/VolumeLoaderBase.cpp
   ${ORTHANC_STONE_DIR}/Framework/Volumes/VolumeReslicer.cpp
-  ${ORTHANC_STONE_DIR}/Framework/Volumes/StructureSetLoader.cpp
   ${ORTHANC_STONE_DIR}/Framework/Widgets/CairoWidget.cpp
   ${ORTHANC_STONE_DIR}/Framework/Widgets/EmptyWidget.cpp
   ${ORTHANC_STONE_DIR}/Framework/Widgets/LayerWidget.cpp