TomoImageStitcher

Architecture

This document describes the data structures and processing stages of TomoImageStitcher. It is the reference you should read when contributing or when the default pipeline does not quite fit your data.

High-level pipeline

list of .h5 files + motor positions
        │
        ▼
┌────────────────────────────────────────┐
│  1. Organize sub-volumes                │   Stitcher.__init__
│     ─ physical → image coordinates      │   get_image_space_coordinates
│     ─ classify into z-layers            │   get_layers_in_z
│     ─ compute the global padding        │   get_padding
│     ─ find overlapping regions          │   get_intersections
└────────────────────────────────────────┘
        │
        ▼
┌────────────────────────────────────────┐
│  2. Correlate every pair                │   compute_shift_in_layers
│     ─ ZNCC pixel search (GPU)           │     → RegistrationKIT.correlate_NCC
│     ─ IC-GN Lucas–Kanade refinement     │     → RegistrationKIT.lucas_kanade_3D_inv_mask
│     ─ mask-aware error metric           │
└────────────────────────────────────────┘
        │
        ▼
┌────────────────────────────────────────┐
│  3. Build displacement pyramid          │   get_displacement_pyramid
│     ─ BFS from a seed volume            │
│     ─ discard bad correlations (NCC)    │   accumulate_displacement
│     ─ weighted average (or best NCC)    │
│     ─ optional affine operator chain    │
└────────────────────────────────────────┘
        │
        ▼
┌────────────────────────────────────────┐
│  4. Stitch and blend                    │   compose_final_displacements
│     ─ chunk-by-chunk transformation     │   stitch_volumes_blend_equalize
│     ─ distance-map blending             │   stitch_layers
│     ─ optional intensity equalization   │
└────────────────────────────────────────┘

Class layout

Important class attributes on Stitcher

Attribute Type Description
file_paths list[str] Input .h5 files.
physical_coordinates_mm (N, 3) ndarray Motor positions in mm.
mm_per_voxel float Spatial calibration.
x_y_z_correspondance (3,) tuple[int] Motor → image axis map.
img_depth, img_height, img_width int Dimensions of the first image.
layers_coordinates list[(n_i, 3) ndarray] Per-layer pixel coordinates.
layers_paths list[list[str]] Per-layer file paths.
layers_intersecting_images_bb_x_y list[... , 2][(x0,y0),(x1,y1)] Bounding boxes of overlaps.
layers_reg_disp_data list[list[(dx,dy,dz)]] Per-pair rigid displacements.
layers_reg_oper_data list[list[4x4 ndarray]] Per-pair affine operators.
layers_reg_ncc_data list[list[int]] Per-pair final NCC.
cumulative_displacements_pyramid_sub_layer per-layer, per-step, per-subvolume accumulated displacements
cumulative_global_operators_pyramid_sub_layer per-layer, per-step, per-subvolume accumulated affine operators
layer_final_displacements list[list[(dx,dy,dz)]] Final per-volume shift.
layer_final_operators list[list[4x4 ndarray]] Final per-volume operator.
layer_final_NCC list[list[float]] Final per-volume NCC.

Coordinate conventions

The pipeline uses two different coordinate systems:

  1. Motor coordinates (x_motor, y_motor, z_motor) in millimetres — what the experimentalist sees.
  2. Image coordinates (x_img, y_img, z_img) in voxels — what the registration engine works with.

The mapping is:

x_img = x_motor / mm_per_voxel * sign(a)
y_img = y_motor / mm_per_voxel * sign(b)
z_img = z_motor / mm_per_voxel * sign(c)

where (a, b, c) is the x_y_z_correspondance tuple passed to the Stitcher constructor. Using negative values for a, b or c flips the corresponding axis in image space, which is useful when the detector orientation does not match the motor one.

Mask convention

Throughout the code, 0 is reserved for “background” / masked pixels. This lets us:

If your data legitimately contains zeros, set stitcher.add_value_for_mask = 1 so the data is shifted by one when loaded and shifted back when written.

Registration engines

ZNCC (RegistrationKIT.correlate_NCC)

Lucas–Kanade (RegistrationKIT.lucas_kanade_3D_inv_mask)

The “rigid” warp is extracted from the affine via polar decomposition (inside _extract_rigid_transform).

Saving conventions

stitcher.compute_shift_in_layers(..., save_reg=True, save_path=...) writes two .h5 files per intersection:

stitcher.stitch_layers(...) writes one file per layer under <saving_path>/Stitched_layers/Layer_<i>.h5. The group /stitched_data contains: