Step-by-step registration example

This tutorial will take you step-by-step through a very simple registration example.

Introduction

Starting with mermaid can initially be a little overwhelming. We therefore walk through a simple step-by-step registration example here. This example should teach you:

  • how to import the most essential mermaid packages
  • how to generate some example data
  • how to run instantiate and run a simple registration
  • how to work with mermaid parameter structures (these are important as they keep track of all the mermaid settings and can also be edited)

Importing modules

First we import some important mermaid modules. The used mermaid modules are:

  • mermaid.simple_interface provides a very high level interface to the image registration algorithms
  • mermaid.example_generation allows to generate simply synthetic data and some real image pairs to test the registration algorithms
  • mermaid.module_parameters allows generating mermaid parameter structures which are used to keep track of all the parameters

Some of the high-level mermaid code and also the plotting depends on numpy (we will phase much of this out in the future). Hence we also import numpy.

# first the simple registration interface (which provides basic, easy to use registration functionality)
import mermaid.simple_interface as SI
# the parameter module which will keep track of all the registration parameters
import mermaid.module_parameters as pars
# and some mermaid functionality to create test data
import mermaid.example_generation as EG

# also import numpy
import numpy as np

mermaid parameters

Registration algorithms tend to have a lot of settings. Starting from the registration model, over the selection and settings for the optimizer, to general compute settings (for example, if mermaid should be run on the GPU or CPU). All the non-compute settings that affect registration results are automatically kept track inside a parameters structure.

So let’s first create an empty mermaid parameter object

# first we create a parameter structure to keep track of all registration settings
params = pars.ParameterDict()

Generating registration example data

Now we create some example data (source and target image examples for a two-dimensional square, of size 64x64) and keep track of the generated settings via this parameter object.

# and let's create two-dimensional squares
I0,I1,spacing = EG.CreateSquares(dim=2,add_noise_to_bg=True).create_image_pair(np.array([64,64]),params=params)

Out:

Creating new category: root.square_example_images
Using default value = 10 for key = len_s of category = root.square_example_images
Using default value = 16 for key = len_l of category = root.square_example_images

Writing out parameters

Parameters can easily be written to a file (or displayed via print). We can write it out including comments for what these settings are as follows. The first command writes out the actual json configuration, the second one comments that explain what the settings are (as json does not allow commented files by default).

params.write_JSON('step_by_step_example_data.json')
params.write_JSON_comments('step_by_step_example_data_with_comments.json')

Out:

Writing parameter file = step_by_step_example_data.json
Writing parameter file = step_by_step_example_data_with_comments.json

Resulting parameters after the example generation

The resulting output in this case looks as follows. This is contained in the files, but we simply print the parameters here.

print(params)

Out:

ext =
{
    "square_example_images": {
        "len_l": 16,
        "len_s": 10
    }
}
int =
{
    "square_example_images": {
        "len_l": 16,
        "len_s": 10
    }
}
com =
{
    "square_example_images": {
        "__doc__": "Controlling the size of a nD cube",
        "len_l": "Maximum side-length of square",
        "len_s": "Mimimum side-length of square"
    }
}
currentCategoryName = root

Performing the registration

Now we are ready to instantiate the registration object from mermaid. As we are not sure what settings to use, let alone know what settings exist, we simply run it first for one step and ask for the json configuration to be written out.

# now we instantiate the registration class
si = SI.RegisterImagePair()

# and use it for registration
# as registration algorithms can have many settings we simply run it first for one iteration and ask it to
# write out a configuration file, including comments to explain the settings.
si.register_images(I0, I1, spacing,
                   model_name='lddmm_shooting_map',
                   nr_of_iterations=1,
                   optimizer_name='sgd',
                   visualize_step=None,
                   json_config_out_filename=('step_by_step_basic_settings.json','step_by_step_basic_settings_with_comments.json'))

Out:

