# spacr.spacrops

## Module Contents

class spacr.spacrops.spacrStitcher(detector: str = 'ORB', nfeatures: int = 6000, max_keypoints: int | None = 2000, downsample: float = 0.5, ransac_thresh_px: float = 3.0, allow_scale: bool = False, allow_rotation: bool = False, outline_source: str = 'otsu', canny: Tuple[int, int] = (40, 120), blur_sigma: float = 0.0, dilate_ksize: int = 0, line_thickness: int = 1, outline_alpha: float = 1.0, outdir: str = './sbs_out', save_qc: bool = True, save_stitched_default: bool = True, all_scores: bool = False, score_threshold: float | None = None, verbose: bool = False, feature_cache_mode: str = 'disk', feature_cache_dir: str | None = None, max_ram_features: int = 256, n_workers_features: int | None = None, pair_batch_size: int = 8000, stream_csv: bool = True, opencv_threads: int = 1, arr_axes: str = 'AUTO', mip: bool = False, z_index: int = 0, t_index: int = 0, squeeze_singleton: bool = True)

  Pairwise stitcher with downsampled scoring and whole-well mosaic assembly.

  Robustness features for very large datasets:

    - feature_cache_mode: “disk” (default) writes DS features to disk with an LRU RAM cap

    - pair_batch_size: limit number of concurrent pair futures

    - stream_csv: write pairwise rows immediately (low RAM)

    - opencv_threads: limit OpenCV internal threading to avoid oversubscription

  ### Transform control

  > allow_scale: if True → use RANSAC affine (rotation+scale+translation)

  >   if False → enforce unit scale (see allow_rotation)

  > allow_rotation: if True → rotation+translation

  >   if False → translation-only

  ### Mosaic

  > Uses topology-aware pruning + maximum spanning tree on pairwise scores to build a cohesive grid. Exports mosaic image and a mosaic.csv manifest.

  ### Axis & Z handling

  > arr_axesstr in {“AUTO”} or a string over {T,C,Z,Y,X} (e.g. “CZYX”, “CYX”, “ZYX”).

  >   Determines how to interpret multi-dimensional TIFFs.

  > mip : bool. If True and Z exists, perform max-intensity projection over Z. z_index : int (if mip=False) choose Z slice index. t_index : int choose time index if T exists.

  detector = ''
  nfeatures = 6000
  max_keypoints = None
  downsample
  ransac_thresh_px
  allow_scale = False
  allow_rotation = False
  outline_source = ''
  canny = (40, 120)
  blur_sigma
  dilate_ksize = 0
  line_thickness = 1
  outline_alpha
  outdir = b'.'
  save_qc = True
  save_stitched_default = True
  all_scores = False
  score_threshold = None
  verbose = False
  feature_cache_mode = ''
  n_workers_features = 0
  pair_batch_size = 8000
  stream_csv = True
  arr_axes = ''
  mip = False
  z_index = 0
  t_index = 0
  squeeze_singleton = True
  set_meta_regex(pattern: str | re.Pattern)
  prepare_features(paths: List[str], channel_index: int, num_workers: int | None = None)

    Precompute features. In ‘disk’ mode, every computed feature is flushed to disk and only an LRU-sized subset is kept in RAM. In ‘ram’ mode, behaves like before.

  stitch_pair(pathA: str, pathB: str, channel_index: int = 0, score_threshold: float | None = None, save_stitched: bool | None = None, force_no_qc: bool = False, qc_only_if_score_ge: float | None = None) → Dict | None
  run_folder(folder: str, csv_path: str, *, channel_index: int = 0, exts: Tuple[str, Ellipsis] = ('.tif', '.tiff'), recursive: bool = False, same_well_only: bool = True, max_site_gap: int = 3, n_workers: int = 8, stitch: bool = True, score_threshold: float | None = None, meta_regex: str | re.Pattern | None = None, mosaic: bool = False, mosaic_out: str | None = None, mosaic_min_score: float | None = None, mosaic_csv_out: str | None = None, mosaic_all_channels: bool = False, mosaic_channel_count: int | None = None, mosaic_channel_index_order: List[int] | None = None, qc_pairs_threshold: int = 1000, qc_only_above_threshold_when_many: bool = True) → str

    When number of candidate pairs exceeds qc_pairs_threshold, QC plotting is suppressed during the first (threaded) scoring pass and, once a threshold is known, QC is produced only for pairs with score >= threshold (in the second pass).

  build_multichannel_mosaic_from_manifest(manifest_csv: str, out_tif: str, out_png: str | None = None, channel_indices: List[int] | None = None, blend: str = 'max', tmp_dir: str | None = None, preview_downsample: int = 8)

    Build a CYX BigTIFF mosaic from a ‘mosaic.csv’ manifest written by spacrStitcher.render_mosaic_from_csv(…).

    Expected CSV columns (per tile row):

      - path, H, W, M00, M01, M02, M10, M11, M12, canvas_x, canvas_y, best_pair_score

    Notes

    - Uses the 2x3 affine in the manifest (already includes global offset).

    - If channel_indices is None, infers the channel count from the first valid tile.

    - blend=”max” takes per-pixel max across overlapping tiles; blend=”overwrite” writes the latest tile over earlier ones where coverage>0.

    - If tmp_dir is provided (or available from self.feature_cache_dir), the output workspace uses a disk memmap to reduce RAM.

  mosaic_all_channels_from_csv_v1(csv_path: str, out_tif: str, *, min_score: float | None = None, channel_count: int | None = None, channel_index_order: List[int] | None = None, angle_tol_deg: float = 30.0, step_tol_frac: float = 0.25, rot_tol_deg: float = 5.0, scale_tol: float = 0.03, cap_one_per_dir: bool = True, out_csv: str | None = None) → str

    Build a CYX mosaic by reusing pairwise transforms computed on (typically) the nuclei channel.

    Parameters:

      - csv_path (str) – Pairwise results CSV produced by run_folder(…).

      - out_tif (str) – Output TIFF path (saved with axes=’CYX’).

      - min_score (Optional[float]) – Minimum pairwise score to keep when building the mosaic graph. If None, auto-knee.

      - channel_count (Optional[int]) – If provided, force this many channels to be mosaicked (0..channel_count-1). Otherwise inferred as the minimum channel count across nodes.

      - channel_index_order (Optional[List[int]]) – Optional explicit channel indices to mosaic (e.g., [0,3,1]). Overrides channel_count if given.

      - angle_tol_deg – Same gating params as single-channel mosaic.

      - step_tol_frac – Same gating params as single-channel mosaic.

      - rot_tol_deg – Same gating params as single-channel mosaic.

      - scale_tol – Same gating params as single-channel mosaic.

      - cap_one_per_dir – Same gating params as single-channel mosaic.

      - out_csv (Optional[str]) – If provided, write a manifest with per-node canvas transforms.

    Returns:

      Path to the saved multi-channel mosaic TIFF (CYX).

    Return type:

      str

  render_mosaic_from_csv_v1(csv_path: str, out_tif: str, out_png: str | None = None, channel_index: int = 0, min_score: float | None = None, *, angle_tol_deg: float = 30.0, step_tol_frac: float = 0.25, rot_tol_deg: float = 5.0, scale_tol: float = 0.03, cap_one_per_dir: bool = True, out_csv: str | None = None) → Tuple[str, str | None]
  render_mosaic_from_csv(csv_path: str, out_tif: str, out_png: str | None = None, channel_index: int = 0, min_score: float | None = None, *, angle_tol_deg: float = 30.0, step_tol_frac: float = 0.25, rot_tol_deg: float = 5.0, scale_tol: float = 0.03, cap_one_per_dir: bool = True, out_csv: str | None = None) → Tuple[str, str | None]
  mosaic_all_channels_from_csv(csv_path: str, out_tif: str | None, *, min_score: float | None = None, channel_count: int | None = None, channel_index_order: List[int] | None = None, angle_tol_deg: float = 30.0, step_tol_frac: float = 0.25, rot_tol_deg: float = 5.0, scale_tol: float = 0.03, cap_one_per_dir: bool = True, out_csv: str | None = None) → str | None

    Build a CYX mosaic by reusing pairwise transforms computed on (typically) the nuclei channel.

    If out_csv is not None and out_tif is None, run in “manifest-only” mode: compute transforms + canvas geometry + per-node canvas transforms and write the manifest CSV, but DO NOT render/write the mosaic TIFF.

