view Framework/Volumes/VolumeReslicer.cpp @ 163:8c5b24892ed2 wasm

LinearAlgebra::InvertMatrix
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 14 Feb 2018 14:26:26 +0100
parents 0a73d76333db
children 01e32beee56c
line wrap: on
line source

#include "VolumeReslicer.h"

#include "../Toolbox/GeometryToolbox.h"

#include <Core/Logging.h>
#include <Core/OrthancException.h>

#include <boost/math/special_functions/round.hpp>

#if defined(_MSC_VER)
#  define ORTHANC_STONE_FORCE_INLINE __forceinline
#elif defined(__GNUC__) || defined(__clang__) || defined(__EMSCRIPTEN__)
#  define ORTHANC_STONE_FORCE_INLINE inline __attribute((always_inline))
#else
#  error Please support your compiler here
#endif


namespace OrthancStone
{
  // Anonymous namespace to avoid clashes between compilation modules
  namespace
  {
    enum TransferFunction
    {
      TransferFunction_Copy,
      TransferFunction_Float,
      TransferFunction_Linear
    };


    template <Orthanc::PixelFormat Format>
    struct PixelTraits;

    template <>
    struct PixelTraits<Orthanc::PixelFormat_Grayscale8>
    {
      typedef uint8_t  PixelType;

      static void SetOutOfVolume(PixelType& target)
      {
        target = 0;
      }

      static void GetVoxel(PixelType& target,
                           const ImageBuffer3D& image,
                           unsigned int x,
                           unsigned int y,
                           unsigned int z)
      {
        assert(x < image.GetWidth() && y < image.GetHeight() && z < image.GetDepth());
        target = image.GetVoxelGrayscale8Unchecked(x, y, z);
      }

      static float GetFloatVoxel(const ImageBuffer3D& image,
                                 unsigned int x,
                                 unsigned int y,
                                 unsigned int z)
      {
        assert(x < image.GetWidth() && y < image.GetHeight() && z < image.GetDepth());
        return static_cast<float>(image.GetVoxelGrayscale8Unchecked(x, y, z));
      }
    };

    template <>
    struct PixelTraits<Orthanc::PixelFormat_Grayscale16>
    {
      typedef uint16_t  PixelType;

      static void SetOutOfVolume(PixelType& target)
      {
        target = 0;
      }

      static void GetVoxel(PixelType& target,
                           const ImageBuffer3D& image,
                           unsigned int x,
                           unsigned int y,
                           unsigned int z)
      {
        assert(x < image.GetWidth() && y < image.GetHeight() && z < image.GetDepth());
        target = image.GetVoxelGrayscale16Unchecked(x, y, z);
      }

      static float GetFloatVoxel(const ImageBuffer3D& image,
                                 unsigned int x,
                                 unsigned int y,
                                 unsigned int z)
      {
        assert(x < image.GetWidth() && y < image.GetHeight() && z < image.GetDepth());
        return static_cast<float>(image.GetVoxelGrayscale16Unchecked(x, y, z));
      }
    };
    

    template <>
    struct PixelTraits<Orthanc::PixelFormat_SignedGrayscale16>
    {
      typedef int16_t  PixelType;

      static void SetOutOfVolume(PixelType& target)
      {
        target = std::numeric_limits<PixelType>::min();
      }

      static void GetVoxel(PixelType& target,
                           const ImageBuffer3D& image,
                           unsigned int x,
                           unsigned int y,
                           unsigned int z)
      {
        assert(x < image.GetWidth() && y < image.GetHeight() && z < image.GetDepth());
        target = image.GetVoxelSignedGrayscale16Unchecked(x, y, z);
      }

      static float GetFloatVoxel(const ImageBuffer3D& image,
                                 unsigned int x,
                                 unsigned int y,
                                 unsigned int z)
      {
        assert(x < image.GetWidth() && y < image.GetHeight() && z < image.GetDepth());
        return static_cast<float>(image.GetVoxelSignedGrayscale16Unchecked(x, y, z));
      }
    };
    

