Basic registration with Twocan defaults

This notebook demonstrates a basic image registration workflow using the Twocan library. We will register an example same-slide co-stained IF and IMC image using the default utilities:

  1. Preprocessors IFprocessor and IMCprocessor

  2. RegEstimator to get the transformation matrix

  3. registation_trial and iou_corr_single_objective which are designed for IF/IMC registration

Custom functions can be provided in place of these defaults to register pairs of images of different technologies or experimental set ups (eg. serial sections)

We will set the IF as the “fixed” or target image, and IMC as the “moving” or soruce image.

import numpy as np
from twocan import RegEstimator, iou_corr_single_objective, get_aligned_coordinates
from twocan.utils import pick_best_registration 
from twocan.plotting import plot_registration, get_merge, AsinhNorm
import matplotlib.pyplot as plt
from spatialdata import read_zarr
from spatialdata.transformations import Affine
import spatialdata_plot
from skimage import transform
import optuna
/home/harrigan/.conda/envs/twocan/lib/python3.9/site-packages/pyproj/__init__.py:95: UserWarning: pyproj unable to set database path.
  _pyproj_global_context_initialize()
/home/harrigan/.conda/envs/twocan/lib/python3.9/site-packages/numba/core/decorators.py:246: RuntimeWarning: nopython is set for njit and is ignored
  warnings.warn('nopython is set for njit and is ignored', RuntimeWarning)

Load data

sdata = read_zarr('data/cell-line-0028-bd18455.zarr')

Both the IF and IMC image are saved into this zarr, with their channel names. The IMC image has a transformation matrix in the ‘aligned’ coordinate system, and we will show how this matrix was found using Twocan. Take a peek:

