Image averaging with VT

%matplotlib inline
import matplotlib.pyplot as plt
import numpy as np
import vt
from timagetk.io.image import _image_from_url
/builds/J-gEBwyb/0/mosaic/timagetk/src/timagetk/components/labelled_image.py:31: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html
  from tqdm.autonotebook import tqdm
def imshow(ax, spim, title="", cmap="gray", vmin=0, vmax=255):
    fig = ax.imshow(spim, cmap=cmap, vmin=vmin, vmax=vmax)
    ax.xaxis.tick_top()
    if title != "":
        ax.set_title(title)
    else:
        try:
            ax.set_title(f"{spim.filename}")
        except:
            pass
    return fig

Load the reference image

tmp_path = _image_from_url('https://zenodo.org/record/7149218/files/090223-p58-flo-top.lsm',
                           hash_value='5548917d3d1490200d0d56cbb31c0d35', hash_method='md5')
ref_img = vt.vtImage(tmp_path)
print(ref_img)
vtImage : { 
 type    : unsigned char, 
 shape  : [460, 460, 59], 
 spacing : [0.200320, 0.200320, 1.000000], 
 }

Create the floating image with a rigid transformation

To simplify this use case example, we create a “random” rigid transformation and apply it to the reference image to create a floating image.

r2f_trsf = vt.create_trsf(ref_img, params='-trsf-type rigid -value random -srandom 12 -angle-range 0.0 0.1')
flo_img = vt.apply_trsf(ref_img, r2f_trsf, params='-interpolation linear')
print(flo_img)
vtImage : { 
 type    : unsigned char, 
 shape  : [460, 460, 59], 
 spacing : [0.200320, 0.200320, 1.000000], 
 }

Invert the transformation to create a registered image

The previous transformation goes from the reference towards the floating image, we have to invert it to obtain the transformation that goes from the floating to the reference image. We are now in the “classical” situation where we want to register an image onto another to “fuse” them.

f2r_trsf = vt.inv_trsf(r2f_trsf)
reg_img = vt.apply_trsf(flo_img, f2r_trsf, ref=ref_img, params='-interpolation linear')
print(reg_img)
vtImage : { 
 type    : unsigned char, 
 shape  : [460, 460, 59], 
 spacing : [0.200320, 0.200320, 1.000000], 
 }

Create the masks

To average the two images, without introducing bias, we have to compute masks defining the position in space of the registered image(s).

Create the reference mask

# create an array filled with 1 shaped like the reference image
ref_arr = np.ones_like(ref_img.copy_to_array(), dtype=np.uint8)
ref_mask = vt.vtImage(ref_arr, ref_img.spacing())  # vt.vtImage do not copy the array (use the pointer)!

Check the reference mask is indeed a bolean array in VT:

np.unique(ref_mask.copy_to_array())  # allowed values are [0, 1] as it is a boolean mask
array([1], dtype=uint8)

Create the floating mask

# create an array filled with 1 shaped like the floating image
flo_arr = np.ones_like(flo_img.copy_to_array(), dtype=np.uint8)
flo_mask = vt.vtImage(flo_arr, flo_img.spacing())   # vt.vtImage do not copy the array (use the pointer)!

Check the floating mask is indeed a bolean array in VT:

np.unique(flo_mask.copy_to_array())  # allowed values are [0, 1] as it is a boolean mask
array([1], dtype=uint8)

Create the registered floating mask

reg_mask = vt.apply_trsf(flo_mask, f2r_trsf, ref=ref_img, params='-interpolation nearest')

Check the registered mask is indeed a bolean array in VT:

np.unique(reg_mask.copy_to_array())  # allowed values are [0, 1] as it is a boolean mask
array([0, 1], dtype=uint8)

Compute the average image without masks

unmasked_avimg = vt.mean_images([ref_img, reg_img], params='-mean')

Compute the average image with masks

masked_avimg = vt.mean_images([ref_img, reg_img], masks=[ref_mask, reg_mask], params='-mean')

Plot the two averaged images

mid_z_sl = unmasked_avimg.shape()[2]//2
unmasked_arr = unmasked_avimg.copy_to_array()[mid_z_sl, :, :]
masked_arr = masked_avimg.copy_to_array()[mid_z_sl, :, :]
import matplotlib.gridspec as gridspec
from matplotlib import colors

fig = plt.figure(figsize=(15, 9), constrained_layout=True)
fig.set_dpi(160)
fig.suptitle("Masks usage in image averaging method")
# Create two panels
gs = gridspec.GridSpec(ncols=2, nrows=1, figure=fig)
unmask_ax = fig.add_subplot(gs[0, 0])
mask_ax = fig.add_subplot(gs[0, 1])

images = []
images.append(imshow(unmask_ax, unmasked_arr, title="Unmasked average"))
images.append(imshow(mask_ax, masked_arr, title="Masked average"))

# Find the min and max of all colors for use in setting the color scale.
vmin = min(image.get_array().min() for image in images)
vmax = max(image.get_array().max() for image in images)
norm = colors.Normalize(vmin=vmin, vmax=vmax)
for im in images:
    im.set_norm(norm)
# Add a colorbar at the bottom
fig.colorbar(images[0], ax=[unmask_ax, mask_ax], orientation='horizontal', fraction=.1)
<matplotlib.colorbar.Colorbar at 0x7fe8112fbf70>
../../_images/fd3f94a27a00c73d64b77fac46e1a4f9f84d6756de0bafeb2b5d5b06a14ec93c.png
reg_mask_arr = reg_mask.copy_to_array()[mid_z_sl, :, :]
ref_mask_arr = ref_mask.copy_to_array()[mid_z_sl, :, :]
sum_arr = reg_mask_arr + ref_mask_arr
fig = plt.figure(figsize=(15, 9), constrained_layout=True)
fig.set_dpi(160)
fig.suptitle("Masks usage in image averaging method")
# Create two panels
gs = gridspec.GridSpec(ncols=2, nrows=1, figure=fig)
reg_ax = fig.add_subplot(gs[0, 0])
sum_ax = fig.add_subplot(gs[0, 1])

images = []
images.append(imshow(reg_ax, reg_mask_arr, title="Registered Mask", cmap='viridis', vmin=0, vmax=2))
images.append(imshow(sum_ax, sum_arr, title="Masks sum", cmap='viridis', vmin=0, vmax=2))

# Find the min and max of all colors for use in setting the color scale.
vmin = min(image.get_array().min() for image in images)
vmax = max(image.get_array().max() for image in images)
norm = colors.Normalize(vmin=vmin, vmax=vmax)
for im in images:
    im.set_norm(norm)
# Add a colorbar at the bottom
fig.colorbar(images[0], ax=[reg_ax, sum_ax], orientation='horizontal', fraction=.1, ticks=[0, 1, 2])
<matplotlib.colorbar.Colorbar at 0x7fe8092a61c0>
../../_images/93252ab584ea010e86979d4aaaebaddd6790946826a9cbe2cedf41cbafeba3ad.png