Skip to content

confusius.decoding

decoding

Pattern decoding for fUSI DataArrays.

This package provides decoders in the spirit of nilearn.decoding:

  • SearchLight: per-voxel local decoding, also known as "spotlight" decoding.

Portions of this package are inspired by nilearn.decoding, which is licensed under the BSD-3-Clause License. See NOTICE for details.

Modules:

  • searchlight

    Searchlight decoding for (time, ...) fUSI DataArrays.

Classes:

SearchLight

Bases: BaseEstimator

Searchlight decoder for fUSI data.

For every voxel of process_mask, fit gathers the mask voxels lying within radius, cross-validates estimator on that neighborhood, and stores the mean score.

This estimator wraps scikit-learn while keeping xarray metadata:

  • Input data are expected as (time, ...) where ... are spatial dimensions.
  • The time dimension is the sample axis. It need not be temporally ordered. For trial-averaged data, rename the trial dimension with .rename(trial="time").
  • scores_ is returned in the spatial geometry of process_mask.

Parameters:

  • estimator

    (BaseEstimator) –

    Estimator or Pipeline.

  • mask

    (DataArray, default: None ) –

    Boolean spatial mask selecting the voxels that may act as features. If not provided, every voxel of the input data is used as a feature voxel. Every spatial dimension must carry a numeric coordinate, because radius is measured in coordinate units.

  • radius

    (float, default: 1.0 ) –

    Neighborhood radius, in the units of the data's spatial coordinates. Check X[dim].attrs.get("units") if unsure. Radii are measured in physical coordinates rather than voxel indices, so anisotropic voxels behave correctly.

  • process_mask

    (DataArray, default: None ) –

    Boolean mask selecting the voxels that act as neighborhood centers. Must be a subset of mask. If not provided, a score is computed at every mask voxel. Use it to restrict the searchlight to a region of interest while still drawing features from the surrounding tissue.

  • cv

    (int or BaseCrossValidator, default: 5 ) –

    Cross-validation strategy. An integer builds a KFold for regressors, whose folds are contiguous blocks of time, or a StratifiedKFold for classifiers, which keeps folds class-balanced but interleaves time. Both use shuffle=False. Any scikit-learn splitter is accepted.

  • scoring

    (str or callable, default: None ) –

    Scorer passed to cross_val_score. If not provided, the estimator's own score is used, which is accuracy for classifiers and the coefficient of determination for regressors.

  • n_jobs

    (int, default: 1 ) –

    Number of joblib workers. Centers are dispatched in batches, not one task each.

  • show_progress

    (bool, default: True ) –

    Whether to display a progress bar during fit.

Attributes:

  • scores_ ((...) xarray.DataArray) –

    Mean cross-validation score at each process_mask center, in the spatial geometry of process_mask. Voxels outside process_mask are numpy.nan. The input's attributes are carried over, including any affines, so the map stays in the same physical space. long_name is set to "Searchlight CV score" and units to the metric implied by scoring: the scorer string when one is given, otherwise "accuracy" for a classifier or "R²" for a regressor. A callable scorer leaves units unset.

Warns:

  • UserWarning

    If radius is small enough that the median neighborhood holds a single voxel. The run still produces a valid map, but it has silently become a univariate analysis rather than a multivariate one. Also if process_mask selects no voxels at all, which yields an entirely NaN map.

Notes

Consecutive fUSI volumes are strongly autocorrelated. Passing a splitter with shuffle=True places near-duplicate neighboring volumes in both the training and test sets, which inflates scores. This is why an integer cv builds KFold folds with shuffle=False for regressors. Classifiers instead get StratifiedKFold, which balances classes across folds but interleaves time rather than keeping contiguous blocks, so on its own it does not prevent this leakage. For data with a run or block structure, or to keep classification folds contiguous, prefer LeaveOneGroupOut with groups.

References

  1. Kriegeskorte, N., Goebel, R., and Bandettini, P. (2006). "Information-based functional brain mapping". PNAS, 103(10), 3863-3868. 

Examples:

>>> import numpy as np
>>> import xarray as xr
>>> from sklearn.linear_model import Ridge
>>> from confusius.decoding import SearchLight
>>>
>>> rng = np.random.default_rng(0)
>>> data = xr.DataArray(
...     rng.standard_normal((40, 1, 5, 5)),
...     dims=["time", "z", "y", "x"],
...     coords={
...         "time": np.arange(40) * 0.5,
...         "z": [0.0],
...         "y": np.arange(5) * 0.2,
...         "x": np.arange(5) * 0.2,
...     },
... )
>>> speed = rng.standard_normal(40)
>>>
>>> searchlight = SearchLight(
...     estimator=Ridge(), radius=0.25, cv=3, show_progress=False
... )
>>> searchlight.fit(data, speed).scores_.dims
('z', 'y', 'x')

Methods:

  • fit

    Run searchlight decoding.

  • get_metadata_routing

    Get metadata routing of this object.

  • get_params

    Get parameters for this estimator.

  • score

    Refuse to produce a single score.

  • set_params

    Set the parameters of this estimator.

fit

fit(
    X: DataArray,
    y: ArrayLike | DataArray,
    groups: ArrayLike | None = None,
) -> SearchLight

Run searchlight decoding.

Parameters:

  • X
    ((time, ...) xarray.DataArray) –

    Functional ultrasound data. The time dimension is the sample axis.

  • y
    ((n_samples,) array-like or xarray.DataArray) –

    Targets aligned with X's time axis.

  • groups
    ((n_samples,) array-like, default: None ) –

    Group labels forwarded to the cross-validator. Required by LeaveOneGroupOut.

Returns:

Raises:

  • ValueError

    If X is h5py-backed, if radius is negative, if process_mask is not a subset of mask, if y or groups do not align with X, if a spatial dimension lacks a numeric coordinate, or if the masked data contains non-finite values.

Warns:

  • UserWarning

    If the median neighborhood holds a single voxel, either because radius is below the voxel spacing or because mask is very sparse. The run still produces a valid map, but it has silently become a univariate analysis rather than a multivariate one. Also if process_mask selects no voxels at all, which yields an entirely NaN map.

get_metadata_routing

get_metadata_routing()

Get metadata routing of this object.

Please check :ref:User Guide <metadata_routing> on how the routing mechanism works.

Returns:

  • routing ( MetadataRequest ) –

    A :class:~sklearn.utils.metadata_routing.MetadataRequest encapsulating routing information.

get_params

get_params(deep=True)

Get parameters for this estimator.

Parameters:

  • deep
    (bool, default: True ) –

    If True, will return the parameters for this estimator and contained subobjects that are estimators.

Returns:

  • params ( dict ) –

    Parameter names mapped to their values.

score

score(X: DataArray, y: ArrayLike) -> float

Refuse to produce a single score.

Parameters:

  • X
    (DataArray) –

    Ignored. SearchLight does not refit a single model.

  • y
    (array - like) –

    Ignored. SearchLight does not refit a single model.

Returns:

  • float

    Never returns.

Raises:

  • NotImplementedError

    Always. A searchlight produces one score per voxel, not one per model, so there is no single number to report. Read scores_ instead.

set_params

set_params(**params)

Set the parameters of this estimator.

The method works on simple estimators as well as on nested objects (such as :class:~sklearn.pipeline.Pipeline). The latter have parameters of the form <component>__<parameter> so that it's possible to update each component of a nested object.

Parameters:

  • **params
    (dict, default: {} ) –

    Estimator parameters.

Returns:

  • self ( estimator instance ) –

    Estimator instance.