ngff_zarr.resample_bounding_box

Find the region of a moving image needed to resample a fixed image grid.

Module Contents

Classes

ResampleBoundingBox

The region of a moving image needed to resample a fixed image grid.

Functions

_grown

region moved and widened by a per-axis displacement range.

_spatial_dims

_check_geometry

Require a finite scale and translation for every spatial axis.

_check_index_arrays

Reject a pipeline result that cannot fill one bound per dimension.

_check_region_contains_corners

Reject a region that does not contain the points it was derived from.

_shifted_translation

Translation of the sub-grid of ngff_image that starts at starts.

_itk_direction

Direction matrix from RFC-4 orientation, matching ngff_image_to_itk_image.

_metadata_only_itk_image

Build an ITK-Wasm image carrying geometry only, with an empty buffer.

_is_ngff_transform

_as_itk_transform_list

Normalize a supported transform input into an ITK-Wasm transform list.

_transform_from_dict

_stage_interval

One list entry’s displacement range, or None for the rest.

_box_inside_field_domain

Whether grid_box stays on the field’s own lattice.

_accumulated_interval

The range the displacement stages of entries can add, or None.

_nonlinear_interval

Split a transform list into its linear part and a displacement range.

_grid_physical_box

The grid’s physical extent per component, direction included.

_direction_bounds

The physical range as the two per-dimension mappings _grown takes.

_identity_region

The region the pipeline reports for the identity, computed directly.

_field_stream

The field a field transform names, its per-chunk range, and its window.

_identity_transform_list

A field transform’s linear part, which is the identity.

resample_bounding_box

Compute the moving-image region needed to resample a fixed image grid.

Data

API

ngff_zarr.resample_bounding_box._SPATIAL_DIMS

(‘x’, ‘y’, ‘z’)

class ngff_zarr.resample_bounding_box.ResampleBoundingBox

The region of a moving image needed to resample a fixed image grid.

Every mapping is keyed by dimension name, so callers never have to track whether a given array is in Zarr or ITK axis order.

dims: tuple[str, ...]

None

start_index: dict[str, int]

None

size: dict[str, int]

None

corners_min: dict[str, float]

None

corners_max: dict[str, float]

None

padded_corners_min: dict[str, float]

None

padded_corners_max: dict[str, float]

None

moving_shape: dict[str, int]

‘field(…)’

clamped() → dict[str, tuple[int, int]]

Return the region intersected with the moving image bounds.

Returns:

{dim: (start, stop)} with 0 <= start <= stop <= shape.

Return type:

dict[str, tuple[int, int]]

property is_empty: bool

Whether the region does not overlap the moving image at all.

slices(dims: collections.abc.Sequence[str] | None = None) → tuple[slice, ...]

Slices selecting the region, clamped to the moving image bounds.

Negative start indices are clamped rather than passed through, since a negative bound would silently wrap around instead of raising.

Parameters:

dims (Sequence[str] | None) – Dimension order of the array to be sliced. Defaults to the spatial dims. Dimensions outside the region (t, c) get a full slice.

Returns:

One slice per entry in dims.

Return type:

tuple[slice, …]

crop(
moving: ngff_zarr.ngff_image.NgffImage,
) → ngff_zarr.ngff_image.NgffImage | None

Lazily crop moving to this region, correcting its translation.

The returned image indexes the same Dask graph – nothing is computed – so only this region’s chunks are read when it is finally materialized.

Parameters:

moving (NgffImage) – The moving image the region was computed against.

Returns:

The cropped image, or None when the region does not overlap the moving image.

Return type:

NgffImage | None

ngff_zarr.resample_bounding_box._grown(
region: ngff_zarr.resample_bounding_box.ResampleBoundingBox,
bound: collections.abc.Mapping[str, tuple[float, float]],
moving: ngff_zarr.ngff_image.NgffImage,
index_bound: collections.abc.Mapping[str, tuple[float, float]] | None = None,
) → ngff_zarr.resample_bounding_box.ResampleBoundingBox

region moved and widened by a per-axis displacement range.

The pipeline walks the boundary of the transformed grid, which reports where a linear map sends the grid exactly and misses whatever a displacement does strictly inside it. A point p of the region reaches p + d with d in bound, so the region’s image lies between the two ends of that range: a field that shifts every point the same way moves the region, and only what varies widens it.

ngff_zarr.resample_bounding_box._spatial_dims(ngff_image: ngff_zarr.ngff_image.NgffImage) → list[str]
ngff_zarr.resample_bounding_box._check_geometry(
label: str,
image: ngff_zarr.ngff_image.NgffImage,
spatial: collections.abc.Sequence[str],
) → None

Require a finite scale and translation for every spatial axis.

A missing or non-finite entry would otherwise reach the pipeline and come back as a plausible-looking region computed from the wrong geometry.

ngff_zarr.resample_bounding_box._check_index_arrays(
result: dict,
itk_dims: collections.abc.Sequence[str],
) → None

Reject a pipeline result that cannot fill one bound per dimension.

The returned mappings are built from these two arrays alone, so a short or non-finite result would otherwise surface as an opaque failure further down, or as an undefined bound a caller slices with.

ngff_zarr.resample_bounding_box._check_region_contains_corners(
result: dict,
itk_dims: collections.abc.Sequence[str],
) → None

Reject a region that does not contain the points it was derived from.

The pipeline computes the integer region in 32-bit index space while reporting the corners as doubles, so a grid whose extent exceeds that range comes back as a wrapped – and typically empty – region. Padding is non-negative, so a correct region always spans its own tight corners; checking that catches the wrap without assuming an axis direction.

ngff_zarr.resample_bounding_box._shifted_translation(
ngff_image: ngff_zarr.ngff_image.NgffImage,
starts: dict,
) → dict

Translation of the sub-grid of ngff_image that starts at starts.

ITK places index i at origin + direction @ (i * spacing) and

Func:

ngff_image_to_itk_image uses translation as the origin, so the origin of a sub-grid is the translation moved by the index offset rotated through the RFC-4 direction matrix. Dimensions with no orientation, and non-spatial dimensions, move along their own axis.

ngff_zarr.resample_bounding_box._itk_direction(
ngff_image: ngff_zarr.ngff_image.NgffImage,
itk_dims: collections.abc.Sequence[str],
) → numpy.ndarray

Direction matrix from RFC-4 orientation, matching ngff_image_to_itk_image.

All-or-nothing: unless every spatial axis carries an orientation that maps onto an LPS axis, the direction falls back to identity.

ngff_zarr.resample_bounding_box._metadata_only_itk_image(
ngff_image: ngff_zarr.ngff_image.NgffImage,
itk_dims: collections.abc.Sequence[str],
direction: numpy.ndarray,
)

Build an ITK-Wasm image carrying geometry only, with an empty buffer.

ngff_image.data is never converted to an array – only its shape and dtype are read, which are Dask metadata. This is what lets the region be computed against images whose pixels are remote or enormous.

ngff_zarr.resample_bounding_box._NGFF_TRANSFORM_TYPES

‘frozenset(…)’

ngff_zarr.resample_bounding_box._is_ngff_transform(transform) → bool
ngff_zarr.resample_bounding_box._as_itk_transform_list(transform) → list

Normalize a supported transform input into an ITK-Wasm transform list.

ITK applies the last entry of a transform list first, and so does an itk.CompositeTransform, so the order carries over unchanged.

ngff_zarr.resample_bounding_box._transform_from_dict(entry: dict)
ngff_zarr.resample_bounding_box._INTERVAL_PARAMETERIZATIONS

None

ngff_zarr.resample_bounding_box._ORTHONORMAL_PARAMETERIZATIONS

‘frozenset(…)’

ngff_zarr.resample_bounding_box._stage_interval(entry, dimension: int)

One list entry’s displacement range, or None for the rest.

Returns:

(low, high) per physical component, or None when the entry is not one of the two interval parameterizations. Whether zero joins the range is the caller’s question: it depends on whether the grid can leave the stage’s domain, where ITK returns the point unchanged.

ngff_zarr.resample_bounding_box._box_inside_field_domain(entry, dimension: int, grid_box) → bool

Whether grid_box stays on the field’s own lattice.

A DisplacementField’s fixed parameters carry its grid: size, origin, spacing, then the direction, row-major, the layout

Func:

~ngff_zarr.ngff_displacement_field_to_itk_transform writes and ITK reads. A point past the lattice is displaced by nothing, so a grid that leaves it takes zero into its range; one that stays inside keeps the values’ own range, which is what makes a constant field an exact shift. Anything not answerable exactly, a rotated field grid included, answers False, which only widens. A BSpline never answers True: its control lattice reaches spline-order points beyond its domain of support, so lying inside the lattice proves nothing about staying on the domain.

ngff_zarr.resample_bounding_box._accumulated_interval(entries, intervals, dimension: int, contained: bool)

The range the displacement stages of entries can add, or None.

ITK applies the last entry of a list first, so the walk runs from the last entry outward, carrying what the stages seen so far can add. A displacement stage adds its own range, since the linear list this widens replaced that stage by the identity; an affine stage maps the range through the signs of its matrix; an orthonormal stage bounds every component by the range’s Euclidean reach. Any other stage returns None, and the caller keeps the boundary walk.

Accumulating outward rather than folding each stage separately is what lets a displacement stage sit outside another one: the range reaching it is carried through, and its own range joins on top.

Parameters:

intervals – One :func:_stage_interval per entry, so a field’s parameters are scanned once for the whole list.

ngff_zarr.resample_bounding_box._nonlinear_interval(entries, dimension: int, grid_box=None)

Split a transform list into its linear part and a displacement range.

Parameters:

grid_box – (low, high) per physical component of the fixed grid, when the caller knows it. A lone DisplacementField whose lattice contains the box keeps the values’ own range; in every other case zero joins the range, since ITK displaces a point beyond a stage’s domain by nothing.

Returns:

(affine_entries, (low, high)) when every stage could be carried: the entries with the displacement stages replaced by the identity, whose boundary walk the pipeline reports exactly, and the range to widen its region by. (entries, None) otherwise, and the caller’s boundary walk stands, with the miss its docstring names.

ngff_zarr.resample_bounding_box._grid_physical_box(grid: ngff_zarr.ngff_image.NgffImage, itk_dims)

The grid’s physical extent per component, direction included.

The directions RFC-4 orientations produce are signed permutations, so each physical component follows exactly one grid axis and the box is the per-axis pair of endpoint positions. None for any other direction.

ngff_zarr.resample_bounding_box._direction_bounds(interval, itk_dims, direction)

The physical range as the two per-dimension mappings _grown takes.

The corners the region reports are physical positions keyed by dimension name, so component j widens the dimension named itk_dims[j]. The start and size are indices, and an index moves through the direction’s transpose: with the signed permutations RFC-4 orientations produce, axis k follows the one component its column names, sign included.

ngff_zarr.resample_bounding_box._identity_region(
grid: ngff_zarr.ngff_image.NgffImage,
moving: ngff_zarr.ngff_image.NgffImage,
padding: int,
) → ngff_zarr.resample_bounding_box.ResampleBoundingBox

The region the pipeline reports for the identity, computed directly.

A field transform’s linear part is the identity, so its ungrown region is plain arithmetic: the grid’s own physical extent, read off in the moving image’s index space, floored and ceiled, padded. Equality with the pipeline is pinned by test_the_identity_region_is_the_pipelines over randomized geometry. Only :func:~ngff_zarr.resample’s per-block loop uses it, where the pipeline round trip measured as three quarters of the graph build; :func:resample_bounding_box itself stays on the pipeline.

grid must have at least one sample per spatial axis, which

Func:

resample_bounding_box establishes before reaching a transform.

ngff_zarr.resample_bounding_box._field_stream(
transform,
fields: collections.abc.Mapping[str, object] | None,
fixed: ngff_zarr.ngff_image.NgffImage,
spatial: collections.abc.Sequence[str],
extent: collections.abc.Mapping[str, int],
)

The field a field transform names, its per-chunk range, and its window.

A displacements or coordinates transform is the identity plus a displacement, so a region is the grid’s own image moved and widened by what the field can displace there. The range comes from the field’s values, read one chunk at a time and kept as two numbers per chunk, so a field larger than memory is bounded without being held.

The field comes back carrying the axis type its component axis has, which a field read from a multiscales keeps in the metadata rather than on the image, so a crop of it is typed without consulting the transform again.

ngff_zarr.resample_bounding_box._identity_transform_list(dims: collections.abc.Sequence[str]) → list

A field transform’s linear part, which is the identity.

ngff_zarr.resample_bounding_box.resample_bounding_box(
transform,
fixed: ngff_zarr.ngff_image.NgffImage,
moving: ngff_zarr.ngff_image.NgffImage,
padding: int = 1,
*,
fields: collections.abc.Mapping[str, object] | None = None,
) → ngff_zarr.resample_bounding_box.ResampleBoundingBox

Compute the moving-image region needed to resample a fixed image grid.

The region is derived from image geometry alone – the pixel buffers of fixed and moving are never read, and their Dask graphs are never computed. That is the point: describe two images and a transform with a few numbers, learn exactly which block of the moving image a resample will touch, and only then move pixels.

Two kinds of transform are accepted, and they are interpreted in different coordinate spaces:

  • An RFC-5 coordinate transformation acts on the intrinsic coordinate system, where a point is translation + scale * index. Its parameters are in Zarr axis order and no direction matrix applies. Its input and output identifiers are not resolved: the transformation is applied from the fixed image’s intrinsic system to the moving image’s, whatever the identifiers name.

  • An ITK transform (for example a CompositeTransform returned by Elastix) acts on ITK physical space, so the geometry is built the way

    func:

    ngff_zarr.ngff_image_to_itk_image builds it, including the direction matrix derived from RFC-4 anatomical orientation.

An ITK transform need not be linear. An RFC-5 transformation is converted first, so it must be one this package can convert: a linear mapping – identity, scale, translation, rotation, affine, mapAxis, byDimension, bijection, or a sequence of them – or a displacements or coordinates transformation whose field is passed in fields.

A field transform is the identity plus a displacement, and the region it reports is the grid’s own image moved and widened by the range that displacement takes. The range is read from the field one chunk at a time, so a field larger than memory is never held; a field that shifts every point the same way moves the region rather than widening it.

An ITK transform is measured by walking the boundary of the transformed grid, which reports a linear map exactly. A DisplacementField or BSpline stage would be missed where it acts strictly inside the grid, so such a stage is bounded the way a field is: the walk measures the list with those stages replaced by the identity, and the range their values can add, carried through the stages applied after them, widens the result. A stage sitting outside another one carries the range reaching it and adds its own on top, so several of them compose. Other non-linear parameterizations, the velocity fields among them, still rely on the walk alone and can under-report a strictly interior excursion.

In both cases the transform maps fixed points into moving space.

Parameters:
  • transform – An RFC-5 coordinate transformation, an itk.Transform, or an ITK-Wasm Transform / TransformList.

  • fixed (NgffImage) – The image whose grid is resampled. Geometry only.

  • moving (NgffImage) – The image to be sampled. Geometry only.

  • padding (int) – Pixels of padding added per side. The default of 1 covers linear interpolation, which reads one neighbor beyond the continuous index bound. Use 0 for the tight region, or more for wider kernels.

  • fields (Mapping[str, NgffImage | NgffMultiscales], optional) –

    The field images an RFC-5 displacements or coordinates transformation points at, keyed by its path, as

    func:

    ngff_zarr.ngff_transform_to_itk_transform takes them. Required for those two, ignored otherwise. A field carrying an anatomical orientation is refused here, since this branch works on the intrinsic systems where none applies: convert it with

    func:

    ngff_zarr.ngff_transform_to_itk_transform, passing fixed and moving, and pass the ITK transform it returns.

Returns:

The region, keyed by dimension name in Zarr order.

Return type:

ResampleBoundingBox

Raises:
  • ValueError – If fixed and moving do not share the same spatial dimensions, if there are not 2, 3 or 4 of them, if a scale or translation entry is missing, zero or non-finite, if padding is negative, if the transform couples spatial and non-spatial axes, or if the region spans more than the index range the pipeline can represent.

  • NotImplementedError – If the RFC-5 transformation is not linear.