Stitch reconstructed volumes#

Use the stitch_volumes workflow to align an ordered list of reconstructed volumes and save three stitched orthogonal slice previews.

The workflow has two tasks:

  • RefineVolumePosition refines the relative [z, x, y] displacement between consecutive volumes.

  • StitchMatchedSlices extracts matching XY, YZ, and XZ slices from the refined positions and stitches each slice set into one TIFF image.

Inputs#

The main inputs are:

  • volume_paths: ordered list of TIFF volume files or TIFF volume directories.

  • guess_distances_mm: estimated relative displacement in millimetres, ordered as [z, x, y]. Provide one vector for all pairs or one vector per consecutive pair.

  • voxel_size_um: voxel size in micrometres. Provide one scalar, one [z, x, y] vector, or one vector per volume.

  • search_radius_px: local refinement radius in pixels around the guessed displacement. It accepts the same scalar/vector/per-pair forms.

  • correlation_window_shape_px: optional [z, x, y] correlation block shape in pixels. When omitted, the task derives a window from search_radius_px.

  • downsample_factor: X/Y downsampling factor used by the coarse correlation step.

  • output_path: optional output directory for the stitched slices.

All volume axes and displacements use this convention:

axis 0 = z
axis 1 = x
axis 2 = y

Stitching model#

Each volume is treated as a 3D array in local coordinates:

p = [z, x, y]

For two consecutive volumes, the initial displacement guess is provided in millimetres:

g_mm = [g_z, g_x, g_y]

The task converts this guess to pixels using the voxel size in micrometres:

g_px = round(1000 * g_mm / voxel_size_um)

The refinement step searches for a residual pixel shift r_px around this guess. For a local overlap block R from the reference volume and M from the moving volume, the task computes a normalized cross-correlation:

C(delta) = sum_p R_norm(p) * M_norm(p - delta)

where:

R_norm = (R - mean(R)) / std(R)
M_norm = (M - mean(M)) / std(M)

Only shifts inside search_radius_px are considered. The residual is the shift with the highest correlation score:

r_px = argmax_delta C(delta)

The refined pairwise displacement is then:

s_pair = g_px + r_px

For more than two volumes, pairwise shifts are accumulated relative to the first volume:

S_0 = [0, 0, 0]
S_i = S_{i-1} + s_pair_i

These cumulative shifts S_i define a common global coordinate system. The task then finds one global point P that belongs to the overlap of every shifted volume. For each axis a, the common overlap interval is:

start_a = max_i S_i[a]
end_a   = min_i S_i[a] + shape_i[a]
P_a     = start_a + floor((end_a - start_a) / 2)

The local assembly point for volume i is:

A_i = P - S_i

These A_i coordinates select corresponding slices from all volumes.

For example, for the stitched XY preview:

slice_i = volume_i[A_i[z], :, :]
row shift = S_i[x]
col shift = S_i[y]

For YZ, the fixed coordinate is x, rows follow z, and columns follow y. For XZ, the fixed coordinate is y, rows follow z, and columns follow x.

Each extracted 2D slice is placed on an output canvas using its row and column shift. If several slices overlap, the task first fits a linear intensity match:

reference ~= scale * moving + offset

on the overlapping pixels. It then blends the overlap with cosine weights so the transition between slices is gradual rather than a hard replacement.

Example inputs#

For two volumes with an expected displacement of 0.2 mm in z, 0.3 mm in x, and -0.4 mm in y:

{
  "volume_paths": [
    "/data/volume_0001.tiff",
    "/data/volume_0002.tiff"
  ],
  "guess_distances_mm": [0.2, 0.3, -0.4],
  "voxel_size_um": [100.0, 100.0, 100.0],
  "search_radius_px": [2, 24, 24],
  "downsample_factor": 8,
  "output_path": "/data/stitched/volume_0001_0002"
}

For more than two volumes, provide one guess per consecutive pair when the relative motion is not constant:

{
  "volume_paths": [
    "/data/volume_0001.tiff",
    "/data/volume_0002.tiff",
    "/data/volume_0003.tiff"
  ],
  "guess_distances_mm": [
    [0.2, 0.3, -0.4],
    [0.2, 0.3, -0.4]
  ],
  "voxel_size_um": 100.0
}

Outputs#

RefineVolumePosition produces:

  • pair_refined_shifts_pixels

  • pair_refined_shifts_mm

  • volume_shifts_pixels

  • volume_shifts_mm

  • assembly_points_pixels

StitchMatchedSlices writes these files in output_path:

  • stitched_xy.tiff

  • stitched_yz.tiff

  • stitched_xz.tiff

  • metadata.json

The metadata.json file stores the refined volume shifts and assembly points used to create the stitched previews.