linumpy.mosaic.motor#

Motor-position-based tile placement for mosaic stitching.

Consolidated from linum_stitch_3d_refined.py and linum_stitch_motor_only.py.

Attributes#

Functions#

compute_motor_positions(nx, ny, tile_shape, ...[, ...])

Compute tile positions based on motor grid (ideal positions).

compute_registration_refinements(volume, tile_shape, ...)

Correlate neighboring tiles within a slice to measure displacement errors.

estimate_affine_from_pairs(pairs, tile_shape, ...)

Estimate a 2x2 affine displacement model from neighbor tile correlations.

pool_pairs_and_fit_global_affine(volumes, ...[, ...])

Pool neighbor-tile pair measurements across many mosaic grids and fit one affine.

compute_affine_positions(nx, ny, transform)

Compute tile positions using a 2x2 affine displacement model.

compute_affine_output_shape(nx, ny, tile_shape, transform)

Compute the output mosaic shape from affine tile positions.

apply_blend_shift_refinement(tile, refinements_for_tile)

Apply registration refinement by shifting tile data in overlap regions.

compare_motor_vs_registration(motor_positions, ...[, ...])

Compare motor-based positions with registration-based positions.

Module Contents#

linumpy.mosaic.motor.logger[source]#
linumpy.mosaic.motor.compute_motor_positions(nx, ny, tile_shape, overlap_fraction, scale_factor=1.0, rotation_deg=0.0)[source]#

Compute tile positions based on motor grid (ideal positions).

Assumes a regular grid where tiles are spaced by (1 - overlap) * tile_size. Optionally applies scale factor and rotation to test hypotheses about stage calibration issues.

Parameters:
  • nx (int) – Number of tiles in each direction.

  • ny (int) – Number of tiles in each direction.

  • tile_shape (tuple) – Tile dimensions (z, height, width).

  • overlap_fraction (float) – Expected overlap between tiles (0-1).

  • scale_factor (float) – Scale applied to step size (default 1.0 = no scaling).

  • rotation_deg (float) – Global grid rotation in degrees (default 0.0).

Returns:

  • positions (list) – List of (row_pos, col_pos) pixel positions for each tile.

  • step_y (int) – Y step in pixels.

  • step_x (int) – X step in pixels.

Return type:

tuple

linumpy.mosaic.motor.compute_registration_refinements(volume, tile_shape, nx, ny, overlap_fraction, max_refinement_px=10.0, *, histogram_match=False, max_empty_fraction=None, use_gpu=False)[source]#

Correlate neighboring tiles within a slice to measure displacement errors.

Phase-correlates overlapping regions of adjacent tiles (horizontal and vertical neighbors) to measure the difference between expected and actual tile positions. Returns both clamped residuals for blend refinement and unclamped absolute displacements for fitting the affine displacement model (Lefebvre et al. 2017, Eqs 1-6).

Note: this operates on tiles within a single slice – it is entirely separate from the Z-slice pairwise registration (linum_register_pairwise.py).

Parameters:
  • volume (np.ndarray) – The mosaic grid volume (Z, nx*tile_h, ny*tile_w).

  • tile_shape (tuple) – Tile dimensions (z, height, width).

  • nx (int) – Number of tiles in each direction.

  • ny (int) – Number of tiles in each direction.

  • overlap_fraction (float) – Expected overlap fraction (0-1).

  • max_refinement_px (float) – Maximum residual shift retained for blend refinement. Larger residuals are clamped. Does not affect the absolute displacements in ‘pairs’.

  • histogram_match (bool, keyword-only) – If True, match the intensity histogram of the second overlap to the first before phase correlation. Improves robustness when tile-edge illumination is uneven; disabled by default to preserve existing behaviour.

  • max_empty_fraction (float or None, keyword-only) – If set, use an Otsu threshold on the central plane to classify tissue vs background, and skip any pair whose overlap contains more than this fraction of background pixels (mirrors the behaviour of linumpy.registration.transforms.estimate_mosaic_transform). When None (default), the prior mean(overlap > 0) < 0.1 heuristic is used.

  • use_gpu (bool, keyword-only) – If True, run the pairwise phase correlations via linumpy.gpu.fft_ops.phase_correlation() (CuPy-accelerated). Falls back silently to the CPU path when CuPy / a CUDA device is not available. Default is False.

Returns:

‘pairs’ is a list of dicts with keys ‘row_delta’, ‘col_delta’, ‘measured_dy’, ‘measured_dx’ – the absolute observed pixel displacements used for affine model estimation.

Return type:

dict with keys ‘horizontal’, ‘vertical’, ‘pairs’, ‘stats’.

linumpy.mosaic.motor.estimate_affine_from_pairs(pairs, tile_shape, overlap_fraction)[source]#

Estimate a 2x2 affine displacement model from neighbor tile correlations.

Fits the Lefebvre et al. (2017) motor displacement model using least-squares on the absolute (step + residual) displacements returned by compute_registration_refinements().

