# Registration pipeline outside of CemrgApp
This example uses CINE and LGE images from Abdul's.
## Data
Two folders, each with an `Images` and a `masks` subfolders.
+ `Cine_dataset`
+ `LGE_images_masks`
The data tree looks as follows:
```
❯ tree .
├── Cine_dataset
│ ├── images
│ │ ├── L71_0000.nii.gz
│ │ └── N93_0000.nii.gz
│ └── masks
│ ├── L71_0000_gt.nii.gz
│ └── N93_0000_gt.nii.gz
└── LGE_images_masks
├── images
│ ├── L71LGE_high-res_psir_3slice_MAG-111_0000.nii.gz
│ ├── L71LGE_high-res_psir_3slice_PSIR-112_0000.nii.gz (*)
│ ├── N93LGE_high-res_psir_stack_MAG-105_0000.nii.gz
│ └── N93LGE_high-res_psir_stack_PSIR-106_0000.nii.gz (*)
└── masks
├── MASK_L71LGE_high-res_psir_3slice_MAG-111.nii.gz
├── MASK_L71LGE_high-res_psir_3slice_PSIR-112.nii.gz (*)
├── MASK_N93LGE_high-res_psir_stack_MAG-105.nii.gz
└── MASK_N93LGE_high-res_psir_stack_PSIR-106.nii.gz (*)
```
Two images: `L71_0000` and `N93_0000`, each with a corresponding CINE
segmentation and corresponding LGE images and masks.
We focus on the ones with the `PSIR`, marked with a `(*)`.
## Registration
We need to get the deformation information file (dof, or DFV).
We need to use MIRTK, these executables come installed by default with CemrgApp.
### Using the user interface
CemrgApp (v2.3-rc6) has a built in function to register, transform images, and
transform meshes.
**1. Navigate the menus at the top of the UI: **
Window > Show View > Cemrg Common Tools,
then click on the **External Tools (MIRTK)** button, three options pop out:
+ Register
+ Transform (allows for images or meshes)
+ Inverse Register
**2. Click on Register**
1. Select the root folder of your data
2. Use the UI to select:
+ the Moving file (LGE)
+ the Target/Fixed file (the Cine)
+ Registration model (Rigid, Affine, or Rigid+Affine)
+ The name of the registration file (optional, default=`MODEL.dof`)
3. Click OK to start the registration
The file will be saved in the root folder you selected at the beginning.
> Note that another option is to create different folders per case, put all the necessary files (cine, lge, and lge-segmentation) and keep everything contained per case.
> **ALSO NOTE:** CemrgApp will give you the command to run in the log file. Only see the terminal from where you're running the app.
### Using the command line
CemrgApp utilises the same binaries. Go to the folder you have CemrgApp in,
inside the `bin/MLib` folder you will find all the MIRTK tools you need.
To do a registration, the command is:
```shell!
register MOVING FIXED -dofout DISPLACEMENT_FILE -model MODEL -verbose 3
```
In the case of our example,
```shell
PATH_TO_CEMRGAPP/bin/MLib/register \
PATH_TO_DATA/LGE_images_masks/images/L71LGE_high-res_psir_3slice_PSIR-112_0000.nii.gz \
PATH_TO_DATA/Cine_dataset/images/L71_0000.nii.gz \
-dofout PATH_TO_DATA/Rigid.dof \
-model Rigid \
-verbose 3
```
From here, you get a `Rigid.dof` file!
## Transforming
### Using the UI
This is simple, similar to the registration, but pick the **Transform** button
instead of the Register one.
### On the command line
Usage is
```shell!
transform-image LGE_SEGMENTATION OUTPUT_SEG_NAME -dofin DISPLACEMENT_FILE
```
In the case of our example:
```
PATH_TO_CEMRGAPP/bin/MLib/transform-image \
PATH_TO_DATA/LGE_images_masks/masks/L71LGE_high-res_psir_3slice_PSIR-112.nii.gz \
PATH_TO_DATA/Cine_dataset/masks/tx_L71LGE_high-res_psir_3slice_PSIR-112-by-Rigid.nii \
-dofin PATH_TO_DATA/Rigid.dof
```
### Transform meshes (vtk files)
The command is very similar:
```shell!
transform-points VTK_INPUT VTK_OUTPUT -dofin DISPLACEMENT_FILE
```
> Note that `DISPLACEMENT_FILE=Rigid.dof` we got from doing the registration
## How to read the dof file
For this, use the terminal. In the same `MLib` folder described before,
do this:
```
info PATH_TO_DATA/Rigid.dof
```
You will see the output like:
```
> info Rigid.dof
tx = -0.8649 ty = 1.0436 tz = 0.2945
rx = -0.6644 ry = -0.0855 rz = -0.4600
```
Save your deformation file like:
```
> info Rigid.dof >> transform_file.txt
```
You can then use the following python example script to
load the file as numpy
```python!
# rigid_transform.py
import numpy as np
import re
from scipy.spatial.transform import Rotation as R
def load_rigid_transform(filepath, degrees=True, euler_order='xyz'):
"""
Reads a rigid transform file with format:
tx = <val> ty = <val> tz = <val>
rx = <val> ry = <val> rz = <val>
Returns:
translation (np.ndarray): shape (3,)
rotation_matrix (np.ndarray): shape (3, 3)
"""
with open(filepath, 'r') as f:
content = f.read()
# Extract floats
numbers = list(map(float, re.findall(r"[-+]?\d*\.\d+|\d+", content)))
if len(numbers) != 6:
raise ValueError("Expected 6 values (tx ty tz rx ry rz), got: {}".format(numbers))
tx, ty, tz = numbers[0:3]
rx, ry, rz = numbers[3:6]
# Euler to rotation matrix
rotation = R.from_euler(euler_order, [rx, ry, rz], degrees=degrees)
rotation_matrix = rotation.as_matrix()
translation = np.array([tx, ty, tz])
return translation, rotation_matrix
def build_transform_matrix(translation, rotation_matrix):
"""
Builds a 4x4 homogeneous transformation matrix from translation and rotation.
"""
transform = np.eye(4)
transform[:3, :3] = rotation_matrix
transform[:3, 3] = translation
return transform
# Optional CLI
if __name__ == "__main__":
import sys
if len(sys.argv) != 2:
print("Usage: python rigid_transform.py <transform_file.txt>")
sys.exit(1)
translation, rotation_matrix = load_rigid_transform(sys.argv[1])
transform = build_transform_matrix(translation, rotation_matrix)
print("Translation vector:", translation)
print("Rotation matrix:\n", rotation_matrix)
print("Full 4x4 transformation matrix:\n", transform)
```
> Abdul has the code to apply the translation vector and rotation matrix