Skip to content

nifti

tit.stats.nifti

TI-Toolbox NIfTI loading helpers for statistical analysis.

Convenience wrappers around nibabel that resolve paths through :func:tit.paths.get_path_manager and return arrays ready for voxelwise group comparison or correlation pipelines.

Public API

load_subject_nifti_ti_toolbox Load a single subject's NIfTI from the BIDS simulation tree. load_group_data_ti_toolbox Stack multiple subjects into a 4-D array. load_grouped_subjects_ti_toolbox Load multiple subjects organized by named groups. covered_mean Group mean over the voxels every subject covers.

See Also

tit.stats.permutation.run_group_comparison : Voxelwise group comparison pipeline. tit.stats.permutation.run_correlation : Voxelwise correlation pipeline.

covered_mean

covered_mean(data_4d)

Mean over the last axis, kept only where every subject has a value.

A 0 is outside that subject's coverage (warped grey matter), not a field value, so a voxel any subject lacks is 0 in the result rather than a mean shrunk by that zero. Returns (mean, covered): mean is float32, covered the boolean every-subject-non-zero mask.

Source code in tit/stats/nifti.py
def covered_mean(data_4d):
    """Mean over the last axis, kept only where every subject has a value.

    A 0 is outside that subject's coverage (warped grey matter), not a field value, so a
    voxel any subject lacks is 0 in the result rather than a mean shrunk by that zero. Returns
    ``(mean, covered)``: *mean* is float32, *covered* the boolean every-subject-non-zero mask.
    """
    covered = np.all(data_4d != 0, axis=-1)
    mean = np.where(covered, np.mean(data_4d, axis=-1, dtype=np.float64), 0)
    return mean.astype(np.float32), covered

load_subject_nifti_ti_toolbox

load_subject_nifti_ti_toolbox(subject_id: str, simulation_name: str, nifti_file_pattern: str = 'grey_{simulation_name}_TI_MNI_MNI_TI_max.nii.gz', dtype=float32) -> tuple[ndarray, Nifti1Image, str]

Load a single subject's NIfTI file from TI-Toolbox BIDS structure.

Parameters

subject_id : str Subject identifier (e.g. '070'). simulation_name : str Simulation folder name (e.g. 'ICP_RHIPPO'). nifti_file_pattern : str, optional Filename pattern with {subject_id} / {simulation_name} placeholders, in the 2-pair spelling; an mTI simulation's file is read from mTI/niftis/ (see :func:tit.analyzer.field_selector.simulation_nifti_path). dtype : numpy dtype, optional Data type for the returned array. Default is np.float32.

Returns

data : numpy.ndarray 3-D array of voxel values. img : nibabel.Nifti1Image The loaded NIfTI image (useful for affine / header). filepath : str Absolute path of the loaded file.

Raises

FileNotFoundError If the resolved NIfTI path does not exist and is not one the simulation can produce now; a simulator NIfTI it did not save is written first, from the field mesh (:func:tit.volume_fields.ensure_nifti_file).

Source code in tit/stats/nifti.py
def load_subject_nifti_ti_toolbox(
    subject_id: str,
    simulation_name: str,
    nifti_file_pattern: str = "grey_{simulation_name}_TI_MNI_MNI_TI_max.nii.gz",
    dtype=np.float32,
) -> tuple[np.ndarray, nib.Nifti1Image, str]:
    """Load a single subject's NIfTI file from TI-Toolbox BIDS structure.

    Parameters
    ----------
    subject_id : str
        Subject identifier (e.g. ``'070'``).
    simulation_name : str
        Simulation folder name (e.g. ``'ICP_RHIPPO'``).
    nifti_file_pattern : str, optional
        Filename pattern with ``{subject_id}`` / ``{simulation_name}``
        placeholders, in the 2-pair spelling; an mTI simulation's file is read
        from ``mTI/niftis/`` (see
        :func:`tit.analyzer.field_selector.simulation_nifti_path`).
    dtype : numpy dtype, optional
        Data type for the returned array.  Default is ``np.float32``.

    Returns
    -------
    data : numpy.ndarray
        3-D array of voxel values.
    img : nibabel.Nifti1Image
        The loaded NIfTI image (useful for affine / header).
    filepath : str
        Absolute path of the loaded file.

    Raises
    ------
    FileNotFoundError
        If the resolved NIfTI path does not exist and is not one the simulation
        can produce now; a simulator NIfTI it did not save is written first,
        from the field mesh (:func:`tit.volume_fields.ensure_nifti_file`).
    """
    from tit.volume_fields import ensure_nifti_file

    # TI/niftis/ for a 2-pair run, mTI/niftis/ (mTI file names) for an mTI run.
    path = simulation_nifti_path(subject_id, simulation_name, nifti_file_pattern)
    nifti_dir, filepath = str(path.parent), str(path)
    ensure_nifti_file(subject_id, simulation_name, path, say=logger.info)

    # Load the file (inline basic loading)
    if not os.path.exists(filepath):
        # Provide extra context to make debugging path/layout issues easier
        if os.path.isdir(nifti_dir):
            try:
                existing = sorted(os.listdir(nifti_dir))
            except OSError:
                existing = []
            preview = existing[:20]
            suffix = ""
            if len(existing) > len(preview):
                suffix = f" (showing first {len(preview)} of {len(existing)})"
            raise FileNotFoundError(
                f"NIfTI file not found: {filepath}. "
                f"Directory exists: {nifti_dir}. "
                f"Files in directory: {preview}{suffix}"
            )
        raise FileNotFoundError(f"NIfTI file not found: {filepath}")

    img = nib.load(filepath)
    data = img.get_fdata(dtype=dtype)

    # Ensure 3D data (squeeze out extra dimensions if present)
    while data.ndim > 3:
        data = np.squeeze(data, axis=-1)

    return data, img, filepath