Note: this uses phase correlation between neighboring tiles within a single slice, not the Z-slice pairwise registration that appears elsewhere in the pipeline.

The model is: pixel_pos = A @ [i, j]^T where A is a general 2x2 matrix. Off-diagonal terms capture the scan-to-stage rotation (θ) and the non-perpendicularity of the motor axes (φ).

Parameters:
  • pairs (list of dict) – Each dict has ‘row_delta’, ‘col_delta’, ‘measured_dy’, ‘measured_dx’.

  • tile_shape (tuple) – Tile dimensions (z, height, width).

  • overlap_fraction (float) – Expected overlap fraction (for diagnostics only).

Returns:

  • transform (np.ndarray) – Fitted 2x2 affine matrix mapping tile index to pixel position.

  • diagnostics (dict) – Extracted displacement model parameters (θ, φ, Ox, Oy) and fit residual statistics.

Return type:

tuple[numpy.ndarray, dict]

linumpy.mosaic.motor.pool_pairs_and_fit_global_affine(volumes, overlap_fraction, *, histogram_match=False, max_empty_fraction=None, n_samples=None, seed=0, use_gpu=False)[source]#

Pool neighbor-tile pair measurements across many mosaic grids and fit one affine.

For each (slice_id, path) entry, load only the central Z plane of the OME-Zarr volume and call compute_registration_refinements() with the supplied options. All resulting pairs are concatenated, optionally sub-sampled with a deterministic seed, and fed to estimate_affine_from_pairs() for a single 2x2 affine fit.

Parameters:
  • volumes (list of (slice_id, path)) – Each path must be a string or pathlib.Path pointing at a *.ome.zarr mosaic grid.

  • overlap_fraction (float) – Expected tile overlap fraction (must match acquisition).

  • histogram_match (bool, keyword-only) – Forwarded to compute_registration_refinements().

  • max_empty_fraction (float or None, keyword-only) – Forwarded to compute_registration_refinements().

  • n_samples (int or None, keyword-only) – If set and the pooled pair count exceeds this value, a reproducible random sub-sample of size n_samples is drawn before fitting.

  • seed (int, keyword-only) – Seed used when sub-sampling. Ignored when n_samples is None.

  • use_gpu (bool, keyword-only) – Forwarded to compute_registration_refinements().

Returns:

  • transform (np.ndarray) – Fitted 2x2 affine matrix.

  • diagnostics (dict) – Full diagnostics including per-slice stats, pooled pair count, chosen backend label, and the output of estimate_affine_from_pairs().

Return type:

tuple[numpy.ndarray, dict]

linumpy.mosaic.motor.compute_affine_positions(nx, ny, transform)[source]#

Compute tile positions using a 2x2 affine displacement model.

This is the corrected version of compute_motor_positions() that accounts for scan-to-stage rotation (θ) and non-perpendicular motor axes (φ) via the off-diagonal terms in the transform matrix.

Parameters:
  • nx (int) – Number of tiles in each direction.

  • ny (int) – Number of tiles in each direction.

  • transform (np.ndarray) – 2x2 affine matrix mapping tile index (i, j) to pixel position (row_px, col_px).

Returns:

positions – Pixel positions for each tile, row-major order.

Return type:

list of (int, int)

linumpy.mosaic.motor.compute_affine_output_shape(nx, ny, tile_shape, transform)[source]#

Compute the output mosaic shape from affine tile positions.

With off-diagonal terms, tiles may extend beyond what the diagonal model predicts. This computes the bounding box over all tile corner positions.

Parameters:
  • nx (int) – Number of tiles in each direction.

  • ny (int) – Number of tiles in each direction.

  • tile_shape (tuple) – Tile dimensions (z, height, width).

  • transform (np.ndarray) – 2x2 affine matrix.

Returns:

(nz, output_height, output_width)

Return type:

tuple of int

linumpy.mosaic.motor.apply_blend_shift_refinement(tile, refinements_for_tile)[source]#

Apply registration refinement by shifting tile data in overlap regions.

Applies a small sub-pixel shift (averaged from all neighbors) to improve blending quality without changing the tile’s position in the mosaic.

Parameters:
  • tile (np.ndarray) – 3D tile data (Z, Y, X).

  • refinements_for_tile (list) – List of dicts with ‘dx’, ‘dy’ refinements from neighbors.

Returns:

Shifted tile (or unmodified if shift is negligible).

Return type:

np.ndarray

linumpy.mosaic.motor.compare_motor_vs_registration(motor_positions, reg_positions, output_path=None)[source]#

Compare motor-based positions with registration-based positions.

Used diagnostically to identify stage calibration issues (systematic offset, dilation/scaling) and registration drift.

Parameters:
  • motor_positions (list) – List of (row, col) positions from motor grid.

  • reg_positions (list) – List of (row, col) positions from image registration.

  • output_path (str or None) – If provided, save comparison JSON to this path.

Returns:

Statistics including mean/std/max differences and diagnostic flags.

Return type:

dict