Coverage for nilearn/decomposition/tests/test_canica.py: 0%
43 statements
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-20 10:58 +0200
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-20 10:58 +0200
1"""Test CanICA."""
3import sys
5import numpy as np
6import pytest
7from numpy.testing import assert_array_almost_equal
9from nilearn.decomposition.canica import CanICA
10from nilearn.decomposition.tests.conftest import (
11 RANDOM_STATE,
12 check_decomposition_estimator,
13)
14from nilearn.image import get_data, iter_img
15from nilearn.surface.surface import get_data as get_surface_data
18@pytest.mark.parametrize("data_type", ["nifti", "surface"])
19def test_threshold_bound_error(canica_data_single_img):
20 """Test that an error is raised when the threshold is higher \
21 than the number of components.
22 """
23 with pytest.raises(ValueError, match="Threshold must not be higher"):
24 canica = CanICA(n_components=4, threshold=5.0, smoothing_fwhm=None)
25 canica.fit(canica_data_single_img)
28@pytest.mark.parametrize("data_type", ["nifti", "surface"])
29def test_percentile_range(rng, canica_data_single_img):
30 """Test that a warning is given when thresholds are stressed."""
31 edge_case = rng.integers(low=1, high=10)
33 # stress thresholding via edge case
34 canica = CanICA(
35 n_components=edge_case,
36 threshold=float(edge_case),
37 smoothing_fwhm=None,
38 )
40 with pytest.warns(UserWarning, match="obtained a critical threshold"):
41 canica.fit(canica_data_single_img)
44# TODO remove skipif when dropping python 3.9
45@pytest.mark.skipif(
46 sys.version_info[1] == 9,
47 reason="fails only on MacOS with python 3.9",
48)
49@pytest.mark.parametrize("data_type", ["nifti"])
50def test_canica_square_img(
51 decomposition_mask_img, canica_components, canica_data
52):
53 """Check content of components."""
54 # We do a large number of inits to be sure to find the good match
56 # Note that
57 # adding smoothing will make this test break
58 smoothing_fwhm = None
60 canica = CanICA(
61 n_components=4,
62 random_state=RANDOM_STATE,
63 mask=decomposition_mask_img,
64 smoothing_fwhm=smoothing_fwhm,
65 n_init=50,
66 )
67 canica.fit(canica_data)
68 maps = get_data(canica.components_img_)
69 maps = np.rollaxis(maps, 3, 0)
71 # FIXME: This could be done more efficiently, e.g. thanks to hungarian
72 # Find pairs of matching components
73 # compute the cross-correlation matrix between components
74 mask = get_data(decomposition_mask_img) != 0
75 K = np.corrcoef(canica_components[:, mask.ravel()], maps[:, mask])[4:, :4]
77 # K should be a permutation matrix, hence its coefficients
78 # should all be close to 0 1 or -1
79 K_abs = np.abs(K)
81 assert np.sum(K_abs > 0.9) == 4
83 K_abs[K_abs > 0.9] -= 1
85 assert_array_almost_equal(K_abs, 0, 1)
88@pytest.mark.timeout(0)
89@pytest.mark.parametrize("data_type", ["nifti", "surface"])
90def test_component_sign(canica_data, data_type):
91 """Check sign of extracted components.
93 Regression test:
94 We should have a heuristic that flips the sign of components in
95 DictLearning to have more positive values than negative values, for
96 instance by making sure that the largest value is positive.
97 """
98 # run CanICA many times (this is known to produce different results)
99 canica = CanICA(
100 n_components=4,
101 random_state=RANDOM_STATE,
102 smoothing_fwhm=None,
103 )
105 for _ in range(3):
106 canica.fit(canica_data)
108 check_decomposition_estimator(canica, data_type)
110 for mp in iter_img(canica.components_img_):
111 mp = get_data(mp) if data_type == "nifti" else get_surface_data(mp)
113 assert -mp.min() <= mp.max()