    template <>
    struct PixelTraits<Orthanc::PixelFormat_BGRA32>
    {
      struct PixelType
      {
        uint8_t  blue_;
        uint8_t  green_;
        uint8_t  red_;
        uint8_t  alpha_;
      };
    };
    

    
    template <Orthanc::PixelFormat InputFormat,
              Orthanc::PixelFormat OutputFormat>
    class PixelWriter
    {
    public:
      typedef typename PixelTraits<InputFormat>::PixelType   InputPixelType;
      typedef PixelTraits<OutputFormat>                      OutputPixelTraits;
      typedef typename PixelTraits<OutputFormat>::PixelType  OutputPixelType;

    private:
      template <typename T>
      static void SetValueInternal(OutputPixelType* pixel,
                                   const T& value)
      {
        if (value < std::numeric_limits<OutputPixelType>::min())
        {
          *pixel = std::numeric_limits<OutputPixelType>::min();
        }
        else if (value > std::numeric_limits<OutputPixelType>::max())
        {
          *pixel = std::numeric_limits<OutputPixelType>::max();
        }
        else
        {
          *pixel = static_cast<OutputPixelType>(value);
        }
      }

    public:
      ORTHANC_STONE_FORCE_INLINE
      void SetFloatValue(OutputPixelType* pixel,
                         float value) const
      {
        SetValueInternal<float>(pixel, value);
      }
      
      ORTHANC_STONE_FORCE_INLINE
      void SetValue(OutputPixelType* pixel,
                    const InputPixelType& value) const
      {
        SetValueInternal<InputPixelType>(pixel, value);
      }
    };


    template <Orthanc::PixelFormat InputFormat>
    class PixelWriter<InputFormat, Orthanc::PixelFormat_BGRA32>
    {
    public:
      typedef typename PixelTraits<InputFormat>::PixelType         InputPixelType;
      typedef PixelTraits<Orthanc::PixelFormat_BGRA32>             OutputPixelTraits;
      typedef PixelTraits<Orthanc::PixelFormat_BGRA32>::PixelType  OutputPixelType;

    private:
      template <typename T>
      static void SetValueInternal(OutputPixelType* pixel,
                                   const T& value)
      {
        uint8_t v;
        if (value < 0)
        {
          v = 0;
        }
        else if (value >= 255.0f)
        {
          v = 255;
        }
        else
        {
          v = static_cast<uint8_t>(value);
        }

        pixel->blue_ = v;
        pixel->green_ = v;
        pixel->red_ = v;
        pixel->alpha_ = 255;
      }

    public:
      ORTHANC_STONE_FORCE_INLINE
      void SetFloatValue(OutputPixelType* pixel,
                         float value) const
      {
        SetValueInternal<float>(pixel, value);
      }

      ORTHANC_STONE_FORCE_INLINE
      void SetValue(OutputPixelType* pixel,
                    const InputPixelType& value) const
      {
        SetValueInternal<InputPixelType>(pixel, value);
      }
    };



    class VoxelReaderBase
    {
    protected:
      const ImageBuffer3D&  source_;
      unsigned int          sourceWidth_;
      unsigned int          sourceHeight_;
      unsigned int          sourceDepth_;

    public:
      VoxelReaderBase(const ImageBuffer3D& source) :
        source_(source),
        sourceWidth_(source.GetWidth()),
        sourceHeight_(source.GetHeight()),
        sourceDepth_(source.GetDepth())
      {
      }

      unsigned int GetSourceWidth() const
      {
        return sourceWidth_;
      }

      unsigned int GetSourceHeight() const
      {
        return sourceHeight_;
      }

      unsigned int GetSourceDepth() const
      {
        return sourceDepth_;
      }

      bool GetNearestCoordinates(unsigned int& sourceX,
                                 unsigned int& sourceY,
                                 unsigned int& sourceZ,
                                 float worldX,
                                 float worldY,
                                 float worldZ) const
      {
        if (worldX >= 0 &&
            worldY >= 0 &&
            worldZ >= 0)
        {
          sourceX = static_cast<unsigned int>(worldX * static_cast<float>(sourceWidth_));
          sourceY = static_cast<unsigned int>(worldY * static_cast<float>(sourceHeight_));
          sourceZ = static_cast<unsigned int>(worldZ * static_cast<float>(sourceDepth_));

          return (sourceX < sourceWidth_ &&
                  sourceY < sourceHeight_ &&
                  sourceZ < sourceDepth_);
        }
        else
        {
          return false;
        }
      }
    };