sdata.images
{'IF': <xarray.DataArray 'image' (c: 3, y: 512, x: 512)> Size: 2MB
dask.array<from-zarr, shape=(3, 512, 512), dtype=uint16, chunksize=(3, 512, 512), chunktype=numpy.ndarray>
Coordinates:
  * c        (c) <U4 48B 'DAPI' 'GFP' 'RFP'
  * y        (y) float64 4kB 0.5 1.5 2.5 3.5 4.5 ... 508.5 509.5 510.5 511.5
  * x        (x) float64 4kB 0.5 1.5 2.5 3.5 4.5 ... 508.5 509.5 510.5 511.5
Attributes:
    transform:  {'global': Identity }, 'IMC': <xarray.DataArray 'image' (c: 24, y: 944, x: 953)> Size: 86MB
dask.array<from-zarr, shape=(24, 944, 953), dtype=float32, chunksize=(24, 944, 953), chunktype=numpy.ndarray>
Coordinates:
  * c        (c) <U12 1kB '-' 'pHH3_S28' '-' '-' ... 'PCNA' 'DNA1' 'DNA2' '-'
  * y        (y) float64 8kB 0.5 1.5 2.5 3.5 4.5 ... 940.5 941.5 942.5 943.5
  * x        (x) float64 8kB 0.5 1.5 2.5 3.5 4.5 ... 949.5 950.5 951.5 952.5
Attributes:
    transform:  {'global': Identity , 'aligned': Sequence \n    Identity \n  ...}

Twocan provides a custom normalization function AsinhNorm to display IMC signal.

The two images are of the same set of cells, but need to be registered in order to line up their signal!

fig, (ax1, ax2, ax3) = plt.subplots(1, 3, figsize=(15, 5))
sdata.pl.render_images('IF', channel='DAPI').pl.show(coordinate_systems=["global"], title="IF: DAPI", ax=ax1)
sdata.pl.render_images('IMC', channel='DNA1', cmap='magma', norm=AsinhNorm(vmax=10)).pl.show(coordinate_systems=["global"],title="IMC: Arcsinh transformed DNA1", ax=ax2)
sdata.pl.render_images('IF', channel='DAPI', alpha = 0.5).pl.show(coordinate_systems=["global"], ax=ax3)
sdata.pl.render_images('IMC', channel='DNA1', cmap='magma', norm=AsinhNorm(vmax=10), alpha = 0.5).pl.show(coordinate_systems=["global"], title="Merge", ax=ax3)
plt.tight_layout()
../_images/0b2f7be23cae01c1887ea0ccfdce8443abad43fce52650accb25c10a3475842c.png

Run optimization to find the best registration parameters

We will register using nuclear channels: DAPI and DNA1/DNA2 respectively. All channels with matching names to the list will be summed along the channel axis to create the registration image. That is: DAPI will be registered to the DNA1+DNA2 signal.

registration_channels = ['DAPI', 'DNA1', 'DNA2']

We’ll now run an optuna study where each trial suggests preprocessing parameters, and attempts a registration. Note that most of the time, no registration is found, and the value of the trial is 0.

The resolution of the IF image (1.714um/px) is different from the IMC (1um/px), we’ll start with a rough rescaling.

from spatialdata.models import Image2DModel

sdata['IF_rescaled'] = Image2DModel.parse(data=np.array([transform.rescale(x, 1.714) for x in sdata['IF']]), c_coords = sdata['IF'].c)
INFO     no axes information specified in the object, setting `dims` to: ('c', 'y', 'x')                           
study = optuna.create_study(direction='maximize', study_name="example_1", sampler=optuna.samplers.TPESampler(seed=435))
[I 2025-05-30 18:29:33,804] A new study created in memory with name: example_1
study.optimize(lambda trial: iou_corr_single_objective(trial, sdata, registration_channels, moving_image = 'IMC', static_image = 'IF_rescaled'), n_trials=50)
[I 2025-05-30 18:29:35,341] Trial 0 finished with value: 0.0 and parameters: {'IF_binarization_threshold': 0.933211670587485, 'IF_gaussian_sigma': 3.970471856959729, 'IMC_arcsinh_normalize': True, 'IMC_arcsinh_cofactor': 82.24156916480698, 'IMC_winsorization_lower_limit': 0.09696780729863419, 'IMC_winsorization_upper_limit': 0.1783479041671231, 'IMC_binarization_threshold': 0.8096163889460634, 'IMC_gaussian_sigma': 0.08971807860817027, 'binarize_images': True, 'registration_max_features': 100000, 'registration_percentile': 0.9, 'moving_image': 'IMC', 'static_image': 'IF_rescaled'}. Best is trial 0 with value: 0.0.
[I 2025-05-30 18:29:35,570] Trial 1 finished with value: 0.0 and parameters: {'IF_binarization_threshold': 0.8268084153290103, 'IF_gaussian_sigma': 4.500237321883176, 'IMC_arcsinh_normalize': False, 'IMC_arcsinh_cofactor': 49.28179415212745, 'IMC_winsorization_lower_limit': 0.12881211103925472, 'IMC_winsorization_upper_limit': 0.02522637646225481, 'IMC_binarization_threshold': 0.1124207087973852, 'IMC_gaussian_sigma': 3.0800572032355618, 'binarize_images': True, 'registration_max_features': 100000, 'registration_percentile': 0.9, 'moving_image': 'IMC', 'static_image': 'IF_rescaled'}. Best is trial 0 with value: 0.0.
[I 2025-05-30 18:29:40,773] Trial 2 finished with value: 0.0 and parameters: {'IF_binarization_threshold': 0.20949664736336693, 'IF_gaussian_sigma': 1.3644956904032257, 'IMC_arcsinh_normalize': False, 'IMC_arcsinh_cofactor': 92.33914120927498, 'IMC_winsorization_lower_limit': 0.16015040101466327, 'IMC_winsorization_upper_limit': 0.0023365688957411204, 'IMC_binarization_threshold': 0.11785527454894162, 'IMC_gaussian_sigma': 0.08167484079027343, 'binarize_images': True, 'registration_max_features': 100000, 'registration_percentile': 0.9, 'moving_image': 'IMC', 'static_image': 'IF_rescaled'}. Best is trial 0 with value: 0.0.
[I 2025-05-30 18:29:45,598] Trial 3 finished with value: 0.4698028125311363 and parameters: {'IF_binarization_threshold': 0.12803552354837922, 'IF_gaussian_sigma': 3.6445753998738195, 'IMC_arcsinh_normalize': False, 'IMC_arcsinh_cofactor': 29.733724325035354, 'IMC_winsorization_lower_limit': 0.19519762421497946, 'IMC_winsorization_upper_limit': 0.06395929638732445, 'IMC_binarization_threshold': 0.8400581824759289, 'IMC_gaussian_sigma': 3.961087608669278, 'binarize_images': True, 'registration_max_features': 100000, 'registration_percentile': 0.9, 'moving_image': 'IMC', 'static_image': 'IF_rescaled'}. Best is trial 3 with value: 0.4698028125311363.
[I 2025-05-30 18:29:47,103] Trial 4 finished with value: 0.0 and parameters: {'IF_binarization_threshold': 0.45060037393377284, 'IF_gaussian_sigma': 1.6093481462475911, 'IMC_arcsinh_normalize': True, 'IMC_arcsinh_cofactor': 47.246045187461974, 'IMC_winsorization_lower_limit': 0.033128687157303126, 'IMC_winsorization_upper_limit': 0.020414803008475136, 'IMC_binarization_threshold': 0.12890852228165572, 'IMC_gaussian_sigma': 1.0218265075202053, 'binarize_images': True, 'registration_max_features': 100000, 'registration_percentile': 0.9, 'moving_image': 'IMC', 'static_image': 'IF_rescaled'}. Best is trial 3 with value: 0.4698028125311363.
[I 2025-05-30 18:30:00,504] Trial 5 finished with value: 0.0 and parameters: {'IF_binarization_threshold': 0.08168951043352457, 'IF_gaussian_sigma': 1.1528651567460835, 'IMC_arcsinh_normalize': False, 'IMC_arcsinh_cofactor': 61.381879739596414, 'IMC_winsorization_lower_limit': 0.09380393616435212, 'IMC_winsorization_upper_limit': 0.17591167211922293, 'IMC_binarization_threshold': 0.2595042643451416, 'IMC_gaussian_sigma': 0.8225699044897111, 'binarize_images': True, 'registration_max_features': 100000, 'registration_percentile': 0.9, 'moving_image': 'IMC', 'static_image': 'IF_rescaled'}. Best is trial 3 with value: 0.4698028125311363.
[I 2025-05-30 18:30:03,030] Trial 6 finished with value: 0.0 and parameters: {'IF_binarization_threshold': 0.7641429128807489, 'IF_gaussian_sigma': 0.3932598896899492, 'IMC_arcsinh_normalize': False, 'IMC_arcsinh_cofactor': 56.99016211399242, 'IMC_winsorization_lower_limit': 0.07301024747769538, 'IMC_winsorization_upper_limit': 0.08195995856107473, 'IMC_binarization_threshold': 0.3049872292385296, 'IMC_gaussian_sigma': 0.2881221705091114, 'binarize_images': True, 'registration_max_features': 100000, 'registration_percentile': 0.9, 'moving_image': 'IMC', 'static_image': 'IF_rescaled'}. Best is trial 3 with value: 0.4698028125311363.
[I 2025-05-30 18:30:03,210] Trial 7 finished with value: 0.0 and parameters: {'IF_binarization_threshold': 0.9646867538180126, 'IF_gaussian_sigma': 1.625422966142337, 'IMC_arcsinh_normalize': True, 'IMC_arcsinh_cofactor': 17.071998859899278, 'IMC_winsorization_lower_limit': 0.16647183620582806, 'IMC_winsorization_upper_limit': 0.0053750219355849895, 'IMC_binarization_threshold': 0.7578883567508103, 'IMC_gaussian_sigma': 0.8659325340793106, 'binarize_images': True, 'registration_max_features': 100000, 'registration_percentile': 0.9, 'moving_image': 'IMC', 'static_image': 'IF_rescaled'}. Best is trial 3 with value: 0.4698028125311363.
[I 2025-05-30 18:30:06,229] Trial 8 finished with value: 0.0 and parameters: {'IF_binarization_threshold': 0.3116169484160014, 'IF_gaussian_sigma': 1.403952129937417, 'IMC_arcsinh_normalize': False, 'IMC_arcsinh_cofactor': 4.090971727970644, 'IMC_winsorization_lower_limit': 0.17896723594254282, 'IMC_winsorization_upper_limit': 0.10426820931331392, 'IMC_binarization_threshold': 0.2073560838302655, 'IMC_gaussian_sigma': 4.78542874606788, 'binarize_images': True, 'registration_max_features': 100000, 'registration_percentile': 0.9, 'moving_image': 'IMC', 'static_image': 'IF_rescaled'}. Best is trial 3 with value: 0.4698028125311363.
[I 2025-05-30 18:30:07,688] Trial 9 finished with value: 0.0 and parameters: {'IF_binarization_threshold': 0.31580996727615873, 'IF_gaussian_sigma': 4.379820976513671, 'IMC_arcsinh_normalize': True, 'IMC_arcsinh_cofactor': 6.328106674296607, 'IMC_winsorization_lower_limit': 0.1568694547962824, 'IMC_winsorization_upper_limit': 0.09287751196316739, 'IMC_binarization_threshold': 0.3311222954178791, 'IMC_gaussian_sigma': 2.8391764422987196, 'binarize_images': True, 'registration_max_features': 100000, 'registration_percentile': 0.9, 'moving_image': 'IMC', 'static_image': 'IF_rescaled'}. Best is trial 3 with value: 0.4698028125311363.
[I 2025-05-30 18:30:08,665] Trial 10 finished with value: 0.0 and parameters: {'IF_binarization_threshold': 0.010056221583625319, 'IF_gaussian_sigma': 3.149113960322229, 'IMC_arcsinh_normalize': False, 'IMC_arcsinh_cofactor': 30.253926210145465, 'IMC_winsorization_lower_limit': 0.002353253270908434, 'IMC_winsorization_upper_limit': 0.05816010037008991, 'IMC_binarization_threshold': 0.9898678610210639, 'IMC_gaussian_sigma': 4.358060069689551, 'binarize_images': True, 'registration_max_features': 100000, 'registration_percentile': 0.9, 'moving_image': 'IMC', 'static_image': 'IF_rescaled'}. Best is trial 3 with value: 0.4698028125311363.
[I 2025-05-30 18:30:10,078] Trial 11 finished with value: 0.0 and parameters: {'IF_binarization_threshold': 0.6582193414578937, 'IF_gaussian_sigma': 3.3720988058941708, 'IMC_arcsinh_normalize': True, 'IMC_arcsinh_cofactor': 99.6318998039571, 'IMC_winsorization_lower_limit': 0.12061414202095798, 'IMC_winsorization_upper_limit': 0.19781644066116685, 'IMC_binarization_threshold': 0.6959447969143482, 'IMC_gaussian_sigma': 3.7979461970142414, 'binarize_images': True, 'registration_max_features': 100000, 'registration_percentile': 0.9, 'moving_image': 'IMC', 'static_image': 'IF_rescaled'}. Best is trial 3 with value: 0.4698028125311363.
[I 2025-05-30 18:30:12,502] Trial 12 finished with value: 0.0 and parameters: {'IF_binarization_threshold': 0.46386320915437385, 'IF_gaussian_sigma': 3.538867929308706, 'IMC_arcsinh_normalize': True, 'IMC_arcsinh_cofactor': 77.53594442569396, 'IMC_winsorization_lower_limit': 0.06794566574798702, 'IMC_winsorization_upper_limit': 0.14173929734180624, 'IMC_binarization_threshold': 0.9775347569057904, 'IMC_gaussian_sigma': 1.8260357642469311, 'binarize_images': True, 'registration_max_features': 100000, 'registration_percentile': 0.9, 'moving_image': 'IMC', 'static_image': 'IF_rescaled'}. Best is trial 3 with value: 0.4698028125311363.
[I 2025-05-30 18:30:12,757] Trial 13 finished with value: 0.0 and parameters: {'IF_binarization_threshold': 0.9922853742060193, 'IF_gaussian_sigma': 2.674795844353602, 'IMC_arcsinh_normalize': True, 'IMC_arcsinh_cofactor': 32.60015308042552, 'IMC_winsorization_lower_limit': 0.1983384088496542, 'IMC_winsorization_upper_limit': 0.12996612525960577, 'IMC_binarization_threshold': 0.7336922019686898, 'IMC_gaussian_sigma': 1.8803303322686098, 'binarize_images': True, 'registration_max_features': 100000, 'registration_percentile': 0.9, 'moving_image': 'IMC', 'static_image': 'IF_rescaled'}. Best is trial 3 with value: 0.4698028125311363.
[I 2025-05-30 18:30:14,026] Trial 14 finished with value: 0.0 and parameters: {'IF_binarization_threshold': 0.5642937347420971, 'IF_gaussian_sigma': 4.9231661626565, 'IMC_arcsinh_normalize': False, 'IMC_arcsinh_cofactor': 74.85004728852668, 'IMC_winsorization_lower_limit': 0.12495043785701579, 'IMC_winsorization_upper_limit': 0.05798772978887293, 'IMC_binarization_threshold': 0.5511984260454887, 'IMC_gaussian_sigma': 3.6346637212540065, 'binarize_images': True, 'registration_max_features': 100000, 'registration_percentile': 0.9, 'moving_image': 'IMC', 'static_image': 'IF_rescaled'}. Best is trial 3 with value: 0.4698028125311363.
[I 2025-05-30 18:30:18,710] Trial 15 finished with value: 0.3259846637162093 and parameters: {'IF_binarization_threshold': 0.2275951866499849, 'IF_gaussian_sigma': 3.8648058245547, 'IMC_arcsinh_normalize': True, 'IMC_arcsinh_cofactor': 32.41997165564982, 'IMC_winsorization_lower_limit': 0.08689930718099402, 'IMC_winsorization_upper_limit': 0.14812491358408114, 'IMC_binarization_threshold': 0.8548740781034729, 'IMC_gaussian_sigma': 2.026994505805673, 'binarize_images': True, 'registration_max_features': 100000, 'registration_percentile': 0.9, 'moving_image': 'IMC', 'static_image': 'IF_rescaled'}. Best is trial 3 with value: 0.4698028125311363.
[I 2025-05-30 18:30:21,894] Trial 16 finished with value: -0.0 and parameters: {'IF_binarization_threshold': 0.15912918138413512, 'IF_gaussian_sigma': 2.3974581465125726, 'IMC_arcsinh_normalize': False, 'IMC_arcsinh_cofactor': 33.26254690634191, 'IMC_winsorization_lower_limit': 0.05794875850091871, 'IMC_winsorization_upper_limit': 0.1305939574574743, 'IMC_binarization_threshold': 0.541856287322811, 'IMC_gaussian_sigma': 2.0983059865424063, 'binarize_images': True, 'registration_max_features': 100000, 'registration_percentile': 0.9, 'moving_image': 'IMC', 'static_image': 'IF_rescaled'}. Best is trial 3 with value: 0.4698028125311363.
[I 2025-05-30 18:30:26,226] Trial 17 finished with value: 0.48056559136202776 and parameters: {'IF_binarization_threshold': 0.26564920335227515, 'IF_gaussian_sigma': 2.761828127547157, 'IMC_arcsinh_normalize': True, 'IMC_arcsinh_cofactor': 19.188574056004484, 'IMC_winsorization_lower_limit': 0.03765271057520447, 'IMC_winsorization_upper_limit': 0.06356872423831622, 'IMC_binarization_threshold': 0.8699721065219372, 'IMC_gaussian_sigma': 3.4336732069956044, 'binarize_images': True, 'registration_max_features': 100000, 'registration_percentile': 0.9, 'moving_image': 'IMC', 'static_image': 'IF_rescaled'}. Best is trial 17 with value: 0.48056559136202776.
[I 2025-05-30 18:30:27,596] Trial 18 finished with value: 0.0 and parameters: {'IF_binarization_threshold': 0.35001073974460406, 'IF_gaussian_sigma': 2.463352761488431, 'IMC_arcsinh_normalize': True, 'IMC_arcsinh_cofactor': 17.795234282126998, 'IMC_winsorization_lower_limit': 0.03414372154969392, 'IMC_winsorization_upper_limit': 0.05586916455220992, 'IMC_binarization_threshold': 0.6489239506705231, 'IMC_gaussian_sigma': 3.6768376221982506, 'binarize_images': True, 'registration_max_features': 100000, 'registration_percentile': 0.9, 'moving_image': 'IMC', 'static_image': 'IF_rescaled'}. Best is trial 17 with value: 0.48056559136202776.
[I 2025-05-30 18:30:28,979] Trial 19 finished with value: 0.0 and parameters: {'IF_binarization_threshold': 0.101156022531177, 'IF_gaussian_sigma': 3.0701199717675265, 'IMC_arcsinh_normalize': False, 'IMC_arcsinh_cofactor': 19.434250939449576, 'IMC_winsorization_lower_limit': 0.03852333555797421, 'IMC_winsorization_upper_limit': 0.03995232547922731, 'IMC_binarization_threshold': 0.869355760309535, 'IMC_gaussian_sigma': 4.305414702579117, 'binarize_images': True, 'registration_max_features': 100000, 'registration_percentile': 0.9, 'moving_image': 'IMC', 'static_image': 'IF_rescaled'}. Best is trial 17 with value: 0.48056559136202776.
[I 2025-05-30 18:30:33,810] Trial 20 finished with value: 0.49500447761300326 and parameters: {'IF_binarization_threshold': 0.013120059525636718, 'IF_gaussian_sigma': 2.756975485271538, 'IMC_arcsinh_normalize': True, 'IMC_arcsinh_cofactor': 38.9285187836703, 'IMC_winsorization_lower_limit': 0.0007437568861841937, 'IMC_winsorization_upper_limit': 0.07950674894827967, 'IMC_binarization_threshold': 0.4124255701889006, 'IMC_gaussian_sigma': 3.0374948776813113, 'binarize_images': True, 'registration_max_features': 100000, 'registration_percentile': 0.9, 'moving_image': 'IMC', 'static_image': 'IF_rescaled'}. Best is trial 20 with value: 0.49500447761300326.
[I 2025-05-30 18:30:39,010] Trial 21 finished with value: 0.4196452545685171 and parameters: {'IF_binarization_threshold': 0.007060656927755275, 'IF_gaussian_sigma': 2.1482431019328754, 'IMC_arcsinh_normalize': True, 'IMC_arcsinh_cofactor': 42.151861957764766, 'IMC_winsorization_lower_limit': 0.0021838599328059373, 'IMC_winsorization_upper_limit': 0.07569502769148886, 'IMC_binarization_threshold': 0.42066406250262045, 'IMC_gaussian_sigma': 3.055337814677518, 'binarize_images': True, 'registration_max_features': 100000, 'registration_percentile': 0.9, 'moving_image': 'IMC', 'static_image': 'IF_rescaled'}. Best is trial 20 with value: 0.49500447761300326.
[I 2025-05-30 18:30:41,098] Trial 22 finished with value: 0.0 and parameters: {'IF_binarization_threshold': 0.1148498130800595, 'IF_gaussian_sigma': 2.8977352211121947, 'IMC_arcsinh_normalize': True, 'IMC_arcsinh_cofactor': 23.06966154283472, 'IMC_winsorization_lower_limit': 0.018393723101273934, 'IMC_winsorization_upper_limit': 0.1033376002491711, 'IMC_binarization_threshold': 0.43419787072061805, 'IMC_gaussian_sigma': 2.57643757372443, 'binarize_images': True, 'registration_max_features': 100000, 'registration_percentile': 0.9, 'moving_image': 'IMC', 'static_image': 'IF_rescaled'}. Best is trial 20 with value: 0.49500447761300326.
[I 2025-05-30 18:30:43,096] Trial 23 finished with value: 0.0 and parameters: {'IF_binarization_threshold': 0.2340977129802473, 'IF_gaussian_sigma': 2.0081913578487107, 'IMC_arcsinh_normalize': True, 'IMC_arcsinh_cofactor': 39.498751021210396, 'IMC_winsorization_lower_limit': 0.05018262356963195, 'IMC_winsorization_upper_limit': 0.07345353316368232, 'IMC_binarization_threshold': 0.6237240458495262, 'IMC_gaussian_sigma': 4.108188431803914, 'binarize_images': True, 'registration_max_features': 100000, 'registration_percentile': 0.9, 'moving_image': 'IMC', 'static_image': 'IF_rescaled'}. Best is trial 20 with value: 0.49500447761300326.
[I 2025-05-30 18:30:47,120] Trial 24 finished with value: 0.2978520825321835 and parameters: {'IF_binarization_threshold': 0.3591972145582696, 'IF_gaussian_sigma': 3.637352324508384, 'IMC_arcsinh_normalize': True, 'IMC_arcsinh_cofactor': 11.706155876844232, 'IMC_winsorization_lower_limit': 0.0218983030321333, 'IMC_winsorization_upper_limit': 0.041604140385579316, 'IMC_binarization_threshold': 0.9076454642645414, 'IMC_gaussian_sigma': 3.3763048515129843, 'binarize_images': True, 'registration_max_features': 100000, 'registration_percentile': 0.9, 'moving_image': 'IMC', 'static_image': 'IF_rescaled'}. Best is trial 20 with value: 0.49500447761300326.
[I 2025-05-30 18:30:52,015] Trial 25 finished with value: 0.44379122532443716 and parameters: {'IF_binarization_threshold': 0.0673894775167069, 'IF_gaussian_sigma': 4.190058246381531, 'IMC_arcsinh_normalize': True, 'IMC_arcsinh_cofactor': 61.25238080961959, 'IMC_winsorization_lower_limit': 0.016583379986700897, 'IMC_winsorization_upper_limit': 0.10924799725900189, 'IMC_binarization_threshold': 0.4358125774744371, 'IMC_gaussian_sigma': 4.8581752763666906, 'binarize_images': True, 'registration_max_features': 100000, 'registration_percentile': 0.9, 'moving_image': 'IMC', 'static_image': 'IF_rescaled'}. Best is trial 20 with value: 0.49500447761300326.
[I 2025-05-30 18:30:56,303] Trial 26 finished with value: 0.6129635918390618 and parameters: {'IF_binarization_threshold': 0.1598777861898527, 'IF_gaussian_sigma': 2.7878017298754973, 'IMC_arcsinh_normalize': False, 'IMC_arcsinh_cofactor': 25.736141205992677, 'IMC_winsorization_lower_limit': 0.1115109835179503, 'IMC_winsorization_upper_limit': 0.08777500667688592, 'IMC_binarization_threshold': 0.7916015953959933, 'IMC_gaussian_sigma': 2.479390152299457, 'binarize_images': True, 'registration_max_features': 100000, 'registration_percentile': 0.9, 'moving_image': 'IMC', 'static_image': 'IF_rescaled'}. Best is trial 26 with value: 0.6129635918390618.
[I 2025-05-30 18:30:58,593] Trial 27 finished with value: 0.0 and parameters: {'IF_binarization_threshold': 0.18137804338502234, 'IF_gaussian_sigma': 2.7443850253603523, 'IMC_arcsinh_normalize': True, 'IMC_arcsinh_cofactor': 23.525103616914556, 'IMC_winsorization_lower_limit': 0.13560169718519047, 'IMC_winsorization_upper_limit': 0.11882285642046075, 'IMC_binarization_threshold': 0.5045709440158614, 'IMC_gaussian_sigma': 2.42269351879898, 'binarize_images': True, 'registration_max_features': 100000, 'registration_percentile': 0.9, 'moving_image': 'IMC', 'static_image': 'IF_rescaled'}. Best is trial 26 with value: 0.6129635918390618.
[I 2025-05-30 18:31:01,591] Trial 28 finished with value: 0.0 and parameters: {'IF_binarization_threshold': 0.26482599990592526, 'IF_gaussian_sigma': 2.027515725503886, 'IMC_arcsinh_normalize': False, 'IMC_arcsinh_cofactor': 11.136682730609353, 'IMC_winsorization_lower_limit': 0.11086721321416342, 'IMC_winsorization_upper_limit': 0.0880866066049919, 'IMC_binarization_threshold': 0.5991508222826513, 'IMC_gaussian_sigma': 1.440331829257788, 'binarize_images': True, 'registration_max_features': 100000, 'registration_percentile': 0.9, 'moving_image': 'IMC', 'static_image': 'IF_rescaled'}. Best is trial 26 with value: 0.6129635918390618.
[I 2025-05-30 18:31:03,183] Trial 29 finished with value: 0.0 and parameters: {'IF_binarization_threshold': 0.565805458257713, 'IF_gaussian_sigma': 0.7000852420032486, 'IMC_arcsinh_normalize': True, 'IMC_arcsinh_cofactor': 41.50222328997258, 'IMC_winsorization_lower_limit': 0.07988733422776645, 'IMC_winsorization_upper_limit': 0.043601919832882065, 'IMC_binarization_threshold': 0.7860012619480009, 'IMC_gaussian_sigma': 3.3119906399494523, 'binarize_images': True, 'registration_max_features': 100000, 'registration_percentile': 0.9, 'moving_image': 'IMC', 'static_image': 'IF_rescaled'}. Best is trial 26 with value: 0.6129635918390618.
[I 2025-05-30 18:31:04,435] Trial 30 finished with value: 0.0 and parameters: {'IF_binarization_threshold': 0.3989098631981137, 'IF_gaussian_sigma': 3.2957191232427787, 'IMC_arcsinh_normalize': True, 'IMC_arcsinh_cofactor': 25.603565029583866, 'IMC_winsorization_lower_limit': 0.10841579274358995, 'IMC_winsorization_upper_limit': 0.09087822014969564, 'IMC_binarization_threshold': 0.008126562674202753, 'IMC_gaussian_sigma': 2.6488462687153653, 'binarize_images': True, 'registration_max_features': 100000, 'registration_percentile': 0.9, 'moving_image': 'IMC', 'static_image': 'IF_rescaled'}. Best is trial 26 with value: 0.6129635918390618.
[I 2025-05-30 18:31:05,981] Trial 31 finished with value: 0.0 and parameters: {'IF_binarization_threshold': 0.14105781235503878, 'IF_gaussian_sigma': 3.8217655873210603, 'IMC_arcsinh_normalize': False, 'IMC_arcsinh_cofactor': 37.39745202510719, 'IMC_winsorization_lower_limit': 0.04791305732149505, 'IMC_winsorization_upper_limit': 0.07045214624534286, 'IMC_binarization_threshold': 0.9338459607837258, 'IMC_gaussian_sigma': 3.9682948761774317, 'binarize_images': True, 'registration_max_features': 100000, 'registration_percentile': 0.9, 'moving_image': 'IMC', 'static_image': 'IF_rescaled'}. Best is trial 26 with value: 0.6129635918390618.
[I 2025-05-30 18:31:08,998] Trial 32 finished with value: 0.0 and parameters: {'IF_binarization_threshold': 0.03931066082320098, 'IF_gaussian_sigma': 2.909233462290834, 'IMC_arcsinh_normalize': False, 'IMC_arcsinh_cofactor': 27.554699192947858, 'IMC_winsorization_lower_limit': 0.1961988334413037, 'IMC_winsorization_upper_limit': 0.06642565058981634, 'IMC_binarization_threshold': 0.8244218823218079, 'IMC_gaussian_sigma': 3.2814767369733326, 'binarize_images': True, 'registration_max_features': 100000, 'registration_percentile': 0.9, 'moving_image': 'IMC', 'static_image': 'IF_rescaled'}. Best is trial 26 with value: 0.6129635918390618.
[I 2025-05-30 18:31:13,606] Trial 33 finished with value: 0.6413005292167133 and parameters: {'IF_binarization_threshold': 0.16195929979150478, 'IF_gaussian_sigma': 2.347222118247061, 'IMC_arcsinh_normalize': False, 'IMC_arcsinh_cofactor': 52.669050432769026, 'IMC_winsorization_lower_limit': 0.1445489179069131, 'IMC_winsorization_upper_limit': 0.04965021533751028, 'IMC_binarization_threshold': 0.6963577977550832, 'IMC_gaussian_sigma': 2.311329941756081, 'binarize_images': True, 'registration_max_features': 100000, 'registration_percentile': 0.9, 'moving_image': 'IMC', 'static_image': 'IF_rescaled'}. Best is trial 33 with value: 0.6413005292167133.
[I 2025-05-30 18:31:17,611] Trial 34 finished with value: 0.49670583570128457 and parameters: {'IF_binarization_threshold': 0.2872516542876298, 'IF_gaussian_sigma': 2.469338472930849, 'IMC_arcsinh_normalize': False, 'IMC_arcsinh_cofactor': 51.85124109750328, 'IMC_winsorization_lower_limit': 0.14070087556448427, 'IMC_winsorization_upper_limit': 0.02400041985823534, 'IMC_binarization_threshold': 0.7027878449745112, 'IMC_gaussian_sigma': 2.2861502135333285, 'binarize_images': True, 'registration_max_features': 100000, 'registration_percentile': 0.9, 'moving_image': 'IMC', 'static_image': 'IF_rescaled'}. Best is trial 33 with value: 0.6413005292167133.
[I 2025-05-30 18:31:21,825] Trial 35 finished with value: 0.5845455161204896 and parameters: {'IF_binarization_threshold': 0.16869648314366822, 'IF_gaussian_sigma': 2.140298827486788, 'IMC_arcsinh_normalize': False, 'IMC_arcsinh_cofactor': 54.360418882819815, 'IMC_winsorization_lower_limit': 0.1409499249440549, 'IMC_winsorization_upper_limit': 0.025956856393035355, 'IMC_binarization_threshold': 0.6586532663589644, 'IMC_gaussian_sigma': 2.264263209505864, 'binarize_images': True, 'registration_max_features': 100000, 'registration_percentile': 0.9, 'moving_image': 'IMC', 'static_image': 'IF_rescaled'}. Best is trial 33 with value: 0.6413005292167133.
[I 2025-05-30 18:31:25,918] Trial 36 finished with value: 0.5092134390593994 and parameters: {'IF_binarization_threshold': 0.18952094720689833, 'IF_gaussian_sigma': 2.2610346419677874, 'IMC_arcsinh_normalize': False, 'IMC_arcsinh_cofactor': 53.45184457586226, 'IMC_winsorization_lower_limit': 0.14228721151454315, 'IMC_winsorization_upper_limit': 0.017695632602341076, 'IMC_binarization_threshold': 0.675821873333952, 'IMC_gaussian_sigma': 2.2546961038443736, 'binarize_images': True, 'registration_max_features': 100000, 'registration_percentile': 0.9, 'moving_image': 'IMC', 'static_image': 'IF_rescaled'}. Best is trial 33 with value: 0.6413005292167133.
[I 2025-05-30 18:31:30,519] Trial 37 finished with value: 0.4766227482106362 and parameters: {'IF_binarization_threshold': 0.19044758935722467, 'IF_gaussian_sigma': 1.8291895689901712, 'IMC_arcsinh_normalize': False, 'IMC_arcsinh_cofactor': 66.38700715822995, 'IMC_winsorization_lower_limit': 0.14965299459392747, 'IMC_winsorization_upper_limit': 0.012470071372920753, 'IMC_binarization_threshold': 0.6663688307635403, 'IMC_gaussian_sigma': 1.5349867288964252, 'binarize_images': True, 'registration_max_features': 100000, 'registration_percentile': 0.9, 'moving_image': 'IMC', 'static_image': 'IF_rescaled'}. Best is trial 33 with value: 0.6413005292167133.
[I 2025-05-30 18:31:31,680] Trial 38 finished with value: 0.0 and parameters: {'IF_binarization_threshold': 0.8205696370214167, 'IF_gaussian_sigma': 2.196195502014332, 'IMC_arcsinh_normalize': False, 'IMC_arcsinh_cofactor': 49.00802667424148, 'IMC_winsorization_lower_limit': 0.17400657565771346, 'IMC_winsorization_upper_limit': 0.030958292399450874, 'IMC_binarization_threshold': 0.5917672742803289, 'IMC_gaussian_sigma': 1.428788939317603, 'binarize_images': True, 'registration_max_features': 100000, 'registration_percentile': 0.9, 'moving_image': 'IMC', 'static_image': 'IF_rescaled'}. Best is trial 33 with value: 0.6413005292167133.
[I 2025-05-30 18:31:35,623] Trial 39 finished with value: 0.35132420791355734 and parameters: {'IF_binarization_threshold': 0.40890346456212967, 'IF_gaussian_sigma': 0.8270080735084813, 'IMC_arcsinh_normalize': False, 'IMC_arcsinh_cofactor': 67.1702254268587, 'IMC_winsorization_lower_limit': 0.14609923439125413, 'IMC_winsorization_upper_limit': 0.014185348386767077, 'IMC_binarization_threshold': 0.7677878203765123, 'IMC_gaussian_sigma': 2.8063865067231153, 'binarize_images': True, 'registration_max_features': 100000, 'registration_percentile': 0.9, 'moving_image': 'IMC', 'static_image': 'IF_rescaled'}. Best is trial 33 with value: 0.6413005292167133.
[I 2025-05-30 18:31:40,122] Trial 40 finished with value: 0.4364260867771481 and parameters: {'IF_binarization_threshold': 0.08432447597369841, 'IF_gaussian_sigma': 1.6563786291983296, 'IMC_arcsinh_normalize': False, 'IMC_arcsinh_cofactor': 53.25171757033678, 'IMC_winsorization_lower_limit': 0.10107209172312834, 'IMC_winsorization_upper_limit': 0.03276156354923886, 'IMC_binarization_threshold': 0.7217563080789836, 'IMC_gaussian_sigma': 1.7061885459602801, 'binarize_images': True, 'registration_max_features': 100000, 'registration_percentile': 0.9, 'moving_image': 'IMC', 'static_image': 'IF_rescaled'}. Best is trial 33 with value: 0.6413005292167133.
[I 2025-05-30 18:31:44,224] Trial 41 finished with value: 0.5112152008839959 and parameters: {'IF_binarization_threshold': 0.2682405496714012, 'IF_gaussian_sigma': 2.543842227123359, 'IMC_arcsinh_normalize': False, 'IMC_arcsinh_cofactor': 55.26507079295737, 'IMC_winsorization_lower_limit': 0.13698637192836535, 'IMC_winsorization_upper_limit': 0.023948613405204405, 'IMC_binarization_threshold': 0.6836447335848829, 'IMC_gaussian_sigma': 2.2733985916924406, 'binarize_images': True, 'registration_max_features': 100000, 'registration_percentile': 0.9, 'moving_image': 'IMC', 'static_image': 'IF_rescaled'}. Best is trial 33 with value: 0.6413005292167133.
[I 2025-05-30 18:31:45,676] Trial 42 finished with value: 0.0 and parameters: {'IF_binarization_threshold': 0.232442289547455, 'IF_gaussian_sigma': 2.3124184547135687, 'IMC_arcsinh_normalize': False, 'IMC_arcsinh_cofactor': 56.15005715256824, 'IMC_winsorization_lower_limit': 0.13254628490969586, 'IMC_winsorization_upper_limit': 0.0019631999485978276, 'IMC_binarization_threshold': 0.7942697974019389, 'IMC_gaussian_sigma': 2.237129126456622, 'binarize_images': True, 'registration_max_features': 100000, 'registration_percentile': 0.9, 'moving_image': 'IMC', 'static_image': 'IF_rescaled'}. Best is trial 33 with value: 0.6413005292167133.
[I 2025-05-30 18:31:50,527] Trial 43 finished with value: 0.6452717822270486 and parameters: {'IF_binarization_threshold': 0.1512829279272145, 'IF_gaussian_sigma': 1.3874555822749557, 'IMC_arcsinh_normalize': False, 'IMC_arcsinh_cofactor': 46.38314977238214, 'IMC_winsorization_lower_limit': 0.158902431621469, 'IMC_winsorization_upper_limit': 0.05007657039379224, 'IMC_binarization_threshold': 0.6750809806151257, 'IMC_gaussian_sigma': 1.1813860222072852, 'binarize_images': True, 'registration_max_features': 100000, 'registration_percentile': 0.9, 'moving_image': 'IMC', 'static_image': 'IF_rescaled'}. Best is trial 43 with value: 0.6452717822270486.
[I 2025-05-30 18:31:55,319] Trial 44 finished with value: 0.6526415943925467 and parameters: {'IF_binarization_threshold': 0.14055633731267614, 'IF_gaussian_sigma': 1.2547537115861847, 'IMC_arcsinh_normalize': False, 'IMC_arcsinh_cofactor': 45.46181262714963, 'IMC_winsorization_lower_limit': 0.16133138741487346, 'IMC_winsorization_upper_limit': 0.049350827068444396, 'IMC_binarization_threshold': 0.6210991405573469, 'IMC_gaussian_sigma': 1.1854010829079855, 'binarize_images': True, 'registration_max_features': 100000, 'registration_percentile': 0.9, 'moving_image': 'IMC', 'static_image': 'IF_rescaled'}. Best is trial 44 with value: 0.6526415943925467.
[I 2025-05-30 18:32:08,259] Trial 45 finished with value: 0.0 and parameters: {'IF_binarization_threshold': 0.13805375817013918, 'IF_gaussian_sigma': 1.3495268956345272, 'IMC_arcsinh_normalize': False, 'IMC_arcsinh_cofactor': 44.36812805691621, 'IMC_winsorization_lower_limit': 0.16071420842719433, 'IMC_winsorization_upper_limit': 0.05084292638083324, 'IMC_binarization_threshold': 0.5740761155413241, 'IMC_gaussian_sigma': 0.3919575433582825, 'binarize_images': True, 'registration_max_features': 100000, 'registration_percentile': 0.9, 'moving_image': 'IMC', 'static_image': 'IF_rescaled'}. Best is trial 44 with value: 0.6526415943925467.
[I 2025-05-30 18:32:13,520] Trial 46 finished with value: 0.5797096729585938 and parameters: {'IF_binarization_threshold': 0.05134767936535489, 'IF_gaussian_sigma': 0.11996442908061855, 'IMC_arcsinh_normalize': False, 'IMC_arcsinh_cofactor': 46.90128001437719, 'IMC_winsorization_lower_limit': 0.18461812293912463, 'IMC_winsorization_upper_limit': 0.0491630431584474, 'IMC_binarization_threshold': 0.501278068821149, 'IMC_gaussian_sigma': 1.2079908611947792, 'binarize_images': True, 'registration_max_features': 100000, 'registration_percentile': 0.9, 'moving_image': 'IMC', 'static_image': 'IF_rescaled'}. Best is trial 44 with value: 0.6526415943925467.
[I 2025-05-30 18:32:18,211] Trial 47 finished with value: 0.5779111063518994 and parameters: {'IF_binarization_threshold': 0.13845766677942714, 'IF_gaussian_sigma': 1.0149848882513304, 'IMC_arcsinh_normalize': False, 'IMC_arcsinh_cofactor': 62.68766238047135, 'IMC_winsorization_lower_limit': 0.1692745885413007, 'IMC_winsorization_upper_limit': 0.033738718554848335, 'IMC_binarization_threshold': 0.7417385370151532, 'IMC_gaussian_sigma': 0.8198469679087235, 'binarize_images': True, 'registration_max_features': 100000, 'registration_percentile': 0.9, 'moving_image': 'IMC', 'static_image': 'IF_rescaled'}. Best is trial 44 with value: 0.6526415943925467.
[I 2025-05-30 18:32:28,621] Trial 48 finished with value: 0.0 and parameters: {'IF_binarization_threshold': 0.2133936772332895, 'IF_gaussian_sigma': 1.1839771806320663, 'IMC_arcsinh_normalize': False, 'IMC_arcsinh_cofactor': 87.19367846157502, 'IMC_winsorization_lower_limit': 0.15341978150827612, 'IMC_winsorization_upper_limit': 0.1602812177460857, 'IMC_binarization_threshold': 0.6303848114057606, 'IMC_gaussian_sigma': 0.541016936903354, 'binarize_images': True, 'registration_max_features': 100000, 'registration_percentile': 0.9, 'moving_image': 'IMC', 'static_image': 'IF_rescaled'}. Best is trial 44 with value: 0.6526415943925467.
[I 2025-05-30 18:32:33,211] Trial 49 finished with value: 0.33115497979530734 and parameters: {'IF_binarization_threshold': 0.3344943891776251, 'IF_gaussian_sigma': 1.8440745722225729, 'IMC_arcsinh_normalize': False, 'IMC_arcsinh_cofactor': 46.54772576489543, 'IMC_winsorization_lower_limit': 0.11917764947096415, 'IMC_winsorization_upper_limit': 0.04735250782719269, 'IMC_binarization_threshold': 0.8151152667917122, 'IMC_gaussian_sigma': 1.1327178980100732, 'binarize_images': True, 'registration_max_features': 100000, 'registration_percentile': 0.9, 'moving_image': 'IMC', 'static_image': 'IF_rescaled'}. Best is trial 44 with value: 0.6526415943925467.

Pick best trial

We can checkout the parameters selected for the best trial Twocan suggests the best trial based on the balanced score, this is not necessarily the same trial as that which maximizes the optimization objectives: this can be extracted with the optuna method study.best_trial

optuna_best_trial = study.best_trial
print(optuna_best_trial)
FrozenTrial(number=44, state=TrialState.COMPLETE, values=[0.6526415943925467], datetime_start=datetime.datetime(2025, 5, 30, 18, 31, 50, 529365), datetime_complete=datetime.datetime(2025, 5, 30, 18, 31, 55, 319088), params={'IF_binarization_threshold': 0.14055633731267614, 'IF_gaussian_sigma': 1.2547537115861847, 'IMC_arcsinh_normalize': False, 'IMC_arcsinh_cofactor': 45.46181262714963, 'IMC_winsorization_lower_limit': 0.16133138741487346, 'IMC_winsorization_upper_limit': 0.049350827068444396, 'IMC_binarization_threshold': 0.6210991405573469, 'IMC_gaussian_sigma': 1.1854010829079855, 'binarize_images': True, 'registration_max_features': 100000, 'registration_percentile': 0.9, 'moving_image': 'IMC', 'static_image': 'IF_rescaled'}, user_attrs={'registration_matrix': array([[ 9.95031054e-01,  1.25336820e-03, -4.52149509e+01],
       [-1.25336820e-03,  9.95031054e-01, -3.89747629e+01]]), 'source_sum': 39953, 'target_sum': 38512, 'logical_and': 33675, 'logical_or': 44790, 'logical_xor': 11115, 'logical_iou': 0.7518419290020094, 'stack_image_max_corr': 0.8680569268846966, 'reg_image_max_corr': 0.8680569268846968, 'stack_cell_max_corr': 0.5409464665823187, 'reg_cell_max_corr': 0.5409464665823185}, system_attrs={}, intermediate_values={}, distributions={'IF_binarization_threshold': FloatDistribution(high=1.0, log=False, low=0.0, step=None), 'IF_gaussian_sigma': FloatDistribution(high=5.0, log=False, low=0.0, step=None), 'IMC_arcsinh_normalize': CategoricalDistribution(choices=(True, False)), 'IMC_arcsinh_cofactor': FloatDistribution(high=100.0, log=False, low=1.0, step=None), 'IMC_winsorization_lower_limit': FloatDistribution(high=0.2, log=False, low=0.0, step=None), 'IMC_winsorization_upper_limit': FloatDistribution(high=0.2, log=False, low=0.0, step=None), 'IMC_binarization_threshold': FloatDistribution(high=1.0, log=False, low=0.0, step=None), 'IMC_gaussian_sigma': FloatDistribution(high=5.0, log=False, low=0.0, step=None), 'binarize_images': CategoricalDistribution(choices=(True,)), 'registration_max_features': CategoricalDistribution(choices=(100000,)), 'registration_percentile': CategoricalDistribution(choices=(0.9,)), 'moving_image': CategoricalDistribution(choices=('IMC',)), 'static_image': CategoricalDistribution(choices=('IF_rescaled',))}, trial_id=44, value=None)
twocan_best_trial = pick_best_registration(study.trials_dataframe())
print(twocan_best_trial)
number                                                                                 44
value                                                                            0.652642
datetime_start                                                 2025-05-30 18:31:50.529365
datetime_complete                                              2025-05-30 18:31:55.319088
duration                                                           0 days 00:00:04.789723
params_IF_binarization_threshold                                                 0.140556
params_IF_gaussian_sigma                                                         1.254754
params_IMC_arcsinh_cofactor                                                     45.461813
params_IMC_arcsinh_normalize                                                        False
params_IMC_binarization_threshold                                                0.621099
params_IMC_gaussian_sigma                                                        1.185401
params_IMC_winsorization_lower_limit                                             0.161331
params_IMC_winsorization_upper_limit                                             0.049351
params_binarize_images                                                               True
params_moving_image                                                                   IMC
params_registration_max_features                                                   100000
params_registration_percentile                                                        0.9
params_static_image                                                           IF_rescaled
user_attrs_logical_and                                                            33675.0
user_attrs_logical_iou                                                           0.751842
user_attrs_logical_or                                                             44790.0
user_attrs_logical_xor                                                            11115.0
user_attrs_prop_source_covered                                                        NaN
user_attrs_prop_target_covered                                                        NaN
user_attrs_reg_cell_max_corr                                                     0.540946
user_attrs_reg_image_max_corr                                                    0.868057
user_attrs_registration_matrix          [[0.9950310537840523, 0.0012533682037721098, -...
user_attrs_source_sum                                                             39953.0
user_attrs_stack_cell_max_corr                                                   0.540946
user_attrs_stack_image_max_corr                                                  0.868057
user_attrs_target_sum                                                             38512.0
state                                                                            COMPLETE
norm_and                                                                         0.918925
norm_iou                                                                         0.751842
norm_corr                                                                        0.868057
balanced_score                                                                   0.713736
Name: 44, dtype: object

We can also check out the registration matrix

twocan_best_trial['user_attrs_registration_matrix'].round(2)
array([[  1.  ,   0.  , -45.21],
       [ -0.  ,   1.  , -38.97]])

Visualize result

plt.figure(figsize=(5, 5))
plot_registration(sdata['IF_rescaled'], sdata['IMC'], twocan_best_trial['user_attrs_registration_matrix'])
plt.title('Registration')
plt.show()
../_images/e00e13c5da4d4319da33d955165a20c63e2d8b1ccca0b7e4ca8500105033599c.png

Register!

Assuming we’ve found a suitable registration matrix using Twocan, we can apply this to transform our moving image to align with the static image.

We’ll show two ways to do this:

  1. SpatialData

  2. skimage

Recommendations

Use SpatialData when:

  • You’re working with spatial omics data (spatial transcriptomics, proteomics, etc.)

  • Data integrity and preservation of original resolutions is critical

  • You plan to integrate with single-cell analysis workflows

  • You can tolerate some API instability for the benefits of a purpose-built solution

Use skimage when:

  • You need direct image manipulation and standard computer vision workflows Visualization flexibility is a priority

  • You’re comfortable with resampling trade-offs

  • You need to export standard image formats for use in other tools

  • You’re working primarily with imaging data rather than omics data

You can of course mix and match when you use each approach: SpatialData stores images such that they can always be turned into numpy arrays, so we recommend keeping raw data in zarrs, and transforming on the fly whenever a numpy array or tiff is needed.

For more info, see https://spatialdata.scverse.org/en/stable/tutorials/notebooks/notebooks/examples/transformations_advanced.html

SpatialData

Pros:

  • Integrated with AnnData ecosystem: Seamlessly works with single-cell and spatial omics analysis pipelines, making it ideal for spatial transcriptomics, proteomics, and other omics data types

  • Maintains original image resolutions: Stores transformation matrices separately rather than resampling images, preserving the native resolution and pixel spacing of each modality

  • Preserves data integrity: By applying transformations only during analysis rather than to the raw data, it avoids introducing interpolation artifacts or information loss

  • Supports multiple coordinate systems: Can handle complex transformations and maintain relationships between different spatial coordinate systems

  • Designed for spatial omics workflows: Built specifically with spatial biology applications in mind, supporting common data types and analysis patterns

Cons:

  • Relatively new library with evolving API: As of May 2025, under active development, which means breaking changes are possible and documentation may be incomplete

  • Learning curve: Helpful to have some familiarity with the SpatialData/AnnData ecosystem

Transform

First we’ll use the transformation matrix found by Twocan to make a SpatialData affine transform, then we’ll write that to the coordinate system in the sdata called ‘aligned’

M = twocan_best_trial['user_attrs_registration_matrix']
affine = Affine(np.vstack([M, np.array([0,0,1])]), input_axes=("x", "y"), output_axes=("x", "y"))
get_aligned_coordinates(sdata['IMC'], sdata['IF_rescaled'], affine)
    
sdata
SpatialData object, with associated Zarr store: /lila/home/harrigan/repair-quant/twocan_repo/notebooks/data/cell-line-0028-bd18455.zarr
└── Images
      ├── 'IF': DataArray[cyx] (3, 512, 512)
      ├── 'IF_rescaled': DataArray[cyx] (3, 878, 878)
      └── 'IMC': DataArray[cyx] (24, 944, 953)
with coordinate systems:
    ▸ 'aligned', with elements:
        IMC (Images)
    ▸ 'global', with elements:
        IF (Images), IF_rescaled (Images), IMC (Images)
with the following elements not in the Zarr store:
    ▸ IF_rescaled (Images)

Visualize the transformation

We’ll use the render_images function to visualize the transformation.

fig, (ax1, ax2, ax3) = plt.subplots(1, 3, figsize=(15, 5))
sdata.pl.render_images('IF_rescaled', channel='DAPI').pl.show(coordinate_systems=["global"], title="IF: DAPI", ax=ax1)
sdata.pl.render_images('IMC', channel='DNA1', cmap='magma', norm=AsinhNorm(vmax=10)).pl.show(coordinate_systems=["aligned"], title="IMC: Arcsinh transformed DNA1", ax=ax2)
sdata.pl.render_images('IF_rescaled', channel='DAPI', alpha = 0.5).pl.show(coordinate_systems=["global"], ax=ax3)
sdata.pl.render_images('IMC', channel='DNA1', cmap='magma', norm=AsinhNorm(vmax=10), alpha = 0.5).pl.show(coordinate_systems=["aligned"], title="Merge", ax=ax3)
plt.tight_layout()
../_images/2876c7b38ba71ed199594c96cf28100154041f58ad7daee1c39479947885d050.png

Skimage/numpy

Pros:

  • Direct manipulation of image arrays: Provides immediate access to pixel data as numpy arrays, allowing for straightforward image processing operations

  • Flexible for visualization and plotting: Use matplotlib for custom plots

Cons:

  • Requires resampling: Forces at least one modality to be transformed to match the resolution of another, which is often necessary but not always desirable

  • Potential information loss: Resampling introduces interpolation artifacts and may lose fine spatial details, especially when downsampling high-resolution images

  • Poor handling of irregular regions: NumPy arrays are rectangular, so handling ragged edges, circular regions, or other non-rectangular areas requires masking and can be cumbersome

  • Metadata loss: Standard image formats like TIFF may not preserve all the spatial metadata, coordinate system information, or transformation history

  • Memory intensive: Storing multiple resampled high-resolution images can consume significant memory

Apply the transformation

We will “stack” the IMC image on top of IF to create one big numpy array with 3 + 24 channels

reg = RegEstimator()
reg.M_ = twocan_best_trial['user_attrs_registration_matrix']

stack = reg.transform(sdata['IMC'], sdata['IF_rescaled'])
stack.shape
(27, 878, 878)

We have to store channel names separately

stack_channels = np.concatenate([sdata['IMC'].c.values, sdata['IF_rescaled'].c.values])

Make a cartoon of the registration

plot_registration(sdata['IF_rescaled'], sdata['IMC'], twocan_best_trial['user_attrs_registration_matrix'])
plt.title('Registration')
plt.show()
../_images/aeaed011d619308bfbebfcd460faeefc86befa79b1016f4d95de25d4c779f450.png

Show the transformed data

stack[stack_channels == 'DNA1'][0].shape
(878, 878)
green, magenta, merge = get_merge(np.arcsinh(stack[stack_channels == 'DNA1'][0]), stack[stack_channels == 'DAPI'][0])
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
axes[0].imshow(magenta) 
axes[0].set_title('IF: DAPI')
axes[1].imshow(green)
axes[1].set_title('IMC: arcsinh transformed DNA1')
axes[2].imshow(merge)
axes[2].set_title('Merge')
plt.tight_layout()
plt.show()
../_images/5df92911d42090e28ff3e83e5218c776ebc6486d3d6c509c3b9881de0f89a176.png