[Vv] Bug in clitkAffineTransform
Benoît Presles
benoit.presles at u-bourgogne.fr
Sat Aug 5 12:35:54 CEST 2017
Dear Thomas,
As you suggested me, I am now using clitkAffineTransform to resample my
images as clitkResampleImage does not work / is not maintained.
Unfortunately, I think there is also a bug in this tool. Indeed, the
output direction is not always set.
Please find attached the fix I did.
Best regards,
Ben
-------------- next part --------------
#File clitkAffineTransform.ggo
package "clitkAffineTransform"
version "1.0"
purpose "Resample with or without affine transform of 2D, 3D, 4D images or vector fields"
option "config" - "Config file" string no
option "verbose" v "Verbose" flag off
section "I/O"
option "input" i "Input image filename" string yes
option "output" o "Output image filename" string yes
option "like" l "Resample output this image (size, spacing, origin, direction)" string no
option "transform_grid" - "Apply affine transform to input grid for output's" flag off
section "Options"
option "size" - "New output size if different from input" int no multiple
option "spacing" - "New output spacing if different from input" double no multiple
option "spacinglike" - "New output spacing like this image" string no
option "origin" - "New output origin if different from input" double no multiple
option "direction" - "New output direction if different from input" double no multiple
option "matrix" m "Affine matrix (homogene) filename" string no
option "elastix" e "Read EulerTransform from elastix output file (combine if multiple)" string no
option "rotate" r "Rotation to apply (radians)" double no multiple
option "translate" t "Translation to apply (mm)" double no multiple
option "pad" - "Edge padding value" double no default="0.0"
section "Interpolation"
option "interp" - "Interpolation: 0=NN, 1=Linear, 2=BSpline, 3=BLUT" int no default="1"
option "interpOrder" - "Order if BLUT or BSpline (0-5)" int no default="3"
option "interpSF" - "Sampling factor if BLUT" int no default="20"
option "interpVF" - "Interpolation: 0=NN, 1=Linear, 2=BSpline, 3=BLUT" int no default="1"
option "interpVFOrder" - "Order if BLUT or BSpline (0-5)" int no default="3"
option "interpVFSF" - "Sampling factor if BLUT" int no default="20"
-------------- next part --------------
/*=========================================================================
Program: vv http://www.creatis.insa-lyon.fr/rio/vv
Authors belong to:
- University of LYON http://www.universite-lyon.fr/
- Léon Bérard cancer center http://www.centreleonberard.fr
- CREATIS CNRS laboratory http://www.creatis.insa-lyon.fr
This software is distributed WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
PURPOSE. See the copyright notices for more information.
It is distributed under dual licence
- BSD See included LICENSE.txt file
- CeCILL-B http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
===========================================================================**/
#ifndef clitkAffineTransformGenericFilter_txx
#define clitkAffineTransformGenericFilter_txx
#include <sstream>
#include <istream>
#include <iterator>
#include <itkCenteredEuler3DTransform.h>
#include "clitkElastix.h"
namespace clitk
{
//-----------------------------------------------------------
// Constructor
//-----------------------------------------------------------
template<class args_info_type>
AffineTransformGenericFilter<args_info_type>::AffineTransformGenericFilter()
{
m_Verbose=false;
m_InputFileName="";
}
//-------------------------------------------------------------------
//-----------------------------------------------------------
// Update
//-----------------------------------------------------------
template<class args_info_type>
void AffineTransformGenericFilter<args_info_type>::Update()
{
// Read the Dimension and PixelType
int Dimension, Components;
std::string PixelType;
ReadImageDimensionAndPixelType(m_InputFileName, Dimension, PixelType, Components);
// Call UpdateWithDim
if(Dimension==2) UpdateWithDim<2>(PixelType, Components);
else
if(Dimension==3) UpdateWithDim<3>(PixelType, Components);
else if (Dimension==4)UpdateWithDim<4>(PixelType, Components);
else {
std::cout<<"Error, Only for 2, 3 or 4 Dimensions!!!"<<std::endl ;
return;
}
}
//-------------------------------------------------------------------
//-------------------------------------------------------------------
// Update with the number of dimensions
//-------------------------------------------------------------------
template<class args_info_type>
template<unsigned int Dimension>
void
AffineTransformGenericFilter<args_info_type>::UpdateWithDim(std::string PixelType, int Components)
{
if (m_Verbose) std::cout << "Image was detected to be "<<Dimension<<"D and "<<Components<<" component(s) of "<< PixelType<<"..."<<std::endl;
if (Components==1) {
if(PixelType == "short") {
if (m_Verbose) std::cout << "Launching filter in "<< Dimension <<"D and signed short..." << std::endl;
UpdateWithDimAndPixelType<Dimension, signed short>();
}
else if(PixelType == "unsigned_short"){
if (m_Verbose) std::cout << "Launching filter in "<< Dimension <<"D and unsigned_short..." << std::endl;
UpdateWithDimAndPixelType<Dimension, unsigned short>();
}
else if (PixelType == "unsigned_char") {
if (m_Verbose) std::cout << "Launching filter in "<< Dimension <<"D and unsigned_char..." << std::endl;
UpdateWithDimAndPixelType<Dimension, unsigned char>();
}
// else if (PixelType == "char"){
// if (m_Verbose) std::cout << "Launching filter in "<< Dimension <<"D and signed_char..." << std::endl;
// UpdateWithDimAndPixelType<Dimension, signed char>();
// }
else {
if (m_Verbose) std::cout << "Launching filter in "<< Dimension <<"D and float..." << std::endl;
UpdateWithDimAndPixelType<Dimension, float>();
}
}
else if (Components==3) {
if (m_Verbose) std::cout << "Launching transform in "<< Dimension <<"D and 3D float (DVF)" << std::endl;
UpdateWithDimAndVectorType<Dimension, itk::Vector<float, Dimension> >();
}
else std::cerr<<"Number of components is "<<Components<<", not supported!"<<std::endl;
}
//-------------------------------------------------------------------
//-------------------------------------------------------------------
// Update with the number of dimensions and the pixeltype
//-------------------------------------------------------------------
template<class args_info_type>
template <unsigned int Dimension, class PixelType>
void
AffineTransformGenericFilter<args_info_type>::UpdateWithDimAndPixelType()
{
// ImageTypes
typedef itk::Image<PixelType, Dimension> InputImageType;
typedef itk::Image<PixelType, Dimension> OutputImageType;
// Read the input
typedef itk::ImageFileReader<InputImageType> InputReaderType;
typename InputReaderType::Pointer reader = InputReaderType::New();
reader->SetFileName( m_InputFileName);
reader->Update();
typename InputImageType::Pointer input= reader->GetOutput();
//Filter
typedef itk::ResampleImageFilter< InputImageType,OutputImageType > ResampleFilterType;
typename ResampleFilterType::Pointer resampler = ResampleFilterType::New();
// Matrix
typename itk::Matrix<double, Dimension+1, Dimension+1> matrix;
if (m_ArgsInfo.rotate_given || m_ArgsInfo.translate_given)
{
if (m_ArgsInfo.matrix_given)
{
std::cerr << "You must use either rotate/translate or matrix options" << std::endl;
return;
}
itk::Array<double> transformParameters(2 * Dimension);
transformParameters.Fill(0.0);
if (m_ArgsInfo.rotate_given)
{
if (Dimension == 2)
transformParameters[0] = m_ArgsInfo.rotate_arg[0];
else
for (unsigned int i = 0; i < 3; i++)
transformParameters[i] = m_ArgsInfo.rotate_arg[i];
}
if (m_ArgsInfo.translate_given)
{
int pos = 3;
if (Dimension == 2)
pos = 1;
for (unsigned int i = 0; i < Dimension && i < 3; i++)
transformParameters[pos++] = m_ArgsInfo.translate_arg[i];
}
if (Dimension == 4)
{
matrix.SetIdentity();
itk::Matrix<double, 4, 4> tmp = GetForwardAffineMatrix3D(transformParameters);
for (unsigned int i = 0; i < 3; ++i)
for (unsigned int j = 0; j < 3; ++j)
matrix[i][j] = tmp[i][j];
for (unsigned int i = 0; i < 3; ++i)
matrix[i][4] = tmp[i][3];
}
else
matrix = GetForwardAffineMatrix<Dimension>(transformParameters);
}
else
{
if (m_ArgsInfo.matrix_given)
{
matrix= clitk::ReadMatrix<Dimension>(m_ArgsInfo.matrix_arg);
if (m_Verbose) std::cout << "Reading the matrix..." << std::endl;
}
else {
if (m_ArgsInfo.elastix_given) {
std::string filename(m_ArgsInfo.elastix_arg);
matrix = createMatrixFromElastixFile<Dimension>(filename, m_Verbose);
}
else
matrix.SetIdentity();
}
}
if (m_Verbose)
std::cout << "Using the following matrix:" << std::endl
<< matrix << std::endl;
typename itk::Matrix<double, Dimension, Dimension> rotationMatrix = clitk::GetRotationalPartMatrix(matrix);
typename itk::Vector<double, Dimension> translationPart = clitk::GetTranslationPartMatrix(matrix);
// Transform
typedef itk::AffineTransform<double, Dimension> AffineTransformType;
typename AffineTransformType::Pointer affineTransform=AffineTransformType::New();
affineTransform->SetMatrix(rotationMatrix);
affineTransform->SetTranslation(translationPart);
// Interp
typedef clitk::GenericInterpolator<args_info_type, InputImageType, double> GenericInterpolatorType;
typename GenericInterpolatorType::Pointer genericInterpolator=GenericInterpolatorType::New();
genericInterpolator->SetArgsInfo(m_ArgsInfo);
// Properties
if (m_ArgsInfo.like_given) {
typename InputReaderType::Pointer likeReader=InputReaderType::New();
likeReader->SetFileName(m_ArgsInfo.like_arg);
likeReader->Update();
resampler->SetOutputParametersFromImage(likeReader->GetOutput());
resampler->SetOutputDirection(likeReader->GetOutput()->GetDirection());
} else if(m_ArgsInfo.transform_grid_flag) {
typename itk::Matrix<double, Dimension+1, Dimension+1> invMatrix( matrix.GetInverse() );
typename itk::Matrix<double, Dimension, Dimension> invRotMatrix( clitk::GetRotationalPartMatrix(invMatrix) );
typename itk::Vector<double,Dimension> invTrans = clitk::GetTranslationPartMatrix(invMatrix);
// Display warning
if (m_ArgsInfo.spacing_given)
std::cout << "Warning --spacing ignored (because --transform_grid_flag)" << std::endl;
if (m_ArgsInfo.origin_given)
std::cout << "Warning --origin ignored (because --transform_grid_flag)" << std::endl;
// Spacing is influenced by affine transform matrix and input direction
typename InputImageType::SpacingType outputSpacing;
outputSpacing = invRotMatrix *
input->GetDirection() *
input->GetSpacing();
// Origin is influenced by translation but not by input direction
typename InputImageType::PointType outputOrigin;
outputOrigin = invRotMatrix *
input->GetOrigin() +
invTrans;
// Size is influenced by affine transform matrix and input direction
// Size is converted to double, transformed and converted back to size type.
vnl_vector<double> vnlOutputSize(Dimension);
for(unsigned int i=0; i< Dimension; i++) {
vnlOutputSize[i] = input->GetLargestPossibleRegion().GetSize()[i];
}
vnlOutputSize = invRotMatrix *
input->GetDirection().GetVnlMatrix() *
vnlOutputSize;
typename OutputImageType::SizeType outputSize;
for(unsigned int i=0; i< Dimension; i++) {
// If the size is negative, we have a flip and we must modify
// the origin and the spacing accordingly.
if(vnlOutputSize[i]<0.) {
vnlOutputSize[i] *= -1.;
outputOrigin[i] = outputOrigin[i] + outputSpacing[i] * (vnlOutputSize[i]-1);
outputSpacing[i] *= -1.;
}
outputSize[i] = lrint(vnlOutputSize[i]);
}
resampler->SetSize( outputSize );
resampler->SetOutputSpacing( outputSpacing );
resampler->SetOutputOrigin( outputOrigin );
} else {
//Size
typename OutputImageType::SizeType outputSize;
if (m_ArgsInfo.size_given) {
for(unsigned int i=0; i< Dimension; i++)
outputSize[i]=m_ArgsInfo.size_arg[i];
} else outputSize=input->GetLargestPossibleRegion().GetSize();
//Spacing
typename OutputImageType::SpacingType outputSpacing;
if (m_ArgsInfo.spacing_given) {
for(unsigned int i=0; i< Dimension; i++)
outputSpacing[i]=m_ArgsInfo.spacing_arg[i];
} else outputSpacing=input->GetSpacing();
//Origin
typename OutputImageType::PointType outputOrigin;
if (m_ArgsInfo.origin_given) {
for(unsigned int i=0; i< Dimension; i++)
outputOrigin[i]=m_ArgsInfo.origin_arg[i];
} else outputOrigin=input->GetOrigin();
//Direction
typename OutputImageType::DirectionType outputDirection;
if (m_ArgsInfo.direction_given) {
for(unsigned int j=0; j< Dimension; j++)
for(unsigned int i=0; i< Dimension; i++)
outputDirection[j][i]=m_ArgsInfo.direction_arg[i+Dimension*j];
} else outputDirection=input->GetDirection();
// Set
resampler->SetSize( outputSize );
resampler->SetOutputSpacing( outputSpacing );
resampler->SetOutputOrigin( outputOrigin );
resampler->SetOutputDirection( outputDirection );
}
if (m_ArgsInfo.spacinglike_given) {
typename InputReaderType::Pointer likeReader=InputReaderType::New();
likeReader->SetFileName(m_ArgsInfo.spacinglike_arg);
likeReader->Update();
// set the support like the image
if (m_ArgsInfo.like_given) {
typename OutputImageType::SizeType outputSize;
outputSize[0] = ceil(resampler->GetSize()[0]*resampler->GetOutputSpacing()[0]
/likeReader->GetOutput()->GetSpacing()[0]);
outputSize[1] = ceil(resampler->GetSize()[1]*resampler->GetOutputSpacing()[1]
/likeReader->GetOutput()->GetSpacing()[1]);
outputSize[2] = ceil(resampler->GetSize()[2]*resampler->GetOutputSpacing()[2]
/likeReader->GetOutput()->GetSpacing()[2]);
if (m_ArgsInfo.verbose_flag) {
std::cout << "Compute the number of pixels such as the support is like " << m_ArgsInfo.like_arg << std::endl;
}
resampler->SetSize( outputSize );
}
resampler->SetOutputSpacing( likeReader->GetOutput()->GetSpacing() );
}
if (m_ArgsInfo.verbose_flag) {
std::cout << "Setting the output size to " << resampler->GetSize() << "..." << std::endl;
std::cout << "Setting the output spacing to " << resampler->GetOutputSpacing() << "..." << std::endl;
std::cout << "Setting the output origin to " << resampler->GetOutputOrigin() << "..." << std::endl;
std::cout << "Setting the output direction to " << resampler->GetOutputDirection() << "..." << std::endl;
}
resampler->SetInput( input );
resampler->SetTransform( affineTransform );
resampler->SetInterpolator( genericInterpolator->GetInterpolatorPointer());
resampler->SetDefaultPixelValue( static_cast<PixelType>(m_ArgsInfo.pad_arg) );
try {
resampler->Update();
} catch(itk::ExceptionObject) {
std::cerr<<"Error resampling the image"<<std::endl;
}
typename OutputImageType::Pointer output = resampler->GetOutput();
// Output
typedef itk::ImageFileWriter<OutputImageType> WriterType;
typename WriterType::Pointer writer = WriterType::New();
writer->SetFileName(m_ArgsInfo.output_arg);
writer->SetInput(output);
writer->Update();
}
//-------------------------------------------------------------------
//-------------------------------------------------------------------
// Update with the number of dimensions and the pixeltype (components)
//-------------------------------------------------------------------
template<class args_info_type>
template<unsigned int Dimension, class PixelType>
void AffineTransformGenericFilter<args_info_type>::UpdateWithDimAndVectorType()
{
// ImageTypes
typedef itk::Image<PixelType, Dimension> InputImageType;
typedef itk::Image<PixelType, Dimension> OutputImageType;
// Read the input
typedef itk::ImageFileReader<InputImageType> InputReaderType;
typename InputReaderType::Pointer reader = InputReaderType::New();
reader->SetFileName( m_InputFileName);
reader->Update();
typename InputImageType::Pointer input= reader->GetOutput();
//Filter
typedef itk::VectorResampleImageFilter< InputImageType,OutputImageType, double > ResampleFilterType;
typename ResampleFilterType::Pointer resampler = ResampleFilterType::New();
// Matrix
typename itk::Matrix<double, Dimension+1, Dimension+1> matrix;
if (m_ArgsInfo.rotate_given || m_ArgsInfo.translate_given)
{
if (m_ArgsInfo.matrix_given)
{
std::cerr << "You must use either rotate/translate or matrix options" << std::endl;
return;
}
itk::Array<double> transformParameters(2 * Dimension);
transformParameters.Fill(0.0);
if (m_ArgsInfo.rotate_given)
{
if (Dimension == 2)
transformParameters[0] = m_ArgsInfo.rotate_arg[0];
else
for (unsigned int i = 0; i < 3; i++)
transformParameters[i] = m_ArgsInfo.rotate_arg[i];
}
if (m_ArgsInfo.translate_given)
{
int pos = 3;
if (Dimension == 2)
pos = 1;
for (unsigned int i = 0; i < Dimension && i < 3; i++)
transformParameters[pos++] = m_ArgsInfo.translate_arg[i];
}
if (Dimension == 4)
{
matrix.SetIdentity();
itk::Matrix<double, 4, 4> tmp = GetForwardAffineMatrix3D(transformParameters);
for (unsigned int i = 0; i < 3; ++i)
for (unsigned int j = 0; j < 3; ++j)
matrix[i][j] = tmp[i][j];
for (unsigned int i = 0; i < 3; ++i)
matrix[i][4] = tmp[i][3];
}
else
matrix = GetForwardAffineMatrix<Dimension>(transformParameters);
}
else
{
if (m_ArgsInfo.matrix_given)
{
matrix= clitk::ReadMatrix<Dimension>(m_ArgsInfo.matrix_arg);
if (m_Verbose) std::cout << "Reading the matrix..." << std::endl;
}
else
matrix.SetIdentity();
}
if (m_Verbose)
std::cout << "Using the following matrix:" << std::endl
<< matrix << std::endl;
typename itk::Matrix<double, Dimension, Dimension> rotationMatrix = clitk::GetRotationalPartMatrix(matrix);
typename itk::Vector<double, Dimension> translationPart = clitk::GetTranslationPartMatrix(matrix);
// Transform
typedef itk::AffineTransform<double, Dimension> AffineTransformType;
typename AffineTransformType::Pointer affineTransform=AffineTransformType::New();
affineTransform->SetMatrix(rotationMatrix);
affineTransform->SetTranslation(translationPart);
// Interp
typedef clitk::GenericVectorInterpolator<args_info_type, InputImageType, double> GenericInterpolatorType;
typename GenericInterpolatorType::Pointer genericInterpolator=GenericInterpolatorType::New();
genericInterpolator->SetArgsInfo(m_ArgsInfo);
// Properties
if (m_ArgsInfo.like_given) {
typename InputReaderType::Pointer likeReader=InputReaderType::New();
likeReader->SetFileName(m_ArgsInfo.like_arg);
likeReader->Update();
resampler->SetSize( likeReader->GetOutput()->GetLargestPossibleRegion().GetSize() );
resampler->SetOutputSpacing( likeReader->GetOutput()->GetSpacing() );
resampler->SetOutputOrigin( likeReader->GetOutput()->GetOrigin() );
resampler->SetOutputDirection( likeReader->GetOutput()->GetDirection() );
} else {
//Size
typename OutputImageType::SizeType outputSize;
if (m_ArgsInfo.size_given) {
for(unsigned int i=0; i< Dimension; i++)
outputSize[i]=m_ArgsInfo.size_arg[i];
} else outputSize=input->GetLargestPossibleRegion().GetSize();
std::cout<<"Setting the size to "<<outputSize<<"..."<<std::endl;
//Spacing
typename OutputImageType::SpacingType outputSpacing;
if (m_ArgsInfo.spacing_given) {
for(unsigned int i=0; i< Dimension; i++)
outputSpacing[i]=m_ArgsInfo.spacing_arg[i];
} else outputSpacing=input->GetSpacing();
std::cout<<"Setting the spacing to "<<outputSpacing<<"..."<<std::endl;
//Origin
typename OutputImageType::PointType outputOrigin;
if (m_ArgsInfo.origin_given) {
for(unsigned int i=0; i< Dimension; i++)
outputOrigin[i]=m_ArgsInfo.origin_arg[i];
} else outputOrigin=input->GetOrigin();
std::cout<<"Setting the origin to "<<outputOrigin<<"..."<<std::endl;
//Direction
typename OutputImageType::DirectionType outputDirection;
if (m_ArgsInfo.direction_given) {
for(unsigned int j=0; j< Dimension; j++)
for(unsigned int i=0; i< Dimension; i++)
outputDirection[j][i]=m_ArgsInfo.direction_arg[i+Dimension*j];
} else outputDirection=input->GetDirection();
std::cout<<"Setting the direction to "<<outputDirection<<"..."<<std::endl;
// Set
resampler->SetSize( outputSize );
resampler->SetOutputSpacing( outputSpacing );
resampler->SetOutputOrigin( outputOrigin );
resampler->SetOutputDirection( outputDirection );
}
resampler->SetInput( input );
resampler->SetTransform( affineTransform );
resampler->SetInterpolator( genericInterpolator->GetInterpolatorPointer());
resampler->SetDefaultPixelValue( static_cast<PixelType>(m_ArgsInfo.pad_arg) );
try {
resampler->Update();
} catch(itk::ExceptionObject) {
std::cerr<<"Error resampling the image"<<std::endl;
}
typename OutputImageType::Pointer output = resampler->GetOutput();
// Output
typedef itk::ImageFileWriter<OutputImageType> WriterType;
typename WriterType::Pointer writer = WriterType::New();
writer->SetFileName(m_ArgsInfo.output_arg);
writer->SetInput(output);
writer->Update();
}
//-------------------------------------------------------------------
} //end clitk
#endif //#define clitkAffineTransformGenericFilter_txx
More information about the vv
mailing list