    template <Orthanc::PixelFormat InputFormat,
              ImageInterpolation Interpolation>
    class VoxelReader;

    
    template <Orthanc::PixelFormat InputFormat>
    class VoxelReader<InputFormat, ImageInterpolation_Nearest> :
      public VoxelReaderBase
    {
    public:
      typedef typename PixelTraits<InputFormat>::PixelType   InputPixelType;

      VoxelReader(const ImageBuffer3D& source) :
        VoxelReaderBase(source)
      {
      }

      ORTHANC_STONE_FORCE_INLINE
      float GetFloatValue(float worldX,
                          float worldY,
                          float worldZ) const
      {
        InputPixelType value;
        GetValue(value, worldX, worldY, worldZ);
        return static_cast<float>(value);
      }

      ORTHANC_STONE_FORCE_INLINE
      void GetValue(InputPixelType& target,
                    float worldX,
                    float worldY,
                    float worldZ) const
      {
        unsigned int sourceX, sourceY, sourceZ;

        if (GetNearestCoordinates(sourceX, sourceY, sourceZ, worldX, worldY, worldZ))
        {
          PixelTraits<InputFormat>::GetVoxel(target, source_, sourceX, sourceY, sourceZ);
        }
        else
        {
          PixelTraits<InputFormat>::SetOutOfVolume(target);
        }
      }
    };
    
    
    template <Orthanc::PixelFormat InputFormat>
    class VoxelReader<InputFormat, ImageInterpolation_Bilinear> :
      public VoxelReaderBase
    {
    private:
      float outOfVolume_;
      
    public:
      VoxelReader(const ImageBuffer3D& source) :
        VoxelReaderBase(source)
      {
        typename PixelTraits<InputFormat>::PixelType value;
        PixelTraits<InputFormat>::SetOutOfVolume(value);
        outOfVolume_ = static_cast<float>(value);
      }

      void SampleVoxels(float& f00,
                        float& f10,
                        float& f01,
                        float& f11,
                        unsigned int sourceX,
                        unsigned int sourceY,
                        unsigned int sourceZ) const
      {
        f00 = PixelTraits<InputFormat>::GetFloatVoxel(source_, sourceX, sourceY, sourceZ);

        if (sourceX + 1 < sourceWidth_)
        {
          f01 = PixelTraits<InputFormat>::GetFloatVoxel(source_, sourceX + 1, sourceY, sourceZ);
        }
        else
        {
          f01 = f00;
        }

        if (sourceY + 1 < sourceHeight_)
        {
          f10 = PixelTraits<InputFormat>::GetFloatVoxel(source_, sourceX, sourceY + 1, sourceZ);
        }
        else
        {
          f10 = f00;
        }

        if (sourceX + 1 < sourceWidth_ &&
            sourceY + 1 < sourceHeight_)
        {
          f11 = PixelTraits<InputFormat>::GetFloatVoxel(source_, sourceX + 1, sourceY + 1, sourceZ);
        }
        else
        {
          f11 = f00;
        }
      }

      float GetOutOfVolume() const
      {
        return outOfVolume_;
      }
      
      float GetFloatValue(float worldX,
                          float worldY,
                          float worldZ) const
      {
        unsigned int sourceX, sourceY, sourceZ;

        if (GetNearestCoordinates(sourceX, sourceY, sourceZ, worldX, worldY, worldZ))
        {
          float f00, f10, f01, f11;
          SampleVoxels(f00, f10, f01, f11, sourceX, sourceY, sourceZ);          
          return GeometryToolbox::ComputeBilinearInterpolation(worldX, worldY, f00, f01, f10, f11);
        }
        else
        {
          return outOfVolume_;
        }
      }
    };


    template <Orthanc::PixelFormat InputFormat>
    class VoxelReader<InputFormat, ImageInterpolation_Trilinear>
    {
    private:
      typedef VoxelReader<InputFormat, ImageInterpolation_Bilinear>  Bilinear;

      Bilinear      bilinear_;
      unsigned int  sourceDepth_;

    public:
      VoxelReader(const ImageBuffer3D& source) :
        bilinear_(source),
        sourceDepth_(source.GetDepth())
      {
      }

