Coverage for nilearn/regions/tests/test_parcellations.py: 0%

223 statements  

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

1"""Test the parcellations tools module.""" 

2 

3import warnings 

4 

5import numpy as np 

6import pandas as pd 

7import pytest 

8from nibabel import Nifti1Image 

9 

10from nilearn.conftest import _affine_eye 

11from nilearn.regions.parcellations import ( 

12 Parcellations, 

13 _check_parameters_transform, 

14) 

15from nilearn.surface import SurfaceImage 

16from nilearn.surface.tests.test_surface import flat_mesh 

17 

18METHODS = [ 

19 "kmeans", 

20 "ward", 

21 "complete", 

22 "average", 

23 "rena", 

24 "hierarchical_kmeans", 

25] 

26 

27 

28@pytest.fixture 

29def test_image(): 

30 data = np.zeros((10, 11, 12, 5)) 

31 data[9, 10, 2] = 1 

32 data[4, 9, 3] = 2 

33 return Nifti1Image(data, affine=_affine_eye()) 

34 

35 

36@pytest.fixture 

37def test_image_2(): 

38 data = np.ones((10, 11, 12, 10)) 

39 data[6, 7, 8] = 2 

40 data[9, 10, 11] = 3 

41 return Nifti1Image(data, affine=_affine_eye()) 

42 

43 

44def test_error_parcellation_method_none(test_image): 

45 with pytest.raises( 

46 ValueError, match="Parcellation method is specified as None. " 

47 ): 

48 Parcellations(method=None, verbose=0).fit(test_image) 

49 

50 

51@pytest.mark.parametrize("method", ["kmens", "avg", "completed"]) 

52def test_errors_raised_in_check_parameters_fit(method, test_image): 

53 """Test whether an error is raised or not given a false method type.""" 

54 with pytest.raises( 

55 ValueError, 

56 match=(f"The method you have selected is not implemented '{method}'"), 

57 ): 

58 Parcellations(method=method, verbose=0).fit(test_image) 

59 

60 

61@pytest.mark.parametrize("method", METHODS) 

62@pytest.mark.parametrize("n_parcel", [5, 10, 15]) 

63def test_parcellations_fit_on_single_nifti_image(method, n_parcel, test_image): 

64 """Test return attributes for each method.""" 

65 parcellator = Parcellations(method=method, n_parcels=n_parcel, verbose=0) 

66 parcellator.fit(test_image) 

67 

68 labels_img = parcellator.labels_img_ 

69 # Test that object returns attribute labels_img_ 

70 assert labels_img is not None 

71 # After inverse_transform, shape must match with 

72 # original input data 

73 assert labels_img.shape == test_image.shape[:3] 

74 # Test object returns attribute masker_ 

75 assert parcellator.masker_ is not None 

76 assert parcellator.mask_img_ is not None 

77 if method not in ["kmeans", "rena", "hierarchical_kmeans"]: 

78 # Test that object returns attribute connectivity_ 

79 # only for AgglomerativeClustering methods 

80 assert parcellator.connectivity_ is not None 

81 

82 

83def test_parcellations_warnings(img_4d_zeros_eye): 

84 parcellator = Parcellations(method="kmeans", n_parcels=7, verbose=0) 

85 

86 with pytest.warns(UserWarning): 

87 parcellator.fit(img_4d_zeros_eye) 

88 

89 

90def test_parcellations_no_warnings(img_4d_zeros_eye): 

91 parcellator = Parcellations(method="kmeans", n_parcels=1, verbose=0) 

92 with warnings.catch_warnings(record=True) as record: 

93 parcellator.fit(img_4d_zeros_eye) 

94 assert all(r.category is not UserWarning for r in record) 

95 

96 

97def test_parcellations_no_int64_warnings(img_4d_zeros_eye): 

98 parcellator = Parcellations(method="kmeans", n_parcels=1, verbose=0) 

99 with warnings.catch_warnings(record=True) as record: 

100 parcellator.fit(img_4d_zeros_eye) 

101 for r in record: 

102 if issubclass(r.category, UserWarning): 

103 assert "image contains 64-bit ints" not in str(r.message) 

104 

105 

106@pytest.mark.parametrize("method", METHODS) 

107def test_parcellations_fit_on_multi_nifti_images( 

108 method, test_image, affine_eye 

109): 

110 fmri_imgs = [test_image] * 3 

111 

112 parcellator = Parcellations(method=method, n_parcels=5, verbose=0) 

113 parcellator.fit(fmri_imgs) 

114 

115 assert parcellator.labels_img_ is not None 

116 

117 # Smoke test with explicit mask image 

