Coverage for nilearn/decomposition/tests/test_base.py: 0%

67 statements  

« prev     ^ index     » next       coverage.py v7.9.1, created at 2025-06-20 10:58 +0200

1import numpy as np 

2import pytest 

3from numpy.testing import assert_array_almost_equal 

4from scipy import linalg 

5 

6from nilearn.conftest import _rng 

7from nilearn.decomposition._base import _fast_svd, _mask_and_reduce 

8from nilearn.decomposition.tests.conftest import ( 

9 N_SAMPLES, 

10 N_SUBJECTS, 

11 RANDOM_STATE, 

12) 

13 

14 

15# We need to use n_features > 500 to trigger the randomized_svd 

16@pytest.mark.parametrize("n_features", [30, 100, 550]) 

17def test_fast_svd(n_features): 

18 """Test fast singular value decomposition.""" 

19 n_samples = 100 

20 k = 10 

21 

22 # generate a matrix X of approximate effective rank `rank` and no noise 

23 # component (very structured signal): 

24 U = _rng().normal(size=(n_samples, k)) 

25 V = _rng().normal(size=(k, n_features)) 

26 X = np.dot(U, V) 

27 

28 assert X.shape == (n_samples, n_features) 

29 

30 # compute the singular values of X using the slow exact method 

31 _, _, V_ = linalg.svd(X, full_matrices=False) 

32 

33 Ur, _, Vr = _fast_svd(X, k, random_state=0) 

34 

35 assert Vr.shape == (k, n_features) 

36 assert Ur.shape == (n_samples, k) 

37 # check the singular vectors too (while not checking the sign) 

38 assert_array_almost_equal( 

39 np.abs(np.diag(np.corrcoef(V_[:k], Vr)))[:k], np.ones(k) 

40 ) 

41 

42 

43@pytest.mark.timeout(0) 

44@pytest.mark.parametrize("data_type", ["nifti", "surface"]) 

45@pytest.mark.parametrize( 

46 "n_components,reduction_ratio,expected_shape_0", 

47 [ 

48 (None, "auto", N_SUBJECTS * N_SAMPLES), 

49 (3, "auto", N_SUBJECTS * 3), 

50 (None, 0.4, N_SUBJECTS * 2), 

51 ], 

52) 

53def test_mask_reducer_multiple_image( 

54 data_type, 

55 n_components, 

56 reduction_ratio, 

57 expected_shape_0, 

58 decomposition_masker, 

59 decomposition_images, 

60): 

61 """Mask and reduce images with several values of input arguments.""" 

62 data = _mask_and_reduce( 

63 masker=decomposition_masker, 

64 imgs=decomposition_images, 

65 n_components=n_components, 

66 reduction_ratio=reduction_ratio, 

67 ) 

68 if data_type == "nifti": 

69 expected_shape = ( 

70 expected_shape_0, 

71 np.prod(decomposition_masker.mask_img_.shape), 

72 ) 

73 elif data_type == "surface": 

74 expected_shape = ( 

75 expected_shape_0, 

76 decomposition_images[0].mesh.n_vertices, 

77 ) 

78 

79 assert data.shape == expected_shape 

80 

81 

82@pytest.mark.parametrize("data_type", ["nifti", "surface"]) 

83def test_mask_reducer_single_image_same_with_multiple_jobs( 

84 data_type, decomposition_masker, decomposition_img 

85): 

86 """Mask and reduce a 3D image and check results is the same \ 

87 when split over several CPUs. 

88 """ 

89 n_components = 3 

90 data_single = _mask_and_reduce( 

91 masker=decomposition_masker, 

92 imgs=decomposition_img, 

93 n_components=n_components, 

94 ) 

95 if data_type == "nifti": 

96 assert data_single.shape == ( 

97 n_components, 

98 np.prod(decomposition_masker.mask_img_.shape), 

99 ) 

100 elif data_type == "surface": 

101 # For surface images, the shape is (n_components, n_vertices) 

102 assert data_single.shape == ( 

103 n_components, 

104 decomposition_masker.mask_img_.shape[0], 

105 ) 

106 

107 # Test n_jobs > 1 

108 data = _mask_and_reduce( 

109 masker=decomposition_masker, 

110 imgs=decomposition_img, 

111 n_components=n_components, 

112 n_jobs=2, 

113 random_state=RANDOM_STATE, 

114 ) 

115 if data_type == "nifti": 

116 assert data.shape == ( 

117 n_components, 

118 np.prod(decomposition_masker.mask_img_.shape), 

119 ) 

120 elif data_type == "surface": 

121 assert data.shape == ( 

122 n_components, 

123 decomposition_masker.mask_img_.shape[0], 

124 ) 

125 assert_array_almost_equal(data_single, data) 

126 

127 

128@pytest.mark.parametrize("data_type", ["nifti", "surface"]) 

129def test_mask_reducer_reduced_data_is_orthogonal( 

130 data_type, decomposition_masker, decomposition_img 

131): 

132 """Test that the reduced data is orthogonal.""" 

133 n_components = 3 

134 data = _mask_and_reduce( 

135 masker=decomposition_masker, 

136 imgs=decomposition_img, 

137 n_components=n_components, 

138 random_state=RANDOM_STATE, 

139 ) 

140 

141 if data_type == "nifti": 

142 assert data.shape == ( 

143 n_components, 

144 np.prod(decomposition_masker.mask_img_.shape), 

145 ) 

146 elif data_type == "surface": 

147 assert data.shape == ( 

148 n_components, 

149 decomposition_masker.mask_img_.shape[-1], 

150 ) 

151 

152 cov = data.dot(data.T) 

153 cov_diag = np.zeros((3, 3)) 

154 for i in range(3): 

155 cov_diag[i, i] = cov[i, i] 

156 

157 assert_array_almost_equal(cov, cov_diag) 

158 

159 

160@pytest.mark.parametrize("data_type", ["nifti", "surface"]) 

161def test_mask_reducer_reduced_reproducible( 

162 data_type, 

163 decomposition_masker, 

164 decomposition_img, 

165): 

166 """Check that same input image give same results.""" 

167 n_components = 3 

168 data1 = _mask_and_reduce( 

169 masker=decomposition_masker, 

170 imgs=decomposition_img, 

171 n_components=n_components, 

172 random_state=RANDOM_STATE, 

173 ) 

174 data2 = _mask_and_reduce( 

175 masker=decomposition_masker, 

176 imgs=[decomposition_img] * 2, 

177 n_components=n_components, 

178 random_state=RANDOM_STATE, 

179 ) 

180 

181 if data_type == "nifti": 

182 assert data1.shape == ( 

183 n_components, 

184 np.prod(decomposition_masker.mask_img_.shape), 

185 ) 

186 elif data_type == "surface": 

187 assert data1.shape == ( 

188 n_components, 

189 decomposition_masker.mask_img_.shape[-1], 

190 ) 

191 

192 assert_array_almost_equal(np.tile(data1, (2, 1)), data2)