Skip to content

confusius.multipose

multipose

Multi-pose data processing utilities.

This module provides functions for processing multi-pose fUSI data, including consolidating multiple poses into a single volume, slice timing correction, and other multi-pose specific operations.

Modules:

  • consolidate

    Multi-pose volume consolidation.

  • slice_timing

    Slice timing correction for multi-pose fUSI data.

Functions:

consolidate_poses

consolidate_poses(
    da: DataArray,
    affines_key: str = "physical_to_lab",
    sweep_dim: str = "z",
    rtol: float = 0.01,
) -> DataArray

Merge pose and sweep_dim dimensions into a single axis ordered by position.

For each (pose, sweep_dim) voxel, the position is computed using the sweep-dim column and translation of the per-pose affine (other spatial dims are zero at voxel centres along the sweep):

pos[p, i] = affine[p, :3, sweep_col] * sweep_mm[i] + affine[p, :3, 3]

where sweep_col is the column index of sweep_dim in the spatial dim ordering (z, y, x) → columns (0, 1, 2).

The primary sweep direction is found via singular value decomposition of all positions. Each voxel is projected onto that axis, the positions are checked for regularity, then the data is reindexed in ascending order along the consolidated sweep axis.

This function is primarily intended for consolidating multi-pose fUSI volumes acquired with an Iconeus system using a purely translational probe sweep. In that workflow, each pose corresponds to one probe position along the elevation axis (z), and the DataArray is produced by load_scan:

scan_3d = load_scan("recording.scan")       # dims: (pose, z, y, x)
volume  = consolidate_poses(scan_3d)        # dims: (z, y, x)

scan_4d = load_scan("recording_4d.scan")    # dims: (time, pose, z, y, x)
volume  = consolidate_poses(scan_4d)        # dims: (time, z, y, x)

The function also works on any DataArray that carries a (npose, 4, 4) affine stack in da.attrs["affines"][affines_key] with columns ordered as (z, y, x, translation). The sweep_dim parameter selects which spatial dimension is being swept across poses. For example, a stack of NIfTI DataArrays concatenated along a new pose dimension with their physical_to_qform affines stacked accordingly.

Parameters:

  • da

    (DataArray) –

    DataArray with a pose dimension and a (npose, 4, 4) affine stack stored in da.attrs["affines"][affines_key]. Typically produced by load_scan for 3Dscan or 4Dscan files.

  • affines_key

    (str, default: "physical_to_lab" ) –

    Key into da.attrs["affines"] that holds the (npose, 4, 4) affine stack. Column order must be (z, y, x, translation).

  • sweep_dim

    (str, default: "z" ) –

    Name of the spatial dimension being swept across poses. Must be one of the spatial dimensions in da.dims. The column index in the affine is determined by the order of spatial dimensions in the DataArray (e.g., if spatial dims are ["z", "y", "x"], then "z" → column 0, "y" → column 1, "x" → column 2).

  • rtol

    (float, default: 0.01 ) –

    Relative tolerance for the regularity check (fraction of mean spacing).

Returns:

  • DataArray

    DataArray with pose merged into sweep_dim, sorted by physical position. The consolidated sweep_dim coordinate holds the projection of each voxel's physical position onto the sweep axis, expressed in the same units as the input sweep_dim coordinate. For inputs that carry a pose_time coordinate, a consolidated slice_time with dims ("time", sweep_dim) is included: each slice inherits the timestamp of the pose it came from.

Raises:

  • ValueError

    If da has no pose dimension, if sweep_dim is not one of the spatial dimensions in da.dims, if the rotation block of the affine is not constant across poses (non-translation sweep), or if the consolidated positions are not regularly spaced within rtol.

Warns:

  • UserWarning

    If the sweep is not purely 1D (secondary/primary singular value ratio > 0.01).

correct_slice_timings

correct_slice_timings(
    da: DataArray,
    method: Literal[
        "linear",
        "nearest",
        "nearest-up",
        "zero",
        "slinear",
        "quadratic",
        "cubic",
        "previous",
        "next",
    ] = "linear",
    fill_value: float
    | tuple[float, float]
    | Literal["extrapolate", "nan"] = "extrapolate",
) -> DataArray

Resample each sweep position to the volume's reference time.

In multi-pose fUSI acquisitions, each sweep position is acquired at a different time within the volume period. This function resamples each position's time series so that all positions appear to have been acquired at the time stored in the time coordinate.

This function works on both:

  • Consolidated data: dims (time, <sweep_dim>, ...) with a slice_time coordinate with dims (time, <sweep_dim>), typically produced by consolidate_poses.
  • Unconsolidated data: dims (time, pose, ...) with a pose_time coordinate with dims (time, pose).

The sweep dimension is inferred from the second dim of whichever timing coordinate is present.

If the input is Dask-backed, the function stays lazy: computation is deferred until .compute() is called. The time dimension must not be chunked; spatial dimensions may be freely chunked.

Parameters:

  • da

    (DataArray) –

    DataArray with a slice_time or pose_time coordinate whose dims are (time, <sweep_dim>).

  • method

    ((linear, nearest, nearest - up, zero, slinear, quadratic, cubic, previous, next), default: "linear" ) –

    Interpolation method passed to scipy.interpolate.interp1d:

    • "linear": linear interpolation.
    • "nearest": nearest-neighbour interpolation; rounds down at half-integers.
    • "nearest-up": nearest-neighbour interpolation; rounds up at half-integers.
    • "zero": zeroth-order spline (step function).
    • "slinear": first-order spline.
    • "quadratic": second-order spline.
    • "cubic": third-order spline.
    • "previous": use previous point's value.
    • "next": use next point's value.
  • fill_value

    (float or tuple[float, float] or {extrapolate, nan}, default: "extrapolate" ) –

    How to handle target times that fall outside the range of a position's acquisition times. "extrapolate" allows linear extrapolation. "nan" inserts NaNs out of bounds. Use a float for a constant fill value, or a tuple (left, right) for different values on each side.

Returns:

  • DataArray

    New DataArray with the same dims and coordinates as the input, but with each position's time series resampled to da.coords["time"].values. The timing coordinate (slice_time or pose_time) is dropped to avoid accidental double-correction.

Raises:

  • ValueError

    If da has no time dimension or only one time point, if neither slice_time nor pose_time coordinate is present, if the timing coordinate does not have dims (time, <sweep_dim>), or if the time dimension is chunked.

Warns:

  • UserWarning

    If a spline method fails due to too few points and falls back to "linear".