118 mask_img = np.ones((10, 11, 12)) 

119 mask_img = Nifti1Image(mask_img, affine_eye) 

120 parcellator = Parcellations( 

121 method=method, n_parcels=5, mask=mask_img, verbose=0 

122 ) 

123 parcellator.fit(fmri_imgs) 

124 

125 

126@pytest.mark.parametrize("method", METHODS) 

127@pytest.mark.parametrize("n_parcel", [5]) 

128def test_parcellations_transform_single_nifti_image( 

129 method, n_parcel, test_image_2 

130): 

131 """Test with NiftiLabelsMasker extraction of timeseries data \ 

132 after building a parcellations image. 

133 """ 

134 parcellator = Parcellations(method=method, n_parcels=n_parcel, verbose=0) 

135 parcellator.fit(test_image_2) 

136 # transform to signals 

137 signals = parcellator.transform(test_image_2) 

138 

139 # Test if the signals extracted are of same shape as inputs 

140 # Here, we simply return numpy array for single subject input 

141 assert signals.shape == (test_image_2.shape[3], n_parcel) 

142 # Test for single subject but in a list. 

143 signals = parcellator.transform([test_image_2]) 

144 assert signals.shape == (test_image_2.shape[3], n_parcel) 

145 

146 

147@pytest.mark.parametrize("verbose", [True, False, -1, 0, 1, 2]) 

148def test_parcellations_transform_verbose(test_image_2, verbose): 

149 """Test verbose mostly for coverage purpose.""" 

150 parcellator = Parcellations(method="kmeans", n_parcels=5, verbose=verbose) 

151 parcellator.fit(test_image_2) 

152 parcellator.transform(test_image_2) 

153 

154 

155@pytest.mark.parametrize("method", METHODS) 

156@pytest.mark.parametrize("n_parcel", [5]) 

157def test_parcellations_transform_multi_nifti_images( 

158 method, n_parcel, test_image_2 

159): 

160 fmri_imgs = [test_image_2] * 3 

161 

162 parcellator = Parcellations(method=method, n_parcels=n_parcel, verbose=0) 

163 parcellator.fit(fmri_imgs) 

164 

165 # transform multi images to signals. 

166 # In return, we have length equal to the number of images 

167 signals = parcellator.transform(fmri_imgs) 

168 

169 assert signals[0].shape == (test_image_2.shape[3], n_parcel) 

170 assert signals[1].shape == (test_image_2.shape[3], n_parcel) 

171 assert signals[2].shape == (test_image_2.shape[3], n_parcel) 

172 assert len(signals) == len(fmri_imgs) 

173 

174 

175def test_check_parameters_transform(test_image_2, rng): 

176 # single confound 

177 confounds = rng.standard_normal(size=(10, 3)) 

178 # Tests to check whether imgs, confounds returned are 

179 # list or not. Pre-check in parameters to work for list 

180 # of multi images and multi confounds 

181 imgs, confounds, single_subject = _check_parameters_transform( 

182 test_image_2, confounds 

183 ) 

184 

185 assert isinstance(imgs, (list, tuple)) 

186 assert isinstance(confounds, (list, tuple)) 

187 assert single_subject 

188 

189 # confounds as pandas DataFrame 

190 imgs, confounds, single_subject = _check_parameters_transform( 

191 test_image_2, pd.DataFrame(np.array(confounds)[0]) 

192 ) 

193 

194 assert isinstance(confounds, (list, tuple)) 

195 

196 # multi images 

197 fmri_imgs = [test_image_2] * 3 

198 confounds_list = [confounds] * 3 

199 imgs, confounds, _ = _check_parameters_transform(fmri_imgs, confounds_list) 

200 

201 assert imgs == fmri_imgs 

202 assert confounds_list == confounds 

203 

204 # Test the error when length of images and confounds are not same 

205 msg = ( 

206 "Number of confounds given does not match with the " 

207 "given number of images" 

208 ) 

209 not_match_confounds_list = [confounds] * 2 

210 with pytest.raises(ValueError, match=msg): 

211 _check_parameters_transform(fmri_imgs, not_match_confounds_list) 

212 

213 

214@pytest.mark.timeout(0) 

215@pytest.mark.parametrize("method", METHODS) 

216@pytest.mark.parametrize("n_parcel", [5]) 

217def test_parcellations_transform_with_multi_confounds_multi_images( 

218 method, n_parcel, test_image_2, rng 

219): 

220 fmri_imgs = [test_image_2] * 3 

221 confounds = rng.standard_normal(size=(10, 3)) 

222 confounds_list = [confounds] * 3 

