changeset 949:32eaf4929b08 toa2019081301

OrthancMultiframeVolumeLoader and OrthancSeriesVolumeProgressiveLoader now implement IGeometryProvider so that the geometry reference can be switched (CT or DOSE, for instance) + VolumeImageGeometry::SetSize renamed to VolumeImageGeometry::SetSizeInVoxels + prevent text layer update if text or properties do not change + a few stream operator<< for debug (Vector, Matrix,...) + fixed memory access aligment issues in ImageBuffer3D::ExtractSagittalSlice + fix for wrong screen Y offset of mpr slices in DicomVolumeImageMPRSlicer.
author Benjamin Golinvaux <bgo@osimis.io>
date Tue, 13 Aug 2019 16:01:05 +0200
parents 141cc19e6b7d
children e8c81a5582d6
files Framework/Loaders/OrthancMultiframeVolumeLoader.cpp Framework/Loaders/OrthancMultiframeVolumeLoader.h Framework/Loaders/OrthancSeriesVolumeProgressiveLoader.cpp Framework/Loaders/OrthancSeriesVolumeProgressiveLoader.h Framework/Scene2D/OpenGLCompositor.cpp Framework/Scene2D/TextSceneLayer.cpp Framework/Scene2DViewport/LayerHolder.cpp Framework/Scene2DViewport/LayerHolder.h Framework/StoneException.h Framework/Toolbox/CoordinateSystem3D.cpp Framework/Toolbox/CoordinateSystem3D.h Framework/Toolbox/DicomInstanceParameters.cpp Framework/Toolbox/DicomStructureSet.cpp Framework/Toolbox/LinearAlgebra.cpp Framework/Toolbox/LinearAlgebra.h Framework/Volumes/DicomVolumeImage.cpp Framework/Volumes/DicomVolumeImage.h Framework/Volumes/DicomVolumeImageMPRSlicer.cpp Framework/Volumes/ImageBuffer3D.cpp Framework/Volumes/VolumeImageGeometry.cpp Framework/Volumes/VolumeImageGeometry.h
diffstat 21 files changed, 256 insertions(+), 32 deletions(-) [+]
line wrap: on
line diff
--- a/Framework/Loaders/OrthancMultiframeVolumeLoader.cpp	Sat Aug 10 13:13:11 2019 +0200
+++ b/Framework/Loaders/OrthancMultiframeVolumeLoader.cpp	Tue Aug 13 16:01:05 2019 +0200
@@ -224,7 +224,7 @@
 
     {
       VolumeImageGeometry geometry;
-      geometry.SetSize(width, height, depth);
+      geometry.SetSizeInVoxels(width, height, depth);
       geometry.SetAxialGeometry(parameters.GetGeometry());
       geometry.SetVoxelDimensions(parameters.GetPixelSpacingX(),
                                   parameters.GetPixelSpacingY(), spacingZ);
@@ -235,6 +235,8 @@
 
     ScheduleFrameDownloads();
 
+
+
     BroadcastMessage(DicomVolumeImage::GeometryReadyMessage(*volume_));
   }
 
@@ -319,7 +321,16 @@
     pixelDataLoaded_ = true;
     BroadcastMessage(DicomVolumeImage::ContentUpdatedMessage(*volume_));
   }
+  
+  bool OrthancMultiframeVolumeLoader::HasGeometry() const
+  {
+    return volume_->HasGeometry();
+  }
 
