diff --git a/ChangeLog.md b/ChangeLog.md index 97de2a81c2..90ed490343 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -2,8 +2,6 @@ ## New features / critical changes -## Changes - ## Bug fixes - *General* @@ -40,6 +38,8 @@ - New VoronoiMapComplete class to store the full Voronoi map (with all co-cycling sites (Robin Lamy, David Coeurjolly, Isabelle Sivignon [#1605](https://github.com/DGtal-team/DGtal/pull/1605)) + - Add Affine Transformation in the geometry transformation module + (Phuc Ngo,[#1571](https://github.com/DGtal-team/DGtal/pull/1571)) - *DEC* - New discrete differential operators on polygonal meshes have been diff --git a/examples/images/CMakeLists.txt b/examples/images/CMakeLists.txt index ddf7c7d200..53ca4c4d4c 100644 --- a/examples/images/CMakeLists.txt +++ b/examples/images/CMakeLists.txt @@ -18,6 +18,7 @@ set(DGTAL_EXAMPLES_SRC exampleRigidtransformation3d exampleArrayImageAdapter exampleConstImageFunctorHolder + exampleAffinetransformation2d ) if( WITH_HDF5 ) diff --git a/examples/images/exampleAffinetransformation2d.cpp b/examples/images/exampleAffinetransformation2d.cpp new file mode 100644 index 0000000000..05ec9c66f3 --- /dev/null +++ b/examples/images/exampleAffinetransformation2d.cpp @@ -0,0 +1,109 @@ +/** + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + **/ + +/** + * @file images/exampleAffinetransformation2d.cpp + * @ingroup Examples + * @author Phuc Ngo (\c hoai-diem-phuc.ngo@loria.fr ) + * Laboratoire Lorrain de Recherche en Informatique et ses Applications (LORIA), France + * + * @date 12/05/2021 + * + * An example file named affinetransformation2d. + * + * This file is part of the DGtal library. + */ + +/** +* Example of 2D affine transformation using forward and backward models. + @see @ref moduleGeometricTransform + \image html church_AffineBackward.png "Result for backward model" +* \example images/exampleAffinetransformation2d.cpp +**/ + +/////////////////////////////////////////////////////////////////////////////// +#include +#include +#include "DGtal/images/ImageSelector.h" +#include "DGtal/images/ImageContainerBySTLVector.h" +#include "DGtal/images/ConstImageAdapter.h" +#include "ConfigExamples.h" +#include "DGtal/helpers/StdDefs.h" +#include "DGtal/base/Common.h" +#include "DGtal/io/readers/PGMReader.h" +#include "DGtal/io/writers/GenericWriter.h" +//! [include] +#include "DGtal/images/AffineTransformation2D.h" +//! [include] +/////////////////////////////////////////////////////////////////////////////// + +using namespace std; +using namespace DGtal; +using namespace functors; +using namespace Z2i; + +int main( int , char** ) +{ + typedef ImageSelector::Type Image; + //! [def] + typedef ForwardAffineTransformation2D < Space > ForwardTrans; + typedef BackwardAffineTransformation2D < Space > BackwardTrans; + typedef DomainGeometricTransformation2D < Domain, ForwardTrans > MyDomainTransformer; + typedef MyDomainTransformer::Bounds Bounds; + //! [def] + trace.beginBlock ( "Example AffineTransformation2d" ); + //! [trans] + ForwardTrans forwardTrans(1.2, -1, 1.6, 2, RealPoint ( 5, 5 )); + BackwardTrans backwardTrans(1.2, -1, 1.6, 2, RealPoint ( 5, 5 )); + //! [trans] + //![init_domain_helper] + MyDomainTransformer domainTransformer ( forwardTrans ); + + Image image = PGMReader::importPGM ( "../church-small.pgm" ); + //! [domain] + Bounds bounds = domainTransformer ( image.domain() ); + Domain transformedDomain ( bounds.first, bounds.second ); + //! [domain] + + trace.beginBlock ( "Backward - Eulerian model" ); + //! [backward] + //![init_domain_helper] + Image backwardTransformedImage ( transformedDomain ); + for ( Domain::ConstIterator it = backwardTransformedImage.domain().begin(); it != backwardTransformedImage.domain().end(); ++it ) + { + Point p = backwardTrans ( *it ); + if(image.domain().isInside(p)) + backwardTransformedImage.setValue ( *it, image(backwardTrans ( *it )) ); + } + backwardTransformedImage >> "backward_transform.pgm"; + //! [backward] + trace.endBlock(); + + trace.beginBlock( "Forward - Lagrangian model" ); + Image forwardTransformedImage ( transformedDomain ); + //! [forward] + for ( Domain::ConstIterator it = image.domain().begin(); it != image.domain().end(); ++it ) + { + forwardTransformedImage.setValue ( forwardTrans ( *it ), image (*it)) ; + } + forwardTransformedImage >> "forward_transform.pgm"; + //! [forward] + trace.endBlock(); + trace.endBlock(); + return 0; +} +// // +/////////////////////////////////////////////////////////////////////////////// diff --git a/src/DGtal/images/AffineTransformation2D.h b/src/DGtal/images/AffineTransformation2D.h new file mode 100755 index 0000000000..9a4b770153 --- /dev/null +++ b/src/DGtal/images/AffineTransformation2D.h @@ -0,0 +1,211 @@ +/** + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + **/ + +#pragma once + +/** + * @file AffineTransformation2D.h + * @author Phuc Ngo (\c hoai-diem-phuc.ngo@loria.fr ) + * Laboratoire Lorrain de Recherche en Informatique et ses Applications (LORIA), France + * + * @date 01/05/2021 + * + * This file is part of the DGtal library. + */ + +#if defined(AffineTransformation2D_RECURSES) +#error Recursive header files inclusion detected in AffineTransformation2D.h +#else // defined(AffineTransformation2D_RECURSES) +/** Prevents recursive inclusion of headers. */ +#define AffineTransformation2D_RECURSES + +#if !defined AffineTransformation2D_h +/** Prevents repeated inclusion of headers. */ +#define AffineTransformation2D_h + +////////////////////////////////////////////////////////////////////////////// +// Inclusions +#include +#include +#include +#include +#include "DGtal/base/Common.h" +#include "DGtal/kernel/BasicPointFunctors.h" +#include +#include +#include +#include "DGtal/images/ImageSelector.h" +#include "DGtal/images/GeometricTransformation2D.h" +////////////////////////////////////////////////////////////////////////////// + +namespace DGtal +{ +namespace functors +{ +///////////////////////////////////////////////////////////////////////////// +// Template class ForwardAffineTransformation2D +/** + * Description of template functor like class 'ForwardAffineTransformation2D'