223 

224 parcellator = Parcellations(method=method, n_parcels=n_parcel, verbose=0) 

225 

226 parcellator.fit(fmri_imgs) 

227 signals = parcellator.transform(fmri_imgs, confounds=confounds_list) 

228 

229 assert isinstance(signals, list) 

230 # n_parcels=5, length of data=10 

231 assert signals[0].shape == (10, n_parcel) 

232 

233 

234@pytest.mark.timeout(0) 

235@pytest.mark.parametrize("method", METHODS) 

236@pytest.mark.parametrize("n_parcel", [5]) 

237def test_fit_transform(method, n_parcel, test_image_2): 

238 fmri_imgs = [test_image_2] * 3 

239 

240 parcellator = Parcellations(method=method, n_parcels=n_parcel, verbose=0) 

241 parcellator.fit_transform(fmri_imgs) 

242 

243 assert parcellator.labels_img_ is not None 

244 if method not in ["kmeans", "rena", "hierarchical_kmeans"]: 

245 assert parcellator.connectivity_ is not None 

246 assert parcellator.masker_ is not None 

247 

248 

249@pytest.mark.timeout(0) 

250@pytest.mark.parametrize("method", METHODS) 

251@pytest.mark.parametrize("n_parcel", [5]) 

252def test_fit_transform_with_confounds(method, n_parcel, test_image_2, rng): 

253 fmri_imgs = [test_image_2] * 3 

254 confounds = rng.standard_normal(size=(10, 3)) 

255 confounds_list = [confounds] * 3 

256 

257 parcellator = Parcellations(method=method, n_parcels=n_parcel, verbose=0) 

258 signals = parcellator.fit_transform(fmri_imgs, confounds=confounds_list) 

259 

260 assert isinstance(signals, list) 

261 assert signals[0].shape == (10, n_parcel) 

262 

263 

264@pytest.mark.parametrize("method", METHODS) 

265@pytest.mark.parametrize("n_parcel", [5]) 

266def test_inverse_transform_single_nifti_image(method, n_parcel, test_image_2): 

267 parcellate = Parcellations(method=method, n_parcels=n_parcel, verbose=0) 

268 parcellate.fit(test_image_2) 

269 

270 assert parcellate.labels_img_ is not None 

271 

272 fmri_reduced = parcellate.transform(test_image_2) 

273 

274 assert isinstance(fmri_reduced, np.ndarray) 

275 # Shape matching with (scans, regions) 

276 assert fmri_reduced.shape == (10, n_parcel) 

277 

278 fmri_compressed = parcellate.inverse_transform(fmri_reduced) 

279 

280 # A single Nifti image for single subject input 

281 assert isinstance(fmri_compressed, Nifti1Image) 

282 # returns shape of fmri_img 

283 assert fmri_compressed.shape == test_image_2.shape 

284 

285 # fmri_reduced in a list 

286 fmri_compressed = parcellate.inverse_transform([fmri_reduced]) 

287 

288 # A single Nifti image for single subject input 

289 assert isinstance(fmri_compressed, Nifti1Image) 

290 # returns shape of fmri_img 

291 assert fmri_compressed.shape == test_image_2.shape 

292 

293 

294def test_transform_single_3d_input_images(affine_eye): 

295 """Test fit_transform single 3D image.""" 

296 data = np.ones((10, 11, 12)) 

297 data[6, 7, 8] = 2 

298 data[9, 10, 11] = 3 

299 img = Nifti1Image(data, affine=affine_eye) 

300 

301 parcellate = Parcellations(method="ward", n_parcels=20, verbose=0) 

302 

303 X = parcellate.fit_transform(img) 

304 

305 assert isinstance(X, np.ndarray) 

306 assert X.shape == (1, 20) 

307 

308 

309def test_transform_list_3d_input_images(affine_eye): 

310 """Test fit_transform list 3D image.""" 

311 data = np.ones((10, 11, 12)) 

312 data[6, 7, 8] = 2 

313 data[9, 10, 11] = 3 

314 img = Nifti1Image(data, affine=affine_eye) 

315 imgs = [img] * 2 

316 

317 parcellate = Parcellations(method="ward", n_parcels=20, verbose=0) 

318 X = parcellate.fit_transform(imgs) 

319 

320 assert isinstance(X, list) 

321 # (number of samples, number of features) 

322 assert np.concatenate(X).shape == (2, 20) 

323 

324 # inverse transform 

325 imgs_ = parcellate.inverse_transform(X) 

326 

327 assert isinstance(imgs_, list) 

328 

329 

330@pytest.mark.parametrize("method", METHODS) 

