spacr.align

Workflow inputs and outputs

Align & Stitch

Inspect tile geometry, overlap and channel mapping, then save the mosaic with coordinate records. OPS also needs the original cycle/site identities.

Open: Home → Align & Stitch.

Inputs and outputs below include conditional alternatives. The guidance and handoff notes say which route applies.

Inputs

  • Microscope images — Source image folder; original files, supported vendor files or imported TIFFs.

Outputs

  • Aligned mosaic and coordinates — Align & Stitch destination: composed image and the tile-coordinate/layout records needed to interpret it.

After this module

  • OPS: Carry tile coordinates and original sequencing cycles into OPS; a flattened mosaic alone is insufficient.

API reference.

Module tutorial.

Stitch and align an arbitrary number of image tiles into one canvas.

A 10x10 grid of 2048x2048 uint16 fields is a 20480x20480 canvas: 800 MB for one channel, 3.2 GB for four. Nothing in this module ever holds that canvas — or the whole input set — in RAM. That constraint drives every design decision here:

  • scan_tiles() reads headers only. .npy goes through numpy.lib.format.read_magic() plus the public read_array_header_* pair; TIFF through tifffile.TiffFile’s series metadata. A thousand tiles cost a thousand seeks, no pixels.

  • estimate_offsets() registers pairs on the overlap strip only, read out of a memory-mapped tile through the same windowed access pattern spacr.crops.MergedField uses for on-demand crops. A 10% overlap on a 2048x2048 tile is a 2048x205 strip — 0.8 MB, not 8 MB.

  • plan_canvas() computes the canvas geometry from the offsets before anything is allocated, so an impossible stitch is refused on arithmetic rather than discovered by the OOM killer.

  • write_stack() fills the output one horizontal band at a time. The peak working set is one band’s float32 accumulator plus one tile window, both bounded by max_buffer_bytes and both independent of the canvas size.

The other half of the problem is being honest about what registered and what did not.

Offsets are solved globally, never accumulated. Chaining tile-to-tile shifts along a row of a 10x10 grid adds every pairwise error together, so the last tile lands tens of pixels out with nothing in the output saying so. Instead every overlapping neighbour pair contributes one equation p_j - p_i = d_ij and the whole set is solved at once as a weighted least-squares problem over the pair graph (see solve_positions()). Redundant edges — the diagonal neighbours of a grid, the i/i+2 pairs of a heavily-overlapped row — then cancel error instead of compounding it, and each tile gets a residual: how far its solved position sits from what its own pairs asked for. A large residual is the signal that a tile did not register, and it is written into the table rather than averaged away.

A pair that does not register is reported, not guessed. Phase correlation over a blank or near-empty overlap returns a confident-looking peak that is pure noise. Every pair is therefore scored by the normalised cross-correlation of the two strips after the measured shift is applied; below min_confidence the pair is dropped, and a tile left with no surviving pair keeps its nominal stage position and is marked method='nominal'. A nominally-placed tile is not the same thing as a registered one and ALIGN_TABLE says which is which, per tile.

Channels share one solution. Registration runs on reference_channel alone and the resulting placement is applied to every channel of that site. Aligning channels independently would shear the composite — a nucleus mask and its own DAPI channel would no longer line up — so it is not offered.

z is not stitched. Tiles are 2-D (H, W) or channel-last 3-D (H, W, C), which is the layout spaCR’s merged/*.npy already uses. A z-stack must be projected first, or aligned one z-plane at a time by calling this module per plane; a 4-D array is refused with a message that says so rather than being silently reinterpreted.

Typical use:

from spacr import align

tiles = align.scan_tiles('plate1/tiles', grid=(10, 10), overlap=0.1)
plan = align.estimate_offsets(tiles)
print(align.format_plan(plan))
result = align.write_stack(plan, 'plate1/stitched')
align.save_coordinates(plan, 'plate1/measurements/measurements.db',
                       canvas=result.canvas, stack_path=result.stack_path)

or in one call from a settings dict, the shape every other spaCR module takes:

align.align_folder({'src': 'plate1/tiles', 'dst': 'plate1/stitched',
                    'grid': (10, 10), 'db_path': '.../measurements.db'})

The coordinates table is keyed exactly the way every measurement table in measurements.db is keyed — plateID / rowID / columnID / fieldID plus the prc / prcf composites, built through spacr.schema, which is the one definition spacr.utils writes them with too — so:

SELECT c.*, a.y, a.x, a.method, a.confidence, a.residual
FROM cell AS c
JOIN align_coordinates AS a USING (plateID, rowID, columnID, fieldID)

puts every measured object next to the stitch quality of the field it came from. That is the join that lets a suspicious cluster of hits be traced back to “those three fields fell back to nominal”.

This module is deliberately free of torch, cellpose, Qt and TensorFlow: it is imported by a GUI screen and by header-only scans, and neither should pay for a deep-learning stack.

Exceptions

AlignError

A tile could not be read, or a stitch could not be written.

Classes

AlignPlan

The full solution: what goes where, and everything that went wrong.

AlignResult

What write_stack() actually did.

CanvasSpec

Canvas geometry, computed before a single byte is allocated.

PairResult

One neighbour pair, registered or refused.

Placement

Where one tile goes, and how much that position is worth.

Tile

One site: everything known about a tile without reading its pixels.

Functions

align_folder(→ List[AlignResult])

Scan, plan, optionally write and optionally record, in one call.

default_settings(→ Dict[str, Any])

Return the settings align_folder() understands, with defaults.

estimate_offsets(→ AlignPlan)

Register every overlapping neighbour pair and solve the whole set at once.

format_plan(→ str)

Render a plan as the block a user should read before writing 800 MB.

group_tiles(→ Dict[Tuple[str, str], List[Tile]])

Split tiles into one stitchable set per (plate, well).

plan_canvas(→ CanvasSpec)

Compute the canvas geometry from the offsets, allocating nothing.

read_coordinates(→ pandas.DataFrame)

Read ALIGN_TABLE back out, closing the loop.

save_coordinates(→ int)

Write one row per tile into measurements.db.

scan_tiles(→ List[Tile])

Return one Tile per stage position, without reading pixels.

solve_positions(→ Tuple[numpy.ndarray, numpy.ndarray, ...)

Solve every tile position at once from the pair graph.

write_stack(→ AlignResult)

Composite the plan into one .npy, one horizontal band at a time.

Module Contents

exception spacr.align.AlignError[source]

Bases: RuntimeError

A tile could not be read, or a stitch could not be written.

Initialize self. See help(type(self)) for accurate signature.

class spacr.align.AlignPlan[source]

The full solution: what goes where, and everything that went wrong.

Variables:
  • tiles – every tile handed in, readable or not.

  • placements – one per readable tile, in tile order.

  • canvas_shape – (H, W, C) the stitch would occupy.

  • overlaps – every candidate pair, accepted or refused.

  • warnings – non-fatal problems — mixed dtypes, disconnected components, tiles with no overlap at all.

  • unplaced – (tile, reason) for every tile that could not be placed. These are excluded from the canvas, not silently zeroed.

  • origin – (y, x) of the canvas origin in the global frame.

  • feather – seam ramp width in pixels, derived from the real overlaps rather than guessed.

  • dtype – dtype the canvas would be written in.

  • reference_channel – the channel registration was measured on.

nominal_placements() → List[Placement][source]

Every tile that did not register, worst confidence first.

property accepted_pairs: List[PairResult][source]

Pairs that fed the global solve.

property canvas_bytes: int[source]

Bytes the written canvas would occupy on disk.

property max_residual: float[source]

Worst per-tile residual in the solve.

property n_nominal: int[source]

Tiles that fell back to their nominal stage position.

property n_registered: int[source]

Tiles placed by registration.

property refused_pairs: List[PairResult][source]

Pairs that were scored and not believed.

class spacr.align.AlignResult[source]

What write_stack() actually did.

Parameters:
  • plan – alignment plan supplied to the write or retained by a preview result.

  • canvas – planned output-canvas geometry.

  • stack_path – path of the written .npy stack, or "" for a dry run, preview, or empty write.

  • n_written – number of distinct tiles that contributed at least one pixel to the canvas.

  • n_skipped – number of tiles whose reader or window read failed while writing; each tile is counted at most once.

  • peak_buffer_bytes – predicted maximum bytes occupied by the band accumulator and weight plane.

  • band_rows – maximum number of canvas rows held in one write band.

  • writer – canvas writer selected for the run, "stream" or "memmap".

  • status – write outcome: "empty" before pixels are written, "complete" when no tile read failed, or "partial" when at least one tile was skipped.

  • warnings – non-fatal dry-run, empty-canvas, read-failure, placement, or artifact-stamping messages.

  • db_path – coordinate database written by save_coordinates(), or "" when coordinates were not saved.

summary() → str[source]

One-block human summary of the write.

class spacr.align.CanvasSpec[source]

Canvas geometry, computed before a single byte is allocated.

Parameters:
  • height – number of rows in the output canvas.

  • width – number of columns in the output canvas.

  • channels – number of image planes in the output canvas.

  • dtype – NumPy dtype name used for the output array.

  • origin_y – global-frame row mapped to canvas row zero, preserving negative vertical offsets.

  • origin_x – global-frame column mapped to canvas column zero, preserving negative horizontal offsets.

canvas_yx(y: float, x: float) → Tuple[int, int][source]

Map a global-frame position onto integer canvas indices.

Parameters:
  • y – global-frame row position, in pixels; origin_y is subtracted and the result rounded to the nearest integer.

  • x – global-frame column position, in pixels; origin_x is subtracted and the result rounded to the nearest integer.

property nbytes: int[source]

Size of the written .npy payload, in bytes.

property shape: Tuple[int, int, int][source]

(H, W, C).

class spacr.align.PairResult[source]

One neighbour pair, registered or refused.

Parameters:
  • i – index of the first tile.

  • j – index of the second tile.

  • dy – row displacement of tile j relative to tile i used by this result: measured when accepted and normally the nominal fallback when refused.

  • dx – column displacement of tile j relative to tile i used by this result: measured when accepted and normally the nominal fallback when refused.

  • nominal_dy – row displacement predicted by nominal stage positions.

  • nominal_dx – column displacement predicted by nominal stage positions.

  • confidence – best non-negative normalized cross-correlation score. It may be below the acceptance threshold for a refused pair and is zero when no usable candidate could be scored.

  • accepted – whether this pair contributes an edge to the global position solve.

  • overlap_px – number of overlap pixels associated with the registration decision.

  • note – explanation for a refused pair, or "" for an accepted pair.

property drift: float[source]

Euclidean distance between the measured and nominal displacement.

class spacr.align.Placement[source]

Where one tile goes, and how much that position is worth.

Parameters:
  • tile – source Tile placed by this result.

  • y – solved row offset in the global frame; negative positions are preserved until canvas planning.

  • x – solved column offset in the global frame.

  • confidence – greatest accepted-pair confidence supporting this tile, zero for nominal fallback, or one for the single-tile case.

  • method – placement method, one of METHOD_REGISTRATION, METHOD_NOMINAL, or METHOD_SINGLE.

  • note – explanation of fallback or refused-pair evidence.

  • residual – root-mean-square disagreement, in pixels, between the solved position and accepted incident pairs.

  • n_pairs – number of accepted registration pairs incident on this tile.

class spacr.align.Tile[source]

One site: everything known about a tile without reading its pixels.

A “site” is a position on the stage, not a file. When each channel is its own file — the Yokogawa case — the sibling paths live in channel_paths and path is the reference channel’s; when the channels are planes of one (H, W, C) array — spaCR’s merged/*.npy — channel_paths is empty and path is the only file. Either way there is exactly one Tile, and so exactly one Placement, per stage position.

Parameters:
  • path – absolute path of the reference-channel file, or the sole file when all channels are planes of one array.

  • index – zero-based tile index used by pair results, the global solve, and the reader cache.

  • plate – plate token parsed from the filename, or "" when absent.

  • well – well token parsed from the filename, such as "B07", or "" when absent.

  • field – one-based field identifier assigned during scanning; zero only on a manually constructed tile without an assigned field.

  • channel – zero-based assembled-channel index selected to drive registration.

  • shape – assembled-site shape (height, width, channels); unreadable headers retain zero height and width.

  • dtype – NumPy dtype name reported by the reference header, or "uint16" as the unreadable-header fallback.

  • grid_row – zero-based nominal row in the acquisition grid.

  • grid_col – zero-based nominal column in the acquisition grid.

  • nominal_y – nominal vertical stage position in pixels.

  • nominal_x – nominal horizontal stage position in pixels.

  • channel_paths – absolute sibling-file paths in assembled channel order, or () when all channels live inside path.

  • error – header-read failure text, or "" when the tile is readable.

property height: int[source]

Tile height in pixels, 0 when unknown.

property n_channels: int[source]

Number of channels this site contributes to the canvas.

property name: str[source]

Short human label, plate_well_field.

property readable: bool[source]

False when the header could not be read.

property width: int[source]

Tile width in pixels, 0 when unknown.

spacr.align.align_folder(settings: Mapping[str, Any] | None = None, **overrides: Any) → List[AlignResult][source]

Scan, plan, optionally write and optionally record, in one call.

Always prints the plan before writing anything, so even a headless run leaves the “these four tiles fell back to nominal” block in the log where a surprised user can find it.

Parameters:
  • settings – see default_settings().

  • overrides – keyword form of the same keys; these win.

Returns:

one AlignResult per stitched group (one per well when group_by_well).

Raises:

ConfigurationError – no src, or no readable tiles.

spacr.align.default_settings(settings: Mapping[str, Any] | None = None) → Dict[str, Any][source]

Return the settings align_folder() understands, with defaults.

Shaped like every other spacr settings factory — pass a partial dict, get it back filled in — so the CLI and the Qt bridge can build a panel from it without special-casing this module.

Parameters:

settings – partial settings; keys given here win.

spacr.align.estimate_offsets(tiles: Sequence[Tile], *, reference_channel: int | None = None, min_confidence: float = DEFAULT_MIN_CONFIDENCE, min_overlap_px: int = DEFAULT_MIN_OVERLAP_PX, upsample: int = DEFAULT_UPSAMPLE, neighbour_radius: int = 1, max_shift: float | None = None, anchor_weight: float = DEFAULT_ANCHOR_WEIGHT, dtype: Any | None = None, max_open_tiles: int = 8, ledger: spacr.errors.RunLedger | None = None) → AlignPlan[source]

Register every overlapping neighbour pair and solve the whole set at once.

Memory: the peak allocation is two overlap strips. For a 10% overlap on 2048x2048 tiles that is 2 x 0.8 MB, whatever the canvas turns out to be.

Parameters:
  • tiles – from scan_tiles(), or built by hand.

  • reference_channel – the plane registration is measured on. Every channel of a site then shares that site’s single solution — registering channels independently would shear the composite. None (the default) takes the channel the tiles already carry, which is the one scan_tiles() selected. This used to default to 0, so scan_tiles(reference_channel=2) followed by the documented estimate_offsets(tiles) silently registered on channel 0 instead — the selection was stored on every tile and read by nothing. Tiles that disagree fall back to 0; the resolved value is recorded on the plan and printed by format_plan(), so whichever way it went is visible rather than assumed.

  • min_confidence – normalised cross-correlation below which a pair is refused and the tile falls back to nominal.

  • min_overlap_px – overlaps narrower than this in either axis are not attempted.

  • upsample – sub-pixel refinement factor; 10 resolves 0.1 px.

  • neighbour_radius – Chebyshev grid distance to consider. 1 is the eight surrounding tiles. Raise it when the overlap exceeds 50%, so the i/i+2 pairs become real redundancy in the solve.

  • max_shift – refuse any pair whose correction exceeds this many pixels. Defaults to a quarter of the smaller tile dimension.

  • anchor_weight – see solve_positions().

  • dtype – force the canvas dtype; defaults to the promotion of every tile’s dtype.

  • max_open_tiles – how many tiles may be memory-mapped at once. A column strip faults in a whole row-major tile, so leaving every tile mapped would make the resident set the size of the input folder; 8 covers the neighbourhood being registered.

  • ledger – optional spacr.errors.RunLedger to record per-tile success/failure into.

Returns:

an AlignPlan; nothing has been allocated or written.

Raises:

ConfigurationError – no readable tiles at all.

spacr.align.format_plan(plan: AlignPlan, *, max_rows: int = 12) → str[source]

Render a plan as the block a user should read before writing 800 MB.

Leads with what is wrong — nominal fallbacks, refused pairs, the worst residual — because that is the part a stitch summary usually buries.

Parameters:

plan – alignment plan to summarise: its canvas shape, origin, placement and pair counts, nominal fallbacks, residuals, unplaced tiles and warnings are listed.

spacr.align.group_tiles(tiles: Sequence[Tile]) → Dict[Tuple[str, str], List[Tile]][source]

Split tiles into one stitchable set per (plate, well).

Fields of different wells are not neighbours and must never be registered against each other. Indices are renumbered within each group so the returned lists can go straight into estimate_offsets().

Parameters:

tiles – tiles to split; each is grouped by its plate and well attributes and sorted by its original index within the group.

spacr.align.plan_canvas(placements: Sequence[Placement], *, dtype: Any | None = None, channels: int | None = None) → CanvasSpec[source]

Compute the canvas geometry from the offsets, allocating nothing.

Negative offsets are the normal case — the solve fixes the gauge to the nominal centroid, so a tile may land above or left of the origin. The canvas is sized to the bounding box of every placed tile and CanvasSpec.origin_y / origin_x carry the mapping back to the global frame, so nothing is ever clipped.

Parameters:
  • placements – from AlignPlan.placements.

  • dtype – canvas dtype; defaults to promoting every tile’s.

  • channels – canvas planes; defaults to the widest tile.

Returns:

a CanvasSpec. height/width are 0 for an empty placement list.

spacr.align.read_coordinates(db_path: str | os.PathLike, *, table: str = ALIGN_TABLE, plate: str | None = None, well: str | None = None) → pandas.DataFrame[source]

Read ALIGN_TABLE back out, closing the loop.

Parameters:
  • db_path – the database save_coordinates() wrote.

  • table – table name.

  • plate – optionally restrict to one plateID.

  • well – optionally restrict to one well, given as 'B07' or as an already-mapped rowID/columnID pair joined by _.

Returns:

the table as a DataFrame, in tile order.

Raises:

ConfigurationError – the database or the table is missing.

spacr.align.save_coordinates(plan: AlignPlan | Iterable[AlignPlan], db_path: str | os.PathLike, *, table: str = ALIGN_TABLE, canvas: CanvasSpec | None = None, stack_path: str = '', if_exists: str = 'replace') → int[source]

Write one row per tile into measurements.db.

The point of this table is that a stitch is a claim: “field 47 of B07 sits at canvas row 3641, column 8192”. Downstream, a cluster of odd measurements in one corner of a well is either biology or a tile that silently fell back to its nominal position, and only this table can tell the two apart. So every row carries method, confidence and residual next to the coordinates.

It is keyed exactly the way conversion_map and every measurement table are keyed, so it joins on the same four columns:

SELECT c.*, a.canvas_y, a.canvas_x, a.method, a.residual
FROM cell AS c
JOIN align_coordinates AS a
  ON  c.plateID  = a.plateID
  AND c.rowID    = a.rowID
  AND c.columnID = a.columnID
  AND c.fieldID  = a.fieldID

or on the single prcf column, which is indexed. The same join works against nucleus / pathogen / cytoplasm / png_list and against conversion_map itself, which chains a stitched coordinate all the way back to the original vendor file.

Tiles that could never be read are written too, with method='unreadable' and NULL coordinates — a missing field must be visible in the table, not absent from it.

Parameters:
  • plan – one AlignPlan or several (one per well).

  • db_path – SQLite database; created if missing.

  • table – table name.

  • canvas – the geometry actually written, when it differs from the plan’s.

  • stack_path – the .npy these coordinates index into.

  • if_exists – 'replace' (default), 'append' or 'fail'.

Returns:

number of rows written.

spacr.align.scan_tiles(src: str | os.PathLike | Sequence[Any], *, grid: Sequence[int] | None = None, overlap: float = DEFAULT_OVERLAP, order: str = 'row-major', recursive: bool = False, positions: Mapping[int, Sequence[float]] | None = None, reference_channel: int | None = None, group_by_well: bool = False) → List[Tile][source]

Return one Tile per stage position, without reading pixels.

Every file’s shape and dtype comes out of its header. A folder of 1000 tiles is scanned in the time it takes to open 1000 files, which is what makes “show me the plan before you write 800 MB” possible.

Files that share (plate, well, field) are collapsed into one tile with one plane per channel: a Yokogawa well with four channels per field produces one Tile per field, not four.

Parameters:
  • src – folder of tiles, a single file, or an explicit list of paths (which fixes the order — useful when the filenames carry no field number).

  • grid – (rows, cols) of the acquisition. Inferred from the tile count when omitted.

  • overlap – nominal neighbour overlap as a fraction of the tile, used only to seed the search; registration corrects it.

  • order – how a flat field sequence maps onto the grid — see ORDERS. Serpentine acquisitions need 'snake-row'.

  • recursive – walk sub-folders too.

  • positions – optional {field: (y, x)} of known stage positions in pixels. Overrides the grid entirely — pass the microscope’s real coordinates when you have them.

  • reference_channel – which channel drives registration. Defaults to the lowest channel present.

  • group_by_well – lay grid out once per well instead of once across the whole folder. A grid is a property of the acquisition and the acquisition is per well — the same field pattern is repeated in every well — so a folder holding two wells imaged 2x2 is two 2x2 grids, not one 2x4. With this off, such a folder was refused with “grid 2x2 has room for 4 tiles but 8 were found”, and grid=None inferred a single 2x4 that laid each well out as a 1x4 strip and put its vertical neighbours outside the overlap. align_folder() passes its own group_by_well setting through.

Returns:

tiles in grid order, each with index set.

Raises:

ConfigurationError – src does not exist, or grid is too small for the tiles found.

spacr.align.solve_positions(n_tiles: int, edges: Sequence[Tuple[int, int, float, float, float]], nominal: numpy.ndarray, *, anchor_weight: float = DEFAULT_ANCHOR_WEIGHT) → Tuple[numpy.ndarray, numpy.ndarray, numpy.ndarray][source]

Solve every tile position at once from the pair graph.

Each edge (i, j, dy, dx, w) contributes one weighted equation p_j - p_i = (dy, dx); each tile contributes one weak equation p_i = nominal_i. Minimising the whole residual at once is what stops error accumulating.

Why not accumulate. Walking a row and adding each measured shift to the previous position gives p_k = sum(d_0..d_k), so every measurement error is carried forward: ten tiles with 0.3 px errors put the last one 3 px out, and nothing in the output says so. Least squares over the same measurements distributes the error instead — and because a real grid has redundant edges (the tile below as well as the tile to the right, or the i/i+2 pairs of a heavily overlapped row), disagreements cancel rather than compound. The surviving disagreement is the per-tile residual, which is returned.

The anchor equations do two jobs: they fix the gauge (a pure pair graph determines positions only up to a global translation) and they place tiles that ended up in no edge at all — an isolated tile comes back at exactly its nominal position rather than at the origin.

Parameters:
  • n_tiles – number of unknown positions.

  • edges – (i, j, dy, dx, weight) per accepted pair.

  • nominal – (n_tiles, 2) array of nominal (y, x).

  • anchor_weight – weight of the “stay at nominal” equations.

Returns:

(positions, residuals, degrees) — (n, 2) solved positions, (n,) RMS per-tile residual in pixels, and (n,) count of accepted edges per tile.

spacr.align.write_stack(plan: AlignPlan, dst: str | os.PathLike, *, blend: str = 'feather', band_rows: int | None = None, max_buffer_bytes: int = DEFAULT_MAX_BUFFER_BYTES, feather: int | None = None, dtype: Any | None = None, subpixel: bool = False, writer: str = 'stream', max_open_tiles: int = 8, overwrite: bool = False, name: str | None = None, dry_run: bool = False, ledger: spacr.errors.RunLedger | None = None) → AlignResult[source]

Composite the plan into one .npy, one horizontal band at a time.

The canvas is never an in-RAM array. The output file is created at full size up front (so a disk that cannot hold it fails immediately, not at 90%), then filled band by band; the only heap allocations are the band’s float32 accumulator, its weight plane, and one tile window. All three are bounded by max_buffer_bytes and none of them scales with the canvas.

Seams are feathered: each tile’s contribution is weighted by a linear ramp that rises from the tile edge inward over AlignPlan.feather pixels, and the band is divided by the total weight. A hard cut would leave a straight, high-contrast edge exactly where two fields meet — and a straight edge is the single most reliable thing a downstream segmentation will find and call an object boundary. Feathering costs one extra float32 plane per band and makes the join a gradient instead. blend='average' weights every contributor equally and blend='none' is last-writer-wins, both kept mainly so the difference can be measured.

Parameters:
  • plan – from estimate_offsets().

  • dst – output .npy, or a folder to write into.

  • blend – one of BLEND_MODES.

  • band_rows – force the band height; normally derived from max_buffer_bytes.

  • max_buffer_bytes – ceiling on the RAM the write may use for the band. This is the knob; see DEFAULT_MAX_BUFFER_BYTES for why the default is small.

  • feather – override the ramp width in pixels.

  • dtype – override the canvas dtype.

  • subpixel – resample each tile by its fractional offset before compositing. Off by default: it costs an interpolation per tile window, and the sub-pixel part is recorded in the table either way.

  • writer – 'stream' (default; never maps the canvas) or 'memmap' (open_memmap plus per-band flush and drop).

  • max_open_tiles – how many tiles may be mapped at once. The tiles intersecting one band are the working set; older mappings are dropped so the input folder never becomes resident.

  • overwrite – replace an existing output. False refuses.

  • name – output filename when dst is a folder.

  • dry_run – compute the geometry and the band plan, write nothing.

  • ledger – optional spacr.errors.RunLedger.

Returns:

an AlignResult, whose peak_buffer_bytes is the band buffer size predicted from the band geometry, computed before anything is allocated and reported on the dry_run path too.

Raises:

ConfigurationError – bad blend/writer, or an existing output without overwrite.

Nested helpers

_count_components.find(a: int) → int

Resolve and compress one tile’s captured component parent.

Parameters:

a – tile index whose union-find representative is required.

Returns:

root index after path-halving each traversed parent link in the captured forest.

spacr/align.py:1737