66
77import nibabel as nib
88import numpy as np
9+ import pandas as pd
910import pytest
1011from nilearn .maskers import NiftiLabelsMasker
1112
@@ -92,6 +93,100 @@ def test_get_target_value_map_raises_for_unsupported_maps():
9293 diagnostics ._get_target_value_map (result )
9394
9495
96+ def test_diagnostics_voxel_thresh_deprecated_alias ():
97+ """voxel_thresh should remain a deprecated alias for target_threshold."""
98+ with pytest .warns (FutureWarning , match = "voxel_thresh" ):
99+ counter = diagnostics .FocusCounter (target_image = "z" , voxel_thresh = 1.0 )
100+
101+ assert counter .target_threshold == 1.0
102+
103+
104+ def test_diagnostics_target_and_voxel_threshold_error ():
105+ """Supplying both threshold names should fail explicitly."""
106+ with pytest .raises (ValueError , match = "target_threshold" ):
107+ diagnostics .FocusCounter (target_image = "z" , target_threshold = 1.0 , voxel_thresh = 1.0 )
108+
109+
110+ def test_corrected_cluster_table_uses_thresholded_support_and_original_z ():
111+ """Corrected cluster tables should report original-z peaks inside thresholded support."""
112+ target_image = "z_desc-size_level-cluster_corr-FWE_method-montecarlo"
113+ mask_data = np .ones ((7 , 7 , 7 ), dtype = bool )
114+ mask_img = nib .Nifti1Image (mask_data .astype (np .int8 ), affine = np .eye (4 ))
115+
116+ class DummyMasker :
117+ def __init__ (self , mask_img ):
118+ self .mask_img_ = mask_img
119+
120+ def transform (self , img ):
121+ return np .asanyarray (img .dataobj )[mask_data ].reshape (1 , - 1 )
122+
123+ def inverse_transform (self , data ):
124+ arr = np .asarray (data )
125+ if arr .ndim > 1 :
126+ arr = np .squeeze (arr , axis = 0 )
127+ out = np .zeros (mask_data .shape , dtype = arr .dtype )
128+ out [mask_data ] = arr
129+ return nib .Nifti1Image (out , affine = mask_img .affine )
130+
131+ class DummyResult :
132+ def __init__ (self , maps , masker ):
133+ self .maps = maps
134+ self .tables = {}
135+ self .diagnostics = []
136+ self .masker = masker
137+ self .estimator = SimpleNamespace (
138+ masker = masker ,
139+ inputs_ = {
140+ "id" : ["study1" ],
141+ "coordinates" : pd .DataFrame (
142+ {"id" : ["study1" ], "x" : [1.0 ], "y" : [1.0 ], "z" : [1.0 ]}
143+ ),
144+ },
145+ )
146+
147+ def get_map (self , name , return_type = "image" ):
148+ values = self .maps [name ]
149+ if return_type == "array" :
150+ return values
151+ return self .masker .inverse_transform (values )
152+
153+ corrected = np .zeros (mask_data .shape , dtype = float )
154+ corrected [(1 , 1 , 1 )] = 6.0
155+ corrected [(1 , 1 , 2 )] = 2.0
156+ corrected [(1 , 2 , 1 )] = 2.0
157+ corrected [(5 , 5 , 5 )] = 1.2
158+ corrected [(5 , 5 , 4 )] = 1.2
159+
160+ original_z = np .zeros (mask_data .shape , dtype = float )
161+ original_z [(1 , 1 , 1 )] = 3.0
162+ original_z [(1 , 1 , 2 )] = 9.0
163+ original_z [(1 , 2 , 1 )] = 2.0
164+ original_z [(5 , 5 , 5 )] = 8.0
165+ original_z [(5 , 5 , 4 )] = 8.0
166+ original_z [(3 , 3 , 3 )] = 99.0
167+
168+ masker = DummyMasker (mask_img )
169+ result = DummyResult (
170+ {
171+ target_image : masker .transform (nib .Nifti1Image (corrected , affine = mask_img .affine ))[0 ],
172+ "z" : masker .transform (nib .Nifti1Image (original_z , affine = mask_img .affine ))[0 ],
173+ },
174+ masker ,
175+ )
176+
177+ counter = diagnostics .FocusCounter (target_image = target_image , target_threshold = 1.64 )
178+ result = counter .transform (result )
179+
180+ clusters_table = result .tables [f"{ target_image } _tab-clust" ]
181+ peak_row = clusters_table .loc [clusters_table ["Peak Stat" ].idxmax ()]
182+
183+ assert clusters_table .shape [0 ] == 1
184+ assert peak_row ["Peak Stat" ] == pytest .approx (9.0 )
185+ assert peak_row [["X" , "Y" , "Z" ]].to_numpy ().tolist () == [1.0 , 1.0 , 2.0 ]
186+ assert not np .any (np .isclose (clusters_table ["Peak Stat" ], 8.0 ))
187+ assert not np .any (np .isclose (clusters_table ["Peak Stat" ], 99.0 ))
188+
189+
95190def test_is_voxelwise_masker_uses_round_trip_when_mask_count_mismatches ():
96191 """Voxelwise detection should fall back to a round-trip feature-shape check."""
97192 mask_data = np .array ([[[1 ], [0 ]], [[1 ], [1 ]]], dtype = np .int8 )
@@ -150,7 +245,7 @@ def test_jackknife_smoke(
150245 testdata = testdata_ibma if meta_type == "ibma" else testdata_cbma_full
151246 res = meta .fit (dset1 , dset2 ) if n_samples == "twosample" else meta .fit (testdata )
152247
153- jackknife = diagnostics .Jackknife (target_image = target_image , voxel_thresh = voxel_thresh )
248+ jackknife = diagnostics .Jackknife (target_image = target_image , target_threshold = voxel_thresh )
154249 results = jackknife .transform (res )
155250
156251 image_name = "_" .join (target_image .split ("_" )[1 :])
@@ -177,7 +272,7 @@ def test_jackknife_with_zero_clusters(testdata_cbma_full):
177272 meta = cbma .ALE ()
178273 res = meta .fit (testdata_cbma_full )
179274
180- jackknife = diagnostics .Jackknife (target_image = "z" , voxel_thresh = 10 )
275+ jackknife = diagnostics .Jackknife (target_image = "z" , target_threshold = 10 )
181276 results = jackknife .transform (res )
182277
183278 contribution_table = results .tables ["z_diag-Jackknife_tab-counts" ]
@@ -200,14 +295,14 @@ def test_jackknife_with_custom_masker_smoke(testdata_ibma):
200295 meta = ibma .SampleSizeBasedLikelihood (mask = masker )
201296 res = meta .fit (testdata_ibma )
202297
203- jackknife = diagnostics .Jackknife (target_image = "z" , voxel_thresh = 0.5 )
298+ jackknife = diagnostics .Jackknife (target_image = "z" , target_threshold = 0.5 )
204299 results = jackknife .transform (res )
205300 contribution_table = results .tables ["z_diag-Jackknife_tab-counts_tail-positive" ]
206301 assert contribution_table .shape [0 ] == len (meta .inputs_ ["id" ])
207302
208303 # A Jackknife with a target_image that isn't present in the MetaResult raises a ValueError.
209304 with pytest .raises (ValueError ):
210- jackknife = diagnostics .Jackknife (target_image = "doggy" , voxel_thresh = 0.5 )
305+ jackknife = diagnostics .Jackknife (target_image = "doggy" , target_threshold = 0.5 )
211306 jackknife .transform (res )
212307
213308
@@ -227,7 +322,7 @@ def test_focuscounter_negative_tail_label_map_naming(testdata_cbma_full):
227322 neg_img = nib .Nifti1Image (neg_data , mask_img .affine )
228323 res .maps ["z" ] = np .squeeze (masker .transform (neg_img ))
229324
230- counter = diagnostics .FocusCounter (target_image = "z" , voxel_thresh = 1.0 )
325+ counter = diagnostics .FocusCounter (target_image = "z" , target_threshold = 1.0 )
231326 results = counter .transform (res )
232327
233328 assert "label_tail-negative" in results .maps
@@ -252,7 +347,7 @@ def test_focuscounter_positive_tail_label_map_naming(testdata_cbma_full):
252347 pos_img = nib .Nifti1Image (pos_data , mask_img .affine )
253348 res .maps ["z" ] = np .squeeze (masker .transform (pos_img ))
254349
255- counter = diagnostics .FocusCounter (target_image = "z" , voxel_thresh = 1.0 )
350+ counter = diagnostics .FocusCounter (target_image = "z" , target_threshold = 1.0 )
256351 results = counter .transform (res )
257352
258353 assert "label_tail-positive" in results .maps
@@ -283,7 +378,7 @@ def _fake_infer(_label_maps, _clusters_table, _n_clusters):
283378 monkeypatch .setattr (diagnostics , "_infer_label_map_tails" , _fake_infer )
284379 caplog .set_level (logging .WARNING , logger = "nimare.diagnostics" )
285380
286- counter = diagnostics .FocusCounter (target_image = "z" , voxel_thresh = 1.0 )
381+ counter = diagnostics .FocusCounter (target_image = "z" , target_threshold = 1.0 )
287382 results = counter .transform (res )
288383
289384 assert any ("Mixed-sign clusters detected" in r .message for r in caplog .records )
@@ -308,7 +403,7 @@ def test_focuscounter_pairwise_negative_tail_uses_group2(testdata_cbma_full):
308403 neg_img = nib .Nifti1Image (neg_data , mask_img .affine )
309404 res .maps ["z_desc-uniformity" ] = np .squeeze (masker .transform (neg_img ))
310405
311- counter = diagnostics .FocusCounter (target_image = "z_desc-uniformity" , voxel_thresh = 1.0 )
406+ counter = diagnostics .FocusCounter (target_image = "z_desc-uniformity" , target_threshold = 1.0 )
312407 results = counter .transform (res )
313408
314409 table_key = "z_desc-uniformity_diag-FocusCounter_tab-counts_tail-negative"
@@ -342,7 +437,7 @@ def test_focuscounter_smoke(
342437 testdata = testdata_ibma if meta_type == "ibma" else testdata_cbma_full
343438 res = meta .fit (dset1 , dset2 ) if n_samples == "twosample" else meta .fit (testdata )
344439
345- counter = diagnostics .FocusCounter (target_image = target_image , voxel_thresh = 1.65 )
440+ counter = diagnostics .FocusCounter (target_image = target_image , target_threshold = 1.65 )
346441 if meta_type == "ibma" :
347442 with pytest .raises (ValueError ):
348443 counter .transform (res )
0 commit comments