      float GetFloatValue(float worldX,
                          float worldY,
                          float worldZ) const
      {
        unsigned int sourceX, sourceY, sourceZ;

        if (bilinear_.GetNearestCoordinates(sourceX, sourceY, sourceZ, worldX, worldY, worldZ))
        {
          float f000, f010, f001, f011, f100, f110, f101, f111;

          bilinear_.SampleVoxels(f000, f010, f001, f011, sourceX, sourceY, sourceZ);

          if (sourceZ + 1 < sourceDepth_)
          {
            bilinear_.SampleVoxels(f100, f110, f101, f111, sourceX, sourceY, sourceZ + 1);
            return GeometryToolbox::ComputeTrilinearInterpolation
              (worldX, worldY, worldZ, f000, f001, f010, f011, f100, f110, f101, f111);
          }
          else
          {
            return GeometryToolbox::ComputeBilinearInterpolation(worldX, worldY, f000, f001, f010, f011);
          }
        }
        else
        {
          return bilinear_.GetOutOfVolume();
        }
      }
    };


    template <typename VoxelReader,
              typename PixelWriter,
              TransferFunction Function>
    class PixelShader;

    
    template <typename VoxelReader,
              typename PixelWriter>
    class PixelShader<VoxelReader, PixelWriter, TransferFunction_Copy>
    {
    private:
      VoxelReader  reader_;
      PixelWriter  writer_;
      
    public:
      PixelShader(const ImageBuffer3D& image,
                  float /* scaling */,
                  float /* offset */) :
        reader_(image)
      {
      }
      
      ORTHANC_STONE_FORCE_INLINE
      void Apply(typename PixelWriter::OutputPixelType* pixel,
                 float worldX,
                 float worldY,
                 float worldZ)
      {
        typename VoxelReader::InputPixelType source;
        reader_.GetValue(source, worldX, worldY, worldZ);
        writer_.SetValue(pixel, source);
      }        
    };

    
    template <typename VoxelReader,
              typename PixelWriter>
    class PixelShader<VoxelReader, PixelWriter, TransferFunction_Float>
    {
    private:
      VoxelReader  reader_;
      PixelWriter  writer_;
      
    public:
      PixelShader(const ImageBuffer3D& image,
                  float /* scaling */,
                  float /* offset */) :
        reader_(image)
      {
      }
      
      ORTHANC_STONE_FORCE_INLINE
      void Apply(typename PixelWriter::OutputPixelType* pixel,
                 float worldX,
                 float worldY,
                 float worldZ)
      {
        writer_.SetFloatValue(pixel, reader_.GetFloatValue(worldX, worldY, worldZ));
      }        
    };

    
    template <typename VoxelReader,
              typename PixelWriter>
    class PixelShader<VoxelReader, PixelWriter, TransferFunction_Linear>
    {
    private:
      VoxelReader  reader_;
      PixelWriter  writer_;
      float        scaling_;
      float        offset_;
      
    public:
      PixelShader(const ImageBuffer3D& image,
                  float scaling,
                  float offset) :
        reader_(image),
        scaling_(scaling),
        offset_(offset)
      {
      }
      
      ORTHANC_STONE_FORCE_INLINE
      void Apply(typename PixelWriter::OutputPixelType* pixel,
                 float worldX,
                 float worldY,
                 float worldZ)
      {
        writer_.SetFloatValue(pixel, scaling_ * reader_.GetFloatValue(worldX, worldY, worldZ) + offset_);
      }        
    };



    class FastRowIterator : public boost::noncopyable
    {
    private:
      float  position_[3];
      float  offset_[3];
      
