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 & predict design, where the fit step fits the model to the data, and the predict step 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:

  1. 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)
    
  1. 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))
    
  1. 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

In the default Leave-One-Volume-Out (LOVO) mode described above, each call to a model’s fit_predict(index) fits on every volume except index and predicts the held-out one. This held-out independence is what makes the registration target valid: the prediction volume k is registered against must not itself depend on volume k.

Models also support a single-fit mode, activated by calling

model.fit_predict(None)

which fits the model once on all available data (that is, no volume is held out) and locks that fit (stored in _locked_fit). Subsequent calls reuse the locked model fit instead of refitting. Do not confuse the single-fit mode with returning the same volume regardless of the index.

Single-fit forfeits independence: because the queried volume was part of the data the locked fit was trained on, its prediction is no longer the unbiased, out-of-sample estimate LOVO provides. Therefore, single-fit can be used where this bias is either absent or tolerable. For example, contexts where the bias is anticipated to be negligible, such as a coarse, low-DOF early rigid-body realignment that a subsequent LOVO stage refines, or trading accuracy for speed during development or debugging.

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:

pip install .[test, doc, style, contributing]

Download data from OSF, OpenNeuro, etc.:

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.