load_group_data_ti_toolbox

load_group_data_ti_toolbox(subject_configs: list[dict], nifti_file_pattern: str = 'grey_{simulation_name}_TI_MNI_MNI_TI_max.nii.gz', dtype=float32, reference: tuple | None = None) -> tuple[ndarray, Nifti1Image, list[str]]

Load and stack multiple subjects into a 4-D array.

Parameters

subject_configs : list of dict Each dict must contain 'subject_id' and 'simulation_name' keys (e.g. {'subject_id': '070', 'simulation_name': 'ICP_RHIPPO'}). nifti_file_pattern : str, optional Filename pattern forwarded to :func:load_subject_nifti_ti_toolbox. dtype : numpy dtype, optional Data type for the returned arrays. Default is np.float32. reference : tuple or None, optional (shape, affine, where) of a grid every subject must be on -- another group's, when two groups are compared voxel by voxel; where names it in the error. None (default) makes the first subject the reference.

Returns

data_4d : numpy.ndarray Shape (X, Y, Z, n_subjects). template_img : nibabel.Nifti1Image Image from the first subject (affine / header reference). subject_ids : list of str Subject identifiers in the same order as the last axis of data_4d.

Raises

ValueError If no subjects could be loaded.

Source code in tit/stats/nifti.py
def load_group_data_ti_toolbox(
    subject_configs: list[dict],
    nifti_file_pattern: str = "grey_{simulation_name}_TI_MNI_MNI_TI_max.nii.gz",
    dtype=np.float32,
    reference: tuple | None = None,
) -> tuple[np.ndarray, nib.Nifti1Image, list[str]]:
    """Load and stack multiple subjects into a 4-D array.

    Parameters
    ----------
    subject_configs : list of dict
        Each dict must contain ``'subject_id'`` and ``'simulation_name'``
        keys (e.g. ``{'subject_id': '070', 'simulation_name': 'ICP_RHIPPO'}``).
    nifti_file_pattern : str, optional
        Filename pattern forwarded to :func:`load_subject_nifti_ti_toolbox`.
    dtype : numpy dtype, optional
        Data type for the returned arrays.  Default is ``np.float32``.
    reference : tuple or None, optional
        ``(shape, affine, where)`` of a grid every subject must be on -- another
        group's, when two groups are compared voxel by voxel; *where* names it in
        the error. ``None`` (default) makes the first subject the reference.

    Returns
    -------
    data_4d : numpy.ndarray
        Shape ``(X, Y, Z, n_subjects)``.
    template_img : nibabel.Nifti1Image
        Image from the first subject (affine / header reference).
    subject_ids : list of str
        Subject identifiers in the same order as the last axis of
        *data_4d*.

    Raises
    ------
    ValueError
        If no subjects could be loaded.
    """
    if len(subject_configs) == 0:
        raise ValueError("No subjects could be loaded successfully")

    data_4d = None
    subject_ids: list[str] = []
    template_affine = None
    template_header = None
    reference_path = None

    for index, config in enumerate(subject_configs):
        subject_id = config["subject_id"]
        simulation_name = config["simulation_name"]

        data, img, filepath = load_subject_nifti_ti_toolbox(
            subject_id, simulation_name, nifti_file_pattern, dtype=dtype
        )

        if reference is not None:
            _check_same_grid(subject_id, filepath, data.shape, img.affine, *reference)

        if data_4d is None:
            template_affine = img.affine.copy()
            template_header = img.header.copy()
            reference_path = filepath
            # Preallocate: np.stack + astype on a list of N volumes peaks at
            # ~3x the final array; writing each subject straight into the
            # output keeps it at 1x + one volume.
            data_4d = np.empty(data.shape + (len(subject_configs),), dtype=dtype)
        else:
            _check_same_grid(
                subject_id,
                filepath,
                data.shape,
                img.affine,
                data_4d.shape[:-1],
                template_affine,
                reference_path,
            )

        data_4d[..., index] = data
        subject_ids.append(subject_id)

        # Clear the image object to free memory
        del img, data

    # Recreate minimal template image
    template_img = nib.Nifti1Image(data_4d[..., 0], template_affine, template_header)

    gc.collect()

    return data_4d, template_img, subject_ids

