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

1"""Test nilearn.plotting.displays.PlotlySurfaceFigure.""" 

2 

3from unittest import mock 

4 

5import numpy as np 

6import pytest 

7from matplotlib.figure import Figure 

8 

9from nilearn._utils.helpers import is_kaleido_installed 

10from nilearn.plotting import plot_surf 

11from nilearn.plotting.displays import PlotlySurfaceFigure 

12 

13try: 

14 import IPython.display # noqa:F401 

15except ImportError: 

16 IPYTHON_INSTALLED = False 

17else: 

18 IPYTHON_INSTALLED = True 

19 

20ENGINE = "plotly" 

21 

22pytest.importorskip( 

23 ENGINE, 

24 reason="Plotly is not installed; required to run the tests!", 

25) 

26 

27 

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() 

38 

39 

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") 

55 

56 

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] 

76 

77 

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() 

90 

91 

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) 

102 

103 

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) 

107 

108 

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 

119 

120 

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 ) 

136 

137 

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,))) 

152 

153 

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 

159 

160 figure.add_contours(surface_image_roi, levels=[1]) 

161 assert len(figure.figure.to_dict().get("data")) == 5 

162 

163 

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 

178 

179 

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) 

184 

185 

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" 

191 

192 

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) 

201 

202 

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