diff --git a/Applications/Projections/otbRigidTransformResample.cxx b/Applications/Projections/otbRigidTransformResample.cxx index 7b869a8ffaca6f606f4e78c245026146629b9015..9629c8790470862fd60e600c2234b1d5e4ac492e 100644 --- a/Applications/Projections/otbRigidTransformResample.cxx +++ b/Applications/Projections/otbRigidTransformResample.cxx @@ -18,11 +18,35 @@ #include "otbWrapperApplication.h" #include "otbWrapperApplicationFactory.h" +#include "itkLinearInterpolateImageFunction.h" +#include "otbBCOInterpolateImageFunction.h" +#include "itkNearestNeighborInterpolateImageFunction.h" + +//Transform +#include "otbCompositeTransform.h" +#include "itkScalableAffineTransform.h" #include "itkTranslationTransform.h" +#include "itkIdentityTransform.h" +#include "itkScaleTransform.h" +#include "itkCenteredRigid2DTransform.h" + #include "otbStreamingResampleImageFilter.h" namespace otb { +enum +{ + Interpolator_NNeighbor, + Interpolator_Linear, + Interpolator_BCO +}; + +enum +{ + Transform_Translation, + Transform_Rotation, +}; + namespace Wrapper { @@ -41,6 +65,15 @@ public: typedef itk::TranslationTransform<double, FloatVectorImageType::ImageDimension> TransformType; typedef otb::StreamingResampleImageFilter<FloatVectorImageType, FloatVectorImageType, double> ResampleFilterType; + //typedef itk::IdentityTransform<double, FloatVectorImageType::ImageDimension> IdentityTransformType; + + typedef itk::ScalableAffineTransform<double, FloatVectorImageType::ImageDimension> ScalableTransformType; + typedef ScalableTransformType::OutputVectorType OutputVectorType; + + /** Rotation transform */ + typedef itk::CenteredRigid2DTransform< double > RotationTransformType; + typedef RotationTransformType::ScalarType ScalarType; + itkTypeMacro(RigidTransformResample, otb::Application); private: @@ -55,7 +88,7 @@ private: SetDocLimitations("None"); SetDocAuthors("OTB-Team"); SetDocSeeAlso("Translation"); - + AddDocTag("Conversion"); AddDocTag(Tags::Geometry); @@ -63,18 +96,64 @@ private: SetParameterDescription("in","The input image to translate."); AddParameter(ParameterType_OutputImage, "out", "Output image"); SetParameterDescription("out","The translated output image."); - AddParameter(ParameterType_Float, "tx", "The X translation (in physical units)"); - SetParameterDescription("tx","The translation value along X axis (in physical units)."); - AddParameter(ParameterType_Float, "ty", "The Y translation (in physical units)"); - SetParameterDescription("ty","The translation value along Y axis (in physical units)"); + + //Transform + AddParameter(ParameterType_Group,"transform","Transform parameters"); + SetParameterDescription("transform","This group of parameters allows to set the transformation to apply."); + + AddParameter(ParameterType_Choice, "transform.type", "Type of transformation"); + SetParameterDescription("transform.type","Type of transformation. Available transformations are translation and rotation with scaling factor"); + + AddChoice("transform.type.translation", "translation"); + SetParameterDescription("transform.type.translation","translation"); + + AddParameter(ParameterType_Float,"transform.type.translation.tx", "The X translation (in physical units)"); + SetParameterDescription("transform.type.translation.tx","The translation value along X axis (in physical units)."); + SetDefaultParameterFloat("transform.type.translation.tx",0.); + AddParameter(ParameterType_Float,"transform.type.translation.ty", "The Y translation (in physical units)"); + SetParameterDescription("transform.type.translation.ty","The translation value along Y axis (in physical units)"); + SetDefaultParameterFloat("transform.type.translation.tx",0.); + + AddChoice("transform.type.rotation", "rotation"); + SetParameterDescription("transform.type.rotation","rotation"); + + AddParameter(ParameterType_Float, "transform.type.rotation.angle", "Rotation angle"); + SetParameterDescription("transform.type.rotation.angle","The rotation angle in degree (values between -180 and 180)"); + SetDefaultParameterFloat("transform.type.rotation.angle",0.); + + AddParameter(ParameterType_Float, "transform.type.rotation.scalex", "X factor"); + SetParameterDescription("transform.type.rotation.scalex","X factor"); + SetDefaultParameterFloat("transform.type.rotation.scalex",1.); + + AddParameter(ParameterType_Float, "transform.type.rotation.scaley", "Y factor"); + SetParameterDescription("transform.type.rotation.scaley","Y factor"); + SetDefaultParameterFloat("transform.type.rotation.scaley",1.); + + // Interpolators + AddParameter(ParameterType_Choice, "interpolator", "Interpolation"); + SetParameterDescription("interpolator","This group of parameters allows to define how the input image will be interpolated during resampling."); + AddChoice("interpolator.nn", "Nearest Neighbor interpolation"); + SetParameterDescription("interpolator.nn","Nearest neighbor interpolation leads to poor image quality, but it is very fast."); + AddChoice("interpolator.linear", "Linear interpolation"); + SetParameterDescription("interpolator.linear","Linear interpolation leads to average image quality but is quite fast"); + AddChoice("interpolator.bco", "Bicubic interpolation"); + AddParameter(ParameterType_Radius, "interpolator.bco.radius", "Radius for bicubic interpolation"); + SetParameterDescription("interpolator.bco.radius","This parameter allows to control the size of the bicubic interpolation filter. If the target pixel size is higher than the input pixel size, increasing this parameter will reduce aliasing artefacts."); + SetDefaultParameterInt("interpolator.bco.radius", 2); + SetParameterString("interpolator","bco"); + + // RAM available + AddRAMParameter("ram"); + SetParameterDescription("ram","This allows to set the maximum amount of RAM available for processing. As the writing task is time consuming, it is better to write large pieces of data, which can be achieved by increasing this parameter (pay attention to your system capabilities)"); // Doc example parameter settings SetDocExampleParameterValue("in", "qb_toulouse_sub.tif"); SetDocExampleParameterValue("out", "rigitTransformImage.tif"); - SetDocExampleParameterValue("tx", "5"); - SetDocExampleParameterValue("ty", "5"); + SetDocExampleParameterValue("transform.type", "rotation"); + SetDocExampleParameterValue("transform.type.scalex", "4."); + SetDocExampleParameterValue("transform.type.scaley", "4."); } - + void DoUpdateParameters() { // Nothing to do here : all parameters are independent @@ -82,32 +161,201 @@ private: void DoExecute() { - FloatVectorImageType* inputImage = GetParameterImage("in"); - - m_Transform = TransformType::New(); - TransformType::OutputVectorType offset; - offset[0] = GetParameterFloat("tx"); - offset[1] = GetParameterFloat("ty"); - otbAppLogINFO( << "Offset : " << offset ); - m_Transform->SetOffset(offset); m_Resampler = ResampleFilterType::New(); m_Resampler->SetInput(inputImage); - m_Resampler->SetTransform(m_Transform); - m_Resampler->SetOutputParametersFromImage(inputImage); + + // Get Interpolator + switch ( GetParameterInt("interpolator") ) + { + case Interpolator_Linear: + { + typedef itk::LinearInterpolateImageFunction<FloatVectorImageType, + double> LinearInterpolationType; + LinearInterpolationType::Pointer interpolator = LinearInterpolationType::New(); + m_Resampler->SetInterpolator(interpolator); + } + break; + case Interpolator_NNeighbor: + { + typedef itk::NearestNeighborInterpolateImageFunction<FloatVectorImageType, + double> NearestNeighborInterpolationType; + NearestNeighborInterpolationType::Pointer interpolator = NearestNeighborInterpolationType::New(); + m_Resampler->SetInterpolator(interpolator); + } + break; + case Interpolator_BCO: + { + typedef otb::BCOInterpolateImageFunction<FloatVectorImageType> BCOInterpolationType; + BCOInterpolationType::Pointer interpolator = BCOInterpolationType::New(); + interpolator->SetRadius(GetParameterInt("interpolator.bco.radius")); + m_Resampler->SetInterpolator(interpolator); + } + break; + } + + // Get Transform + switch ( GetParameterInt("transform.type") ) + { + case Transform_Translation: + { + TransformType::Pointer transform = TransformType::New(); + TransformType::OutputVectorType offset; + offset[0] = GetParameterFloat("transform.type.translation.tx"); + offset[1] = GetParameterFloat("transform.type.translation.ty"); + otbAppLogINFO( << "Offset : " << offset ); + transform->SetOffset(offset); + m_Resampler->SetTransform(transform); + m_Resampler->SetOutputParametersFromImage(inputImage); + } + break; + + case Transform_Rotation: + { + ScalableTransformType::Pointer transform = ScalableTransformType::New(); + + FloatVectorImageType::IndexType origin = inputImage->GetLargestPossibleRegion().GetIndex(); + FloatVectorImageType::SpacingType spacing = inputImage->GetSpacing(); + + FloatVectorImageType::IndexType center; + center[0] = origin[0] + inputImage->GetLargestPossibleRegion().GetSize()[0] / 2.0; + center[1] = origin[1] + inputImage->GetLargestPossibleRegion().GetSize()[1] / 2.0; + + + FloatVectorImageType::PointType centerPoint; + inputImage->TransformIndexToPhysicalPoint(center, centerPoint); + + //image boundary + FloatVectorImageType::IndexType ULindex, URindex, LRindex, LLindex; + + ULindex[0]=origin[0]; + ULindex[1]=origin[1]; + URindex[0]=origin[0]+ inputImage->GetLargestPossibleRegion().GetSize()[0]; + URindex[1]=origin[1]; + LRindex[0]=origin[0]+ inputImage->GetLargestPossibleRegion().GetSize()[0]; + LRindex[1]=origin[1]+ inputImage->GetLargestPossibleRegion().GetSize()[1]; + LLindex[0]=origin[0]; + LLindex[1]=origin[1]+ inputImage->GetLargestPossibleRegion().GetSize()[1]; + + FloatVectorImageType::PointType orig, ULpoint, URpoint, LRpoint, LLpoint; + inputImage->TransformIndexToPhysicalPoint(ULindex, ULpoint); + inputImage->TransformIndexToPhysicalPoint(URindex, URpoint); + inputImage->TransformIndexToPhysicalPoint(LRindex, LRpoint); + inputImage->TransformIndexToPhysicalPoint(LLindex, LLpoint); + + // Scale Transform + OutputVectorType scale; + scale[0] = 1.0 / GetParameterFloat("transform.type.rotation.scalex"); + scale[1] = 1.0 / GetParameterFloat("transform.type.rotation.scaley"); + + //angle of rotation + ScalarType rot_angle = GetParameterFloat("transform.type.rotation.angle"); + if (rot_angle < -180 || rot_angle > 180) + { + itkExceptionMacro(<<"The rotation angle must value be between -180 and 180."); + } + + transform->SetIdentity(); + if(spacing[0] > 0 && spacing[1] > 0) transform->Rotate2D( rot_angle * CONST_PI_180 ); + else transform->Rotate2D( - rot_angle * CONST_PI_180 ); + + transform->SetCenter( centerPoint ); + transform->Scale( scale ); + + //inverse transform + ScalableTransformType::Pointer inverseTransform = ScalableTransformType::New(); + transform->GetInverse(inverseTransform); + m_Resampler->SetTransform(transform); + + + FloatVectorImageType::PointType ULpointTrans, URpointTrans, LRpointTrans, LLpointTrans, CenterPointTrans; + + ULpointTrans=inverseTransform->TransformPoint(ULpoint); + URpointTrans=inverseTransform->TransformPoint(URpoint); + LRpointTrans=inverseTransform->TransformPoint(LRpoint); + LLpointTrans=inverseTransform->TransformPoint(LLpoint); + CenterPointTrans=inverseTransform->TransformPoint(centerPoint); + + //compute min and max + std::vector<FloatVectorImageType::PointType> voutput; + voutput.push_back(ULpointTrans); + voutput.push_back(URpointTrans); + voutput.push_back(LRpointTrans); + voutput.push_back(LLpointTrans); + + double minX = voutput[0][0]; + double maxX = voutput[0][0]; + double minY = voutput[0][1]; + double maxY = voutput[0][1]; + + for(unsigned int i = 0; i<voutput.size(); i++) + { + // Origins + if ( minX > voutput[i][0] ) + { + minX = voutput[i][0]; + } + if ( minY > voutput[i][1] ) + { + minY = voutput[i][1]; + } + + // Sizes + if ( maxX < voutput[i][0] ) + { + maxX = voutput[i][0]; + } + if ( maxY < voutput[i][1] ) + { + maxY = voutput[i][1]; + } + } + + if( spacing[0] > 0 ) orig[0] = minX; + else orig[0] = maxX; + + if( spacing[1] > 0 ) orig[1] = minY; + else orig[1] = maxY; + + m_Resampler->SetOutputOrigin(orig); + + //size of output image + ResampleFilterType::SizeType size; + size[0]=vcl_abs(maxX-minX); + size[1]=vcl_abs(maxY-minY); + + // Evaluate spacing + FloatVectorImageType::SpacingType OutputSpacing; + OutputSpacing=spacing; + + //FIXME Find a way to update the output image spacing after resampling + //OutputSpacing[0] = scale[0] * spacing[0]; + //OutputSpacing[1] = scale[1] * spacing[1]; + + m_Resampler->SetOutputSpacing(OutputSpacing); + + // Evaluate size + ResampleFilterType::SizeType recomputedSize; + recomputedSize[0] = static_cast<unsigned int>(vcl_floor(vcl_abs(size[0]/OutputSpacing[0]))); + recomputedSize[1] = static_cast<unsigned int>(vcl_floor(vcl_abs(size[1]/OutputSpacing[1]))); + m_Resampler->SetOutputSize( recomputedSize ); + otbAppLogINFO( << "Output image size : " << recomputedSize ); + } + break; + } + FloatVectorImageType::PixelType defaultValue; itk::PixelBuilder<FloatVectorImageType::PixelType>::Zero(defaultValue, inputImage->GetNumberOfComponentsPerPixel()); m_Resampler->SetEdgePaddingValue(defaultValue); + m_Resampler->UpdateOutputInformation(); - // Output Image SetParameterOutputImage("out", m_Resampler->GetOutput()); } ResampleFilterType::Pointer m_Resampler; - TransformType::Pointer m_Transform; }; //class diff --git a/Testing/Applications/Projections/CMakeLists.txt b/Testing/Applications/Projections/CMakeLists.txt index 467ffe590dab63d1cb7980aa8ea8a31cf68d3c28..b4b01e9eb892d9d1ffe66941ffbb3c28b3a874b2 100644 --- a/Testing/Applications/Projections/CMakeLists.txt +++ b/Testing/Applications/Projections/CMakeLists.txt @@ -19,14 +19,16 @@ OTB_TEST_APPLICATION(NAME apTvPrOrthorectification_UTM ${BASELINE}/owTvOrthorectifTest_UTM.tif ${TEMP}/apTvPrOrthorectifTest_UTM.tif) -ENDIF(OTB_DATA_USE_LARGEINPUT) +ENDIF(OTB_DATA_USE_LARGEINPUT) OTB_TEST_APPLICATION(NAME apTvPrRigidTransformResample APP RigidTransformResample OPTIONS -in ${INPUTDATA}/poupees.tif -out ${TEMP}/apTvPrRigidTransformResampleTest.tif - -tx 5 - -ty -5 + -transform.type translation + -transform.type.translation.tx 5 + -transform.type.translation.ty -5 + -interpolator nn VALID --compare-image ${NOTOL} ${BASELINE}/owTvRigidTransformResampleTest.tif ${TEMP}/apTvPrRigidTransformResampleTest.tif) @@ -79,48 +81,48 @@ OTB_TEST_APPLICATION(NAME apTvPrBundleToPerfectSensor VALID --compare-image ${EPSILON_7} ${BASELINE}/apTvPrBundleToPerfectSensor.tif ${TEMP}/apTvPrBundleToPerfectSensor.tif) - + OTB_TEST_APPLICATION(NAME apTvPrVectorDataReprojectionFromMapToImage APP VectorDataReprojection OPTIONS -in.vd ${INPUTDATA}/ToulousePoints-examples.shp -out.vd ${TEMP}/apTvPrVectorDataReprojectionFromMapToImage.shp - -out.proj image + -out.proj image -out.proj.image.in ${INPUTDATA}/QB_Toulouse_Ortho_XS.tif VALID --compare-ogr ${NOTOL} ${BASELINE_FILES}/prTvVectorDataProjectionFilterFromMapToImage.shp ${TEMP}/apTvPrVectorDataReprojectionFromMapToImage.shp) -# With QGIS the two vectordata are equal +# With QGIS the two vectordata are equal OTB_TEST_APPLICATION(NAME apTuPrVectorDataReprojectionFromImageToMap APP VectorDataReprojection OPTIONS -in.vd ${INPUTDATA}/ToulousePointsUTM31N.shp -in.kwl ${INPUTDATA}/QB_Toulouse_Ortho_XS.tif -out.vd ${TEMP}/apTvPrVectorDataReprojectionFromImageToMap.shp - -out.proj user + -out.proj user -out.proj.user.map utm -out.proj.user.map.utm.northhem true -out.proj.user.map.utm.zone 31) # VALID --compare-ogr ${NOTOL} # ${INPUTDATA}/ToulousePoints-examples.shp -# ${TEMP}/apTvPrVectorDataReprojectionFromImageToMap.shp) - - +# ${TEMP}/apTvPrVectorDataReprojectionFromImageToMap.shp) + + OTB_TEST_APPLICATION(NAME apTvPrVectorDataReprojectionFromMapToGeo APP VectorDataReprojection OPTIONS -in.vd ${INPUTDATA}/ToulousePoints-examples.shp -out.vd ${TEMP}/apTvPrVectorDataReprojectionFromMapToGeo.kml - -out.proj user + -out.proj user -out.proj.user.map wgs VALID --compare-ogr ${NOTOL} ${BASELINE_FILES}/prTvVectorDataProjectionFilterFromMapToGeo.kml ${TEMP}/apTvPrVectorDataReprojectionFromMapToGeo.kml) - + OTB_TEST_APPLICATION(NAME apTvPrVectorDataReprojectionFromMapToMap APP VectorDataReprojection OPTIONS -in.vd ${INPUTDATA}/ToulousePoints-examples.shp -out.vd ${TEMP}/apTvPrVectorDataReprojectionFromMapToMap.kml - -out.proj user + -out.proj user -out.proj.user.map lambert93 VALID --compare-ascii ${NOTOL} ${BASELINE_FILES}/prTvVectorDataProjectionFilterFromMapToMap.kml @@ -130,20 +132,20 @@ OTB_TEST_APPLICATION(NAME apTvPrVectorDataReprojectionFromMapToMap2 APP VectorDataReprojection OPTIONS -in.vd ${BASELINE_FILES}/prTvVectorDataProjectionFilterFromMapToMap.kml -out.vd ${TEMP}/apTvPrVectorDataReprojectionFromMapToMap2.kml - -out.proj user + -out.proj user -out.proj.user.map utm -out.proj.user.map.utm.northhem true -out.proj.user.map.utm.zone 31 VALID --compare-ogr ${NOTOL} ${BASELINE_FILES}/apTvPrVectorDataReprojectionFromMapToMap2.kml ${TEMP}/apTvPrVectorDataReprojectionFromMapToMap2.kml) - + IF(OTB_DATA_USE_LARGEINPUT) OTB_TEST_APPLICATION(NAME apTvPrVectorDataReprojectionFromMapToSensor APP VectorDataReprojection OPTIONS -in.vd ${INPUTDATA}/ToulousePoints-examples.shp -out.vd ${TEMP}/apTvPrVectorDataReprojectionFromMapToSensor.shp - -out.proj image + -out.proj image -out.proj.image.in ${LARGEINPUT}/QUICKBIRD/TOULOUSE/000000128955_01_P001_PAN/02APR01105228-P1BS-000000128955_01_P001.TIF VALID --compare-ogr ${NOTOL} ${BASELINE_FILES}/prTvVectorDataProjectionFilterFromMapToSensor.shp @@ -151,23 +153,23 @@ OTB_TEST_APPLICATION(NAME apTvPrVectorDataReprojectionFromMapToSensor ENDIF(OTB_DATA_USE_LARGEINPUT) -# With QGIS the two vectordata are equal +# With QGIS the two vectordata are equal OTB_TEST_APPLICATION(NAME apTuPrVectorDataReprojectionFromGeoToMap APP VectorDataReprojection OPTIONS -in.vd ${INPUTDATA}/ToulousePointsWGS.kml -out.vd ${TEMP}/apTvPrVectorDataReprojectionFromGeoToMap.shp - -out.proj image + -out.proj image -out.proj.image.in ${INPUTDATA}/QB_Toulouse_Ortho_PAN.tif) # VALID --compare-ogr ${NOTOL} # ${INPUTDATA}/ToulousePoints-examples.shp # ${TEMP}/apTvPrVectorDataReprojectionFromGeoToMap.shp) - - - - \ No newline at end of file + + + +