load_grouped_subjects_ti_toolbox

load_grouped_subjects_ti_toolbox(subject_configs: list[dict], nifti_file_pattern: str = 'grey_{simulation_name}_TI_MNI_MNI_TI_max.nii.gz', dtype=float32) -> tuple[dict[str, ndarray], Nifti1Image, dict[str, list[str]]]

Load subjects organized by named groups.

Each config dict must include a 'group' key in addition to the fields required by :func:load_group_data_ti_toolbox. Configs without a 'group' key are assigned to the 'default' group.

Parameters

subject_configs : list of dict Each dict must contain 'subject_id', 'simulation_name', and 'group' (e.g. 'Responders'). nifti_file_pattern : str, optional Filename pattern forwarded to :func:load_subject_nifti_ti_toolbox. dtype : numpy dtype, optional Data type for the returned arrays. Default is np.float32.

Returns

groups_data : dict of str to numpy.ndarray Mapping from group name to a 4-D array (X, Y, Z, n_subjects). template_img : nibabel.Nifti1Image Image from the first loaded subject. groups_ids : dict of str to list of str Mapping from group name to ordered list of subject identifiers.

Source code in tit/stats/nifti.py
def load_grouped_subjects_ti_toolbox(
    subject_configs: list[dict],
    nifti_file_pattern: str = "grey_{simulation_name}_TI_MNI_MNI_TI_max.nii.gz",
    dtype=np.float32,
) -> tuple[dict[str, np.ndarray], nib.Nifti1Image, dict[str, list[str]]]:
    """Load subjects organized by named groups.

    Each config dict must include a ``'group'`` key in addition to the
    fields required by :func:`load_group_data_ti_toolbox`.  Configs
    without a ``'group'`` key are assigned to the ``'default'`` group.

    Parameters
    ----------
    subject_configs : list of dict
        Each dict must contain ``'subject_id'``, ``'simulation_name'``,
        and ``'group'`` (e.g. ``'Responders'``).
    nifti_file_pattern : str, optional
        Filename pattern forwarded to :func:`load_subject_nifti_ti_toolbox`.
    dtype : numpy dtype, optional
        Data type for the returned arrays.  Default is ``np.float32``.

    Returns
    -------
    groups_data : dict of str to numpy.ndarray
        Mapping from group name to a 4-D array ``(X, Y, Z, n_subjects)``.
    template_img : nibabel.Nifti1Image
        Image from the first loaded subject.
    groups_ids : dict of str to list of str
        Mapping from group name to ordered list of subject identifiers.
    """
    # Organize configs by group
    groups = {}
    for config in subject_configs:
        group_name = config.get("group", "default")
        if group_name not in groups:
            groups[group_name] = []
        groups[group_name].append(config)

    # Load each group. Every group after the first is held to the first group's
    # grid: averages and differences are taken voxel by voxel and saved with the
    # first group's affine, so a group on another grid (even with an equal shape)
    # would give a map of voxels that are not the same points.
    groups_data = {}
    groups_ids = {}
    template_img = None
    reference = None

    for group_name, group_configs in groups.items():
        if reference is None:
            data_4d, img, subject_ids = load_group_data_ti_toolbox(
                group_configs, nifti_file_pattern, dtype=dtype
            )
            template_img = img
            reference = (
                data_4d.shape[:-1],
                img.affine,
                f"group {group_name!r} (subject {subject_ids[0]})",
            )
        else:
            data_4d, img, subject_ids = load_group_data_ti_toolbox(
                group_configs, nifti_file_pattern, dtype=dtype, reference=reference
            )

        groups_data[group_name] = data_4d
        groups_ids[group_name] = subject_ids

    return groups_data, template_img, groups_ids