# 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: ```text axis 0 = z axis 1 = x axis 2 = y ``` ## Stitching model Each volume is treated as a 3D array in local coordinates: ```text p = [z, x, y] ``` For two consecutive volumes, the initial displacement guess is provided in millimetres: ```text g_mm = [g_z, g_x, g_y] ``` The task converts this guess to pixels using the voxel size in micrometres: ```text 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: ```text C(delta) = sum_p R_norm(p) * M_norm(p - delta) ``` where: ```text 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: ```text r_px = argmax_delta C(delta) ``` The refined pairwise displacement is then: ```text s_pair = g_px + r_px ``` For more than two volumes, pairwise shifts are accumulated relative to the first volume: ```text 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: ```text 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: ```text A_i = P - S_i ``` These `A_i` coordinates select corresponding slices from all volumes. For example, for the stitched `XY` preview: ```text 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: ```text 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`: ```json { "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: ```json { "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.