    public:
      FastRowIterator(const Orthanc::ImageAccessor& slice,
                      const Extent2D& extent,
                      const CoordinateSystem3D& plane,
                      const OrientedBoundingBox& box,
                      unsigned int y)
      {
        const double width = static_cast<double>(slice.GetWidth());
        const double height = static_cast<double>(slice.GetHeight());
        assert(y < height);

        Vector q1 = plane.MapSliceToWorldCoordinates
          (extent.GetX1() + extent.GetWidth() * static_cast<double>(0) / static_cast<double>(width + 1),
           extent.GetY1() + extent.GetHeight() * static_cast<double>(y) / static_cast<double>(height + 1));

        Vector q2 = plane.MapSliceToWorldCoordinates
          (extent.GetX1() + extent.GetWidth() * static_cast<double>(width - 1) / static_cast<double>(width + 1),
           extent.GetY1() + extent.GetHeight() * static_cast<double>(y) / static_cast<double>(height + 1));

        Vector r1, r2;
        box.ToInternalCoordinates(r1, q1);
        box.ToInternalCoordinates(r2, q2);

        position_[0] = static_cast<float>(r1[0]);
        position_[1] = static_cast<float>(r1[1]);
        position_[2] = static_cast<float>(r1[2]);
        
        Vector tmp = (r2 - r1) / static_cast<double>(width - 1);
        offset_[0] = static_cast<float>(tmp[0]);
        offset_[1] = static_cast<float>(tmp[1]);
        offset_[2] = static_cast<float>(tmp[2]);
      }

      ORTHANC_STONE_FORCE_INLINE
      void Next()
      {
        position_[0] += offset_[0];
        position_[1] += offset_[1];
        position_[2] += offset_[2];
      }

      ORTHANC_STONE_FORCE_INLINE
      void GetNearestCoordinates(float& x,
                                 float& y,
                                 float& z) const
      {
        x = position_[0];
        y = position_[1];
        z = position_[2];
      }
    };


    class SlowRowIterator : public boost::noncopyable
    {
    private:
      const Orthanc::ImageAccessor&  slice_;
      const Extent2D&                extent_;
      const CoordinateSystem3D&      plane_;
      const OrientedBoundingBox&     box_;
      unsigned int                   x_;
      unsigned int                   y_;
      
    public:
      SlowRowIterator(const Orthanc::ImageAccessor& slice,
                      const Extent2D& extent,
                      const CoordinateSystem3D& plane,
                      const OrientedBoundingBox& box,
                      unsigned int y) :
        slice_(slice),
        extent_(extent),
        plane_(plane),
        box_(box),
        x_(0),
        y_(y)
      {
        assert(y_ < slice_.GetHeight());
      }

      void Next()
      {
        x_++;
      }

      void GetNearestCoordinates(float& x,
                                 float& y,
                                 float& z) const
      {
        assert(x_ < slice_.GetWidth());
        
        const double width = static_cast<double>(slice_.GetWidth());
        const double height = static_cast<double>(slice_.GetHeight());
        
        Vector q = plane_.MapSliceToWorldCoordinates
          (extent_.GetX1() + extent_.GetWidth() * static_cast<double>(x_) / (width + 1.0),
           extent_.GetY1() + extent_.GetHeight() * static_cast<double>(y_) / (height + 1.0));

        Vector r;
        box_.ToInternalCoordinates(r, q);

        x = static_cast<float>(r[0]);
        y = static_cast<float>(r[1]);
        z = static_cast<float>(r[2]);
      }
    };


    template <typename RowIterator,
              Orthanc::PixelFormat InputFormat,
              Orthanc::PixelFormat OutputFormat,
              ImageInterpolation Interpolation,
              TransferFunction Function>
    static void ProcessImage(Orthanc::ImageAccessor& slice,
                             const Extent2D& extent,
                             const ImageBuffer3D& source,
                             const CoordinateSystem3D& plane,
                             const OrientedBoundingBox& box,
                             float scaling,
                             float offset)
    {
      typedef VoxelReader<InputFormat, Interpolation>   Reader;
      typedef PixelWriter<InputFormat, OutputFormat>    Writer;
      typedef PixelShader<Reader, Writer, Function>     Shader;

      const unsigned int outputWidth = slice.GetWidth();
      const unsigned int outputHeight = slice.GetHeight();

      Shader shader(source, scaling, offset);

      for (unsigned int y = 0; y < outputHeight; y++)
      {
        typename Writer::OutputPixelType* p = reinterpret_cast<typename Writer::OutputPixelType*>(slice.GetRow(y));

        RowIterator it(slice, extent, plane, box, y);

        for (unsigned int x = 0; x < outputWidth; x++, p++)
        {
          float worldX, worldY, worldZ;
          it.GetNearestCoordinates(worldX, worldY, worldZ);
          shader.Apply(p, worldX, worldY, worldZ);
          it.Next();
        }
      }
    }


