Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Euler transform with optional upper/lower limits to each parameter #511

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

zigaso
Copy link

@zigaso zigaso commented Aug 18, 2021

Intro

This adds a new transform dubbed the AdvancedLimitedEulerTransform, which implements per axis upper and/or lower limiting. It achieves this functionality by clamping the transform gradient based on the current value of each parameter, using the Softplus function.

In the parameter file, set the transformation as:

(Transform "AdvancedLimitedEulerTransform")

Additional parameter to set are:

// SharpnessOfLimits defines the cutoff sharpness; higher values represent a sharper cutoff
(SharpnessOfLimits "200.0000000000") 
// UpperLimits and LowerLimits are defined to absolute values of each of three Euler angles and translations, ie (a, b, c, tx, ty, tz) 
(UpperLimits 0.5000000000 0.5000000000 0.5000000000 10.0000000000 10.0000000000 10.0000000000)
(LowerLimits -0.5000000000 -0.5000000000 -0.5000000000 -10.0000000000 -10.0000000000 -10.0000000000)

Note: axis limiting is implemented for 3D images and for use with gradient based optimizers! Using 2D input images will throw an error, while using another optimizer will not effectively apply the limits.

Example application

For instance if 3D CT was translated +15 mm along the X axis, and then registered onto its original version, and with the registration limited to +/- 10 mm along the X axis like this (see TransformParameters.tx15.txt):

transformix.exe -in CT_fix.nii.gz -tp TransformParameters.tx15.txt -out .
// convert result.nrrd to CT_fix_tx15.nii using your favourite converter
elastix.exe -f CT_fix.nii.gz -m CT_fix_tx15.nii -p ElastixParameters_tx15.txt -out output

The corresponding output can be found in folder output, ie. from TransformParameters.0.txt see the following line:

(TransformParameters -0.000304 -0.008137 -0.017665 -10.700775 -0.109858 0.028943)

where indeed the translation along the X axis was limited at -10 mm. The limits are also propagated into the transform parameter file:
...
(SharpnessOfLimits "200.0000000000")
(UpperLimits 0.5000000000 0.5000000000 0.5000000000 10.0000000000 10.0000000000 10.0000000000)
(LowerLimits -0.5000000000 -0.5000000000 -0.5000000000 -10.0000000000 -10.0000000000 -10.0000000000)

Additionally, if a limit was reached, this is indicated in the transform parameters file by value close to 1 as follows:

(LowerLimitsReached 0.0000000000 0.0000000000 0.0000000000 0.9990956587 0.0000000000 0.0000000000)
(UpperLimitsReached 0.0000000000 0.0000000000 0.0000000000 0.0000000000 0.0000000000 0.0000000000)


Specific axis could also be effectively disabled by settings the upper and lower limits to same (zero) value:

(LowerLimits 0.0000000000 0.0000000000 0.0000000000 0.0000000000 0.0000000000 0.0000000000)
(UpperLimits 0.0000000000 0.0000000000 0.0000000000 0.0000000000 0.0000000000 0.0000000000)

