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

1"""Test CanICA.""" 

2 

3import sys 

4 

5import numpy as np 

6import pytest 

7from numpy.testing import assert_array_almost_equal 

8 

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 

16 

17 

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) 

26 

27 

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) 

32 

33 # stress thresholding via edge case 

34 canica = CanICA( 

35 n_components=edge_case, 

36 threshold=float(edge_case), 

37 smoothing_fwhm=None, 

38 ) 

39 

40 with pytest.warns(UserWarning, match="obtained a critical threshold"): 

41 canica.fit(canica_data_single_img) 

42 

43 

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 

55 

56 # Note that 

57 # adding smoothing will make this test break 

58 smoothing_fwhm = None 

59 

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) 

70 

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] 

76 

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) 

80 

81 assert np.sum(K_abs > 0.9) == 4 

82 

83 K_abs[K_abs > 0.9] -= 1 

84 

85 assert_array_almost_equal(K_abs, 0, 1) 

86 

87 

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. 

92 

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 ) 

104 

105 for _ in range(3): 

106 canica.fit(canica_data) 

107 

108 check_decomposition_estimator(canica, data_type) 

109 

110 for mp in iter_img(canica.components_img_): 

111 mp = get_data(mp) if data_type == "nifti" else get_surface_data(mp) 

112 

113 assert -mp.min() <= mp.max()