    template <typename RowIterator,
              Orthanc::PixelFormat InputFormat,
              Orthanc::PixelFormat OutputFormat>
    static void ProcessImage(Orthanc::ImageAccessor& slice,
                             const Extent2D& extent,
                             const ImageBuffer3D& source,
                             const CoordinateSystem3D& plane,
                             const OrientedBoundingBox& box,
                             ImageInterpolation interpolation,
                             bool hasLinearFunction,
                             float scaling,
                             float offset)
    {
      if (hasLinearFunction)
      {
        switch (interpolation)
        {
          case ImageInterpolation_Nearest:
            ProcessImage<RowIterator, InputFormat, OutputFormat,
                         ImageInterpolation_Nearest, TransferFunction_Linear>
              (slice, extent, source, plane, box, scaling, offset);
            break;

          case ImageInterpolation_Bilinear:
            ProcessImage<RowIterator, InputFormat, OutputFormat,
                         ImageInterpolation_Bilinear, TransferFunction_Linear>
              (slice, extent, source, plane, box, scaling, offset);
            break;

          case ImageInterpolation_Trilinear:
            ProcessImage<RowIterator, InputFormat, OutputFormat,
                         ImageInterpolation_Trilinear, TransferFunction_Linear>
              (slice, extent, source, plane, box, scaling, offset);
            break;

          default:
            throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
        }        
      }
      else
      {
        switch (interpolation)
        {
          case ImageInterpolation_Nearest:
            ProcessImage<RowIterator, InputFormat, OutputFormat,
                         ImageInterpolation_Nearest, TransferFunction_Copy>
              (slice, extent, source, plane, box, 0, 0);
            break;

          case ImageInterpolation_Bilinear:
            ProcessImage<RowIterator, InputFormat, OutputFormat,
                         ImageInterpolation_Bilinear, TransferFunction_Float>
              (slice, extent, source, plane, box, 0, 0);
            break;

          case ImageInterpolation_Trilinear:
            ProcessImage<RowIterator, InputFormat, OutputFormat,
                         ImageInterpolation_Trilinear, TransferFunction_Float>
              (slice, extent, source, plane, box, 0, 0);
            break;

          default:
            throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
        }        
      }
    }
    
    
    template <typename RowIterator>
    static void ProcessImage(Orthanc::ImageAccessor& slice,
                             const Extent2D& extent,
                             const ImageBuffer3D& source,
                             const CoordinateSystem3D& plane,
                             const OrientedBoundingBox& box,
                             ImageInterpolation interpolation,
                             bool hasLinearFunction,
                             float scaling,
                             float offset)
    {
      if (source.GetFormat() == Orthanc::PixelFormat_Grayscale16 &&
          slice.GetFormat() == Orthanc::PixelFormat_Grayscale8)
      {
        ProcessImage<RowIterator,
                     Orthanc::PixelFormat_Grayscale16,
                     Orthanc::PixelFormat_Grayscale8>
          (slice, extent, source, plane, box, interpolation, hasLinearFunction, scaling, offset);
      }
      else if (source.GetFormat() == Orthanc::PixelFormat_Grayscale16 &&
               slice.GetFormat() == Orthanc::PixelFormat_Grayscale16)
      {
        ProcessImage<RowIterator,
                     Orthanc::PixelFormat_Grayscale16,
                     Orthanc::PixelFormat_Grayscale16>
          (slice, extent, source, plane, box, interpolation, hasLinearFunction, scaling, offset);
      }
      else if (source.GetFormat() == Orthanc::PixelFormat_SignedGrayscale16 &&
               slice.GetFormat() == Orthanc::PixelFormat_BGRA32)
      {
        ProcessImage<RowIterator,
                     Orthanc::PixelFormat_SignedGrayscale16,
                     Orthanc::PixelFormat_BGRA32>
          (slice, extent, source, plane, box, interpolation, hasLinearFunction, scaling, offset);
      }
      else if (source.GetFormat() == Orthanc::PixelFormat_Grayscale16 &&
               slice.GetFormat() == Orthanc::PixelFormat_BGRA32)
      {
        ProcessImage<RowIterator,
                     Orthanc::PixelFormat_Grayscale16,
                     Orthanc::PixelFormat_BGRA32>
          (slice, extent, source, plane, box, interpolation, hasLinearFunction, scaling, offset);
      }
      else
      {
        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
      }
    }
  }
    
    