class spacr.spacrops.StitchedMultiAligner(detector: str = 'ORB', nfeatures: int = 6000, max_keypoints: int | None = 2000, downsample: float = 0.5, ransac_thresh_px: float = 3.0, allow_scale: bool = False, allow_rotation: bool = False, outdir: str = './align_out', opencv_threads: int = 1, arr_axes: str = 'AUTO', mip: bool = False, z_index: int = 0, t_index: int = 0, squeeze_singleton: bool = True)

  Align an arbitrary number of stitched mosaics (multi-channel, arbitrary axes) to a common reference using the nuclei/ Hoechst channel. Saves a channel-concatenated aligned stack and a CSV manifest describing the mapping from input channels to output channels and the estimated transforms/scores.

  Output image axes: CYX (channels stacked in the order inputs are provided).

  Parameters:

    - detector ({"ORB","SIFT"}) – Feature detector for keypoint matching.

    - nfeatures (int) – Feature budget for detector.

    - max_keypoints (Optional[int]) – Hard cap on kept keypoints after detection (by detector’s internal ranking).

    - downsample (float in (0,1]) – Downsample factor for feature/score pass.

    - ransac_thresh_px (float) – Reprojection threshold (pixels) for affine estimation (downsampled space).

    - allow_scale (bool) – If False, constrain to rotation+translation (or translation only if allow_rotation=False).

    - allow_rotation (bool) – If False, constrain to translation only.

    - outdir (str) – Output directory for images/csv.

    - opencv_threads (int) – Limit OpenCV internal threading (avoid oversubscription).

    - reading) (# Axis/Z/time handling (for TIFF)

    - arr_axes ("AUTO" or a string over {T,C,Z,Y,X})

    - mip (bool) – If True and Z exists, max-project Z.

    - z_index (int) – If mip=False, choose Z slice.

    - t_index (int) – Choose T index if T exists.

    - squeeze_singleton (bool) – Squeeze 1-length axes after slicing.

  Notes

  - Alignment is done to the first image in paths (reference).

  - For each image, you can provide a per-image nuclei channel index via nuclei_channel_indices. If None, defaults to 0 for all.

  detector = ''
  nfeatures = 6000
  max_keypoints = None
  downsample
  ransac_thresh_px
  allow_scale = False
  allow_rotation = False
  outdir = b'.'
  arr_axes = ''
  mip = False
  z_index = 0
  t_index = 0
  squeeze_singleton = True
  align(paths: List[str], nuclei_channel_indices: List[int] | None = None, out_tif: str | None = None, out_png_preview: str | None = None, csv_path: str | None = None) → Tuple[str, str | None, str | None]

spacr.spacrops.stitch_cycle_wells(settings)

spacr.spacrops.get_preprocess_ops_settings(settings)

class spacr.spacrops.FOVAlignAndCropper(detector: str = 'ORB', nfeatures: int = 6000, max_keypoints: int | None = 2000, downsample: float = 0.5, ransac_thresh_px: float = 3.0, allow_scale: bool = False, allow_rotation: bool = False, outdir: str = './fov_out', opencv_threads: int = 1, arr_axes: str = 'AUTO', mip: bool = False, z_index: int = 0, t_index: int = 0, squeeze_singleton: bool = True, folder_image_scale: float = 1.0)

  Align each image in a folder (arbitrary channels) to a stitched mosaic (arbitrary channels) using the Hoechst/nuclei channel, then extract the FOV region from the mosaic at the aligned location. For each input FOV, saves:

  > - a .npy array with shape (C_fov + C_mosaic, H_fov, W_fov):

  >     [FOV channels stacked; mosaic channels warped into FOV frame and stacked]

  > - a CSV row with file paths, transform, score, and the mosaic-space top-left of the aligned FOV bbox.

  Parameters:

    - detector – Same semantics as in StitchedMultiAligner.

    - nfeatures – Same semantics as in StitchedMultiAligner.

    - max_keypoints – Same semantics as in StitchedMultiAligner.

    - downsample – Same semantics as in StitchedMultiAligner.

    - ransac_thresh_px – Same semantics as in StitchedMultiAligner.

    - allow_scale – Same semantics as in StitchedMultiAligner.

    - allow_rotation – Same semantics as in StitchedMultiAligner.

    - outdir – Same semantics as in StitchedMultiAligner.

    - opencv_threads – Same semantics as in StitchedMultiAligner.

    - arr_axes – Same TIFF axis handling semantics as in StitchedMultiAligner.

    - mip – Same TIFF axis handling semantics as in StitchedMultiAligner.

    - z_index – Same TIFF axis handling semantics as in StitchedMultiAligner.

    - t_index – Same TIFF axis handling semantics as in StitchedMultiAligner.

    - squeeze_singleton – Same TIFF axis handling semantics as in StitchedMultiAligner.

  Notes

  - Alignment is (FOV -> mosaic). Extraction uses the inverse transform to warp the mosaic into the FOV frame, guaranteeing the combined array has the FOV’s native (H_fov, W_fov).

  outdir = b'.'
  folder_image_scale
  run(stitched_path: str, folder: str, *, stitched_nuclei_idx: int = 0, fov_nuclei_idx: int = 0, exts: Tuple[str, Ellipsis] = ('.tif', '.tiff'), recursive: bool = False, csv_path: str | None = None, npy_dir: str | None = None, folder_image_scale: float | None = None) → str

    Align each FOV in folder to the stitched mosaic at stitched_path using the nuclei channel, then save a stacked array [FOV channels; mosaic channels warped into FOV] to .npy and a CSV row.

    Known scale handling:

      - If the FOV magnification differs from the mosaic, set folder_image_scale to the FOV→mosaic pixel-scale factor (e.g., mosaic 10× vs FOV 20× -> 0.5; mosaic 20× vs FOV 10× -> 2.0).

    Returns:

      Path to the CSV manifest.

    Return type:

      str

spacr.spacrops.align_image_to_stitch(stitch_dst_root: str, align_src: str, *, meta_regex: str = '(?P<mag>\\d+X)_c(?P<chan>\\d+)_?(?P<well>[A-H]\\d{1,2}).*?Site[-_](?P<site>\\d+)\\.(?:tif|tiff)$', well_group: str = 'well', channel_index: int = 0, relative_scale: float = 2.0, downsample: float = 0.5, nfeatures: int = 4000, ransac_thresh_px: float = 3.0, allow_scale: bool = False, allow_rotation: bool = False, qc_outlines: bool = False, recursive_align_src: bool = True, exts: tuple = ('.tif', '.tiff')) → Dict[str, Dict[str, str]]

  For each WELL that already has a stitched mosaic under stitch_dst_root/<WELL>/_stitch/mosaic_allc.tif, align all 20× images from align_src (grouped by WELL via meta_regex) to that mosaic using FOVAlignAndCropper, and write per-well crop manifests and .npy crops.

  Returns:

    {“mosaic”: <path>, “align_folder”: <per-well link>, “manifest_csv”: <path>} }

  Return type:

    { WELL

spacr.spacrops.ops_preprocess(settings)