Creating new category: root.model
Creating new category: root.model.deformation
Creating key = use_map; category = root.model.deformation; value = True
Creating new category: root.model.registration_model
Creating key = type; category = root.model.registration_model; value = lddmm_shooting_map
Creating new category: root.optimizer
Creating key = name; category = root.optimizer; value = sgd
Creating new category: root.optimizer.single_scale
Creating key = nr_of_iterations; category = root.optimizer.single_scale; value = 1
Using default value = 1.0 for key = map_low_res_factor of category = root.model.deformation
mapLowResFactor = 1: performing computations at original resolution.
Overwriting key = use_map; category = root.model.deformation; value =  True -> True
Overwriting key = map_low_res_factor; category = root.model.deformation; value =  1.0 -> 1.0
Using default value = False for key = compute_similarity_measure_at_low_res of category = root.model.deformation
Using default value = 0.0001 for key = rel_ftol of category = root.optimizer.single_scale
Using default value = 1 for key = spline_order of category = root.model.registration_model
Using default value = none for key = weight_clipping_type of category = root.optimizer
Using default value = 1.0 for key = weight_clipping_value of category = root.optimizer
Creating new category: root.optimizer.gradient_clipping
Using default value = True for key = clip_display of category = root.optimizer.gradient_clipping
Using default value = False for key = clip_individual_gradient of category = root.optimizer.gradient_clipping
Using default value = 1.0158730158730158 for key = clip_individual_gradient_value of category = root.optimizer.gradient_clipping
Using default value = True for key = clip_shared_gradient of category = root.optimizer.gradient_clipping
Using default value = 1.0 for key = clip_shared_gradient_value of category = root.optimizer.gradient_clipping
Overwriting key = type; category = root.model.registration_model; value =  lddmm_shooting_map -> lddmm_shooting_map
Overwriting key = type; category = root.model.registration_model; value =  lddmm_shooting_map -> lddmm_shooting_map
Creating new category: root.model.registration_model.env
Using default value = False for key = get_momentum_from_external_network of category = root.model.registration_model.env
Using map-based lddmm_shooting_map model
Using default value = True for key = use_CFL_clamping of category = root.model.registration_model
Using default value = True for key = use_odeint of category = root.model.registration_model.env
Using default value = False for key = use_ode_tuple of category = root.model.registration_model.env
Creating new category: root.model.registration_model.forward_model
Creating new category: root.model.registration_model.forward_model.smoother
Using default value = multiGaussian for key = type of category = root.model.registration_model.forward_model.smoother
Using default value = [0.05, 0.1, 0.15, 0.2, 0.25] for key = multi_gaussian_stds of category = root.model.registration_model.forward_model.smoother
Using default value = [0.06666666666666667, 0.13333333333333333, 0.19999999999999998, 0.26666666666666666, 0.3333333333333333] for key = multi_gaussian_weights of category = root.model.registration_model.forward_model.smoother
the param of smoother is <mermaid.smoother_factory.MultiGaussianFourierSmoother object at 0x7f30e2ebac18>
Creating new category: root.model.registration_model.shooting_vector_momentum
Using default value = False for key = use_velocity_mask_on_boundary of category = root.model.registration_model.shooting_vector_momentum
works in mermaid iter mode
Using default value = 1.0 for key = reg_factor of category = root.model.registration_model.env
Creating new category: root.model.registration_model.loss
Using default value = False for key = display_max_displacement of category = root.model.registration_model.loss
Using default value = False for key = limit_displacement of category = root.model.registration_model.loss
Using default value = 0.05 for key = max_displacement of category = root.model.registration_model.loss
LDDMMShootingVectorMomentumMapNet(
  (integrator): ODEWrapBlock()
)
Creating new category: root.optimizer.sgd
Creating new category: root.optimizer.sgd.individual
Using default value = 0.01 for key = lr of category = root.optimizer.sgd.individual
Using default value = 0.9 for key = momentum of category = root.optimizer.sgd.individual
Using default value = 0.0 for key = dampening of category = root.optimizer.sgd.individual
Using default value = 0.0 for key = weight_decay of category = root.optimizer.sgd.individual
Using default value = True for key = nesterov of category = root.optimizer.sgd.individual
Creating new category: root.optimizer.sgd.shared
Using default value = 0.01 for key = lr of category = root.optimizer.sgd.shared
Using default value = 0.9 for key = momentum of category = root.optimizer.sgd.shared
Using default value = 0.0 for key = dampening of category = root.optimizer.sgd.shared
Using default value = 0.0 for key = weight_decay of category = root.optimizer.sgd.shared
Using default value = True for key = nesterov of category = root.optimizer.sgd.shared
Using default value = True for key = use_step_size_scheduler of category = root.optimizer
Creating new category: root.optimizer.scheduler
Using default value = True for key = verbose of category = root.optimizer.scheduler
Using default value = 0.5 for key = factor of category = root.optimizer.scheduler
Using default value = 10 for key = patience of category = root.optimizer.scheduler
Using default value = rk4 for key = solver of category = root.model.registration_model.forward_model
Using default value = True for key = adjoin_on of category = root.model.registration_model.forward_model
Using default value = 1e-05 for key = rtol of category = root.model.registration_model.forward_model
Using default value = 1e-05 for key = atol of category = root.model.registration_model.forward_model
Using default value = 20 for key = number_of_time_steps of category = root.model.registration_model.forward_model
Creating new category: root.model.registration_model.similarity_measure
Using default value = ssd for key = type of category = root.model.registration_model.similarity_measure
Using ssd similarity measure
Using default value = 0.1 for key = sigma of category = root.model.registration_model.similarity_measure
    0-Tot: E=013.2673 | simE=013.2673 | regE=000.0000 | optParE=000.0000 | relF=  n/a    | 
    0-Img: E=013.2673 | simE=013.2673 | regE=000.0000 |
-->Elapsed time 10.70291[s]
Writing parameter file = step_by_step_basic_settings.json
Writing parameter file = step_by_step_basic_settings_with_comments.json

Total running time of the script: ( 0 minutes 10.759 seconds)

Gallery generated by Sphinx-Gallery