[ElastixParameters_tx15.txt](https://github.com/SuperElastix/elastix/files/7007427/ElastixParameters_tx15.txt)
[TransformParameters.tx15.txt](https://github.com/SuperElastix/elastix/files/7007429/TransformParameters.tx15.txt)


@zigaso
Copy link
Author

zigaso commented Aug 24, 2021

Hi,

it seem the Elastix framework was refactored recently and this is causing the build to fail. The problem seems to be in the waythe output transform parameter file is to be written.

We identified the following issue:

In header Core/ComponentBaseClasses/elxTransformBase.hxx

 /** Allows a derived transform class to write its data to file, by overriding this member function. */
  virtual void
  WriteDerivedTransformDataToFile(void) const
  {}

Parameters to write are passed in the implementation of function WriteToFile(xl::xoutsimple & transformationParameterInfo, const ParametersType & param) and at the end of function the following happens:

  transformationParameterInfo << Conversion::ParameterMapToString(parameterMap);

  WriteDerivedTransformDataToFile();

There is an incentive to override the WriteDerivedTransformDataToFile(), however, it does not accept any parameters, while the parameterMap seems to be written to output stream prior to calling this function.

In our current implementation we want to compute an output vector, i.e. LimitsReached, based on the current transform paramters, i.e. EulerTransformParameters and the parametersUpperLimits, LowerLimits and SharpnessOfLimits.

In previous Elastix version (4.9) there used to be a mechanism to override the WriteToFile() function and thus generate the transform parameter file, with all the parameters available in the function.

My question is how is it possible, in the refactored elastix framework, to compute output values and write them into the transform parameters file? Was this option considered? If yes, is there a use case such that to take a look?

Many thanks,
Ziga

@N-Dekker
Copy link
Member

N-Dekker commented Aug 30, 2021

@zigaS-fe-uni-lj Thank you for your pull request! Indeed, with a recent refactoring, the support to override elastix::TransformBase::WriteToFile was removed. We observed that in most cases, it is sufficient for elastix::TransformBase::WriteToFile to just write the content of its transform parameter map. If your (derived) transform class has some extra transform parameters to be added to the map, you may now override elastix::TransformBase::CreateDerivedTransformParametersMap(void), instead, returning a map of the extra transform parameters! I think it should be as follows:

template <class TElastix>
auto
AdvancedLimitedEulerTransformElastix<TElastix>::CreateDerivedTransformParametersMap(void) const -> ParameterMapType
{
  ParameterMapType parameterMap{
    { "CenterOfRotationPoint", Conversion::ToVectorOfStrings(m_LimitedEulerTransform->GetCenter()) },
    { "SharpnessOfLimits", { Conversion::ToString(m_LimitedEulerTransform->GetSharpnessOfLimits()) } },
    { "UpperLimits", Conversion::ToVectorOfStrings(m_LimitedEulerTransform->GetUpperLimits()) },
    { "LowerLimits", Conversion::ToVectorOfStrings(m_LimitedEulerTransform->GetLowerLimits()) }
  };

  if (SpaceDimension == 3)
  {
    parameterMap["ComputeZYX"] = { Conversion::ToString(m_LimitedEulerTransform->GetComputeZYX()) };
  }
  return parameterMap;
}

When this member function is added to AdvancedLimitedEulerTransformElastix, I think both overrides, WriteToFile and CreateTransformParametersMap, may just be removed from AdvancedLimitedEulerTransformElastix. I think, in your case, it would not be necessary to override WriteDerivedTransformDataToFile(void). However, you would need to try it out!

Maybe it's also helpful to take a look at the current revision (develop branch, git HEAD) of elxEulerTransform.h and elxEulerTransform.hxx at https://github.com/SuperElastix/elastix/tree/develop/Components/Transforms/EulerTransform

Hope this helps! Kind regards, Niels

Comment on lines +440 to +441
m_UpperLimits[i] = 3.14f;
m_LowerLimits[i] = -3.14f;
Copy link
Member

@N-Dekker N-Dekker Nov 30, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you mean π, please use vnl_math::pi (and then do #include <vnl/vnl_math.h>.) As an alternative, M_PI (with #include <cmath>) is also fine to me.

upperLimits.SetSize(6);
for( unsigned int i = 0; i < 6; i++ )
{
upperLimits[ i ] = i < 3 ? 3.14f : 200.0f;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did you specifically choose 200.0f here? Would std::numeric_limits<float>::max() be fine as well, as a default? Or even std::numeric_limits<float>::infinity()?

If you still think 200.0f is the most appropriate default value, can you please make it a named constant? Possibly a static constexpr member of the class?

@N-Dekker
Copy link
Member

When a user does not specify those new parameters (SharpnessOfLimits, UpperLimits, LowerLimits) would AdvancedLimitedEulerTransform behave exactly like the existing EulerTransform? I would like it like that!

@N-Dekker
Copy link
Member

@zigaso Just for my understanding, does your proposal only support 3D, no 2D? Because I see that you wrote "itkAdvancedLimitedEuler3DTransform.h", but no corresponding 2D transform!

Just want to be sure that that's your intention! 😃

@cosfrist

This comment has been minimized.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants