ngff_zarr.displacement_field_transform

Bridge ITK displacement fields and RFC-5 displacements transformations.

A displacement field is a transformation and an array at once. ITK keeps the array inside the transform, as a vector image with its own grid; RFC-5 keeps it in the store, as a multiscale image the displacements entry points at by path. So the two conversions here are the only ones in this package whose result has two parts, and they stay pure: nothing is read from or written to a store. The caller writes the field next to its image, and loads it back, with

func:

~ngff_zarr.to_ome_zarr and :func:~ngff_zarr.from_ome_zarr.

The same two conventions as the affine conversion apply, plus one.

Axis order ITK orders a vector’s components fastest-axis-first, x then y then z, and lays the field out as [z][y][x][component]. RFC-5 says the i-th component of the field refers to the i-th axis of the output coordinate system, so components follow dims; the same permutation as for an affine’s matrix is applied to the component axis.

Frames An ITK vector is a difference of two physical points. An RFC-5 displacement is a difference of two intrinsic points, d = q' - q, so the two differ by more than a rotation as soon as the images sit in different frames. Both directions follow from one identity, phi_out(q + d(q)) = phi_in(q) + v(q), worked out in :func:_frame_terms on top of the affine conversion’s change of frame. The field’s own grid follows phi_in^-1, so its origin moves and its spacing does not.

Grid direction RFC-5 maps the field’s array coordinates to the input system with the field’s own coordinateTransformations, which this package writes as a scale and a translation. That can only express a grid oriented like the input image, so a field whose direction matrix differs from the fixed image’s (the identity when no frames are given) is refused rather than written with a mapping a reader would misread. Registrations sample the field on the fixed grid, so the common case is exact.

Module Contents

Classes

FieldBound

The range of displacement each chunk of a field can produce.

Functions

_check_dims

Validate the spatial-only dims a displacement field is defined on.

_frames

The two images’ directions and origins in ITK order, or None.

_unoriented_frames

The frame pair of two images with no orientation and no translation.

_frame_terms

The per-point terms relating ITK vectors and RFC-5 displacements.

_grid_shift

matrix q + vector at every grid point, or None when it is zero.

_convert_field_values

The per-voxel value conversion both field directions share.

_is_identity

_decode_field

Read any supported field input into ITK conventions.

_decode_itkwasm_transform

The field an ITK-Wasm DisplacementField entry packs.

_decode_image_dict

The field a vector image dictionary holds.

itk_displacement_field_to_ngff_transform

Convert an ITK displacement field to an RFC-5 displacements transform.

field_image

The field image a field transform names, checked against dims.

convert_itk_field_block

Convert one block of a field between ITK’s convention and RFC-5’s.

check_unoriented_field

Refuse a field whose grid cannot be placed without the two images.

_block_extrema

Smallest and largest displacement per component over one block.

field_displacement_bound

Bound the displacement of every chunk of field, one chunk at a time.

field_window

The field indices a grid of shape at translation reads.

ngff_displacement_field_to_itk_transform

Convert an RFC-5 displacements or coordinates transform to ITK.

_fields_entry

The field fields holds for transform, with a message otherwise.

Data

API

ngff_zarr.displacement_field_transform._COMPONENT_DIM

‘c’

ngff_zarr.displacement_field_transform._check_dims(dims: collections.abc.Sequence[str]) → tuple[str, ...]

Validate the spatial-only dims a displacement field is defined on.

ngff_zarr.displacement_field_transform._frames(fixed, moving, dims)

The two images’ directions and origins in ITK order, or None.

ngff_zarr.displacement_field_transform._unoriented_frames(
dimension: int,
) → ngff_zarr.itk_transform_to_ngff_transform._FrameGeometry

The frame pair of two images with no orientation and no translation.

With it the terms below all vanish, so the conversion that changes frames and the one that does not are the same arithmetic rather than two branches.

ngff_zarr.displacement_field_transform._frame_terms(frames: ngff_zarr.itk_transform_to_ngff_transform._FrameGeometry)

The per-point terms relating ITK vectors and RFC-5 displacements.

An ITK vector is a difference of two physical points; an RFC-5 displacement is a difference of two intrinsic points. Writing phi_out^-1 . phi_in as q -> M q + b – the change of frame the affine conversion applies, given the identity as its mapping – the defining identity phi_out(q + d(q)) = phi_in(q) + v(q) rearranges to

d(q) = D_out^-1 v + (M - I) q + b
v(q) = D_out (d - (M - I) q - b)

so one derivation serves both directions. The two q terms vanish when the images share a frame, leaving d = D_out^-1 v.

Returns:

(D_out, M - I, b).

ngff_zarr.displacement_field_transform._grid_shift(shape, origin, spacing, matrix, vector)

matrix q + vector at every grid point, or None when it is zero.

shape is the field’s [z][y][x] layout, so the index arrays come out slowest-axis-first and are reversed into ITK component order. The result is (*shape, N), ready to add to or subtract from the field.

ngff_zarr.displacement_field_transform._convert_field_values(
vectors: numpy.ndarray,
origin: numpy.ndarray,
spacing: numpy.ndarray,
frames: ngff_zarr.itk_transform_to_ngff_transform._FrameGeometry,
*,
absolute: bool = False,
inverse: bool = False,
) → numpy.ndarray

The per-voxel value conversion both field directions share.

vectors is (*grid, N) with ITK-ordered components; origin and spacing are the GRID the values sit on, in ITK order – for a window of a larger field, the window’s own origin, which is what makes the conversion per-block: every term is a function of the voxel’s position and the frames alone.

Forward (inverse=False) turns ITK vectors into RFC-5 values: v @ inverse_out.T then the positional frame term added. Inverse takes the frame term off first and rotates back, so the two are exact inverses. shift_matrix is M - I; an absolute (coordinates) field holds q + d rather than d, so absolute folds the grid point itself into the positional term, in one pass over the grid.

ngff_zarr.displacement_field_transform._is_identity(matrix: numpy.ndarray) → bool
ngff_zarr.displacement_field_transform._decode_field(transform)

Read any supported field input into ITK conventions.

Returns (vectors, origin, spacing, direction): the field as a [z][y][x][component] array with components in ITK order, and its grid in ITK component order.

ngff_zarr.displacement_field_transform._decode_itkwasm_transform(transform)

The field an ITK-Wasm DisplacementField entry packs.

ngff_zarr.displacement_field_transform._decode_image_dict(image)

The field a vector image dictionary holds.

ngff_zarr.displacement_field_transform.itk_displacement_field_to_ngff_transform(
transform,
dims: collections.abc.Sequence[str],
*,
path: str,
fixed: ngff_zarr.ngff_image.NgffImage | None = None,
moving: ngff_zarr.ngff_image.NgffImage | None = None,
) → tuple[ngff_zarr.v06.zarr_metadata.Displacements, ngff_zarr.ngff_image.NgffImage]

Convert an ITK displacement field to an RFC-5 displacements transform.

The result has two parts, because the field is an array: the transform metadata, which names path, and the field itself as an image to be written at that path. Write the field first, into a subgroup of the store the image goes to, then the image::

transform, field = itk_displacement_field_to_ngff_transform(
    itk_transform, ["z", "y", "x"], path="displacement_field"
)
to_ome_zarr(f"{store}/displacement_field", to_multiscales(field))
multiscales.metadata.coordinateTransformations = [transform]
to_ome_zarr(store, multiscales, overwrite=False)
Parameters:
  • transform – An itk.DisplacementFieldTransform, a vector itk.Image or itkwasm.Image holding the field directly (the form a warp comes in from most registration tools), an ITK-Wasm DisplacementField transform, or a one-entry list of it. The field maps fixed points into moving space.

  • dims (Sequence[str]) – The spatial axis names of the input coordinate system, in RFC-5 (Zarr) order. The field must be defined on these axes and no others.

  • path (str) – Where the field will be written, relative to the image’s group. Recorded on the returned transform.

  • fixed (NgffImage, optional) – The fixed and moving images the field relates. Passing both re-expresses the vectors on the images’ intrinsic coordinate systems, including the direction matrix derived from RFC-4 anatomical orientation, and gives the field the fixed image’s orientation and units. Omitting them is exact only when neither image carries an anatomical orientation.

  • moving (NgffImage, optional) – See fixed. Pass both or neither.

Returns:

The displacements transform, with interpolation set to linear (ITK’s interpolator for a field), and the field as an NgffImage whose first axis carries the components, type: "displacement", followed by dims.

Return type:

tuple[Displacements, NgffImage]

Raises:

ValueError – If the field’s grid is not oriented like the fixed image (the identity without frames), which the field’s scale and translation could not express; resample the field onto the fixed grid first. Also for a field whose dimensionality does not match dims.

ngff_zarr.displacement_field_transform.field_image(
transform: ngff_zarr.v06.zarr_metadata.Displacements | ngff_zarr.v06.zarr_metadata.Coordinates,
field,
dims: collections.abc.Sequence[str],
) → ngff_zarr.ngff_image.NgffImage

The field image a field transform names, checked against dims.

Takes the NgffImage or the NgffMultiscales a caller passes in fields, and returns the single-scale image, after checking that its axes are the component axis followed by dims in order and that it holds one component per input axis.

Parameters:
  • transform (Displacements | Coordinates) – The displacements or coordinates transform.

  • field – The field image or multiscales, as fields holds it.

  • dims (Sequence[str]) – The spatial axis names of the input coordinate system, in RFC-5 (Zarr) order.

Returns:

The field as a single-scale image; the finest level of a multiscales.

Return type:

NgffImage

Raises:

ValueError – If the field has no single component axis of the type the transform calls for, if its axes are not that axis followed by dims, or if it holds a number of components other than len(dims).

ngff_zarr.displacement_field_transform.convert_itk_field_block(
values: numpy.ndarray,
dims: collections.abc.Sequence[str],
*,
translation: collections.abc.Sequence[float],
spacing: collections.abc.Sequence[float],
fixed: ngff_zarr.ngff_image.NgffImage | None = None,
moving: ngff_zarr.ngff_image.NgffImage | None = None,
transform_type: str = 'displacements',
inverse: bool = False,
) → numpy.ndarray

Convert one block of a field between ITK’s convention and RFC-5’s.

The block-level face of the two whole-field converters, for a field that is never assembled: a producer computing an ITK-convention field region by region converts each block on its way into a store created with metadata_only=True, and a consumer reads a window back the same way. Every term of the conversion is a function of the voxel’s position and the frames alone, so converting a block with the BLOCK’s own origin equals cutting that block from the converted whole – which is what the tests pin, against the whole-field converters themselves.

Parameters:
  • values (np.ndarray) – The block, channel-first (N, *block) with the spatial axes in dims order. Forward, the components are ITK’s (a vector’s x, then y, then z); with inverse=True they follow dims, as the store holds them.

  • dims (Sequence[str]) – The spatial axis names of the input coordinate system, in RFC-5 (Zarr) order.

  • translation (Sequence[float]) – Where the block starts on the field’s grid, per axis in dims order: the field’s own translation advanced by the block’s voxel offset times spacing. This is the RFC-5 value, in the input coordinate system’s frame – with fixed/moving it is NOT the block’s ITK physical origin, which the fixed image’s direction moves away from it, and the whole-field converters take this same value.

  • spacing (Sequence[float]) – The field’s scale per axis, in dims order.

  • fixed (NgffImage, optional) –

    The fixed and moving images the field relates; see

    func:

    itk_displacement_field_to_ngff_transform. Pass both or neither; with neither the frames are one, and the conversion is the component permutation alone.

  • moving (NgffImage, optional) – See fixed.

  • transform_type (str, optional) – displacements (offsets) or coordinates (absolute output positions).

  • inverse (bool, optional) – Convert the store’s values back to ITK’s convention.

Returns:

The converted block, channel-first, contiguous, components in dims order (ITK’s with inverse=True).

Return type:

np.ndarray

Raises:

ValueError – If the block’s component count does not match dims, or for an unknown transform_type.

ngff_zarr.displacement_field_transform.check_unoriented_field(
field: ngff_zarr.ngff_image.NgffImage,
dims: collections.abc.Sequence[str],
) → None

Refuse a field whose grid cannot be placed without the two images.

An ITK transform lives in physical space, and an anatomical orientation only says where a grid sits once the images it relates are known.

Parameters:
  • field (NgffImage) – The field image.

  • dims (Sequence[str]) – The spatial axis names, in RFC-5 order.

Raises:

ValueError – If the field carries an anatomical orientation.

class ngff_zarr.displacement_field_transform.FieldBound

The range of displacement each chunk of a field can produce.

A field is read through a kernel that is non-negative and sums to one, so a displacement anywhere in a chunk is a convex combination of that chunk’s values and lies between their smallest and largest. That makes the range a bound on every interpolated displacement, not a sample of one: walking the boundary of a region instead misses a bump the region encloses.

The range is kept rather than its magnitude, so a field that shifts every point the same way moves the region it bounds instead of widening it.

The granularity is the field’s own chunking, since that is what a read costs. A field stored in one chunk therefore reports one range for the whole volume, which is also the case where the field fits in memory.

dims: tuple[str, ...]

None

low: numpy.ndarray

None

high: numpy.ndarray

None

offsets: tuple[numpy.ndarray, ...]

None

over(
window: collections.abc.Sequence[tuple[int, int]],
outside: bool = False,
) → dict[str, tuple[float, float]]

The displacement range over the chunks window touches.

Parameters:
  • window (Sequence[tuple[int, int]]) – (start, stop) per spatial axis, in field index space and in dims order.

  • outside (bool) – Whether the grid the window came from also has points beyond the field. ITK displaces those by nothing, so zero belongs in the range as much as the values do; leaving it out lets a field that displaces every point it covers one way carry the region away from the points it does not cover.

Returns:

{dim: (low, high)} per component, keyed by dimension. Zero for an empty window, where the field displaces nothing.

Return type:

dict[str, tuple[float, float]]

ngff_zarr.displacement_field_transform._block_extrema(
block,
block_info=None,
*,
origin=None,
spacing=None,
offset=None,
)

Smallest and largest displacement per component over one block.

Comes back as one array of twice the components, the minima before the maxima, so a single pass over the field answers both. offset is where the array this block came from starts in the field, since the block is located against its own array rather than the field.

ngff_zarr.displacement_field_transform.field_displacement_bound(
transform: ngff_zarr.v06.zarr_metadata.Displacements | ngff_zarr.v06.zarr_metadata.Coordinates,
field: ngff_zarr.ngff_image.NgffImage,
dims: collections.abc.Sequence[str],
window: collections.abc.Sequence[tuple[int, int]] | None = None,
) → ngff_zarr.displacement_field_transform.FieldBound

Bound the displacement of every chunk of field, one chunk at a time.

One pass over the field, reading a chunk and keeping two numbers per component. The values are not held: what comes back is a few floats per chunk, which is what lets a caller size its reads against a field that does not fit in memory.

Parameters:
  • transform (Displacements | Coordinates) – The displacements or coordinates transform the field belongs to.

  • field (NgffImage) – The field image, as :func:field_image returns it.

  • dims (Sequence[str]) – The spatial axis names of the input coordinate system, in RFC-5 (Zarr) order.

  • window (Sequence[tuple[int, int]] | None) –

    The field indices a caller will ask about, as

    func:

    field_window returns them. The pass then covers the chunks that window touches and no others, which is what a grid smaller than the field it is defined on saves. Defaults to the whole field.

Returns:

The per-chunk range, keyed by the field’s own indices.

Return type:

FieldBound

ngff_zarr.displacement_field_transform.field_window(
field: ngff_zarr.ngff_image.NgffImage,
dims: collections.abc.Sequence[str],
translation: collections.abc.Mapping[str, float],
scale: collections.abc.Mapping[str, float],
shape: collections.abc.Sequence[int],
margin: int = 1,
) → tuple[tuple[tuple[int, int], ...], bool]

The field indices a grid of shape at translation reads.

The field is evaluated at the grid’s own points, so the window is that grid’s extent expressed in field indices, whatever the displacement is: what a displacement sizes is the moving read, which

Class:

FieldBound answers.

Parameters:
  • field (NgffImage) – The field image.

  • dims (Sequence[str]) – The spatial axis names, in RFC-5 order.

  • translation (Mapping[str, float]) – The grid’s translation, keyed by dimension.

  • scale (Mapping[str, float]) – The grid’s scale, keyed by dimension.

  • shape (Sequence[int]) – The grid’s extent, in dims order.

  • margin (int) – Lattice points kept beyond the bracketing pair, so that linear interpolation at a point on the boundary reads the same values it reads from the whole field.

Returns:

(start, stop) per axis, in dims order, clamped to the field, and whether the grid also has points beyond the field, which ITK displaces by nothing. :meth:FieldBound.over takes the second as its outside.

Return type:

tuple[tuple[tuple[int, int], …], bool]

ngff_zarr.displacement_field_transform.ngff_displacement_field_to_itk_transform(
transform: ngff_zarr.v06.zarr_metadata.Displacements | ngff_zarr.v06.zarr_metadata.Coordinates,
field,
dims: collections.abc.Sequence[str],
*,
fixed: ngff_zarr.ngff_image.NgffImage | None = None,
moving: ngff_zarr.ngff_image.NgffImage | None = None,
) → list

Convert an RFC-5 displacements or coordinates transform to ITK.

The counterpart of :func:itk_displacement_field_to_ngff_transform, and what :func:~ngff_zarr.ngff_transform_to_itk_transform calls when handed a field transform with its field. The field is the image stored at transform.path; load it with from_ome_zarr(f"{store}/{transform.path}").

A coordinates field holds the absolute output position of each grid point where a displacements field holds the offset from it. The two differ by the position of the grid point itself, so both reach ITK as one DisplacementField: ITK has no absolute-coordinate transform.

Parameters:
  • transform (Displacements | Coordinates) – The displacements or coordinates transform.

  • field – The field image: an NgffImage whose component axis is the one with axes_types displacement (coordinate for a coordinates transform), followed by dims in order; or an NgffMultiscales, whose finest level is used.

  • dims (Sequence[str]) – The spatial axis names of the input coordinate system, in RFC-5 (Zarr) order.

  • fixed (NgffImage, optional) –

    The fixed and moving images the field relates; see

    func:

    itk_displacement_field_to_ngff_transform. The field’s own orientation, if any, must be the fixed image’s.

  • moving (NgffImage, optional) – See fixed. Pass both or neither.

Returns:

A single-entry ITK-Wasm TransformList of parameterization DisplacementField. itk.transform_from_dict turns it into a native itk.DisplacementFieldTransform.

Return type:

list[itkwasm.Transform]

Raises:

ValueError – If the field’s axes are not the component axis followed by dims, or if its orientation is not the fixed image’s.

ngff_zarr.displacement_field_transform._fields_entry(
transform: ngff_zarr.v06.zarr_metadata.Displacements | ngff_zarr.v06.zarr_metadata.Coordinates,
fields: collections.abc.Mapping[str, object] | None,
)

The field fields holds for transform, with a message otherwise.