331@pytest.mark.parametrize("n_parcels", [5, 25]) 

332def test_parcellation_all_methods_with_surface(method, n_parcels, rng): 

333 """Test if all parcellation methods work on surface.""" 

334 n_samples = 35 

335 mesh = { 

336 "left": flat_mesh(10, 8), 

337 "right": flat_mesh(9, 7), 

338 } 

339 data = { 

340 "left": rng.standard_normal( 

341 size=(mesh["left"].coordinates.shape[0], n_samples) 

342 ), 

343 "right": rng.standard_normal( 

344 size=(mesh["right"].coordinates.shape[0], n_samples) 

345 ), 

346 } 

347 surf_img = SurfaceImage(mesh=mesh, data=data) 

348 parcellate = Parcellations(method=method, n_parcels=n_parcels) 

349 # fit and transform the data 

350 X_transformed = parcellate.fit_transform(surf_img) 

351 # inverse transform the transformed data 

352 X_inverse = parcellate.inverse_transform(X_transformed) 

353 

354 # make sure the n_features in transformed data were reduced to n_clusters 

355 assert X_transformed.shape == (n_samples, n_parcels) 

356 

357 # make sure the inverse transformed data has the same shape as the original 

358 assert X_inverse.shape == surf_img.shape 

359 

360 

361@pytest.mark.parametrize("method", METHODS) 

362def test_parcellation_with_surface_and_confounds(method, rng): 

363 """Test if parcellation works on surface with confounds.""" 

364 n_samples = 36 

365 mesh = { 

366 "left": flat_mesh(10, 8), 

367 "right": flat_mesh(9, 7), 

368 } 

369 data = { 

370 "left": rng.standard_normal( 

371 size=(mesh["left"].coordinates.shape[0], n_samples) 

372 ), 

373 "right": rng.standard_normal( 

374 size=(mesh["right"].coordinates.shape[0], n_samples) 

375 ), 

376 } 

377 surf_img = SurfaceImage(mesh=mesh, data=data) 

378 confounds = rng.standard_normal(size=(n_samples, 3)) 

379 parcellate = Parcellations(method=method, n_parcels=5) 

380 X_transformed = parcellate.fit_transform(surf_img, confounds=[confounds]) 

381 

382 assert X_transformed.shape == (n_samples, 5) 

383 

384 

385@pytest.mark.parametrize("method", METHODS) 

386def test_parcellation_with_multi_surface(method, rng): 

387 """Test if parcellation works with surface data from multiple 

388 'subjects'. 

389 """ 

390 n_samples = 36 

391 mesh = { 

392 "left": flat_mesh(10, 8), 

393 "right": flat_mesh(9, 7), 

394 } 

395 data = { 

396 "left": rng.standard_normal( 

397 size=(mesh["left"].coordinates.shape[0], n_samples) 

398 ), 

399 "right": rng.standard_normal( 

400 size=(mesh["right"].coordinates.shape[0], n_samples) 

401 ), 

402 } 

403 surf_img = SurfaceImage(mesh=mesh, data=data) 

404 surf_imgs = [surf_img] * 3 

405 parcellate = Parcellations(method=method, n_parcels=5) 

406 X_transformed = parcellate.fit_transform(surf_imgs) 

407 

408 assert X_transformed[0].shape == (n_samples, 5) 

409 assert len(X_transformed) == 3 

410 

411 

412@pytest.mark.parametrize("method", METHODS) 

413def test_parcellation_with_surface_mask(method, rng): 

414 """Test if parcellation works with surface data and a mask.""" 

415 n_samples = 36 

416 mesh = { 

417 "left": flat_mesh(10, 8), 

418 "right": flat_mesh(9, 7), 

419 } 

420 data = { 

421 "left": rng.standard_normal( 

422 size=(mesh["left"].coordinates.shape[0], n_samples) 

423 ), 

424 "right": rng.standard_normal( 

425 size=(mesh["right"].coordinates.shape[0], n_samples) 

426 ), 

427 } 

428 surf_img = SurfaceImage(mesh=mesh, data=data) 

429 mask_data = { 

430 "left": np.ones(mesh["left"].coordinates.shape[0]).astype(bool), 

431 "right": np.ones(mesh["right"].coordinates.shape[0]).astype(bool), 

432 } 

433 surf_mask = SurfaceImage(mesh=mesh, data=mask_data) 

434 parcellate = Parcellations(method=method, n_parcels=5, mask=surf_mask) 

435 X_transformed = parcellate.fit_transform(surf_img) 

436 

437 assert X_transformed.shape == (n_samples, 5)