  void VolumeReslicer::CheckIterators(const ImageBuffer3D& source,
                                      const CoordinateSystem3D& plane,
                                      const OrientedBoundingBox& box) const
  {
    for (unsigned int y = 0; y < slice_->GetHeight(); y++)
    {
      FastRowIterator fast(*slice_, extent_, plane, box, y);
      SlowRowIterator slow(*slice_, extent_, plane, box, y);

      for (unsigned int x = 0; x < slice_->GetWidth(); x++)
      {
        float px, py, pz;
        fast.GetNearestCoordinates(px, py, pz);

        float qx, qy, qz;
        slow.GetNearestCoordinates(qx, qy, qz);

        Vector d;
        LinearAlgebra::AssignVector(d, px - qx, py - qy, pz - qz);
        double norm = boost::numeric::ublas::norm_2(d);
        if (norm > 0.0001)
        {
          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
        }
          
        fast.Next();
        slow.Next();
      }
    }
  }

    
  void VolumeReslicer::Reset()
  {
    success_ = false;
    extent_.Reset();
    slice_.reset(NULL);
  }


  float VolumeReslicer::GetMinOutputValue() const
  {
    switch (outputFormat_)
    {
      case Orthanc::PixelFormat_Grayscale8:
      case Orthanc::PixelFormat_Grayscale16:
      case Orthanc::PixelFormat_BGRA32:
        return 0.0f;
        break;

      default:
        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
    }
  }

    
  float VolumeReslicer::GetMaxOutputValue() const
  {
    switch (outputFormat_)
    {
      case Orthanc::PixelFormat_Grayscale8:
      case Orthanc::PixelFormat_BGRA32:
        return static_cast<float>(std::numeric_limits<uint8_t>::max());
        break;

      case Orthanc::PixelFormat_Grayscale16:
        return static_cast<float>(std::numeric_limits<uint16_t>::max());
        break;

      default:
        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
    }
  }
    

  VolumeReslicer::VolumeReslicer() :
    outputFormat_(Orthanc::PixelFormat_Grayscale8),
    interpolation_(ImageInterpolation_Nearest),
    fastMode_(true),
    success_(false)
  {
    ResetLinearFunction();
  }


  void VolumeReslicer::GetLinearFunction(float& scaling,
                                         float& offset) const
  {
    if (hasLinearFunction_)
    {
      scaling = scaling_;
      offset = offset_;
    }
    else
    {
      scaling = 1.0f;
      offset = 0.0f;
    }
  }

  
  void VolumeReslicer::ResetLinearFunction()
  {
    Reset();
    hasLinearFunction_ = false;
    scaling_ = 1.0f;
    offset_ = 0.0f;
  }
    

  void VolumeReslicer::SetLinearFunction(float scaling,
                                         float offset)
  {
    Reset();
    hasLinearFunction_ = true;
    scaling_ = scaling;
    offset_ = offset;
  }

  
  void VolumeReslicer::SetWindow(float low,
                                 float high)
  {
    //printf("Range in pixel values: %f->%f\n", low, high);
    float scaling = (GetMaxOutputValue() - GetMinOutputValue()) / (high - low);
    float offset = GetMinOutputValue() - scaling * low;
    
    SetLinearFunction(scaling, offset);

    /*float x = scaling_ * low + offset_;
    float y = scaling_ * high + offset_;
    printf("%f %f (should be %f->%f)\n", x, y, GetMinOutputValue(), GetMaxOutputValue());*/
  }
  

