Coverage for nilearn/plotting/surface/tests/test_plotly_surface_figure.py: 0%
102 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"""Test nilearn.plotting.displays.PlotlySurfaceFigure."""
3from unittest import mock
5import numpy as np
6import pytest
7from matplotlib.figure import Figure
9from nilearn._utils.helpers import is_kaleido_installed
10from nilearn.plotting import plot_surf
11from nilearn.plotting.displays import PlotlySurfaceFigure
13try:
14 import IPython.display # noqa:F401
15except ImportError:
16 IPYTHON_INSTALLED = False
17else:
18 IPYTHON_INSTALLED = True
20ENGINE = "plotly"
22pytest.importorskip(
23 ENGINE,
24 reason="Plotly is not installed; required to run the tests!",
25)
28@pytest.mark.skipif(
29 is_kaleido_installed(),
30 reason="This test only runs if Plotly is installed, but not kaleido.",
31)
32def test_plotly_surface_figure_savefig_error():
33 """Test that an ImportError is raised when saving \
34 a PlotlySurfaceFigure without having kaleido installed.
35 """
36 with pytest.raises(ImportError, match="`kaleido` is required"):
37 PlotlySurfaceFigure().savefig()
40@pytest.mark.skipif(
41 not is_kaleido_installed(),
42 reason="Kaleido is not installed; required for this test.",
43)
44def test_plotly_surface_figure():
45 """Test ValueError when saving a PlotlySurfaceFigure without specifying
46 output file.
47 """
48 ps = PlotlySurfaceFigure()
49 assert ps.output_file is None
50 assert ps.figure is None
51 ps.show()
52 with pytest.raises(ValueError, match="You must provide an output file"):
53 ps.savefig()
54 ps.savefig("foo.png")
57@pytest.mark.skipif(
58 not IPYTHON_INSTALLED,
59 reason="IPython is not installed; required for this test.",
60)
61@pytest.mark.skipif(
62 not is_kaleido_installed(),
63 reason="Kaleido is not installed; required for this test.",
64)
65@pytest.mark.parametrize("renderer", ["png", "jpeg", "svg"])
66def test_plotly_show(plotly, renderer):
67 """Test PlotlySurfaceFigure.show method."""
68 ps = PlotlySurfaceFigure(plotly.graph_objects.Figure())
69 assert ps.output_file is None
70 assert ps.figure is not None
71 with mock.patch("IPython.display.display") as mock_display:
72 ps.show(renderer=renderer)
73 assert len(mock_display.call_args.args) == 1
74 key = "svg+xml" if renderer == "svg" else renderer
75 assert f"image/{key}" in mock_display.call_args.args[0]
78@pytest.mark.skipif(
79 not is_kaleido_installed(),
80 reason="Kaleido is not installed; required for this test.",
81)
82def test_plotly_savefig(plotly, tmp_path):
83 """Test PlotlySurfaceFigure.savefig method."""
84 figure = plotly.graph_objects.Figure()
85 ps = PlotlySurfaceFigure(figure, output_file=tmp_path / "foo.png")
86 assert ps.output_file == tmp_path / "foo.png"
87 assert ps.figure is not None
88 ps.savefig()
89 assert (tmp_path / "foo.png").exists()
92@pytest.mark.parametrize("input_obj", ["foo", Figure(), ["foo", "bar"]])
93def test_instantiation_error_plotly_surface_figure(input_obj):
94 """Test if PlotlySurfaceFigure raises TypeError if an object other than
95 :obj:`plotly Figure` object is specified.
96 """
97 with pytest.raises(
98 TypeError,
99 match=("`PlotlySurfaceFigure` accepts only plotly Figure objects."),
100 ):
101 PlotlySurfaceFigure(input_obj)
104def test_distant_line_segments_detected_as_not_intersecting():
105 """Test that distant lines are detected as not intersecting."""
106 assert not PlotlySurfaceFigure._do_segs_intersect(0, 0, 1, 1, 5, 5, 6, 6)
109def test_plotly_surface_figure_warns_on_isolated_roi(in_memory_mesh):
110 """Test that a warning is generated for ROIs with isolated vertices."""
111 figure = plot_surf(in_memory_mesh, engine=ENGINE)
112 # the method raises an error because the (randomly generated)
113 # vertices don't form regions
114 try:
115 with pytest.raises(UserWarning, match="contains isolated vertices:"):
116 figure.add_contours(levels=[0], roi_map=np.array([0, 1] * 10))
117 except Exception:
118 pass
121@pytest.mark.parametrize("levels,labels", [([0], ["a", "b"]), ([0, 1], ["a"])])
122def test_value_error_add_contours_levels_labels(
123 levels, labels, in_memory_mesh
124):
125 """Test that add_contours raises a ValueError when called with levels and \
126 labels that have incompatible lengths.
127 """
128 figure = plot_surf(in_memory_mesh, engine=ENGINE)
129 with pytest.raises(
130 ValueError,
131 match=("levels and labels need to be either the same length or None."),
132 ):
133 figure.add_contours(
134 levels=levels, labels=labels, roi_map=np.ones((10,))
135 )
138@pytest.mark.parametrize(
139 "levels,lines",
140 [([0], [{}, {}]), ([0, 1], [{}, {}, {}])],
141)
142def test_value_error_add_contours_levels_lines(levels, lines, in_memory_mesh):
143 """Test that add_contours raises a ValueError when called with levels and \
144 lines that have incompatible lengths.
145 """
146 figure = plot_surf(in_memory_mesh, engine=ENGINE)
147 with pytest.raises(
148 ValueError,
149 match=("levels and lines need to be either the same length or None."),
150 ):
151 figure.add_contours(levels=levels, lines=lines, roi_map=np.ones((10,)))
154def test_add_contours(surface_image_roi):
155 """Test that add_contours updates data in PlotlySurfaceFigure."""
156 figure = plot_surf(surface_image_roi.mesh, engine=ENGINE)
157 figure.add_contours(surface_image_roi)
158 assert len(figure.figure.to_dict().get("data")) == 4
160 figure.add_contours(surface_image_roi, levels=[1])
161 assert len(figure.figure.to_dict().get("data")) == 5
164@pytest.mark.parametrize("hemi", ["left", "right", "both"])
165def test_add_contours_hemi(surface_image_roi, hemi):
166 """Test that add_contours works with all hemi inputs."""
167 if hemi == "both":
168 n_vertices = surface_image_roi.mesh.n_vertices
169 else:
170 n_vertices = surface_image_roi.data.parts[hemi].shape[0]
171 figure = plot_surf(
172 surface_image_roi.mesh,
173 engine=ENGINE,
174 hemi=hemi,
175 )
176 figure.add_contours(surface_image_roi)
177 assert figure._coords.shape[0] == n_vertices
180def test_add_contours_plotly_surface_image(surface_image_roi):
181 """Test that add_contours works with SurfaceImage."""
182 figure = plot_surf(surf_map=surface_image_roi, hemi="left", engine=ENGINE)
183 figure.add_contours(roi_map=surface_image_roi)
186def test_add_contours_has_name(surface_image_roi):
187 """Test that contours added to a PlotlySurfaceFigure can be named."""
188 figure = plot_surf(surface_image_roi.mesh, engine=ENGINE)
189 figure.add_contours(surface_image_roi, levels=[1], labels=["x"])
190 assert figure.figure.to_dict().get("data")[2].get("name") == "x"
193def test_add_contours_lines_duplicated(surface_image_roi):
194 """Test that the specifications of length 1 line provided to \
195 add_contours are duplicated to all requested contours.
196 """
197 figure = plot_surf(surface_image_roi.mesh, engine=ENGINE)
198 figure.add_contours(surface_image_roi, lines=[{"width": 10}])
199 newlines = figure.figure.to_dict().get("data")[2:]
200 assert all(x.get("line").__contains__("width") for x in newlines)
203@pytest.mark.parametrize(
204 "key,value",
205 [
206 ("color", "yellow"),
207 ("width", 10),
208 ],
209)
210def test_add_contours_line_properties(key, value, surface_image_roi):
211 """Test that the specifications of a line provided to add_contours are \
212 stored in the PlotlySurfaceFigure data.
213 """
214 figure = plot_surf(surface_image_roi.mesh, engine=ENGINE)
215 figure.add_contours(surface_image_roi, levels=[1], lines=[{key: value}])
216 newline = figure.figure.to_dict().get("data")[2].get("line")
217 assert newline.get(key) == value