Repository: nilearn/nilearn

Issue:
AxisError when computing `reporting.get_cluster_table` 
This issue concerns what seems to me like a bug in the `reporting.get_cluster_table` method. I have been encoutering a `AxisError` raised by numpy when trying to compute the table with certain `stat_threshold` values.

With a high threshold value (e.g `3.`) the computation works fine, but when using smaller threshold values (1e-2), the error is thrown.

The error happens because the `ijk` value passed to a numpy `apply_along_axis` [here](https://github.com/nilearn/nilearn/blob/ac1a934e3b2b4061f894b518c960412df9ea4f11/nilearn/reporting/_get_clusters_table.py#L83) is an empty list. I don't know why yet. You can reproduce the error with the snippet below.

I guess it makes less sense to extract peaks with a lower threshold, but it still worth investigating why the error occurs.

### Expected behavior

`get_clusters_table` return the clusters table whatever the `stat_threshold` value

### Actual behavior

`get_clusters_table` raises an error when `stat_threshold` values are small.

### Steps and code to reproduce bug

```python
import tempfile

from nilearn import reporting
from nilearn.datasets import fetch_neurovault_ids

DATA_PATH = tempfile.TemporaryDirectory()

collection_id = 307
neurovault_element = fetch_neurovault_ids(
    collection_ids=[collection_id],
    mode="download_new",
    data_dir=str(DATA_PATH),
)
sample_brain_map = neurovault_element.images[0]

# Working fine with a stat threshold of 3.
neurovault_peaks = reporting.get_clusters_table(
    sample_brain_map,
    stat_threshold=3.,
    min_distance=12.,
)

# Raising error with a stat threshold of 1e-2
neurovault_peaks = reporting.get_clusters_table(
    sample_brain_map,
    stat_threshold=1e-2,
    min_distance=12.,
)
```



Discussion:
Thx for raising it.
Somehow, the definition of maxima on l.74 of `_get_clusters_table` returns inconsistent results. My impression is that the maximum filter gets wrong due to Nans. The resason why it is seen only with low thresholds is that we are looking at a region that would be subthreshold, hence not screened by the procedure, when we use a threshold of 3.

IIUC, the fix is to impute Nans by 0 in all these computations to avoid such numerical weirdies. Does that sound OK ?

Resolving patch:
diff --git a/nilearn/reporting/_get_clusters_table.py b/nilearn/reporting/_get_clusters_table.py
--- a/nilearn/reporting/_get_clusters_table.py
+++ b/nilearn/reporting/_get_clusters_table.py
@@ -12,7 +12,7 @@
 import nibabel as nib
 from scipy import ndimage
 
-from nilearn.image import get_data
+from nilearn._utils.niimg import _safe_get_data
 from nilearn.image.resampling import coord_transform
 from nilearn._utils import check_niimg_3d
 
@@ -168,10 +168,8 @@ def get_clusters_table(stat_img, stat_threshold, cluster_threshold=None,
     stat_img = check_niimg_3d(stat_img)
     # If cluster threshold is used, there is chance that stat_map will be
     # modified, therefore copy is needed
-    if cluster_threshold is None:
-        stat_map = get_data(stat_img)
-    else:
-        stat_map = get_data(stat_img).copy()
+    stat_map = _safe_get_data(stat_img, ensure_finite=True,
+                              copy_data=(cluster_threshold is not None))
 
     # Define array for 6-connectivity, aka NN1 or "faces"
     conn_mat = np.zeros((3, 3, 3), int)


Test patch:
diff --git a/nilearn/reporting/tests/test_reporting.py b/nilearn/reporting/tests/test_reporting.py
--- a/nilearn/reporting/tests/test_reporting.py
+++ b/nilearn/reporting/tests/test_reporting.py
@@ -117,6 +117,12 @@ def test_get_clusters_table(tmp_path):
     )
     assert len(cluster_table) == 2
 
+    # Test that nans are handled correctly (No numpy axis errors are raised)
+    data[data == 0] = np.nan
+    stat_img_nans = nib.Nifti1Image(data, affine=np.eye(4))
+    cluster_table = get_clusters_table(stat_img_nans, 1e-2, 0, two_sided=False)
+    assert len(cluster_table) == 1
+
 
 def test_get_clusters_table_not_modifying_stat_image():
     shape = (9, 10, 11)