Mercurial > hg > orthanc-stone
changeset 182:2cbfb08f3a95 wasm
ImageGeometry.cpp
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Thu, 15 Mar 2018 12:12:01 +0100 |
parents | ff8556874557 |
children | 98da3a8d4820 |
files | Framework/Toolbox/ImageGeometry.cpp Framework/Toolbox/ImageGeometry.h Framework/Toolbox/SubpixelReader.h Resources/CMake/OrthancStoneConfiguration.cmake |
diffstat | 4 files changed, 828 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Toolbox/ImageGeometry.cpp Thu Mar 15 12:12:01 2018 +0100 @@ -0,0 +1,556 @@ +/** + * 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 "ImageGeometry.h" + +#include "Extent2D.h" +#include "SubpixelReader.h" + +#include <Core/Images/ImageProcessing.h> +#include <Core/Logging.h> + + +namespace OrthancStone +{ + static void AddTransformedPoint(Extent2D& extent, + const Matrix& a, + double x, + double y) + { + assert(a.size1() == 3 && + a.size2() == 3); + + Vector p = LinearAlgebra::Product(a, LinearAlgebra::CreateVector(x, y, 1)); + + if (!LinearAlgebra::IsCloseToZero(p[2])) + { + extent.AddPoint(p[0] / p[2], p[1] / p[2]); + } + } + + + bool GetPerpectiveTransformExtent(unsigned int& x1, + unsigned int& y1, + unsigned int& x2, + unsigned int& y2, + const Matrix& a, + unsigned int sourceWidth, + unsigned int sourceHeight, + unsigned int targetWidth, + unsigned int targetHeight) + { + if (targetWidth == 0 || + targetHeight == 0) + { + return false; + } + + Extent2D extent; + AddTransformedPoint(extent, a, 0, 0); + AddTransformedPoint(extent, a, sourceWidth, 0); + AddTransformedPoint(extent, a, 0, sourceHeight); + AddTransformedPoint(extent, a, sourceWidth, sourceHeight); + + if (extent.IsEmpty()) + { + return false; + } + else + { + int tmp; + + tmp = std::floor(extent.GetX1()); + if (tmp < 0) + { + x1 = 0; + } + else + { + x1 = static_cast<unsigned int>(tmp); + } + + tmp = std::floor(extent.GetY1()); + if (tmp < 0) + { + y1 = 0; + } + else + { + y1 = static_cast<unsigned int>(tmp); + } + + tmp = std::ceil(extent.GetX2()); + if (tmp < 0) + { + return false; + } + else if (static_cast<unsigned int>(tmp) >= targetWidth) + { + x2 = targetWidth - 1; + } + else + { + x2 = static_cast<unsigned int>(tmp); + } + + tmp = std::ceil(extent.GetY2()); + if (tmp < 0) + { + return false; + } + else if (static_cast<unsigned int>(tmp) >= targetHeight) + { + y2 = targetHeight - 1; + } + else + { + y2 = static_cast<unsigned int>(tmp); + } + + return (x1 <= x2 && + y1 <= y2); + } + } + + + template <typename Reader, + bool HasOffsetX, + bool HasOffsetY> + static void ApplyAffineTransformToRow(typename Reader::PixelType* p, + Reader& reader, + unsigned int x1, + unsigned int x2, + float positionX, + float positionY, + float offsetX, + float offsetY) + { + typename Reader::PixelType value; + + for (unsigned int x = x1; x <= x2; x++, p++) + { + if (reader.GetValue(value, positionX, positionY)) + { + *p = value; + } + else + { + Reader::Traits::SetZero(*p); + } + + if (HasOffsetX) + { + positionX += offsetX; + } + + if (HasOffsetY) + { + positionY += offsetY; + } + } + } + + + template <Orthanc::PixelFormat Format, + ImageInterpolation Interpolation> + static void ApplyAffineInternal(Orthanc::ImageAccessor& target, + const Orthanc::ImageAccessor& source, + const Matrix& a) + { + assert(target.GetFormat() == Format && + source.GetFormat() == Format); + + typedef SubpixelReader<Format, Interpolation> Reader; + typedef typename Reader::PixelType PixelType; + + if (Format == Orthanc::PixelFormat_RGB24) + { + Orthanc::ImageProcessing::Set(target, 0, 0, 0, 255); + } + else + { + Orthanc::ImageProcessing::Set(target, 0); + } + + Matrix inva; + if (!LinearAlgebra::InvertMatrixUnsafe(inva, a)) + { + // Singular matrix + return; + } + + Reader reader(source); + + unsigned int x1, y1, x2, y2; + + if (GetPerpectiveTransformExtent(x1, y1, x2, y2, a, + source.GetWidth(), source.GetHeight(), + target.GetWidth(), target.GetHeight())) + { + const size_t targetPitch = target.GetPitch(); + uint8_t *targetRow = reinterpret_cast<uint8_t*>(reinterpret_cast<PixelType*>(target.GetRow(y1)) + x1); + + for (unsigned int y = y1; y <= y2; y++) + { + Vector start; + LinearAlgebra::AssignVector(start, static_cast<double>(x1) + 0.5, + static_cast<double>(y) + 0.5, 1); + start = boost::numeric::ublas::prod(inva, start); + assert(LinearAlgebra::IsNear(1.0, start(2))); + + Vector offset; + LinearAlgebra::AssignVector(offset, static_cast<double>(x1) + 1.5, + static_cast<double>(y) + 0.5, 1); + offset = boost::numeric::ublas::prod(inva, offset) - start; + assert(LinearAlgebra::IsNear(0.0, offset(2))); + + float startX = static_cast<float>(start[0]); + float startY = static_cast<float>(start[1]); + float offsetX = static_cast<float>(offset[0]); + float offsetY = static_cast<float>(offset[1]); + + PixelType* pixel = reinterpret_cast<PixelType*>(targetRow); + if (LinearAlgebra::IsCloseToZero(offsetX)) + { + ApplyAffineTransformToRow<Reader, false, true> + (pixel, reader, x1, x2, startX, startY, offsetX, offsetY); + } + else if (LinearAlgebra::IsCloseToZero(offsetY)) + { + ApplyAffineTransformToRow<Reader, true, false> + (pixel, reader, x1, x2, startX, startY, offsetX, offsetY); + } + else + { + ApplyAffineTransformToRow<Reader, true, true> + (pixel, reader, x1, x2, startX, startY, offsetX, offsetY); + } + + targetRow += targetPitch; + } + } + } + + + void ApplyAffineTransform(Orthanc::ImageAccessor& target, + const Orthanc::ImageAccessor& source, + double a11, + double a12, + double b1, + double a21, + double a22, + double b2, + ImageInterpolation interpolation) + { + if (source.GetFormat() != target.GetFormat()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat); + } + + if (interpolation != ImageInterpolation_Nearest && + interpolation != ImageInterpolation_Bilinear) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + Matrix a; + a.resize(3, 3); + a(0, 0) = a11; + a(0, 1) = a12; + a(0, 2) = b1; + a(1, 0) = a21; + a(1, 1) = a22; + a(1, 2) = b2; + a(2, 0) = 0; + a(2, 1) = 0; + a(2, 2) = 1; + + switch (source.GetFormat()) + { + case Orthanc::PixelFormat_Grayscale8: + switch (interpolation) + { + case ImageInterpolation_Nearest: + ApplyAffineInternal<Orthanc::PixelFormat_Grayscale8, + ImageInterpolation_Nearest>(target, source, a); + break; + + case ImageInterpolation_Bilinear: + ApplyAffineInternal<Orthanc::PixelFormat_Grayscale8, + ImageInterpolation_Bilinear>(target, source, a); + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + break; + + case Orthanc::PixelFormat_Grayscale16: + switch (interpolation) + { + case ImageInterpolation_Nearest: + ApplyAffineInternal<Orthanc::PixelFormat_Grayscale16, + ImageInterpolation_Nearest>(target, source, a); + break; + + case ImageInterpolation_Bilinear: + ApplyAffineInternal<Orthanc::PixelFormat_Grayscale16, + ImageInterpolation_Bilinear>(target, source, a); + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + break; + + case Orthanc::PixelFormat_SignedGrayscale16: + switch (interpolation) + { + case ImageInterpolation_Nearest: + ApplyAffineInternal<Orthanc::PixelFormat_SignedGrayscale16, + ImageInterpolation_Nearest>(target, source, a); + break; + + case ImageInterpolation_Bilinear: + ApplyAffineInternal<Orthanc::PixelFormat_SignedGrayscale16, + ImageInterpolation_Bilinear>(target, source, a); + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + break; + + case Orthanc::PixelFormat_RGB24: + switch (interpolation) + { + case ImageInterpolation_Nearest: + ApplyAffineInternal<Orthanc::PixelFormat_RGB24, + ImageInterpolation_Nearest>(target, source, a); + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + } + + + template <Orthanc::PixelFormat Format, + ImageInterpolation Interpolation> + static void ApplyPerspectiveInternal(Orthanc::ImageAccessor& target, + const Orthanc::ImageAccessor& source, + const Matrix& a, + const Matrix& inva) + { + assert(target.GetFormat() == Format && + source.GetFormat() == Format); + + typedef SubpixelReader<Format, Interpolation> Reader; + typedef typename Reader::PixelType PixelType; + + Reader reader(source); + unsigned int x1, y1, x2, y2; + + const float floatWidth = source.GetWidth(); + const float floatHeight = source.GetHeight(); + + if (GetPerpectiveTransformExtent(x1, y1, x2, y2, a, + source.GetWidth(), source.GetHeight(), + target.GetWidth(), target.GetHeight())) + { + const size_t targetPitch = target.GetPitch(); + uint8_t *targetRow = reinterpret_cast<uint8_t*>(reinterpret_cast<PixelType*>(target.GetRow(y1)) + x1); + + for (unsigned int y = y1; y <= y2; y++) + { + PixelType *p = reinterpret_cast<PixelType*>(targetRow); + + for (unsigned int x = x1; x <= x2; x++) + { + Vector v; + LinearAlgebra::AssignVector(v, static_cast<double>(x) + 0.5, + static_cast<double>(y) + 0.5, 1); + + Vector vv = LinearAlgebra::Product(inva, v); + + assert(!LinearAlgebra::IsCloseToZero(vv[2])); + const double w = 1.0 / vv[2]; + const float sourceX = static_cast<float>(vv[0] * w); + const float sourceY = static_cast<float>(vv[1] * w); + + // Make sure no integer overflow will occur after truncation + // (the static_cast<unsigned int> could otherwise throw an + // exception in WebAssembly if strong perspective effects) + if (sourceX < floatWidth && + sourceY < floatHeight) + { + reader.GetValue(*p, sourceX, sourceY); + } + else + { + Reader::Traits::SetZero(*p); + } + + p++; + } + + targetRow += targetPitch; + } + } + } + + + void ApplyPerspectiveTransform(Orthanc::ImageAccessor& target, + const Orthanc::ImageAccessor& source, + const Matrix& a, + ImageInterpolation interpolation) + { + if (source.GetFormat() != target.GetFormat()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat); + } + + if (a.size1() != 3 || + a.size2() != 3) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageSize); + } + + if (interpolation != ImageInterpolation_Nearest && + interpolation != ImageInterpolation_Bilinear) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + // Check whether we are dealing with an affine transform + if (LinearAlgebra::IsCloseToZero(a(2, 0)) && + LinearAlgebra::IsCloseToZero(a(2, 1))) + { + double w = a(2, 2); + if (LinearAlgebra::IsCloseToZero(w)) + { + LOG(ERROR) << "Singular perspective matrix"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + else + { + ApplyAffineTransform(target, source, + a(0, 0) / w, a(0, 1) / w, a(0, 2) / w, + a(1, 0) / w, a(1, 1) / w, a(1, 2) / w, + interpolation); + return; + } + } + + if (target.GetFormat() == Orthanc::PixelFormat_RGB24) + { + Orthanc::ImageProcessing::Set(target, 0, 0, 0, 255); + } + else + { + Orthanc::ImageProcessing::Set(target, 0); + } + + Matrix inva; + if (!LinearAlgebra::InvertMatrixUnsafe(inva, a)) + { + return; + } + + switch (source.GetFormat()) + { + case Orthanc::PixelFormat_Grayscale8: + switch (interpolation) + { + case ImageInterpolation_Nearest: + ApplyPerspectiveInternal<Orthanc::PixelFormat_Grayscale8, + ImageInterpolation_Nearest>(target, source, a, inva); + break; + + case ImageInterpolation_Bilinear: + ApplyPerspectiveInternal<Orthanc::PixelFormat_Grayscale8, + ImageInterpolation_Bilinear>(target, source, a, inva); + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + break; + + case Orthanc::PixelFormat_Grayscale16: + switch (interpolation) + { + case ImageInterpolation_Nearest: + ApplyPerspectiveInternal<Orthanc::PixelFormat_Grayscale16, + ImageInterpolation_Nearest>(target, source, a, inva); + break; + + case ImageInterpolation_Bilinear: + ApplyPerspectiveInternal<Orthanc::PixelFormat_Grayscale16, + ImageInterpolation_Bilinear>(target, source, a, inva); + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + break; + + case Orthanc::PixelFormat_SignedGrayscale16: + switch (interpolation) + { + case ImageInterpolation_Nearest: + ApplyPerspectiveInternal<Orthanc::PixelFormat_SignedGrayscale16, + ImageInterpolation_Nearest>(target, source, a, inva); + break; + + case ImageInterpolation_Bilinear: + ApplyPerspectiveInternal<Orthanc::PixelFormat_SignedGrayscale16, + ImageInterpolation_Bilinear>(target, source, a, inva); + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + break; + + case Orthanc::PixelFormat_RGB24: + switch (interpolation) + { + case ImageInterpolation_Nearest: + ApplyPerspectiveInternal<Orthanc::PixelFormat_RGB24, + ImageInterpolation_Nearest>(target, source, a, inva); + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Toolbox/ImageGeometry.h Thu Mar 15 12:12:01 2018 +0100 @@ -0,0 +1,59 @@ +/** + * 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 + +#include "../Enumerations.h" +#include "LinearAlgebra.h" + +#include <Core/Images/ImageAccessor.h> + + +namespace OrthancStone +{ + // Returns the "useful" portion of the target image when applying a + // 3x3 perspective transform "a" (i.e. the bounding box where points + // of the source image are mapped to) + bool GetPerpectiveTransformExtent(unsigned int& x1, + unsigned int& y1, + unsigned int& x2, + unsigned int& y2, + const Matrix& a, + unsigned int sourceWidth, + unsigned int sourceHeight, + unsigned int targetWidth, + unsigned int targetHeight); + + void ApplyAffineTransform(Orthanc::ImageAccessor& target, + const Orthanc::ImageAccessor& source, + double a11, + double a12, + double b1, + double a21, + double a22, + double b2, + ImageInterpolation interpolation); + + void ApplyPerspectiveTransform(Orthanc::ImageAccessor& target, + const Orthanc::ImageAccessor& source, + const Matrix& a, + ImageInterpolation interpolation); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Toolbox/SubpixelReader.h Thu Mar 15 12:12:01 2018 +0100 @@ -0,0 +1,212 @@ +/** + * 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 + +#include "../Enumerations.h" +#include "GeometryToolbox.h" + +#include <Core/Images/ImageTraits.h> + +#include <boost/noncopyable.hpp> +#include <cmath> + +namespace OrthancStone +{ + namespace Internals + { + class SubpixelReaderBase : public boost::noncopyable + { + private: + const Orthanc::ImageAccessor& source_; + unsigned int width_; + unsigned int height_; + + public: + SubpixelReaderBase(const Orthanc::ImageAccessor& source) : + source_(source), + width_(source.GetWidth()), + height_(source.GetHeight()) + { + } + + ORTHANC_FORCE_INLINE + const Orthanc::ImageAccessor& GetSource() const + { + return source_; + } + + ORTHANC_FORCE_INLINE + unsigned int GetWidth() const + { + return width_; + } + + ORTHANC_FORCE_INLINE + unsigned int GetHeight() const + { + return height_; + } + }; + } + + + template <Orthanc::PixelFormat Format, + ImageInterpolation Interpolation> + class SubpixelReader; + + + template <Orthanc::PixelFormat Format> + class SubpixelReader<Format, ImageInterpolation_Nearest> : + public Internals::SubpixelReaderBase + { + public: + typedef Orthanc::PixelTraits<Format> Traits; + typedef typename Traits::PixelType PixelType; + + SubpixelReader(const Orthanc::ImageAccessor& source) : + SubpixelReaderBase(source) + { + } + + inline bool GetValue(PixelType& target, + float x, + float y) const; + }; + + + template <Orthanc::PixelFormat Format> + class SubpixelReader<Format, ImageInterpolation_Bilinear> : + public Internals::SubpixelReaderBase + { + public: + typedef Orthanc::PixelTraits<Format> Traits; + typedef typename Traits::PixelType PixelType; + + SubpixelReader(const Orthanc::ImageAccessor& source) : + SubpixelReaderBase(source) + { + } + + inline bool GetValue(PixelType& target, + float x, + float y); + }; + + + + template <Orthanc::PixelFormat Format> + bool SubpixelReader<Format, ImageInterpolation_Nearest>::GetValue(PixelType& target, + float x, + float y) const + { + if (x < 0 || + y < 0) + { + return false; + } + else + { + unsigned int ux = static_cast<unsigned int>(std::floor(x)); + unsigned int uy = static_cast<unsigned int>(std::floor(y)); + + if (ux < GetWidth() && + uy < GetHeight()) + { + Orthanc::ImageTraits<Format>::GetPixel(target, GetSource(), ux, uy); + return true; + } + else + { + return false; + } + } + } + + + + template <Orthanc::PixelFormat Format> + bool SubpixelReader<Format, ImageInterpolation_Bilinear>::GetValue(PixelType& target, + float x, + float y) + { + x -= 0.5f; + y -= 0.5f; + + if (x < 0 || + y < 0) + { + return false; + } + else + { + unsigned int ux = static_cast<unsigned int>(std::floor(x)); + unsigned int uy = static_cast<unsigned int>(std::floor(y)); + + float f00, f01, f10, f11; + + if (ux < GetWidth() && + uy < GetHeight()) + { + f00 = Orthanc::ImageTraits<Format>::GetFloatPixel(GetSource(), ux, uy); + } + else + { + return false; + } + + if (ux + 1 < GetWidth()) + { + f01 = Orthanc::ImageTraits<Format>::GetFloatPixel(GetSource(), ux + 1, uy); + } + else + { + f01 = f00; + } + + if (uy + 1 < GetHeight()) + { + f10 = Orthanc::ImageTraits<Format>::GetFloatPixel(GetSource(), ux, uy + 1); + } + else + { + f10 = f00; + } + + if (ux + 1 < GetWidth() && + uy + 1 < GetHeight()) + { + f11 = Orthanc::ImageTraits<Format>::GetFloatPixel(GetSource(), ux + 1, uy + 1); + } + else + { + f11 = f00; + } + + float ax = x - static_cast<float>(ux); + float ay = y - static_cast<float>(uy); + float value = GeometryToolbox::ComputeBilinearInterpolationUnitSquare(ax, ay, f00, f01, f10, f11); + + Traits::FloatToPixel(target, value); + return true; + } + } +}
--- a/Resources/CMake/OrthancStoneConfiguration.cmake Wed Mar 14 17:48:02 2018 +0100 +++ b/Resources/CMake/OrthancStoneConfiguration.cmake Thu Mar 15 12:12:01 2018 +0100 @@ -178,6 +178,7 @@ ${ORTHANC_STONE_DIR}/Framework/Toolbox/Extent2D.cpp ${ORTHANC_STONE_DIR}/Framework/Toolbox/FiniteProjectiveCamera.cpp ${ORTHANC_STONE_DIR}/Framework/Toolbox/GeometryToolbox.cpp + ${ORTHANC_STONE_DIR}/Framework/Toolbox/ImageGeometry.cpp ${ORTHANC_STONE_DIR}/Framework/Toolbox/LinearAlgebra.cpp ${ORTHANC_STONE_DIR}/Framework/Toolbox/MessagingToolbox.cpp ${ORTHANC_STONE_DIR}/Framework/Toolbox/OrientedBoundingBox.cpp