  void VolumeReslicer::FitRange(const ImageBuffer3D& image)
  {
    float minInputValue, maxInputValue;

    if (!image.GetRange(minInputValue, maxInputValue) ||
        maxInputValue < 1)
    {
      ResetLinearFunction();
    }
    else
    {
      SetWindow(minInputValue, maxInputValue);
    }
  }  

  
  void VolumeReslicer::SetWindowing(ImageWindowing windowing,
                                    const ImageBuffer3D& image,
                                    float rescaleSlope,
                                    float rescaleIntercept)
  {
    if (windowing == ImageWindowing_Custom ||
        windowing == ImageWindowing_Default)
    {
      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
    }

    float center, width;
    ComputeWindowing(center, width, windowing, 0, 0);

    float a = (center - width / 2.0f - rescaleIntercept) / rescaleSlope;
    float b = (center + width / 2.0f - rescaleIntercept) / rescaleSlope;
    SetWindow(a, b);
  }


  void VolumeReslicer::SetOutputFormat(Orthanc::PixelFormat format)
  {
    if (format != Orthanc::PixelFormat_Grayscale8 &&
        format != Orthanc::PixelFormat_Grayscale16 &&
        format != Orthanc::PixelFormat_BGRA32)
    {
      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
    }

    if (hasLinearFunction_)
    {
      LOG(WARNING) << "Calls to VolumeReslicer::SetOutputFormat() should be done before VolumeReslicer::FitRange()";
    }
    
    outputFormat_ = format;
    Reset();
  }


  void VolumeReslicer::SetInterpolation(ImageInterpolation interpolation)
  {
    if (interpolation != ImageInterpolation_Nearest &&
        interpolation != ImageInterpolation_Bilinear &&
        interpolation != ImageInterpolation_Trilinear)
    {
      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
    }

    interpolation_ = interpolation;
    Reset();
  }


  const Extent2D& VolumeReslicer::GetOutputExtent() const
  {
    if (success_)
    {
      return extent_;
    }
    else
    {
      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
    }
  }

  
  const Orthanc::ImageAccessor& VolumeReslicer::GetOutputSlice() const
  {
    if (success_)
    {
      assert(slice_.get() != NULL);
      return *slice_;
    }
    else
    {
      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
    }
  }

  
  Orthanc::ImageAccessor* VolumeReslicer::ReleaseOutputSlice()
  {
    if (success_)
    {
      assert(slice_.get() != NULL);
      success_ = false;
      return slice_.release();
    }
    else
    {
      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
    }
  }

  
  void VolumeReslicer::Apply(const ImageBuffer3D& source,
                             const CoordinateSystem3D& plane)
  {
    // Choose the default voxel size as the finest voxel dimension
    // of the source volumetric image
    const OrthancStone::Vector dim = source.GetVoxelDimensions(OrthancStone::VolumeProjection_Axial);
    double voxelSize = dim[0];
    
    if (dim[1] < voxelSize)
    {
      voxelSize = dim[1];
    }
    
    if (dim[2] < voxelSize)
    {
      voxelSize = dim[2];
    }

    if (voxelSize <= 0)
    {
      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
    }

    Apply(source, plane, voxelSize);
  }

  
  void VolumeReslicer::Apply(const ImageBuffer3D& source,
                             const CoordinateSystem3D& plane,
                             double voxelSize)
  {
    Reset();

    // Firstly, compute the intersection of the source volumetric
    // image with the reslicing plane. This leads to a polygon with 3
    // to 6 vertices. We compute the extent of the intersection
    // polygon, with respect to the coordinate system of the reslicing
    // plane.
    OrientedBoundingBox box(source);

    if (!box.ComputeExtent(extent_, plane))
    {
      // The plane does not intersect with the bounding box of the volume
      slice_.reset(new Orthanc::Image(outputFormat_, 0, 0, false));
      success_ = true;
      return;
    }

    // Secondly, the extent together with the voxel size gives the
    // size of the output image
    unsigned int width = boost::math::round(extent_.GetWidth() / voxelSize);
    unsigned int height = boost::math::round(extent_.GetHeight() / voxelSize);

    slice_.reset(new Orthanc::Image(outputFormat_, width, height, false));

    //CheckIterators(source, plane, box);
      
    if (fastMode_)
    {
      ProcessImage<FastRowIterator>(*slice_, extent_, source, plane, box,
                                    interpolation_, hasLinearFunction_, scaling_, offset_);
    }
    else
    {
      ProcessImage<SlowRowIterator>(*slice_, extent_, source, plane, box,
                                    interpolation_, hasLinearFunction_, scaling_, offset_);
    }

    success_ = true;
  }
}