Design Principles¶
Overview¶
NiFreeze’s architecture is modular, facilitating extensibility and adaptability. NiFreeze is designed around the following core concepts:
Data ingestion: Handles input data, providing generalized 4D data management across modalities.
Preprocessing pipelines: Implements modality-specific preprocessing steps prior to artifact estimation. Examples of preprocessing operations are value clipping and smoothing.
Modelling: Implements statistical models to estimate and correct artifacts. Models follow a
fit & predictdesign, where thefitstep fits the model to the data, and thepredictstep estimates the volume that has been held out.Estimation: Orchestrates the registration target prediction. Estimates the motion and distortion artifacts of the data employing a particular model and iterating over the data using a leave-one-volume-out approach. It computes the registration transforms on the estimated, artifact-free volumes. Estimator instances can be stacked so that the output of an estimator instance becomes the input to the subsequent instance.
Main Concepts¶
Data Ingestion¶
NiFreeze implements a set of modality-specific 4D data readers that expose a uniform API, enabling transparent access to individual volumes by index.
dMRI¶
dRMI modality gradients are handled internally arrays arranged according to
the [gx gy gz b] layout, where [gx gy gz] are the components of a
given gradient vector, and b is the b-value in units of \(s/mm^2\).
# ToDo
# Or use [ R A S+ b ] instead of [gx gy gz b] as in usage.rst
The dMRI data class holds a single b0 volume. The rationale behind this is
that this volume serves as a canonical reference for transforming non-zero
gradient volumes. Users can compute such reference b0 volume using their
preferred method and provide it when instantiating a api/nifreeze.data.dmri.DWI
class (e.g. through the from_nii()); otherwise,
NiFreeze computes a reference b0 as the median volume across
non-diffusion-weighted volumes. Note that following this design choice,
the gradient data hosted by the api/nifreeze.data.dmri.DWI instance
only contains (nonzero) diffusion-weighted gradient values.
Preprocessing¶
NiFreeze provides a set of minimal preprocessing utilities under the form
of api/nifreeze.estimator.Filters. api/nifreeze.estimator.Filters
can be specified as inputs to estimators so that the data used by the
estimator is processed through the given filter instance.
Modeling¶
These modules encapsulate the algorithms used for artifact estimation and correction. By abstracting the modeling logic, NiFreeze enables the integration of diverse methodologies tailored to specific research requirements.
Models do not preprocess the data, and users are expected to have their data preprocessed minimally (e.g., computing a unique, meaningful b0 volume for DWI data, smoothing and thresholding the PET data, clipping or detrending the data).
Iterators¶
Iterators in NiFreeze manage the traversal of data volumes, allowing access operations to particular data volumes. They are designed to be extensible, allowing developers to implement custom traversal logic as needed.
Estimation¶
NiFreeze estimators rely on predictive models to reconstruct artifact-free volumes from artifact-affected 4D datasets. Each model maintains access to the full dataset, while the estimator traverses the data according to a specified iteration strategy (e.g., forward, reverse, or random ordering). At each step, the estimator selects a target (held-out) volume as defined by the iterator, fits the model on the remaining volumes, and predicts the held-out volume. A registration process estimates the affine transformation between the held-out and predicted volumes, storing the result at the corresponding index in the dataset’s motion parameter attribute.
Important
The Update & iterate procedure follows a strict design principle: resampling is intentionally excluded from the update step; including it would alter the data distribution, making it impossible to attribute observed improvements solely to the predictive model.
Corrected datasets and associated metadata are produced in standardized formats for downstream analysis.
Extending NiFreeze¶
To extend NiFreeze’s functionality,
Creating New Iterators¶
To extend NiFreeze’s functionality, such as implementing a custom iterator, follow these general steps:
Create a new function: Define a new iterator function implementing the desired logic.
from typing import Iterator def my_iterator(**kwargs) -> Iterator[int]: # Size is the number of volumes in the dataset size = get_size(**kwargs) # Implement custom iteration logic index_order = establish_order(size) return (x for x in index_order)
Integrate the function: Incorporate the newly created iterator into the NiFreeze workflow by updating the configuration or pipeline definitions to utilize your new implementation.
list(my_iterator(10))
Test the Implementation: Ensure that your custom class functions as intended by running unit tests and validating the outputs against expected results.
Creating New Models¶
To implement a new model:
1. Subclass the api/nifreeze.model.base.BaseModel class.
1. Implement the fit(data) method, which returns a 3D volume.
1. Register the model in your pipeline or CLI wrapper.
Example: Extrapolation model¶
from nifreeze.data.base import BaseDataset
from nifreeze.model.base import BaseModel
class ExtrapolationModel(BaseModel):
"""Model a volume extrapolating across training volumes."""
__slots__ = {
"_param1": "Parameter 1",
"_param2": "Parameter 2",
}
def __init__(self, dataset: BaseDataset, param1: float, param2: float, **kwargs):
super().__init__(dataset, **kwargs)
self._param1 = param1
self._param2 = param2
def _fit(self, index, n_jobs=None, **kwargs):
# Fit the data
pass
def fit_predict(self, index: int, **kwargs):
# Compute prediction
return pred
so fit_predict is suitable for case where the model is fit only once per iteration, and predict is for cases where
the model is fit across multiple volumes in each iteration.
Usage¶
model = ExtrapolationModel()
result = model.fit(data, index=3)
Fitting and Prediction¶
Models have a _locked_fit property that allows to fit all available data if no index is provided. This is achieved
by calling:
.. code-block:: python
model.fit_predict(None)
In this case, the predicted data will always be the same regardless of the index. The single_ prefix in the fitting
strategy provided to the api/nifreeze.estimator.Estimator instances tells the estimator to proceed this way.
Usage¶
The NiFreeze command-line interface supports specifying multiple models, which are applied in a cascade fashion. Users can indicate which model should be used to address a specific problem, such as head motion or eddy current correction.
The transformations computed at each level are stored in an HDF5 file and are used to initialize the immediately next level of the registration process.
Refer to the Usage document for a self-contained usage example.
Contributing to NiFreeze¶
Set up for Development¶
Install the appropriate modules:
.. code-block:: bash
pip install .[test, doc, style, contributing]
Download data from OSF, OpenNeuro, etc.:
.. code-block:: bash
download
Typechecking, spellchecking, style, doc
Developer Resources¶
Refer to the Developer’s Corner document for detailed development guidelines.
For guidance on contributing to NiFreeze, refer to the NiPreps Contributing Guidelines:
https://www.nipreps.org/community/CONTRIBUTING/
This resource provides detailed instructions on community principles and contributor acknowledgements.