+  const OrthancStone::VolumeImageGeometry& OrthancMultiframeVolumeLoader::GetImageGeometry() const
+  {
+    return volume_->GetGeometry();
+  }
 
   OrthancMultiframeVolumeLoader::OrthancMultiframeVolumeLoader(boost::shared_ptr<DicomVolumeImage> volume,
                                                                IOracle& oracle,
--- a/Framework/Loaders/OrthancMultiframeVolumeLoader.h	Sat Aug 10 13:13:11 2019 +0200
+++ b/Framework/Loaders/OrthancMultiframeVolumeLoader.h	Tue Aug 13 16:01:05 2019 +0200
@@ -30,7 +30,8 @@
 {
   class OrthancMultiframeVolumeLoader :
     public LoaderStateMachine,
-    public IObservable
+    public IObservable,
+    public IGeometryProvider
   {
   private:
     class LoadRTDoseGeometry;
@@ -56,6 +57,9 @@
 
     void SetUncompressedPixelData(const std::string& pixelData);
 
+    virtual bool HasGeometry() const ORTHANC_OVERRIDE;
+    virtual const VolumeImageGeometry& GetImageGeometry() const ORTHANC_OVERRIDE;
+
   public:
     OrthancMultiframeVolumeLoader(boost::shared_ptr<DicomVolumeImage> volume,
                                   IOracle& oracle,
--- a/Framework/Loaders/OrthancSeriesVolumeProgressiveLoader.cpp	Sat Aug 10 13:13:11 2019 +0200
+++ b/Framework/Loaders/OrthancSeriesVolumeProgressiveLoader.cpp	Tue Aug 13 16:01:05 2019 +0200
@@ -190,7 +190,7 @@
       const DicomInstanceParameters& parameters = *slices_[0];
 
       geometry_.reset(new VolumeImageGeometry);
-      geometry_->SetSize(parameters.GetImageInformation().GetWidth(),
+      geometry_->SetSizeInVoxels(parameters.GetImageInformation().GetWidth(),
                          parameters.GetImageInformation().GetHeight(),
                          static_cast<unsigned int>(slices.GetSlicesCount()));
       geometry_->SetAxialGeometry(slices.GetSliceGeometry(0));
--- a/Framework/Loaders/OrthancSeriesVolumeProgressiveLoader.h	Sat Aug 10 13:13:11 2019 +0200
+++ b/Framework/Loaders/OrthancSeriesVolumeProgressiveLoader.h	Tue Aug 13 16:01:05 2019 +0200
@@ -44,7 +44,8 @@
   class OrthancSeriesVolumeProgressiveLoader : 
     public IObserver,
     public IObservable,
-    public IVolumeSlicer
+    public IVolumeSlicer,
+    public IGeometryProvider
   {
   private:
     static const unsigned int LOW_QUALITY = 0;
@@ -78,12 +79,12 @@
 
       void ComputeGeometry(SlicesSorter& slices);
 
-      bool HasGeometry() const
+      virtual bool HasGeometry() const
       {
         return geometry_.get() != NULL;
       }
 
-      const VolumeImageGeometry& GetImageGeometry() const;
+      virtual const VolumeImageGeometry& GetImageGeometry() const;
 
       const DicomInstanceParameters& GetSliceParameters(size_t index) const;
 
@@ -139,7 +140,7 @@
     subscribing, for instance if they are created or listening only AFTER the
     "geometry loaded" message is broadcast 
     */
-    bool HasGeometry() const
+    bool HasGeometry() const ORTHANC_OVERRIDE
     {
       return seriesGeometry_.HasGeometry();
     }
@@ -147,7 +148,7 @@
     /**
     Same remark as HasGeometry
     */
-    const VolumeImageGeometry& GetImageGeometry() const
+    const VolumeImageGeometry& GetImageGeometry() const ORTHANC_OVERRIDE
     {
       return seriesGeometry_.GetImageGeometry();
     }
--- a/Framework/Scene2D/OpenGLCompositor.cpp	Sat Aug 10 13:13:11 2019 +0200
+++ b/Framework/Scene2D/OpenGLCompositor.cpp	Tue Aug 13 16:01:05 2019 +0200
@@ -107,6 +107,7 @@
         const Font* font = GetFont(l.GetFontIndex());
         if (font == NULL)
         {
+          LOG(WARNING) << "There is no font at index " << l.GetFontIndex();
           return NULL;
         }
         else
--- a/Framework/Scene2D/TextSceneLayer.cpp	Sat Aug 10 13:13:11 2019 +0200
+++ b/Framework/Scene2D/TextSceneLayer.cpp	Tue Aug 13 16:01:05 2019 +0200
@@ -50,32 +50,47 @@
   void TextSceneLayer::SetPosition(double x,
                                    double y)
   {
-    x_ = x;
-    y_ = y;
-    revision_ ++;
+    if (x != x_ || y != y_)
+    {
+      x_ = x;
+      y_ = y;
+      revision_++;
+    }
   }
 
   void TextSceneLayer::SetText(const std::string& utf8)
   {
-    utf8_ = utf8;
-    revision_ ++;
+    if (utf8 != utf8_)
+    {
+      utf8_ = utf8;
+      revision_++;
+    }
   }
 
   void TextSceneLayer::SetFontIndex(size_t fontIndex)
   {
-    fontIndex_ = fontIndex;
-    revision_ ++;
+    if (fontIndex != fontIndex_)
+    {
+      fontIndex_ = fontIndex;
+      revision_++;
+    }
   }
 
   void TextSceneLayer::SetAnchor(BitmapAnchor anchor)
   {
-    anchor_ = anchor;
-    revision_ ++;
+    if (anchor != anchor_)
+    {
+      anchor_ = anchor;
+      revision_++;
+    }
   }
 
   void TextSceneLayer::SetBorder(unsigned int border)
   {
-    border_ = border;
-    revision_ ++;
+    if (border != border_)
+    {
+      border_ = border;
+      revision_++;
+    }
   }
 }
--- a/Framework/Scene2DViewport/LayerHolder.cpp	Sat Aug 10 13:13:11 2019 +0200
+++ b/Framework/Scene2DViewport/LayerHolder.cpp	Tue Aug 13 16:01:05 2019 +0200
@@ -81,6 +81,12 @@
     return controller->GetScene();
   }
 
+  void LayerHolder::DeleteLayersIfNeeded()
+  {
+    if (baseLayerIndex_ != -1)
+      DeleteLayers();
+  }
+  
   void LayerHolder::DeleteLayers()
   {
     for (int i = 0; i < textLayerCount_ + polylineLayerCount_; ++i)
--- a/Framework/Scene2DViewport/LayerHolder.h	Sat Aug 10 13:13:11 2019 +0200
+++ b/Framework/Scene2DViewport/LayerHolder.h	Tue Aug 13 16:01:05 2019 +0200
@@ -69,6 +69,11 @@
     void DeleteLayers();
 
     /**
+    This removes the layers from the scene if they are already created
+    */
+    void DeleteLayersIfNeeded();
+
+    /**
     Please note that the returned pointer belongs to the scene.Don't you dare
     storing or deleting it, you fool!
 
--- a/Framework/StoneException.h	Sat Aug 10 13:13:11 2019 +0200
+++ b/Framework/StoneException.h	Tue Aug 13 16:01:05 2019 +0200
@@ -22,6 +22,9 @@
 #pragma once
 
 #include "Core/OrthancException.h"
+#include "Toolbox/LinearAlgebra.h"
+
+#include <iostream>
 #include <boost/lexical_cast.hpp>
 
 namespace OrthancStone
@@ -126,7 +129,6 @@
       return boost::lexical_cast<std::string>(applicationErrorCode_).c_str();
     }
   };
-
 }
 
 // See https://isocpp.org/wiki/faq/misc-technical-issues#macros-with-multi-stmts
@@ -151,6 +153,18 @@
 # define GET_ORTHANC_ASSERT(_1,_2,NAME,...) NAME
 # define ORTHANC_ASSERT(...) ORTHANC_EXPAND(GET_ORTHANC_ASSERT(__VA_ARGS__, ORTHANC_ASSERT2, ORTHANC_ASSERT1, UNUSED)(__VA_ARGS__))
 
+
+
+
+
+
+
+
+
+
+
+
+
 /*
 Explanation:
 
--- a/Framework/Toolbox/CoordinateSystem3D.cpp	Sat Aug 10 13:13:11 2019 +0200
+++ b/Framework/Toolbox/CoordinateSystem3D.cpp	Tue Aug 13 16:01:05 2019 +0200
@@ -220,4 +220,13 @@
       return false;
     }
   }
+
+  std::ostream& operator<< (std::ostream& s, const CoordinateSystem3D& that)
+  {
+    s << "origin: " << that.origin_ << " normal: " << that.normal_ 
+      << " axisX: " << that.axisX_ << " axisY: " << that.axisY_ 
+      << " D: " << that.d_;
+    return s;
+  }
+
 }
--- a/Framework/Toolbox/CoordinateSystem3D.h	Sat Aug 10 13:13:11 2019 +0200
+++ b/Framework/Toolbox/CoordinateSystem3D.h	Tue Aug 13 16:01:05 2019 +0200
@@ -25,6 +25,8 @@
 
 #include <Plugins/Samples/Common/IDicomDataset.h>
 
+#include <iosfwd>
+
 namespace OrthancStone
 {
   // Geometry of a 3D plane
@@ -52,6 +54,8 @@
       SetupCanonical();
     }
 
+    friend std::ostream& operator<< (std::ostream& s, const CoordinateSystem3D& that);
+
     CoordinateSystem3D(const Vector& origin,
                        const Vector& axisX,
                        const Vector& axisY);
--- a/Framework/Toolbox/DicomInstanceParameters.cpp	Sat Aug 10 13:13:11 2019 +0200
+++ b/Framework/Toolbox/DicomInstanceParameters.cpp	Tue Aug 13 16:01:05 2019 +0200
@@ -44,7 +44,7 @@
       if (dicom.CopyToString(increment, Orthanc::DICOM_TAG_FRAME_INCREMENT_POINTER, false))
       {
         Orthanc::Toolbox::ToUpperCase(increment);
-        if (increment != "3004,000C")  // This is the "Grid Frame Offset Vector" tag
+        if (increment != "3004,000C")  // This is the "Grid Frame Offset Vector" tag (DICOM_TAG_GRID_FRAME_OFFSET_VECTOR)
         {
           LOG(ERROR) << "RT-DOSE: Bad value for the \"FrameIncrementPointer\" tag";
           return;
--- a/Framework/Toolbox/DicomStructureSet.cpp	Sat Aug 10 13:13:11 2019 +0200
+++ b/Framework/Toolbox/DicomStructureSet.cpp	Tue Aug 13 16:01:05 2019 +0200
@@ -362,7 +362,6 @@
     return structures_[index];
   }
 
-
   DicomStructureSet::DicomStructureSet(const OrthancPlugins::FullOrthancDataset& tags)
   {
     OrthancPlugins::DicomDatasetReader reader(tags);
@@ -388,7 +387,7 @@
       structures_[i].name_ = reader.GetStringValue
         (OrthancPlugins::DicomPath(DICOM_TAG_STRUCTURE_SET_ROI_SEQUENCE, i,
                                    DICOM_TAG_ROI_NAME),
-         "No interpretation");
+         "No name");
 
       Vector color;
       if (ParseVector(color, tags, OrthancPlugins::DicomPath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, i,
--- a/Framework/Toolbox/LinearAlgebra.cpp	Sat Aug 10 13:13:11 2019 +0200
+++ b/Framework/Toolbox/LinearAlgebra.cpp	Tue Aug 13 16:01:05 2019 +0200
@@ -21,14 +21,18 @@
 
 #include "LinearAlgebra.h"
 
+#include "../StoneException.h"
+
 #include <Core/Logging.h>
 #include <Core/OrthancException.h>
 #include <Core/Toolbox.h>
 
-#include <stdio.h>
 #include <boost/lexical_cast.hpp>
 #include <boost/numeric/ublas/lu.hpp>
 
+#include <stdio.h>
+#include <iostream>
+
 namespace OrthancStone
 {
   namespace LinearAlgebra
@@ -652,4 +656,38 @@
       return m;
     }
   }
+
+  std::ostream& operator<<(std::ostream& s, const Vector& vec)
+  {
+    s << "(";
+    for (size_t i = 0; i < vec.size(); ++i)
+    {
+      s << vec(i);
+      if (i < (vec.size() - 1))
+        s << ", ";
+    }
+    s << ")";
+    return s;
+  }
+
+  std::ostream& operator<<(std::ostream& s, const Matrix& m)
+  {
+    ORTHANC_ASSERT(m.size1() == m.size2());
+    s << "(";
+    for (size_t i = 0; i < m.size1(); ++i)
+    {
+      s << "(";
+      for (size_t j = 0; j < m.size2(); ++j)
+      {
+        s << m(i,j);
+        if (j < (m.size2() - 1))
+          s << ", ";
+      }
+      s << ")";
+      if (i < (m.size1() - 1))
+        s << ", ";
+    }
+    s << ")";
+    return s;
+  }
 }
--- a/Framework/Toolbox/LinearAlgebra.h	Sat Aug 10 13:13:11 2019 +0200
+++ b/Framework/Toolbox/LinearAlgebra.h	Tue Aug 13 16:01:05 2019 +0200
@@ -38,6 +38,10 @@
   typedef boost::numeric::ublas::matrix<double>   Matrix;
   typedef boost::numeric::ublas::vector<double>   Vector;
 
+  // logs, debugging...
+  std::ostream& operator<<(std::ostream& s, const Vector& vec);
+  std::ostream& operator<<(std::ostream& s, const Matrix& m);
+
   namespace LinearAlgebra
   {
     void Print(const Vector& v);
--- a/Framework/Volumes/DicomVolumeImage.cpp	Sat Aug 10 13:13:11 2019 +0200
+++ b/Framework/Volumes/DicomVolumeImage.cpp	Tue Aug 13 16:01:05 2019 +0200
@@ -21,8 +21,11 @@
 
 #include "DicomVolumeImage.h"
 
+#include "../StoneException.h"
+
 #include <Core/OrthancException.h>
 
+
 namespace OrthancStone
 {
   void DicomVolumeImage::CheckHasGeometry() const
--- a/Framework/Volumes/DicomVolumeImage.h	Sat Aug 10 13:13:11 2019 +0200
+++ b/Framework/Volumes/DicomVolumeImage.h	Tue Aug 13 16:01:05 2019 +0200
@@ -28,6 +28,14 @@
 
 namespace OrthancStone
 {
+  class IGeometryProvider
+  {
+  public:
+    virtual ~IGeometryProvider() {}
+    virtual bool HasGeometry() const = 0;
+    virtual const VolumeImageGeometry& GetImageGeometry() const = 0;
+  };
+
   /**
   This class combines a 3D image buffer, a 3D volume geometry and
   information about the DICOM parameters of the series.
--- a/Framework/Volumes/DicomVolumeImageMPRSlicer.cpp	Sat Aug 10 13:13:11 2019 +0200
+++ b/Framework/Volumes/DicomVolumeImageMPRSlicer.cpp	Tue Aug 13 16:01:05 2019 +0200
@@ -21,7 +21,11 @@
 
 #include "DicomVolumeImageMPRSlicer.h"
 
+#include "../StoneException.h"
+
 #include <Core/OrthancException.h>
+//#include <Core/Images/PngWriter.h>
+#include <Core/Images/JpegWriter.h>
 
 namespace OrthancStone
 {
@@ -76,14 +80,76 @@
       ImageBuffer3D::SliceReader reader(volume_.GetPixelData(), projection_, sliceIndex_);
       texture.reset(dynamic_cast<TextureBaseSceneLayer*>
                     (configurator->CreateTextureFromDicom(reader.GetAccessor(), parameters)));
+
+      // <DEBUG-BLOCK>
+#if 0
+      Orthanc::JpegWriter writer;
+      writer.SetQuality(60);
+      static int index = 0;
+      std::string filePath = "C:\\temp\\sliceReader_P";
+      filePath += boost::lexical_cast<std::string>(projection_);
+      filePath += "_I";
+      filePath += boost::lexical_cast<std::string>(index);
+      filePath += ".jpg";
+      index++;
+      writer.WriteToFile(filePath, reader.GetAccessor());
+#endif
+      // <END-OF-DEBUG-BLOCK>
     }
-
+    
     const CoordinateSystem3D& system = volume_.GetGeometry().GetProjectionGeometry(projection_);
       
     double x0, y0, x1, y1;
     cuttingPlane.ProjectPoint(x0, y0, system.GetOrigin());
     cuttingPlane.ProjectPoint(x1, y1, system.GetOrigin() + system.GetAxisX());
+
+    // <DEBUG-BLOCK>
+#if 0
+    {
+      LOG(ERROR) << "+----------------------------------------------------+";
+      LOG(ERROR) << "| DicomVolumeImageMPRSlicer::Slice::CreateSceneLayer |";
+      LOG(ERROR) << "+----------------------------------------------------+";
+      std::string projectionString;
+      switch (projection_)
+      {
+      case VolumeProjection_Coronal:
+        projectionString = "CORONAL";
+        break;
+      case VolumeProjection_Axial:
+        projectionString = "CORONAL";
+        break;
+      case VolumeProjection_Sagittal:
+        projectionString = "SAGITTAL";
+        break;
+      default:
+        ORTHANC_ASSERT(false);
+      }
+      if(volume_.GetGeometry().GetDepth() == 200)
+        LOG(ERROR) << "| CT     IMAGE 512x512 with projection " << projectionString;
+      else
+        LOG(ERROR) << "| RTDOSE IMAGE NNNxNNN with projection " << projectionString;
+      LOG(ERROR) << "+----------------------------------------------------+";
+      LOG(ERROR) << "| cuttingPlane = " << cuttingPlane;
+      LOG(ERROR) << "| point to project = " << system.GetOrigin();
+      LOG(ERROR) << "| result = x0: " << x0 << " y0: " << y0;
+      LOG(ERROR) << "+----------------------- END ------------------------+";
+    }
+#endif
+    // <END-OF-DEBUG-BLOCK>
+
+#if 1 // BGO 2019-08-13
+    // The sagittal coordinate system has a Y vector going down. The displayed
+    // image (scene coords) has a Y vector pointing upwards (towards the patient 
+    // coord Z index)
+    // we need to flip the Y local coordinates to get the scene-coord offset.
+    // TODO: this is quite ugly. Isn't there a better way?
+    if(projection_ == VolumeProjection_Sagittal)
+      texture->SetOrigin(x0, -y0);
+    else
+      texture->SetOrigin(x0, y0);
+#else
     texture->SetOrigin(x0, y0);
+#endif
 
     double dx = x1 - x0;
     double dy = y1 - y0;
@@ -96,6 +162,19 @@
     Vector tmp = volume_.GetGeometry().GetVoxelDimensions(projection_);
     texture->SetPixelSpacing(tmp[0], tmp[1]);
 
+    // <DEBUG-BLOCK>
+    {
+      //using std::endl;
+      //std::stringstream ss;
+      //ss << "DicomVolumeImageMPRSlicer::Slice::CreateSceneLayer | cuttingPlane = " << cuttingPlane << " | projection_ = " << projection_ << endl;
+      //ss << "volume_.GetGeometry().GetProjectionGeometry(projection_) = " << system << endl;
+      //ss << "cuttingPlane.ProjectPoint(x0, y0, system.GetOrigin()); --> | x0 = " << x0 << " | y0 = " << y0 << "| x1 = " << x1 << " | y1 = " << y1 << endl;
+      //ss << "volume_.GetGeometry() = " << volume_.GetGeometry() << endl;
+      //ss << "volume_.GetGeometry() = " << volume_.GetGeometry() << endl;
+      //LOG(ERROR) << ss.str();
+    }
+    // <END-OF-DEBUG-BLOCK>
+
     return texture.release();
   }
 
--- a/Framework/Volumes/ImageBuffer3D.cpp	Sat Aug 10 13:13:11 2019 +0200
+++ b/Framework/Volumes/ImageBuffer3D.cpp	Tue Aug 13 16:01:05 2019 +0200
@@ -75,12 +75,14 @@
 
   Orthanc::Image*  ImageBuffer3D::ExtractSagittalSlice(unsigned int slice) const
   {
+    //LOG(TRACE) << "ImageBuffer3D::ExtractSagittalSlice this= " << std::hex << this << std::dec << " width_ = " << width_ << " height_ = " << height_ << " depth_ = " << depth_ << " slice = " << slice;
     if (slice >= width_)
     {
       throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
     }
 
     std::auto_ptr<Orthanc::Image> result(new Orthanc::Image(format_, height_, depth_, false));
+    //LOG(TRACE) << "ImageBuffer3D::ExtractSagittalSlice result will be an image of WIDTH = " << height_ << " and HEIGHT = " << depth_;
 
     unsigned int bytesPerPixel = Orthanc::GetBytesPerPixel(format_);
 
@@ -91,10 +93,10 @@
 
       for (unsigned int y = 0; y < height_; y++)
       {
-        const void* source = (reinterpret_cast<const uint8_t*>(image_.GetConstRow(y + z * height_)) +
-                              bytesPerPixel * slice);
-
-        memcpy(target, source, bytesPerPixel);
+        const void* source = (reinterpret_cast<const uint8_t*>(image_.GetConstRow(y + z * height_)) + bytesPerPixel * slice);
+        const uint8_t* byteSrc = reinterpret_cast<const uint8_t*>(source);
+        for (size_t byte = 0; byte < bytesPerPixel; ++byte)
+          target[byte] = byteSrc[byte];
         target += bytesPerPixel;
       }
     }
--- a/Framework/Volumes/VolumeImageGeometry.cpp	Sat Aug 10 13:13:11 2019 +0200
+++ b/Framework/Volumes/VolumeImageGeometry.cpp	Tue Aug 13 16:01:05 2019 +0200
@@ -46,6 +46,8 @@
                                                 -0.5 * voxelDimensions_[1]) -
       0.5 * voxelDimensions_[2] * axialGeometry_.GetNormal());
 
+    LOG(TRACE) << "VolumeImageGeometry::Invalidate() origin = " << origin(0) << "," << origin(1) << "," << origin(2) << " | width_ = " << width_ << " | height_ = " << height_ << " | depth_ = " << depth_;
+
     Vector scaling;
     
     if (width_ == 0 ||
@@ -80,7 +82,7 @@
   }
 
 
-  void VolumeImageGeometry::SetSize(unsigned int width,
+  void VolumeImageGeometry::SetSizeInVoxels(unsigned int width,
                                     unsigned int height,
                                     unsigned int depth)
   {
@@ -323,4 +325,19 @@
 
     return plane;
   }
+
+  std::ostream& operator<<(std::ostream& s, const VolumeImageGeometry& v)
+  {
+    s << "width: " << v.width_ << " height: " << v.height_
+      << " depth: "             << v.depth_
+      << " axialGeometry: "     << v.axialGeometry_
+      << " coronalGeometry: "   << v.coronalGeometry_
+      << " sagittalGeometry: "  << v.sagittalGeometry_
+      << " voxelDimensions_: "  << v.voxelDimensions_
+      << " height: "            << v.height_
+      << " transform: "         << v.transform_
+      << " transformInverse: "  << v.transformInverse_;
+    return s;
+  }
+
 }
--- a/Framework/Volumes/VolumeImageGeometry.h	Sat Aug 10 13:13:11 2019 +0200
+++ b/Framework/Volumes/VolumeImageGeometry.h	Tue Aug 13 16:01:05 2019 +0200
@@ -24,6 +24,8 @@
 #include "../StoneEnumerations.h"
 #include "../Toolbox/CoordinateSystem3D.h"
 
+#include <iosfwd>
+
 namespace OrthancStone
 {
   class VolumeImageGeometry
@@ -41,6 +43,8 @@
 
     void Invalidate();
 
+    friend std::ostream& operator<<(std::ostream& s, const VolumeImageGeometry& v);
+
   public:
     VolumeImageGeometry();
 
@@ -86,7 +90,7 @@
       return transformInverse_;
     }
 
-    void SetSize(unsigned int width,
+    void SetSizeInVoxels(unsigned int width,
                  unsigned int height,
                  unsigned int depth);
 
@@ -120,7 +124,7 @@
     axial, sagittal or coronal cut and returns 
     the slice number corresponding to this cut.
 
-    If the cutting plane is not parallel to the tree x = 0, y = 0 or z = 0
+    If the cutting plane is not parallel to the three x = 0, y = 0 or z = 0
     planes, it is considered as arbitrary and the method returns false. 
     Otherwise, it returns true.
     */