nifreeze.testing.perturbations module¶
Known perturbations of a dataset, for the controls of nifreeze.analysis.sensitivity.
The sensitivity analysis compares predictions on a reference dataset with
predictions on perturbed copies of it, whose ground truth it is given (a
Perturbation). Realistic copies come from
a simulator; these helpers resample a dataset through rigid motions and
eddy-current-like distortions instead, so that tests can check the analysis
against answers known in closed form.
- nifreeze.testing.perturbations.eddy_distortion(dataset: Any, magnitude: float, axis: int, degree: int = 1, seed: int = 0, gradients: Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | complex | bytes | str | _NestedSequence[complex | bytes | str] | None = None, mask: ndarray | None = None) Distortion[source]¶
Draw distortions that follow the diffusion gradients, as eddy currents do.
The fields that a diffusion gradient leaves behind scale with its amplitude and change sign with its direction. Each volume’s coefficients are therefore one fixed random linear map of its gradient vector, scaled by \(\sqrt{b / b_{max}}\) (the gradient amplitude, at fixed timing). b=0 volumes are not distorted, and antipodal gradients get opposite distortions.
- Parameters:
dataset (
BaseDataset) – The aligned dataset.magnitude (
float) – Largest displacement (mm) of any voxel of the mask, over all volumes.axis (
int) – Voxel axis of phase encoding.degree (
int, optional) – Degree of the polynomial (1: translation, scaling and shears).seed (
int, optional) – Seed for the linear map.gradients (
ArrayLike, optional) – Gradient table,(n_volumes, 4)with the b-value last; the dataset’s ifNone.mask (
ndarray, optional) – Voxels over which the polynomial is normalized andmagnitudereached.
- Returns:
The distortions.
- Return type:
- nifreeze.testing.perturbations.mask_centroid(dataset: Any, mask: ndarray | None = None) ndarray[source]¶
World coordinates of the centroid of a mask (the brain mask by default).
- Parameters:
dataset (
BaseDataset) – The dataset.mask (
ndarray, optional) – Boolean mask; the dataset’s brain mask, or the whole volume, ifNone.
- Returns:
The centroid, of shape
(3,).- Return type:
- nifreeze.testing.perturbations.one_at_a_time(volumes: Iterable[int], magnitude: float, seed: int = 0, center: Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | complex | bytes | str | _NestedSequence[complex | bytes | str] = (0.0, 0.0, 0.0)) list[Perturbation][source]¶
Move each volume alone, to rank the volumes by their influence.
- Parameters:
volumes (
Iterable) – The volumes to move, one per perturbation.magnitude (
float) – Motion magnitude (degrees and mm, seerigid_motion()).seed (
int, optional) – Seed for the rotation axes and translation directions.center (
ArrayLike, optional) – World coordinates of a point on every rotation axis.
- Returns:
One perturbation per volume.
- Return type:
listofPerturbation
- nifreeze.testing.perturbations.perturb(dataset: Any, perturbation: Perturbation, order: int = 3) Any[source]¶
Copy a dataset with some of its volumes moved or distorted.
Each moved or distorted volume is resampled once from the aligned original, through its motion and then its distortion. A distortion with an EPI readout (
readout) instead forms the moved volume as that readout would. Everything else (gradients, b=0 reference, mask) is left as it was, as a model would see it when the motion and the distortion go uncorrected.- Parameters:
dataset (
BaseDataset) – The aligned dataset.perturbation (
Perturbation) – The volumes to move, their motions, and the distortion.order (
int, optional) – Interpolation order.
- Returns:
A copy of the dataset with
float32volumes.- Return type:
- nifreeze.testing.perturbations.perturbation_plan(n_volumes: int, magnitudes: Sequence[float], counts: Sequence[int], seeds: int | Sequence[int] = 5, center: Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | complex | bytes | str | _NestedSequence[complex | bytes | str] = (0.0, 0.0, 0.0), fixed: Iterable[int] = ()) list[Perturbation][source]¶
Draw the perturbations of a sensitivity experiment.
For each seed, the moved volumes are the first
countof a random ordering of the volumes not listed infixed, each with a random rotation axis and translation direction. Volumes moved with a smaller count are thus also moved with a larger one, and they follow the same axis and direction at every magnitude, so the curves over magnitude and count are paired.- Parameters:
n_volumes (
int) – Number of volumes in the dataset.magnitudes (
Sequence) – Motion magnitudes (degrees and mm, seerigid_motion()).counts (
Sequence) – Numbers of moved volumes.seeds (
intorSequence, optional) – Number of seeds (0, 1, ...), or the seeds themselves.center (
ArrayLike, optional) – World coordinates of a point on every rotation axis (seemask_centroid()).fixed (
Iterable, optional) – Volumes that never move.
- Returns:
One perturbation per seed, count and magnitude.
- Return type:
listofPerturbation
Examples
>>> plan = perturbation_plan(10, magnitudes=[0.5, 1.0], counts=[1, 3], seeds=2, fixed=[0]) >>> len(plan) 8 >>> any(0 in p.moved for p in plan) False >>> set(plan[0].moved) <= set(plan[2].moved) True
- nifreeze.testing.perturbations.rigid_motion(magnitude: float, axis: Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | complex | bytes | str | _NestedSequence[complex | bytes | str], direction: Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | complex | bytes | str | _NestedSequence[complex | bytes | str], center: Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | complex | bytes | str | _NestedSequence[complex | bytes | str] = (0.0, 0.0, 0.0)) ndarray[source]¶
Rotate by
magnitudedegrees aboutaxisand shift bymagnitudemm.- Parameters:
- Returns:
The
(4, 4)motion, mapping head positions before the motion to positions after it.- Return type:
Examples
>>> motion = rigid_motion(90.0, (0, 0, 1), (1, 0, 0), center=(10, 0, 0)) >>> np.round(motion @ (10, 0, 5, 1), 6).tolist() [100.0, 0.0, 5.0, 1.0] >>> np.round(motion @ (11, 0, 0, 1), 6).tolist() [100.0, 1.0, 0.0, 1.0]