+ * \brief Aim: implements forward rigid transformation of point in the 2D integer space. + * Warring: This version uses closest neighbor interpolation. + * + * @tparam TSpace a 2 dimensional space. + * @tparam TInputValue type of the input point e.g., TSpace::RealPoint + * @tparam TOutputValue type of the output point e.g., TSpace::Point + * @tparam TFunctor a functor operating on the output e.g., a rounding function. + * + * @see exampleAffineTransformation3d.cpp + */ +template < typename TSpace, typename TInputValue = typename TSpace::RealPoint, typename TOutputValue = typename TSpace::Point, + typename TFunctor = VectorRounding < TInputValue, TOutputValue > > +class ForwardAffineTransformation2D : public GeometricTransformation2D +{ + ///Checking concepts + BOOST_CONCEPT_ASSERT(( concepts::CSpace )); + BOOST_STATIC_ASSERT(( TSpace::dimension == 2 )); + BOOST_STATIC_ASSERT(( TOutputValue::dimension == 2 )); + BOOST_STATIC_ASSERT(( TInputValue::dimension == 2 )); + + // ----------------------- Types ------------------------------ +public: + typedef typename TSpace::RealPoint RealPoint; + typedef typename TSpace::RealVector RealVector; + typedef Eigen::Matrix RealMatrix; + + // ----------------------- Interface -------------------------------------- +public: + /** + * Constructor. + * @param aOrigin the center of affine transform. + * @param aMatrix the affine matrix. + * @param aTranslate the 2D dimensional vector which represents translation. + */ + ForwardAffineTransformation2D ( const RealPoint & aOrigin, const RealMatrix & aMatrix, const RealVector & aTranslate ) + { + this->origin = aOrigin; + BOOST_ASSERT((aMatrix(0,0)*aMatrix(1,1)!=aMatrix(1,0)*aMatrix(0,1))); + this->transform_matrix = aMatrix; + this->translation = aTranslate; + } + + /** + * Constructor. + * @param a11, a12, a21, a22 the values of affine matrix. + * @param aTranslate the 2D dimensional vector which represents translation. + */ + ForwardAffineTransformation2D ( const double a11, const double a12, const double a21, const double a22, const RealVector & aTranslate ) + { + BOOST_ASSERT((a11*a22!=a12*a21)); + this->origin = RealPoint(0,0); + RealMatrix aMatrix; + aMatrix(0,0) = a11; + aMatrix(0,1) = a12; + aMatrix(1,0) = a21; + aMatrix(1,1) = a22; + this->transform_matrix = aMatrix; + this->translation = aTranslate; + } +}; //end of class ForwardAffineTransformation2D + +///////////////////////////////////////////////////////////////////////////// +// Template class BackwardAffineTransformation2D +/** + * Description of template functor like class 'BackwardAffineTransformation2D'

+ * \brief Aim: implements backward rigid transformation of point in the 2D integer space. + * Warring: This version uses closest neighbor interpolation. + * + * @tparam TSpace a 2 dimensional space. + * @tparam TInputValue type of the input point e.g., TSpace::RealPoint + * @tparam TOutputValue type of the output point e.g., TSpace::Point + * @tparam TFunctor a functor operating on the output e.g., a rounding function. + * + * @see exampleAffineTransformation3d.cpp + */ +template < typename TSpace, typename TInputValue = typename TSpace::RealPoint, typename TOutputValue = typename TSpace::Point, + typename TFunctor = VectorRounding < TInputValue, TOutputValue > > +class BackwardAffineTransformation2D : public GeometricTransformation2D +{ + ///Checking concepts + BOOST_CONCEPT_ASSERT(( concepts::CSpace )); + BOOST_STATIC_ASSERT(( TSpace::dimension == 2 )); + BOOST_STATIC_ASSERT(( TOutputValue::dimension == 2 )); + //BOOST_STATIC_ASSERT(( TInputValue::dimension == 2 )); + + // ----------------------- Types ------------------------------ +public: + typedef typename TSpace::RealPoint RealPoint; + typedef typename TSpace::RealVector RealVector; + typedef Eigen::Matrix RealMatrix; + + // ----------------------- Interface -------------------------------------- +public: + /** + * Constructor. + * @param aOrigin the center of affine transform. + * @param aMatrix the affine matrix. + * @param aTranslate the 2D dimensional vector which represents translation. + */ + BackwardAffineTransformation2D ( const RealPoint& aOrigin, const RealMatrix & aMatrix, const RealVector & aTranslate ) + { + this->origin = aOrigin; + BOOST_ASSERT((aMatrix(0,0)*aMatrix(1,1)!=aMatrix(1,0)*aMatrix(0,1))); + this->transform_matrix = aMatrix; + this->translation = aTranslate; + } + + /** + * Constructor. + * @param a11, a12, a21, a22 the values of affine matrix. + * @param aTranslate the 2D dimensional vector which represents translation. + */ + BackwardAffineTransformation2D ( const double a11, const double a12, const double a21, const double a22, const RealVector & aTranslate ) + { + BOOST_ASSERT((a11*a22!=a12*a21)); + this->origin = RealPoint(0,0); + RealMatrix aMatrix; + double det = a11*a22-a21*a12; + aMatrix(0,0) = a22/det; + aMatrix(0,1) = -a12/det; + aMatrix(1,0) = -a21/det; + aMatrix(1,1) = a11/det; + this->transform_matrix = aMatrix; + this->translation = aTranslate; + } + + TOutputValue operator()( const TInputValue & aInput ) const override + { + RealPoint p; + double a = this->transform_matrix(0,0);//transform_matrix.at(0).at(0); + double b = this->transform_matrix(0,1);//transform_matrix.at(0).at(1); + double c = this->transform_matrix(1,0);//transform_matrix.at(1).at(0); + double d = this->transform_matrix(1,1);//transform_matrix.at(1).at(1); + p[0] = ( a * ( aInput[0] - this->origin[0] - this->translation[0] ) + + b * ( aInput[1] - this->origin[1] - this->translation[1] ) ) + this->origin[0]; + + p[1] = ( c * ( aInput[0] - this->origin[0] - this->translation[0] ) + + d * ( aInput[1] - this->origin[1] - this->translation[1] ) ) + this->origin[1]; + return this->functor ( p ); + } +}; + +}// namespace DGtal::functors +}// namespace DGtal + +#endif // !defined AffineTransformation2D_h + +#undef AffineTransformation2D_RECURSES +#endif // else defined(AffineTransformation2D_RECURSES) + diff --git a/src/DGtal/images/GeometricTransformation2D.h b/src/DGtal/images/GeometricTransformation2D.h new file mode 100755 index 0000000000..41633a95ef --- /dev/null +++ b/src/DGtal/images/GeometricTransformation2D.h @@ -0,0 +1,214 @@ +/** + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + **/ + +#pragma once + +/** + * @file GeometricTransformation2D.h + * @author Phuc Ngo (\c hoai-diem-phuc.ngo@loria.fr ) + * Laboratoire Lorrain de Recherche en Informatique et ses Applications (LORIA), France + * + * @date 08/01/2021 + * + * This file is part of the DGtal library. + */ + +#if defined(GeometricTransformation2D_RECURSES) +#error Recursive header files inclusion detected in GeometricTransformation2D.h +#else // defined(GeometricTransformation2D_RECURSES) +/** Prevents recursive inclusion of headers. */ +#define GeometricTransformation2D_RECURSES + +#if !defined GeometricTransformation2D_h +/** Prevents repeated inclusion of headers. */ +#define GeometricTransformation2D_h + +////////////////////////////////////////////////////////////////////////////// +// Inclusions +#include +#include +#include +#include +#include "DGtal/base/Common.h" +#include "DGtal/kernel/BasicPointFunctors.h" +#include +#include +#include +#include +////////////////////////////////////////////////////////////////////////////// + +namespace DGtal +{ +namespace functors +{ +///////////////////////////////////////////////////////////////////////////// +// Template class GeometricTransformation2D +/** + * Description of template functor like class 'GeometricTransformation2D'

+ * \brief Aim: implements geometric transformation of point in the 2D integer space. + * This version uses closest neighbor interpolation. + * + * @tparam TSpace a 2 dimensional space. + * @tparam TInputValue type of the input point e.g., TSpace::RealPoint + * @tparam TOutputValue type of the output point e.g., TSpace::Point + * @tparam TFunctor a functor operating on the output e.g., a rounding function. + * + * @see exampleAffinetransformation2d.cpp + */ +template < typename TSpace, typename TInputValue = typename TSpace::RealPoint, typename TOutputValue = typename TSpace::Point, + typename TFunctor = VectorRounding < TInputValue, TOutputValue > > +class GeometricTransformation2D +{ + ///Checking concepts + BOOST_CONCEPT_ASSERT(( concepts::CSpace )); + BOOST_STATIC_ASSERT(( TSpace::dimension == 2 )); + BOOST_STATIC_ASSERT(( TOutputValue::dimension == 2 )); + BOOST_STATIC_ASSERT(( TInputValue::dimension == 2 )); + + // ----------------------- Types ------------------------------ +public: + typedef typename TSpace::RealPoint RealPoint; + typedef typename TSpace::RealVector RealVector; + typedef Eigen::Matrix RealMatrix; + // ------------------------- Protected Datas ------------------------------ + protected: + TInputValue origin; //origin of transformation + RealMatrix transform_matrix; //Transformation matrix + RealVector translation; //Transmation vector + TFunctor functor; //Interpolation function + // ----------------------- Interface -------------------------------------- +public: + + /** + * Defaut Constructor without parameter + */ + GeometricTransformation2D () { } + /** + * Constructor. + * @param aOrigin the center of rotation. + * @param aMatrix the transformation matrix. + * @param aTranslate the 2D dimensional vector which represents translation. + */ + GeometricTransformation2D ( const RealPoint & aOrigin, const RealMatrix & aMatrix, const RealVector & aTranslate ) + : origin(aOrigin), translation(aTranslate) { + transform_matrix(0,0)=aMatrix(0,0); + transform_matrix(0,1)=aMatrix(0,1); + transform_matrix(1,0)=aMatrix(1,0); + transform_matrix(1,1)=aMatrix(1,1); + //std::cout << "transform_matrix:\n" << transform_matrix << std::endl; + } + + /** + * Operator + * + * @return the transformed point. + */ + inline + virtual + TOutputValue operator()( const TInputValue & aInput ) const + { + RealPoint p; + double a = transform_matrix(0,0);//transform_matrix.at(0).at(0); + double b = transform_matrix(0,1);//transform_matrix.at(0).at(1); + double c = transform_matrix(1,0);//transform_matrix.at(1).at(0); + double d = transform_matrix(1,1);//transform_matrix.at(1).at(1); + p[0] = ( ( a * ( aInput[0] - origin[0] ) + + b * ( aInput[1] - origin[1] ) ) + translation[0] ) + origin[0]; + + p[1] = ( ( c * ( aInput[0] - origin[0] ) + + d * ( aInput[1] - origin[1] ) ) + translation[1] ) + origin[1]; + return functor ( p ); + } + +}; + +///////////////////////////////////////////////////////////////////////////// +// Template class DomainGeometricTransformation2D +/** + * Description of template functor like class 'DomainGeometricTransformation2D'

+ * \brief Aim: implements bounds of transformed domain. + * + * @tparam TDomain a 2 dimensional domain. + * @tparam TGeometricTransformFunctor a functor which represent two dimensional geometric transformation. + * + * @see FIXME: exampleRigidtransformation2d.cpp + */ +template +class DomainGeometricTransformation2D +{ + ///Checking concepts + BOOST_STATIC_ASSERT(( TDomain::dimension == 2 )); + BOOST_CONCEPT_ASSERT(( concepts::CDomain )); + + // ----------------------- Types ------------------------------ +public: + typedef std::pair < typename TDomain::Space::Point, typename TDomain::Space::Point > Bounds; + // ------------------------- Protected Datas ------------------------------ +protected: + const TGeometricTransformFunctor & transform; + // ----------------------- Interface -------------------------------------- +public: + /** + * Constructor. + * @param aFunctor - geometric transformation functor. + */ + DomainGeometricTransformation2D ( const TGeometricTransformFunctor & aFunctor ) : transform ( aFunctor ) {} + + /** + * Operator + * + * @return bounds of the transformed domain. + */ + inline + Bounds operator()( const TDomain & aInput ) const + { + typedef typename TDomain::Point Point; + Point points[4]; + points[0] = transform ( aInput.lowerBound() ); + points[1] = transform ( aInput.upperBound() ); + points[2] = transform ( Point ( aInput.upperBound()[0], aInput.lowerBound()[1] ) ); + points[3] = transform ( Point ( aInput.lowerBound()[0], aInput.upperBound()[1] ) ); + + Point t_min ( INT_MAX, INT_MAX ), t_max ( INT_MIN, INT_MIN ); + for ( unsigned int i = 0; i < 4 ; i++ ) + { + if ( points[i][0] < t_min[0] ) + t_min[0] = points[i][0]; + if ( points[i][1] < t_min[1] ) + t_min[1] = points[i][1]; + + if ( points[i][0] > t_max[0] ) + t_max[0] = points[i][0]; + if ( points[i][1] > t_max[1] ) + t_max[1] = points[i][1]; + } + + Bounds bounds; + bounds.first = t_min; + bounds.second = t_max; + return bounds; + } +}; + +}// namespace DGtal::functors +}// namespace DGtal + +#endif // !defined GeometricTransformation2D_h + +#undef GeometricTransformation2D_RECURSES +#endif // else defined(GeometricTransformation2D_RECURSES) + + diff --git a/src/DGtal/images/doc/images/church_AffineBackward.png b/src/DGtal/images/doc/images/church_AffineBackward.png new file mode 100644 index 0000000000..e57401f621 Binary files /dev/null and b/src/DGtal/images/doc/images/church_AffineBackward.png differ diff --git a/src/DGtal/images/doc/images/church_AffineForward.png b/src/DGtal/images/doc/images/church_AffineForward.png new file mode 100644 index 0000000000..72f055e91c Binary files /dev/null and b/src/DGtal/images/doc/images/church_AffineForward.png differ diff --git a/src/DGtal/images/doc/moduleGeometricalTransformation.dox b/src/DGtal/images/doc/moduleGeometricalTransformation.dox index a65746de32..b036645bee 100644 --- a/src/DGtal/images/doc/moduleGeometricalTransformation.dox +++ b/src/DGtal/images/doc/moduleGeometricalTransformation.dox @@ -18,7 +18,7 @@ namespace DGtal { //---------------------------------------- /*! @page moduleGeometricTransform Geometric transformations -@writers Kacper Pluta +@writers Kacper Pluta, Phuc Ngo @date 2014/07/17 [TOC] @@ -147,7 +147,80 @@ and for backward as well: \image html cat10_backward.jpg \image latex cat10_backward.jpg + +\section secAffine2D Affine transformations + +Affine transformations in \f$\mathbb{R}^n\f$ is a geometric transformation which +preserves collinearity and parallelism. Thus, rigid transformations are also affine transformations. + +In general an affine transformation can be written like: + +\f[ +\begin{array}{l l} + \mathcal{A}: \mathbb{R}^n & \to \mathbb{R}^n \\ + \mathbf{x} & \mapsto \mathbf{A . x} + \mathbf{t} +\end{array} +\f] + +where \f$\textbf{A}\f$ denotes an invertible matrix and \f$\textbf{t}\f$ +translation vector. Then, discrete affine transformations can be +defined as follows: + +\f[ + A = \mathcal{D} \circ \mathcal{A}_{| \mathbb{Z}^n} +\f] + +where \f$\mathcal{D}\f$ is a standard rounding function. + +\subsection subsecAffine2D Affine transformations in 2D discrete space +In 2D, \f$\textbf{A}\f$ is a \f$2\times2\f$ matrix: + +\f[ +\mathbf{A} (a11,a12,a21,a22) = +\begin{pmatrix} +a11 & a12\\ +a21 & a22 +\end{pmatrix} +\f] + +As rigid transformations, DGtal contains two models of discrete affine transformations: forward and backward. +To use them, we need to: + +a) add those includes: +@snippet images/exampleAffinetransformation2d.cpp include + +b) define these types: +@snippet images/exampleAffinetransformation2d.cpp def + +where DomainGeometricTransformation2D is a helper functor which returns +lower and upper bounds of transformed domain. + +c1) create transformation by providing information about the matrix \f$\textbf{A}\f$, and translation: + +@snippet images/exampleAffinetransformation2d.cpp trans + +c2) or create transformation by providing the origin, matrix \f$\textbf{A}\f$, and translation (e.g. @ref src/DGtal/images/AffineTransformation2D.h) + +d) if needed create a transformed domain eg: +@snippet images/exampleAffinetransformation2d.cpp init_domain_helper +@snippet images/exampleAffinetransformation2d.cpp domain + +e1) apply rigid transformation to image with forward model: +@snippet images/exampleAffinetransformation2d.cpp forward + +e2) or with backward which can be used with ConstImageAdapter or ImageAdapter: +@snippet images/exampleAffinetransformation2d.cpp backward + +Below image presents result for froward model and image "Church.pgm" +\image html church_AffineForward.png +\image latex church_AffineForward.png + +and for backward as well: +\image html church_AffineBackward.png +\image latex church_AffineBackward.png + */ + }