# Difference between imposed motion (SimpleITK) and registration output (ITK, ITK-Snap)

**URL:** https://discourse.itk.org/t/difference-between-imposed-motion-simpleitk-and-registration-output-itk-itk-snap/4714
**Category:** Algorithms
**Tags:** registration, python, cpp, simpleitk
**Created:** [January 10, 2022, 11:25am UTC](https://discourse.itk.org/t/difference-between-imposed-motion-simpleitk-and-registration-output-itk-itk-snap/4714 "2022-01-10T11:25:53Z")
**Posts on this page:** 11
**Page:** 1

<div class="post-metadata">

### Author: ![Keyn34](https://discourse.itk.org/letter_avatar_proxy/v4/letter/k/9fc29f/32.png) [@Keyn34](https://discourse.itk.org/u/Keyn34)
#### Post date: [January 10, 2022, 11:25am UTC](https://discourse.itk.org/t/difference-between-imposed-motion-simpleitk-and-registration-output-itk-itk-snap/4714/1 "2022-01-10T11:25:53Z")

</div>

Hello once again everyone,

A quick question concerning applying translation and rotation to volumes and then registering them to compare the results to the imposed motion:

I have a python script that uses SimpleITK to impose random motion on a series of phantom scans. The script outputs the values of translation and rotation to an excel file for later comparison. The upper limit for translation is 5mm and for rotation 0.1rad. All values are printed out and well below those maximum values.

I run a 3D Euler Rigid Registration, implemented in C++ on the reference volume and the volumes where motion was imposed. The output values for the transformation file are completely different than the imposed motion ones. Also, when I use ITK-Snap to perform a registration, the results of the registration are way over the upper limit specified in the python script.

For example:  
imposed translation motion output on x-axis: 0,810653599 mm  
registration result translation on x axis with ITK-Snap: 42,32 mm

Is there a resource to read on why all the values are so different? Does the confusion stem from the usage of the virtual and physical images within the registration framework and do I have to translate the values between the two domains?

Any help would be highly appreciated, thanks!

---

<div class="post-metadata">

### Author: ![zivy](https://discourse.itk.org/user_avatar/discourse.itk.org/zivy/32/1726_2.png) [@zivy](https://discourse.itk.org/u/zivy)
#### Post date: [January 10, 2022, 1:55pm UTC](https://discourse.itk.org/t/difference-between-imposed-motion-simpleitk-and-registration-output-itk-itk-snap/4714/2 "2022-01-10T13:55:27Z")

</div>

Hello @Keyn34,

Based on your description it is hard to tell what is going on. Is the ITK-SNAP registration correct? Is the SimpleITK transformation applied correctly to the images?

Let’s start with a simple setup, known, arbitrary, SimpleITK translation:

```auto
import SimpleITK as sitk

image = sitk.ReadImage('training_001_ct.mha')
transform = sitk.Euler3DTransform()
transform.SetTranslation([20,0,0])
resampled_image = sitk.Resample(image, transform)
sitk.WriteImage(resampled_image, 'transformed_image.mha')

```

Load each image in ITK-SNAP and place the cursor at the same anatomical landmark. Open the tools-\>image information, info tab and see the “3D cursor coordinates, world units ITK”. Confirm that the landmark was translated as expected. Then run the ITK-SNAP registration and see if you get the expected translation in x-direction (choice of moving/fixed image will yield the expected or inverse transformation depending on the image roles).

---

<div class="post-metadata">

### Author: ![Keyn34](https://discourse.itk.org/letter_avatar_proxy/v4/letter/k/9fc29f/32.png) [@Keyn34](https://discourse.itk.org/u/Keyn34)
#### Post date: [January 10, 2022, 4:23pm UTC](https://discourse.itk.org/t/difference-between-imposed-motion-simpleitk-and-registration-output-itk-itk-snap/4714/3 "2022-01-10T16:23:14Z")

</div>

Thank you for your response @zivy.

The ITK-SNAP registration is visually absolutely correct and aligns the images as expected. This is also confirmed by running your code snippet with a volume of the datasets:

 ![image](https://discourse.itk.org/uploads/default/original/2X/c/ccfd6165121c2cacc246f52dd866fc68c2f848cd.png)

Also, checking the landmarks shows that the translation of 20 results in a shift of 10 with a voxel size of ~2 and a correct shift of 20 in “world units ITK” This sounds good to me. Would a snippet of the script which imposes motion help for further investigation?

I think we can assume that something is wrong in the python script, which imposes motion in that case, right?

Thank you again in advance.

---

<div class="post-metadata">

### Author: ![zivy](https://discourse.itk.org/user_avatar/discourse.itk.org/zivy/32/1726_2.png) [@zivy](https://discourse.itk.org/u/zivy)
#### Post date: [January 10, 2022, 5:36pm UTC](https://discourse.itk.org/t/difference-between-imposed-motion-simpleitk-and-registration-output-itk-itk-snap/4714/4 "2022-01-10T17:36:26Z")

</div>

Hello @Keyn34,

Yes, based on your results I’d suspect the script used for resampling (imposing motion) isn’t doing what you want it to do. Please share any code you can so we can further help.

---

<div class="post-metadata">

### Author: ![Keyn34](https://discourse.itk.org/letter_avatar_proxy/v4/letter/k/9fc29f/32.png) [@Keyn34](https://discourse.itk.org/u/Keyn34)
#### Post date: [January 10, 2022, 6:37pm UTC](https://discourse.itk.org/t/difference-between-imposed-motion-simpleitk-and-registration-output-itk-itk-snap/4714/5 "2022-01-10T18:37:16Z")

</div>

Thank you again @zivy.

It’s not my script, and I am not too familiar with python and SimpleITK, but the following parts of code is where I suspect that maybe something could be wrong:

- Function to impose motion:

```auto
# Function to impose predefined motion on to the 3D volumes
def impose_motion(itk_img, translation, rotation, fpath_mtn_impsd_img):
    dim = itk_img.GetDimension()
    translation_transform = sitk.TranslationTransform(dim, translation)
    rigid_euler = sitk.Euler3DTransform()
    rigid_euler.SetTranslation(translation_transform.GetOffset())
    rigid_euler.SetRotation(*rotation)
    mtn_impsd_img = sitk.Resample(
        itk_img, itk_img, rigid_euler, sitk.sitkNearestNeighbor, 0, itk_img.GetPixelID()
    )
    
    
    print("Translation in function: "+
          str(rigid_euler.GetTranslation()))
          
    print("Rotation in function: "+
          str(rigid_euler.GetAngleX())+
          "/"+
          str(rigid_euler.GetAngleY())+
          "/"+
          str(rigid_euler.GetAngleZ()))
          
    sitk.WriteImage(mtn_impsd_img, fpath_mtn_impsd_img)

```

- Code where for every file, a random value set is generated and passed to the impose motion function (nested within a for each loop):

```auto
        translation = tuple(
            translation_upper_limit
            - translation_upper_limit * np.random.random_sample(3)
        )
        print("Generated Translation values: "+
          str(translation))
        
        rotation = tuple(
            rotation_upper_limit - rotation_upper_limit * np.random.random_sample(3)
        )
        print("Generated Rotation values: "+
          str(rotation))
        
        
        img_to_mtn_imps = os.path.join(iter_path, org_files[y])
        f_name.append(pathlib.Path(img_to_mtn_imps).stem)
        x_tx.append(translation[0]), y_tx.append(translation[1]), z_tx.append(
            translation[2]
        )
        pitch.append(rotation[0]), roll.append(rotation[1])
        yaw.append(rotation[2])
        itk_img = sitk.ReadImage(img_to_mtn_imps)
        impose_motion(itk_img, translation, rotation, img_to_mtn_imps)

```

I suspect an error in the function itself, around the `SetTranslation` and `SetRotation` calls.

---

<div class="post-metadata">

### Author: ![zivy](https://discourse.itk.org/user_avatar/discourse.itk.org/zivy/32/1726_2.png) [@zivy](https://discourse.itk.org/u/zivy)
#### Post date: [January 10, 2022, 8:13pm UTC](https://discourse.itk.org/t/difference-between-imposed-motion-simpleitk-and-registration-output-itk-itk-snap/4714/6 "2022-01-10T20:13:44Z")

</div>

Hello @Keyn34,

The `impose_motion` is a bit strange: (1) what is the role of `translation_transform`, doesn’t seem to be required, use the `translation` parameter directly. (2) If interpolating real valued, anatomical, images, the go-to interpolation method would be sitkLinear and not `sitkNearestNeighbor` (use nearest neighbor if interpolating a discrete label image).

There appears to be a bug in the invocation of this function:

```auto
itk_img = sitk.ReadImage(img_to_mtn_imps)
impose_motion(itk_img, translation, rotation, img_to_mtn_imps)

```

The `itk_image` is read from the file given by `img_to_mtn_imps` and the resampled image is written to the file by the same name, last parameter passed to `impose_motion`. Is overwriting the original file intentional?

---

<div class="post-metadata">

### Author: ![Keyn34](https://discourse.itk.org/letter_avatar_proxy/v4/letter/k/9fc29f/32.png) [@Keyn34](https://discourse.itk.org/u/Keyn34)
#### Post date: [January 11, 2022, 9:29am UTC](https://discourse.itk.org/t/difference-between-imposed-motion-simpleitk-and-registration-output-itk-itk-snap/4714/7 "2022-01-11T09:29:16Z")

</div>

Hello @zivy,

> [@zivy](#):
>
> There appears to be a bug in the invocation of this function:
> 
> ```auto
> itk_img = sitk.ReadImage(img_to_mtn_imps)
> impose_motion(itk_img, translation, rotation, img_to_mtn_imps)
> 
> ```
> 
> The `itk_image` is read from the file given by `img_to_mtn_imps` and the resampled image is written to the file by the same name, last parameter passed to `impose_motion`. Is overwriting the original file intentional?

To my understanding, yes, this is intentional. The files get copied first and the copied versions are then overwritten.

I tested rotation with the code snippet you provided:

```auto
image = sitk.ReadImage(pathToFile)
transform = sitk.Euler3DTransform()
transform.SetTranslation([20,0,0])
transform.SetRotation(0.0,0.0,0.3);
resampled_image = sitk.Resample(image, transform)
sitk.WriteImage(resampled_image, pathToWorkingDirectory+'\\nifti37_transformed_image.nii')

```

Resulting in:

 ![image](https://discourse.itk.org/uploads/default/original/2X/e/e544167501c8622f3967cd23f9f5dcd2d409db2a.png)  
But I think it should look like this (shift of 20 on x, rotation of 0.3 rad around z):  
 ![image](https://discourse.itk.org/uploads/default/original/2X/7/72ace2c21e470971b6ea536fa86b1f997dda02a6.png)

And I got the following result from the registration:

 ![image](https://discourse.itk.org/uploads/default/original/2X/a/a12decb2adfce7f9282dded6d12c0c6454511761.png)

Could it just be that the rotation has such an impact on the translation that the raw, generated, values are simply not valid anymore, or can’t be taken as a reference?  
Because now the rotation seems to be correct? 0.3 rad should be ~17.19° around the z axis.

---

<div class="post-metadata">

### Author: ![zivy](https://discourse.itk.org/user_avatar/discourse.itk.org/zivy/32/1726_2.png) [@zivy](https://discourse.itk.org/u/zivy)
#### Post date: [January 11, 2022, 2:04pm UTC](https://discourse.itk.org/t/difference-between-imposed-motion-simpleitk-and-registration-output-itk-itk-snap/4714/8 "2022-01-11T14:04:12Z")

</div>

Hello @Keyn34,

OK, so the problem is with the understanding of how the transforms work. By default rotation is around the world point (0,0,0), this is an arbitrary point relative to the image. It is not the origin of the image or its center. What you want is to rotate around the image center. Add the following line after setting the rotation:

```auto
transform.SetCenter(image.TransformContinuousIndexToPhysicalPoint([(sz-1)/2 for sz in image.GetSize()]))

```

I highly recommend reading the SimpleITK [fundamental concepts](https://simpleitk.readthedocs.io/en/master/fundamentalConcepts.html) and going over the [transformations](https://github.com/InsightSoftwareConsortium/SimpleITK-Notebooks/blob/master/Python/22_Transforms.ipynb) and [transformations and resampling](https://github.com/InsightSoftwareConsortium/SimpleITK-Notebooks/blob/master/Python/21_Transforms_and_Resampling.ipynb) Jupyter notebooks. This will give you a solid understanding of how things work.

---

<div class="post-metadata">

### Author: ![Keyn34](https://discourse.itk.org/letter_avatar_proxy/v4/letter/k/9fc29f/32.png) [@Keyn34](https://discourse.itk.org/u/Keyn34)
#### Post date: [January 11, 2022, 2:43pm UTC](https://discourse.itk.org/t/difference-between-imposed-motion-simpleitk-and-registration-output-itk-itk-snap/4714/9 "2022-01-11T14:43:55Z")

</div>

Thank you @zivy for pointing that out. I thought SimpleITK automatically assumes the center of the rotation as the image center. I will gladly read through your provided resources!

One last question, as I just applied your suggested center-shift:  
After the shift on x by 20, and rotation around z by 0.3 is applied, is there a way to confirm the registration just by the values? So for example, registering the images with ITK-Snap results in:

 ![image](https://discourse.itk.org/uploads/default/original/2X/3/30f965eb8523a0774f11190c80879ecee4fe5e48.png)

Having a 5.55 shift on the y-axis and 18.85 on the x-axis, instead of just 20 on x. Is it too naive or wrong to assume that the values should be the inverse ones of the imposed motion because there are more ways to transfer back the volume for a successful registration?  
I am aware of other registration evaluation methods, I just ask out of curiosity and for understanding the topic a little better.

---

<div class="post-metadata">

### Author: ![zivy](https://discourse.itk.org/user_avatar/discourse.itk.org/zivy/32/1726_2.png) [@zivy](https://discourse.itk.org/u/zivy)
#### Post date: [January 11, 2022, 4:05pm UTC](https://discourse.itk.org/t/difference-between-imposed-motion-simpleitk-and-registration-output-itk-itk-snap/4714/10 "2022-01-11T16:05:37Z")

</div>

Hello @Keyn34,

Unfortunately, there is no simple answer here. Key theoretical concepts underlying spatial transformations:

- There are multiple parametric representations for the same spatial transformation and the parameterizations are not necessarily unique. Limiting ourselves to rigid 3D transformations, two ways to specify the rotation in SimpleITK are Euler angles or a unit quaternion (a.k.a. versor). Let’s assume we chose Euler angles, there are twelve possible angle combinations out of which SimpleITK supports ZXY and ZYX. Finally, using the default ZXY parameterization we have two parameterizations which represent the same rotation:  
T(0,0,90) = T(180,180,270) = \left[\begin{array}{ccc} 0 & -1 & 0 \\ 1 & 0 & 0 \\ 0 & 0 & 1 \end{array}\right]
- The registration error is spatially varying, so judging whether a registration is sufficiently accurate is dependent on the spatial region of interest and the associated task.

Looking at the parameter values can serve as a sanity check but not much more. The `print_transformation_differences` found in the Transformations notebook may be of interest. [This notebook](https://github.com/SimpleITK/TUTORIAL/blob/master/06_advanced_registration.ipynb) in the SimpleITK tutorial illustrates various ways of evaluating the registration (from summary stats to histogram, to a spatial visualization).

---

<div class="post-metadata">

### Author: ![Keyn34](https://discourse.itk.org/letter_avatar_proxy/v4/letter/k/9fc29f/32.png) [@Keyn34](https://discourse.itk.org/u/Keyn34)
#### Post date: [January 12, 2022, 9:57am UTC](https://discourse.itk.org/t/difference-between-imposed-motion-simpleitk-and-registration-output-itk-itk-snap/4714/11 "2022-01-12T09:57:46Z")

</div>

Hello @zivy

Thank you so much for your time and explanations. It cleared up a lot of things for me - very, very helpful.

The issue with the discrepancy between imposed motion and registration result was mainly resolved by setting the center of rotation accordingly.

> [@zivy](#):
>
> Looking at the parameter values can serve as a sanity check but not much more. The `print_transformation_differences` found in the Transformations notebook may be of interest. [This notebook](https://github.com/SimpleITK/TUTORIAL/blob/master/06_advanced_registration.ipynb) in the SimpleITK tutorial illustrates various ways of evaluating the registration (from summary stats to histogram, to a spatial visualization).

Also thank you for this information, I will gladly look through the notebooks. Especially `print_transformation_differences` sounds very helpful on a first glance.

Again, thanks a lot!
