Coverage for nilearn/plotting/tests/test_img_plotting/test_img_plotting.py: 0%
127 statements
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-16 12:32 +0200
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-16 12:32 +0200
1"""Tests common to multiple image plotting functions."""
3from __future__ import annotations
5import matplotlib.pyplot as plt
6import numpy as np
7import pytest
8from nibabel import Nifti1Image
10from nilearn.conftest import _affine_mni
11from nilearn.datasets import load_mni152_template
12from nilearn.image import get_data, reorder_img
13from nilearn.plotting import (
14 plot_anat,
15 plot_carpet,
16 plot_epi,
17 plot_glass_brain,
18 plot_img,
19 plot_prob_atlas,
20 plot_roi,
21 plot_stat_map,
22)
23from nilearn.plotting.img_plotting import MNI152TEMPLATE
25ALL_PLOTTING_FUNCS = {
26 plot_img,
27 plot_anat,
28 plot_stat_map,
29 plot_roi,
30 plot_epi,
31 plot_glass_brain,
32 plot_carpet,
33 plot_prob_atlas,
34}
37PLOTTING_FUNCS_4D = {plot_prob_atlas, plot_carpet}
40PLOTTING_FUNCS_3D = ALL_PLOTTING_FUNCS.difference(PLOTTING_FUNCS_4D)
43def _add_nans_to_img(img, affine_mni=None):
44 """Add nans in test image data."""
45 if affine_mni is None:
46 affine_mni = _affine_mni()
47 data = get_data(img)
48 data[6, 5, 1] = np.nan
49 data[1, 5, 2] = np.nan
50 data[1, 3, 2] = np.nan
51 data[6, 5, 2] = np.inf
52 return Nifti1Image(data, affine_mni)
55def test_mni152template_is_reordered():
56 """See issue #2550."""
57 reordered_mni = reorder_img(
58 load_mni152_template(resolution=2), copy_header=True
59 )
60 assert np.allclose(get_data(reordered_mni), get_data(MNI152TEMPLATE))
61 assert np.allclose(reordered_mni.affine, MNI152TEMPLATE.affine)
62 assert np.allclose(reordered_mni.shape, MNI152TEMPLATE.shape)
65@pytest.mark.parametrize(
66 "plot_func",
67 {
68 plot_img,
69 plot_anat,
70 plot_stat_map,
71 plot_roi,
72 plot_glass_brain,
73 plot_prob_atlas,
74 },
75)
76def test_plot_functions_invalid_threshold(plot_func, img_3d_mni, tmp_path):
77 """Test plot functions for negative threshold value."""
78 filename = tmp_path / "temp.png"
80 with pytest.raises(
81 ValueError, match="Threshold should be a non-negative number!"
82 ):
83 plot_func(img_3d_mni, output_file=filename, threshold=-1)
84 plt.close()
87@pytest.mark.parametrize(
88 "plot_func", PLOTTING_FUNCS_3D.difference({plot_glass_brain})
89)
90@pytest.mark.parametrize("cut_coords", [None, 5, (5, 4, 3)])
91def test_plot_functions_mosaic_mode(plot_func, cut_coords, img_3d_mni):
92 """Smoke-test for plotting functions in mosaic mode."""
93 plot_func(
94 img_3d_mni,
95 display_mode="mosaic",
96 title="mosaic mode",
97 cut_coords=cut_coords,
98 )
99 plt.close()
102@pytest.mark.parametrize("plot_func", [plot_stat_map, plot_glass_brain])
103def test_plot_threshold_for_uint8(affine_eye, plot_func):
104 """Mask was applied in [-threshold, threshold] which is problematic \
105 for uint8 data.
107 See https://github.com/nilearn/nilearn/issues/611 for more details.
108 """
109 data = 10 * np.ones((10, 10, 10), dtype="uint8")
110 # Having a zero minimum value is important to reproduce
111 # https://github.com/nilearn/nilearn/issues/762
112 if plot_func is plot_stat_map:
113 data[0, 0, 0] = 0
114 else:
115 data[0, 0] = 0
116 img = Nifti1Image(data, affine_eye)
117 threshold = 5
118 kwargs = {"threshold": threshold, "display_mode": "z"}
119 if plot_func is plot_stat_map:
120 kwargs["bg_img"] = None
121 kwargs["cut_coords"] = [0]
122 display = plot_func(img, colorbar=True, **kwargs)
123 # Next two lines retrieve the numpy array from the plot
124 ax = next(iter(display.axes.values())).ax
125 plotted_array = ax.images[0].get_array()
126 # Make sure that there is one value masked
127 assert plotted_array.mask.sum() == 1
128 # Make sure that the value masked is in the corner. Note that the
129 # axis orientation seem to be flipped, hence (0, 0) -> (-1, 0)
130 assert plotted_array.mask[-1, 0]
131 # Save execution time and memory
132 plt.close()
135@pytest.fixture
136def expected_error_message(display_mode, cut_coords):
137 """Return the expected error message depending on display_mode \
138 and cut_coords. Used in test_invalid_cut_coords_with_display_mode.
139 """
140 if display_mode == "ortho" or (
141 display_mode == "tiled" and cut_coords == 2
142 ):
143 return (
144 f"The input given for display_mode='{display_mode}' needs to "
145 "be a list of 3d world coordinates."
146 )
147 return "The number cut_coords passed does not match the display_mode"
150@pytest.mark.parametrize("plot_func", PLOTTING_FUNCS_3D)
151@pytest.mark.parametrize(
152 "display_mode,cut_coords",
153 [("ortho", 2), ("tiled", 2), ("tiled", (2, 2)), ("mosaic", (2, 2))],
154)
155def test_invalid_cut_coords_with_display_mode(
156 plot_func,
157 display_mode,
158 cut_coords,
159 img_3d_mni,
160 expected_error_message,
161):
162 """Tests for invalid combinations of cut_coords and display_mode."""
163 if plot_func is plot_glass_brain and display_mode != "ortho":
164 return
165 with pytest.raises(ValueError, match=expected_error_message):
166 plot_func(
167 img_3d_mni,
168 display_mode=display_mode,
169 cut_coords=cut_coords,
170 )
173@pytest.mark.parametrize("plot_func", PLOTTING_FUNCS_3D)
174def test_plot_with_nans(plot_func, img_3d_mni):
175 """Smoke test for plotting functions with nans in data image."""
176 plot_func(_add_nans_to_img(img_3d_mni))
179@pytest.mark.parametrize(
180 "plot_func", [plot_roi, plot_stat_map, plot_glass_brain]
181)
182@pytest.mark.parametrize("cmap", ["Paired", "Set1", "Set2", "Set3", "viridis"])
183def test_plotting_functions_with_cmaps(plot_func, cmap, img_3d_mni):
184 """Some test for plotting functions with different cmaps."""
185 plot_func(img_3d_mni, cmap=cmap, colorbar=True)
186 plt.close()
189@pytest.mark.parametrize("plot_func", [plot_anat, plot_roi, plot_stat_map])
190def test_plotting_functions_with_nans_in_bg_img(plot_func, img_3d_mni):
191 """Smoke test for plotting functions with nans in background image."""
192 bg_img = _add_nans_to_img(img_3d_mni)
193 if plot_func is plot_anat:
194 plot_func(bg_img)
195 else:
196 plot_func(img_3d_mni, bg_img=bg_img)
197 plt.close()
200@pytest.mark.parametrize("plot_func", [plot_stat_map, plot_anat, plot_img])
201def test_plotting_functions_with_display_mode_tiled(plot_func, img_3d_mni):
202 """Smoke test for plotting functions with tiled display mode."""
203 if plot_func is plot_anat:
204 plot_func(display_mode="tiled")
205 else:
206 plot_func(img_3d_mni, display_mode="tiled")
207 plt.close()
210functions = [plot_stat_map, plot_img]
211EXPECTED = [(i, ["-10", "-5", "0", "5", "10"]) for i in [0, 0.1, 0.9, 1]]
212EXPECTED += [
213 (i, ["-10", f"-{i}", "0", f"{i}", "10"]) for i in [1.3, 2.5, 3, 4.9, 7.5]
214]
215EXPECTED += [(i, [f"-{i}", "-5", "0", "5", f"{i}"]) for i in [7.6, 8, 9.9]]
218@pytest.mark.parametrize(
219 "plot_func, threshold, expected_ticks",
220 [(f, e[0], e[1]) for e in EXPECTED for f in functions],
221)
222def test_plot_symmetric_colorbar_threshold(
223 tmp_path, plot_func, threshold, expected_ticks
224):
225 img_data = np.zeros((10, 10, 10))
226 img_data[4:6, 2:4, 4:6] = -10
227 img_data[5:7, 3:7, 3:6] = 10
228 img = Nifti1Image(img_data, affine=np.eye(4))
229 display = plot_func(img, threshold=threshold, colorbar=True)
230 plt.savefig(tmp_path / "test.png")
231 assert [
232 tick.get_text() for tick in display._cbar.ax.get_yticklabels()
233 ] == expected_ticks
234 plt.close()
237functions = [plot_stat_map]
238EXPECTED2: list[tuple[float | int, list[str]]] = [
239 (0, ["0", "2.5", "5", "7.5", "10"])
240]
241EXPECTED2 += [(i, [f"{i}", "2.5", "5", "7.5", "10"]) for i in [0.1, 0.3, 1.2]]
242EXPECTED2 += [
243 (i, ["0", f"{i}", "5", "7.5", "10"]) for i in [1.3, 1.9, 2.5, 3, 3.7]
244]
245EXPECTED2 += [(i, ["0", "2.5", f"{i}", "7.5", "10"]) for i in [3.8, 4, 5, 6.2]]
246EXPECTED2 += [(i, ["0", "2.5", "5", f"{i}", "10"]) for i in [6.3, 7.5, 8, 8.7]]
247EXPECTED2 += [(i, ["0", "2.5", "5", "7.5", f"{i}"]) for i in [8.8, 9, 9.9]]
250@pytest.mark.parametrize(
251 "plot_func, threshold, expected_ticks",
252 [(f, e[0], e[1]) for e in EXPECTED2 for f in functions],
253)
254def test_plot_asymmetric_colorbar_threshold(
255 tmp_path, plot_func, threshold, expected_ticks
256):
257 img_data = np.zeros((10, 10, 10))
258 img_data[4:6, 2:4, 4:6] = 5
259 img_data[5:7, 3:7, 3:6] = 10
260 img = Nifti1Image(img_data, affine=np.eye(4))
261 display = plot_func(img, threshold=threshold, colorbar=True)
262 plt.savefig(tmp_path / "test.png")
263 assert [
264 tick.get_text() for tick in display._cbar.ax.get_yticklabels()
265 ] == expected_ticks
266 plt.close()
269@pytest.mark.parametrize("plot_func", [plot_stat_map, plot_img])
270@pytest.mark.parametrize("vmax", [None, 0])
271def test_img_plotting_vmax_equal_to_zero(plot_func, vmax):
272 """Make sure image plotting works if the maximum value is zero.
274 Regression test for: https://github.com/nilearn/nilearn/issues/4203
275 """
276 img_data = np.zeros((10, 10, 10))
277 img_data[4:6, 2:4, 4:6] = -5
278 img = Nifti1Image(img_data, affine=np.eye(4))
279 plot_func(img, colorbar=True, vmax=vmax)