Registration of two sessions from the same subject¶
This example shows how to align two power Doppler images acquired from the same
subject in different sessions. We use
register_volume with a rigid transform,
which is appropriate when the imaged anatomy is the same but the probe placement
differs slightly between the two recordings.
We pick two angio acquisitions from the Cybis Pereira 2026
dataset using
fetch_cybis_pereira_2026: subject
rat75, slice slice32, recorded on consecutive days (sessions 20220523 and
20220524).
Fetch and load both recordings¶
Each recording is a single power Doppler image of one slice. We convert it to decibels for both display and registration, which is usually more stable on the log-compressed dynamic range.
from pathlib import Path
import matplotlib as mpl
import matplotlib.pyplot as plt
import xarray as xr
import confusius as cf
from confusius.datasets import fetch_cybis_pereira_2026
from confusius.registration import register_volume
# Adapt background color to the current Matplotlib style.
bg_color = mpl.colors.to_hex(mpl.rcParams["figure.facecolor"])
xr.set_options(display_expand_data=False)
sessions = ["20220523", "20220524"]
acq = "slice32"
bids_root = fetch_cybis_pereira_2026(
datasets="rawdata",
subjects="rat75",
sessions=sessions,
datatypes="angio",
acqs=acq,
)
def _load_angio_for_registration(session: str) -> xr.DataArray:
"""Load an angio acquisition and scale its intensity for registration."""
path = (
Path(bids_root)
/ "sub-rat75"
/ f"ses-{session}"
/ "angio"
/ f"sub-rat75_ses-{session}_acq-{acq}_rec-minframe2d_pwd.nii.gz"
)
return cf.load(path).fusi.scale.db().compute()
fixed = _load_angio_for_registration(sessions[0])
moving = _load_angio_for_registration(sessions[1])
fixed
/tmp/ipykernel_3966/3805360382.py:36: UserWarning: fUSI-BIDS validation warning:
- ClutterFilters.str: Input should be a valid string
- ClutterFilters.list[str].0: Input should be a valid string
return cf.load(path).fusi.scale.db().compute()
/tmp/ipykernel_3966/3805360382.py:36: UserWarning: fUSI-BIDS validation warning:
- ClutterFilters.str: Input should be a valid string
- ClutterFilters.list[str].0: Input should be a valid string
return cf.load(path).fusi.scale.db().compute()
- z: 1
- y: 112
- x: 128
- -19.87 -18.5 -16.81 -17.79 -14.17 ... -21.8 -21.61 -22.72 -22.63
array([[[-19.86943521, -18.49816633, -16.81460287, ..., -20.34940616, -20.95297252, -21.0594714 ], [-20.37608604, -18.82660631, -16.94106874, ..., -19.85784515, -21.06570853, -21.44495125], [-20.51718032, -20.88758297, -19.33940193, ..., -20.89870803, -22.26240509, -22.35662323], ..., [-15.87173364, -16.1433349 , -15.9975841 , ..., -20.36189552, -20.24644136, -20.41257363], [-19.52239672, -18.86263447, -18.68976199, ..., -20.05567394, -20.46102047, -20.02077658], [-21.18090479, -21.5516875 , -22.47616461, ..., -21.60698958, -22.72244543, -22.63380267]]], shape=(1, 112, 128)) - z(z)float64-1.118e+03
- units :
- mm
- voxdim :
- 0.3999999761581421
array([-1118.279541])
- y(y)float64-909.0 -896.8 ... 439.4 451.6
- units :
- mm
- voxdim :
- 12.258064270019531
array([-909.032288, -896.774223, -884.516159, -872.258095, -860.000031, -847.741966, -835.483902, -823.225838, -810.967773, -798.709709, -786.451645, -774.193581, -761.935516, -749.677452, -737.419388, -725.161324, -712.903259, -700.645195, -688.387131, -676.129066, -663.871002, -651.612938, -639.354874, -627.096809, -614.838745, -602.580681, -590.322617, -578.064552, -565.806488, -553.548424, -541.290359, -529.032295, -516.774231, -504.516167, -492.258102, -480.000038, -467.741974, -455.48391 , -443.225845, -430.967781, -418.709717, -406.451653, -394.193588, -381.935524, -369.67746 , -357.419395, -345.161331, -332.903267, -320.645203, -308.387138, -296.129074, -283.87101 , -271.612946, -259.354881, -247.096817, -234.838753, -222.580688, -210.322624, -198.06456 , -185.806496, -173.548431, -161.290367, -149.032303, -136.774239, -124.516174, -112.25811 , -100.000046, -87.741982, -75.483917, -63.225853, -50.967789, -38.709724, -26.45166 , -14.193596, -1.935532, 10.322533, 22.580597, 34.838661, 47.096725, 59.35479 , 71.612854, 83.870918, 96.128983, 108.387047, 120.645111, 132.903175, 145.16124 , 157.419304, 169.677368, 181.935432, 194.193497, 206.451561, 218.709625, 230.96769 , 243.225754, 255.483818, 267.741882, 279.999947, 292.258011, 304.516075, 316.774139, 329.032204, 341.290268, 353.548332, 365.806396, 378.064461, 390.322525, 402.580589, 414.838654, 427.096718, 439.354782, 451.612846]) - x(x)float64-462.4 -449.7 ... 1.149e+03
- units :
- mm
- voxdim :
- 12.688172340393066
array([-462.365601, -449.677428, -436.989256, -424.301084, -411.612912, -398.92474 , -386.236568, -373.548396, -360.860224, -348.172052, -335.48388 , -322.795707, -310.107535, -297.419363, -284.731191, -272.043019, -259.354847, -246.666675, -233.978503, -221.290331, -208.602159, -195.913986, -183.225814, -170.537642, -157.84947 , -145.161298, -132.473126, -119.784954, -107.096782, -94.40861 , -81.720438, -69.032265, -56.344093, -43.655921, -30.967749, -18.279577, -5.591405, 7.096767, 19.784939, 32.473111, 45.161283, 57.849456, 70.537628, 83.2258 , 95.913972, 108.602144, 121.290316, 133.978488, 146.66666 , 159.354832, 172.043005, 184.731177, 197.419349, 210.107521, 222.795693, 235.483865, 248.172037, 260.860209, 273.548381, 286.236553, 298.924726, 311.612898, 324.30107 , 336.989242, 349.677414, 362.365586, 375.053758, 387.74193 , 400.430102, 413.118274, 425.806447, 438.494619, 451.182791, 463.870963, 476.559135, 489.247307, 501.935479, 514.623651, 527.311823, 539.999995, 552.688168, 565.37634 , 578.064512, 590.752684, 603.440856, 616.129028, 628.8172 , 641.505372, 654.193544, 666.881717, 679.569889, 692.258061, 704.946233, 717.634405, 730.322577, 743.010749, 755.698921, 768.387093, 781.075265, 793.763438, 806.45161 , 819.139782, 831.827954, 844.516126, 857.204298, 869.89247 , 882.580642, 895.268814, 907.956986, 920.645159, 933.333331, 946.021503, 958.709675, 971.397847, 984.086019, 996.774191, 1009.462363, 1022.150535, 1034.838707, 1047.52688 , 1060.215052, 1072.903224, 1085.591396, 1098.279568, 1110.96774 , 1123.655912, 1136.344084, 1149.032256]) - time()float64599.2
- volume_acquisition_reference :
- start
- units :
- s
array(599.209575)
- sform_code :
- 5
- qform_code :
- 1
- clutter_filter_window_duration :
- 200
- clutter_filters :
- [{'FilterType': 'Fixed-threshold SVD', 'Threshold': 60}]
- IsHadamard :
- True
- manufacturer :
- Iconeus
- manufacturers_model_name :
- Iconeus One
- plane_wave_angles :
- [-3.0, -1.0, 1.0, 3.0]
- power_doppler_integration_duration :
- 200
- probe_central_frequency :
- 15.625
- ProbeElevationAperture :
- 1.5
- ProbeElevationFocus :
- 8
- probe_model :
- IcoPrime light
- probe_number_of_elements :
- 128
- probe_pitch :
- 0.11
- probe_radius_of_curvature :
- 0
- probe_type :
- linear
- probe_voltage :
- 15
- UltrafastSamplingFrequency :
- 500.0
- pulse_repetition_frequency :
- 2000.0
- affines :
- {'physical_to_sform': array([[ 9.99029587e-01, 1.47998935e-17, -3.48994977e-02, -1.72215206e+01], [ 4.52440656e-02, 3.97759383e-16, 9.99390827e-01, -3.96352835e+02], [ 4.17761078e-19, -1.00000000e+00, 5.64600960e-17, -1.37139789e+03], [ 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 1.00000000e+00]]), 'physical_to_qform': array([[1., 0., 0., 0.], [0., 1., 0., 0.], [0., 0., 1., 0.], [0., 0., 0., 1.]])}
- units :
- dB
- scaling :
- 10*log10(x/max)
- z: 1
- y: 112
- x: 128
- -22.22 -22.53 -23.2 -20.78 -20.63 ... -25.88 -25.99 -25.86 -25.84
array([[[-22.22170345, -22.53014493, -23.1997532 , ..., -19.9998522 , -21.87579216, -22.24978228], [-22.7455659 , -21.95198542, -20.08606197, ..., -22.08711121, -23.75721916, -23.68565874], [-22.5436206 , -22.84849361, -19.73018955, ..., -22.7340935 , -24.03095053, -24.51766265], ..., [-24.44969569, -23.36497407, -23.45954239, ..., -24.8960767 , -25.14390549, -25.16290915], [-25.51832687, -25.14719523, -24.78346509, ..., -25.87028958, -26.06853857, -26.2992882 ], [-24.93807723, -24.42417414, -24.06650479, ..., -25.98793503, -25.85946308, -25.83676812]]], shape=(1, 112, 128)) - z(z)float64-1.29e+03
- units :
- mm
- voxdim :
- 0.3999999761581421
array([-1290.32251])
- y(y)float64-952.0 -939.8 ... 396.3 408.6
- units :
- mm
- voxdim :
- 12.258064270019531
array([-952.042969, -939.784904, -927.52684 , -915.268776, -903.010712, -890.752647, -878.494583, -866.236519, -853.978455, -841.72039 , -829.462326, -817.204262, -804.946198, -792.688133, -780.430069, -768.172005, -755.91394 , -743.655876, -731.397812, -719.139748, -706.881683, -694.623619, -682.365555, -670.107491, -657.849426, -645.591362, -633.333298, -621.075233, -608.817169, -596.559105, -584.301041, -572.042976, -559.784912, -547.526848, -535.268784, -523.010719, -510.752655, -498.494591, -486.236526, -473.978462, -461.720398, -449.462334, -437.204269, -424.946205, -412.688141, -400.430077, -388.172012, -375.913948, -363.655884, -351.39782 , -339.139755, -326.881691, -314.623627, -302.365562, -290.107498, -277.849434, -265.59137 , -253.333305, -241.075241, -228.817177, -216.559113, -204.301048, -192.042984, -179.78492 , -167.526855, -155.268791, -143.010727, -130.752663, -118.494598, -106.236534, -93.97847 , -81.720406, -69.462341, -57.204277, -44.946213, -32.688148, -20.430084, -8.17202 , 4.086044, 16.344109, 28.602173, 40.860237, 53.118301, 65.376366, 77.63443 , 89.892494, 102.150558, 114.408623, 126.666687, 138.924751, 151.182816, 163.44088 , 175.698944, 187.957008, 200.215073, 212.473137, 224.731201, 236.989265, 249.24733 , 261.505394, 273.763458, 286.021523, 298.279587, 310.537651, 322.795715, 335.05378 , 347.311844, 359.569908, 371.827972, 384.086037, 396.344101, 408.602165]) - x(x)float64-462.4 -449.8 ... 1.135e+03
- units :
- mm
- voxdim :
- 12.580644607543945
array([-462.365601, -449.784956, -437.204312, -424.623668, -412.043024, -399.462379, -386.881735, -374.301091, -361.720446, -349.139802, -336.559158, -323.978514, -311.397869, -298.817225, -286.236581, -273.655937, -261.075292, -248.494648, -235.914004, -223.333359, -210.752715, -198.172071, -185.591427, -173.010782, -160.430138, -147.849494, -135.26885 , -122.688205, -110.107561, -97.526917, -84.946273, -72.365628, -59.784984, -47.20434 , -34.623695, -22.043051, -9.462407, 3.118237, 15.698882, 28.279526, 40.86017 , 53.440814, 66.021459, 78.602103, 91.182747, 103.763391, 116.344036, 128.92468 , 141.505324, 154.085969, 166.666613, 179.247257, 191.827901, 204.408546, 216.98919 , 229.569834, 242.150478, 254.731123, 267.311767, 279.892411, 292.473055, 305.0537 , 317.634344, 330.214988, 342.795633, 355.376277, 367.956921, 380.537565, 393.11821 , 405.698854, 418.279498, 430.860142, 443.440787, 456.021431, 468.602075, 481.182719, 493.763364, 506.344008, 518.924652, 531.505297, 544.085941, 556.666585, 569.247229, 581.827874, 594.408518, 606.989162, 619.569806, 632.150451, 644.731095, 657.311739, 669.892384, 682.473028, 695.053672, 707.634316, 720.214961, 732.795605, 745.376249, 757.956893, 770.537538, 783.118182, 795.698826, 808.27947 , 820.860115, 833.440759, 846.021403, 858.602048, 871.182692, 883.763336, 896.34398 , 908.924625, 921.505269, 934.085913, 946.666557, 959.247202, 971.827846, 984.40849 , 996.989134, 1009.569779, 1022.150423, 1034.731067, 1047.311712, 1059.892356, 1072.473 , 1085.053644, 1097.634289, 1110.214933, 1122.795577, 1135.376221]) - time()float64574.5
- volume_acquisition_reference :
- start
- units :
- s
array(574.5147)
- sform_code :
- 5
- qform_code :
- 1
- clutter_filter_window_duration :
- 200
- clutter_filters :
- [{'FilterType': 'Fixed-threshold SVD', 'Threshold': 60}]
- IsHadamard :
- True
- manufacturer :
- Iconeus
- manufacturers_model_name :
- Iconeus One
- plane_wave_angles :
- [-7.0, -5.0, -3.0, -1.0, 1.0, 3.0, 5.0, 7.0]
- power_doppler_integration_duration :
- 200
- probe_central_frequency :
- 15.625
- ProbeElevationAperture :
- 1.5
- ProbeElevationFocus :
- 8
- probe_model :
- IcoPrime light
- probe_number_of_elements :
- 128
- probe_pitch :
- 0.11
- probe_radius_of_curvature :
- 0
- probe_type :
- linear
- probe_voltage :
- 15
- UltrafastSamplingFrequency :
- 400.0
- pulse_repetition_frequency :
- 3200.0
- affines :
- {'physical_to_sform': array([[ 9.96194698e-01, 1.16001203e-17, -8.71557496e-02, -4.52078876e+01], [ 8.71557456e-02, 3.97563657e-16, 9.96194697e-01, -3.78977789e+02], [-4.16181963e-19, -1.00000000e+00, 5.64753608e-17, -1.41440857e+03], [ 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 1.00000000e+00]]), 'physical_to_qform': array([[1., 0., 0., 0.], [0., 1., 0., 0.], [0., 0., 1., 0.], [0., 0., 0., 1.]])}
- units :
- dB
- scaling :
- 10*log10(x/max)
Inspect the misalignment before registration¶
The two images share anatomy but live on slightly different grids because the probe
was re-positioned between sessions. We visualise the alignment with
plot_composite, which resamples moving onto
fixed's grid and draws the two as a red/cyan composite: matched anatomy appear in
white, while any residual red/cyan fringe reveals the displacement that
register_volume will correct.
One subtlety: the two angio recordings sit at slightly different z
coordinates in physical space, so resampling moving onto fixed's grid would
place every voxel outside the fixed slab and return an empty image. We force
moving.z to match fixed.z before plotting so the overlay actually has
something to show. The remaining in-plane misalignment is what we're after.
Run the registration¶
A rigid transform captures the rotation and translation difference between the two
sessions. register_volume returns three
values:
- the moving image (only aligned to the fixed grid if
resample=Trueis used); - the rigid transform matrix that maps fixed-physical coordinates to moving-physical coordinates;
- a
RegistrationDiagnosticsdataclass holding the per-iteration metric values and the optimizer stop condition, which we use below to plot the convergence curve.
Watch registration progress live
Pass show_progress=True to
register_volume to follow the
optimization in real time. A live matplotlib window opens during the call and
updates at every iteration with both the similarity-metric curve and a
fixed/moving composite overlay. It is the fastest way to tell whether the
optimizer is making progress, stuck in a local minimum, or diverging—and to
decide which arguments to tweak from the warning above.
Registration is sensitive to its arguments
The result depends heavily on the choice of transform_type, metric,
learning_rate, number_of_iterations, convergence_window_size,
centering_initialization, and the multi-resolution settings
(use_multi_resolution, shrink_factors, smoothing_sigmas). The values used in
this example were empirically found to work well in this case, but you should
definitely try different arguments (start with the default!) if the result is not
satisfactory—inspect the
RegistrationDiagnostics
convergence curve and the post-registration overlay, and sweep these arguments
until you get a stable, well-converged result.
registered, transform, diagnostics = register_volume(
moving=moving,
fixed=fixed,
transform_type="rigid",
show_progress=True,
number_of_iterations=500,
convergence_window_size=100,
learning_rate=30,
)
print(f"Iterations: {diagnostics.n_iterations}")
print(f"Final metric: {diagnostics.final_metric_value:.4f}")
print(f"Stop condition: {diagnostics.stop_condition}")
transform
Iterations: 363
Final metric: -0.8102
Stop condition: GradientDescentOptimizerv4Template: Convergence checker passed at iteration 363.
array([[ 1.00000000e+00, -1.50275483e-23, -9.15836291e-24,
-1.24687096e-30],
[ 1.44710827e-23, 9.98316682e-01, -5.79982953e-02,
-4.09198007e+01],
[ 1.00145187e-23, 5.79982953e-02, 9.98316682e-01,
1.67666294e+02],
[ 0.00000000e+00, 0.00000000e+00, 0.00000000e+00,
1.00000000e+00]])
Check the alignment after registration¶
Plotting the same fixed/moving overlay before and after registration makes the correction obvious: the red/cyan fringe in the first panel should be replaced by a more uniformly desaturated grey in the second.
fig, axes = plt.subplots(1, 2, figsize=(10, 4))
fig.patch.set_facecolor(bg_color)
for ax, moving_view, title in [
(axes[0], moving, "Before"),
(axes[1], registered, "After"),
]:
cf.plotting.plot_composite(fixed, moving_view, axes=ax, bg_color=bg_color)
ax.set_title(title)
_ = fig.suptitle("Fixed (red) / moving (cyan)")
Inspect convergence with the registration diagnostics¶
diagnostics.metric_values holds the optimizer's similarity-metric value at each
iteration. With the default metric="correlation", SimpleITK minimizes the negative
normalized cross-correlation, so a lower (more negative) value means a better fit.
The curve typically drops sharply at the start and then plateaus.
fig, ax = plt.subplots(figsize=(7, 3))
fig.patch.set_facecolor(bg_color)
ax.plot(diagnostics.metric_values, color="#d93a54")
ax.set_xlabel("Iteration")
ax.set_ylabel(f"Similarity metric ({diagnostics.metric})")
_ = ax.set_title(diagnostics.stop_condition)
The resulting rigid transform is encoded in physical units and can be reused, composed
with other transforms, or applied to additional volumes from the same session with
resample_volume.
Total running time: 80.2 s







