# 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