Diffeomorphic Registration with binary and fuzzy images#

This example demonstrates registration of a binary and a fuzzy image. This could be seen as aligning a fuzzy (sensed) image to a binary (e.g., template) image.

import matplotlib.pyplot as plt
import numpy as np
from skimage import draw, filters

from dipy.align.imwarp import SymmetricDiffeomorphicRegistration
from dipy.align.metrics import SSDMetric
from dipy.viz import regtools

Let’s generate a sample template image as the combination of three ellipses. We will generate the fuzzy (sensed) version of the image by smoothing the reference image.

def draw_ellipse(img, center, axis):
    rr, cc = draw.ellipse(center[0], center[1], axis[0], axis[1], shape=img.shape)
    img[rr, cc] = 1
    return img


img_ref = np.zeros((64, 64))
img_ref = draw_ellipse(img_ref, (25, 15), (10, 5))
img_ref = draw_ellipse(img_ref, (20, 45), (15, 10))
img_ref = draw_ellipse(img_ref, (50, 40), (7, 15))

img_in = filters.gaussian(img_ref, sigma=3)

Let’s define a small visualization function.

def show_images(img_ref, img_warp, fig_name):
    fig, axarr = plt.subplots(ncols=2, figsize=(12, 5))
    axarr[0].set_title("warped image & reference contour")
    axarr[0].imshow(img_warp)
    axarr[0].contour(img_ref, colors="r")
    ssd = np.sum((img_warp - img_ref) ** 2)
    axarr[1].set_title(f"difference, SSD={ssd:.02f}")
    im = axarr[1].imshow(img_warp - img_ref)
    plt.colorbar(im)
    fig.tight_layout()
    fig.savefig(fig_name + ".png")


show_images(img_ref, img_in, "input")
register binary fuzzy

Input images before alignment.

Let’s use the general Registration function with some naive parameters, such as set step_length as 1 assuming maximal step 1 pixel and a reasonably small number of iterations since the deformation with already aligned images should be minimal.

sdr = SymmetricDiffeomorphicRegistration(
    metric=SSDMetric(img_ref.ndim),
    step_length=1.0,
    level_iters=[50, 100],
    inv_iter=50,
    ss_sigma_factor=0.1,
    opt_tol=1.0e-3,
)

Perform the registration with equal images.

mapping = sdr.optimize(img_ref.astype(float), img_ref.astype(float))
img_warp = mapping.transform(img_ref, interpolation="linear")
show_images(img_ref, img_warp, "output-0")
regtools.plot_2d_diffeomorphic_map(mapping, delta=5, fname="map-0.png")
  • register binary fuzzy
  • register binary fuzzy
(array([[  0.,   0.,   0., ...,   0.,   0.,   0.],
       [  0., 127., 127., ..., 127., 127., 127.],
       [  0., 127., 127., ..., 127., 127., 127.],
       ...,
       [  0., 127., 127., ..., 127., 127., 127.],
       [  0., 127., 127., ..., 127., 127., 127.],
       [  0., 127., 127., ..., 127., 127., 127.]], dtype=float32), array([[  0.,   0.,   0., ...,   0.,   0.,   0.],
       [  0., 127., 127., ..., 127., 127., 127.],
       [  0., 127., 127., ..., 127., 127., 127.],
       ...,
       [  0., 127., 127., ..., 127., 127., 127.],
       [  0., 127., 127., ..., 127., 127., 127.],
       [  0., 127., 127., ..., 127., 127., 127.]], dtype=float32))

Registration results for default parameters and equal images.

Perform the registration with binary and fuzzy images.

mapping = sdr.optimize(img_ref.astype(float), img_in.astype(float))
img_warp = mapping.transform(img_in, interpolation="linear")
show_images(img_ref, img_warp, "output-1")
regtools.plot_2d_diffeomorphic_map(mapping, delta=5, fname="map-1.png")
  • register binary fuzzy
  • register binary fuzzy
(array([[  0.      ,   0.      ,   0.      , ...,   0.      ,   0.      ,
          0.      ],
       [  0.      ,  90.25679 ,  82.0045  , ..., 110.842926, 118.748566,
        127.      ],
       [  0.      , 100.90447 , 127.      , ..., 127.      , 127.      ,
        127.      ],
       ...,
       [  0.      , 113.901726, 127.      , ...,  75.54517 , 120.67299 ,
        127.      ],
       [  0.      , 127.      , 127.      , ..., 127.      , 127.      ,
        127.      ],
       [  0.      , 127.      , 127.      , ..., 127.      , 127.      ,
        127.      ]], dtype=float32), array([[  0.      ,   0.      ,   0.      , ...,   0.      ,   0.      ,
          0.      ],
       [  0.      , 127.00001 , 127.      , ..., 127.      ,  84.74348 ,
        127.      ],
       [  0.      , 127.      , 126.99999 , ...,  39.307434, 113.24896 ,
        127.      ],
       ...,
       [  0.      , 124.19529 , 121.33288 , ..., 102.8345  , 127.      ,
        127.      ],
       [  0.      , 127.      , 127.      , ..., 126.99999 , 113.077385,
        127.      ],
       [  0.      , 127.      , 127.      , ..., 127.      , 127.      ,
        127.      ]], dtype=float32))

Registration results for a naive parameter configuration.

Note, we are still using a multi-scale approach which makes step_length in the upper level multiplicatively larger. What happens if we set step_length to a rather small value?

Perform the registration and examine the output.

mapping = sdr.optimize(img_ref.astype(float), img_in.astype(float))
img_warp = mapping.transform(img_in, interpolation="linear")
show_images(img_ref, img_warp, "output-2")
regtools.plot_2d_diffeomorphic_map(mapping, delta=5, fname="map-2.png")
  • register binary fuzzy
  • register binary fuzzy
/opt/homebrew/Caskroom/miniforge/base/envs/py312-test/lib/python3.12/site-packages/dipy/viz/regtools.py:314: RuntimeWarning: More than 20 figures have been opened. Figures created through the pyplot interface (`matplotlib.pyplot.figure`) are retained until explicitly closed and may consume too much memory. (To control this warning, see the rcParam `figure.max_open_warning`). Consider using `matplotlib.pyplot.close()`.
  plt.figure()

(array([[  0.      ,   0.      ,   0.      , ...,   0.      ,   0.      ,
          0.      ],
       [  0.      , 107.26481 , 107.818726, ...,  94.22971 , 106.94557 ,
        127.      ],
       [  0.      , 111.12815 , 127.      , ..., 127.      , 127.      ,
        127.      ],
       ...,
       [  0.      , 122.529335, 127.      , ..., 127.      , 127.      ,
        127.      ],
       [  0.      , 126.99735 , 127.      , ..., 127.      , 126.99999 ,
        127.      ],
       [  0.      , 127.      , 127.      , ..., 127.      , 127.      ,
        127.      ]], dtype=float32), array([[  0.       ,   0.       ,   0.       , ...,   0.       ,
          0.       ,   0.       ],
       [  0.       , 127.       , 127.       , ...,  38.591007 ,
        126.99999  , 127.       ],
       [  0.       , 127.       , 127.       , ...,  34.394188 ,
        127.       , 127.       ],
       ...,
       [  0.       , 124.35901  , 122.08593  , ...,   3.9184685,
         70.04836  , 127.       ],
       [  0.       , 127.       , 127.       , ...,  65.81175  ,
        127.       , 127.       ],
       [  0.       , 127.       , 127.       , ..., 127.       ,
        127.       , 127.       ]], dtype=float32))

Registration results for decreased step size.

An alternative scenario is to use just a single-scale level. Even though the warped image may look fine, the estimated deformations show that it is off the mark.

sdr = SymmetricDiffeomorphicRegistration(
    metric=SSDMetric(img_ref.ndim),
    step_length=1.0,
    level_iters=[100],
    inv_iter=50,
    ss_sigma_factor=0.1,
    opt_tol=1.0e-3,
)

Perform the registration.

mapping = sdr.optimize(img_ref.astype(float), img_in.astype(float))
img_warp = mapping.transform(img_in, interpolation="linear")
show_images(img_ref, img_warp, "output-3")
regtools.plot_2d_diffeomorphic_map(mapping, delta=5, fname="map-3.png")
  • register binary fuzzy
  • register binary fuzzy
(array([[  0.      ,   0.      ,   0.      , ...,   0.      ,   0.      ,
          0.      ],
       [  0.      , 113.812126, 112.30992 , ...,  67.23495 ,  93.087524,
        127.      ],
       [  0.      , 115.750916, 127.      , ..., 127.      , 127.      ,
        127.      ],
       ...,
       [  0.      , 126.96909 , 127.      , ..., 127.      , 123.27975 ,
        127.      ],
       [  0.      , 126.97683 , 127.00001 , ..., 127.      , 127.      ,
        127.      ],
       [  0.      , 127.      , 127.      , ..., 127.      , 127.      ,
        127.      ]], dtype=float32), array([[  0.       ,   0.       ,   0.       , ...,   0.       ,
          0.       ,   0.       ],
       [  0.       , 127.       , 127.       , ..., 127.       ,
         68.370316 , 127.       ],
       [  0.       , 127.       , 127.00001  , ...,  89.27986  ,
         94.97414  , 127.       ],
       ...,
       [  0.       , 126.99135  , 126.97298  , ..., 127.00001  ,
         34.990112 , 127.       ],
       [  0.       , 127.       , 127.       , ..., 100.59303  ,
          1.7804474, 127.       ],
       [  0.       , 127.       , 127.       , ..., 127.       ,
        127.       , 127.       ]], dtype=float32))

Registration results for single level.

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

Gallery generated by Sphinx-Gallery