Repository: mne-tools/mne-python

Issue:
BUG: _plot_mri_contours makes too many figures
@hoechenberger it looks like https://github.com/mne-tools/mne-python/pull/9754/files#r719376494 was never actually addressed in #9754 unless I'm missing it somehow in the diff?


Discussion:
... also it seems weird to have moved the `fig` creation out of the conditional, just to close the figure:

https://github.com/mne-tools/mne-python/pull/9754/files#diff-29e49a197d8f4e70c13388d2374c60cb9a2811aeb0ccfaf67e39ec82717456dbR392-R397

It seems like we should only create this figure if it's needed, rather than always creating it and closing it if it's not needed. *EDIT:* though maybe I'm missing some reason it needs to be created, since it was created in both cases before, too...?
I think I simply forgot about this change request in the end. I will take a look shortly

Resolving patch:
diff --git a/mne/report/report.py b/mne/report/report.py
--- a/mne/report/report.py
+++ b/mne/report/report.py
@@ -316,11 +316,17 @@ def _scale_mpl_figure(fig, scale):
     fig.canvas.draw()
 
 
-def _get_mri_contour_figs(*, sl, n_jobs, mri_fname, surfaces,
-                          orientation, src, show, show_orientation, width,
-                          slices_as_figures):
-    import matplotlib.pyplot as plt
+def _get_bem_contour_figs_as_arrays(
+    *, sl, n_jobs, mri_fname, surfaces, orientation, src, show,
+    show_orientation, width
+):
+    """Render BEM surface contours on MRI slices.
 
+    Returns
+    -------
+    list of array
+        A list of NumPy arrays that represent the generated Matplotlib figures.
+    """
     # Matplotlib <3.2 doesn't work nicely with process-based parallelization
     from matplotlib import __version__ as MPL_VERSION
     if _compare_version(MPL_VERSION, '>=', '3.2'):
@@ -328,7 +334,6 @@ def _get_mri_contour_figs(*, sl, n_jobs, mri_fname, surfaces,
     else:
         prefer = 'threads'
 
-    plt.close('all')
     use_jobs = min(n_jobs, max(1, len(sl)))
     parallel, p_fun, _ = parallel_func(_plot_mri_contours, use_jobs,
                                        prefer=prefer)
@@ -337,7 +342,7 @@ def _get_mri_contour_figs(*, sl, n_jobs, mri_fname, surfaces,
             slices=s, mri_fname=mri_fname, surfaces=surfaces,
             orientation=orientation, src=src, show=show,
             show_orientation=show_orientation, width=width,
-            slices_as_figures=slices_as_figures
+            slices_as_subplots=False
         )
         for s in np.array_split(sl, use_jobs)
     )
@@ -2461,10 +2466,10 @@ def _render_one_bem_axis(self, *, mri_fname, surfaces,
 
         sl = np.arange(0, n_slices, decim)
         logger.debug(f'Rendering BEM {orientation} with {len(sl)} slices')
-        figs = _get_mri_contour_figs(
+        figs = _get_bem_contour_figs_as_arrays(
             sl=sl, n_jobs=n_jobs, mri_fname=mri_fname, surfaces=surfaces,
             orientation=orientation, src=None, show=False,
-            show_orientation='always', width=width, slices_as_figures=True
+            show_orientation='always', width=width
         )
 
         # Render the slider
diff --git a/mne/viz/misc.py b/mne/viz/misc.py
--- a/mne/viz/misc.py
+++ b/mne/viz/misc.py
@@ -11,6 +11,7 @@
 # License: Simplified BSD
 
 import copy
+import io
 from glob import glob
 from itertools import cycle
 import os.path as op
@@ -302,21 +303,22 @@ def plot_source_spectrogram(stcs, freq_bins, tmin=None, tmax=None,
 
 
 def _plot_mri_contours(*, mri_fname, surfaces, src, orientation='coronal',
-                       slices=None, slices_as_figures=False, show=True,
-                       show_indices=False, show_orientation=False, width=512):
-    """Plot BEM contours on anatomical slices.
+                       slices=None, show=True, show_indices=False,
+                       show_orientation=False, width=512,
+                       slices_as_subplots=True):
+    """Plot BEM contours on anatomical MRI slices.
 
     Parameters
     ----------
-    slices_as_figures : bool
-        If ``True``, create one figure per slice. If ``False``, add all slices
-        as subplots to a single figure.
+    slices_as_subplots : bool
+        Whether to add all slices as subplots to a single figure, or to
+        create a new figure for each slice. If ``False``, return NumPy
+        arrays instead of Matplotlib figures.
 
     Returns
     -------
-    matplotlib.figure.Figure | list of matplotlib.figure.Figure
-        The plotted slices. If ``slices_as_figures`` is ``True``, a single
-        figure. If ``False``, one figure per slice.
+    matplotlib.figure.Figure | list of array
+        The plotted slices.
     """
     import matplotlib.pyplot as plt
     from matplotlib import patheffects
@@ -336,6 +338,8 @@ def _plot_mri_contours(*, mri_fname, surfaces, src, orientation='coronal',
     axis, x, y = _mri_orientation(orientation)
 
     n_slices = data.shape[axis]
+
+    # if no slices were specified, pick some equally-spaced ones automatically
     if slices is None:
         slices = np.round(
             np.linspace(
@@ -345,7 +349,7 @@ def _plot_mri_contours(*, mri_fname, surfaces, src, orientation='coronal',
             )
         ).astype(int)
 
-        # omit first and last one (no much brain visible there anyway)
+        # omit first and last one (not much brain visible there anyway…)
         slices = slices[1:-1]
 
     slices = np.atleast_1d(slices).copy()
@@ -379,22 +383,20 @@ def _plot_mri_contours(*, mri_fname, surfaces, src, orientation='coronal',
             sources.append(apply_trans(mri_rasvox_t, points * 1e3))
         sources = np.concatenate(sources, axis=0)
 
-    if slices_as_figures:
+    # get the figure dimensions right
+    if slices_as_subplots:
+        n_col = 4
+        fig, axs, _, _ = _prepare_trellis(len(slices), n_col)
+        fig.set_facecolor('k')
+        dpi = fig.get_dpi()
+        n_axes = len(axs)
+    else:
         n_col = n_axes = 1
         dpi = 96
         # 2x standard MRI resolution is probably good enough for the
         # traces
         w = width / dpi
         figsize = (w, w / data.shape[x] * data.shape[y])
-    else:
-        n_col = 4
-
-    fig, axs, _, _ = _prepare_trellis(len(slices), n_col)
-    fig.set_facecolor('k')
-    dpi = fig.get_dpi()
-    n_axes = len(axs)
-    if slices_as_figures:
-        plt.close(fig)
 
     bounds = np.concatenate(
         [[-np.inf], slices[:-1] + np.diff(slices) / 2.,
@@ -409,11 +411,11 @@ def _plot_mri_contours(*, mri_fname, surfaces, src, orientation='coronal',
     for ai, (sl, lower, upper) in enumerate(
         zip(slices, bounds[:-1], bounds[1:])
     ):
-        if slices_as_figures:
+        if slices_as_subplots:
+            ax = axs[ai]
+        else:
             fig = _figure_agg(figsize=figsize, dpi=dpi, facecolor='k')
             ax = fig.add_axes([0, 0, 1, 1], frame_on=False, facecolor='k')
-        else:
-            ax = axs[ai]
 
         # adjust the orientations for good view
         slicer[axis] = sl
@@ -460,15 +462,28 @@ def _plot_mri_contours(*, mri_fname, surfaces, src, orientation='coronal',
                 ax.text(dat.shape[1] / 2., dat.shape[0] - 1, ylabels[1],
                         ha='center', va='top', **kwargs)
 
-        if slices_as_figures:
+        if not slices_as_subplots:
+            # convert to NumPy array
+            with io.BytesIO() as buff:
+                fig.savefig(
+                    buff, format='raw', bbox_inches='tight', pad_inches=0,
+                    dpi=dpi
+                )
+                w_, h_ = fig.canvas.get_width_height()
+                plt.close(fig)
+                buff.seek(0)
+                fig_array = np.frombuffer(buff.getvalue(), dtype=np.uint8)
+                fig = fig_array.reshape((int(h_), int(w_), -1))
+
             figs.append(fig)
 
-    if not slices_as_figures:
+    if slices_as_subplots:
         fig.subplots_adjust(left=0., bottom=0., right=1., top=1., wspace=0.,
                             hspace=0.)
         plt_show(show, fig=fig)
-
-    return figs if slices_as_figures else fig
+        return fig
+    else:
+        return figs
 
 
 @fill_doc
@@ -585,7 +600,8 @@ def plot_bem(subject, subjects_dir=None, orientation='coronal',
     fig =  _plot_mri_contours(
         mri_fname=mri_fname, surfaces=surfaces, src=src,
         orientation=orientation, slices=slices, show=show,
-        show_indices=show_indices, show_orientation=show_orientation
+        show_indices=show_indices, show_orientation=show_orientation,
+        slices_as_subplots=True
     )
     return fig
 
diff --git a/tutorials/intro/70_report.py b/tutorials/intro/70_report.py
--- a/tutorials/intro/70_report.py
+++ b/tutorials/intro/70_report.py
@@ -68,7 +68,7 @@
 # .. sidebar: Viewing the report
 #
 #    On successful creation of the report, the :meth:`~mne.Report.save` method
-#    will open the HTML in a new tab in the browser. To disable this, use the
+#    will open the HTML in a new tab in your browser. To disable this, use the
 #    ``open_browser=False`` parameter of :meth:`~mne.Report.save`.
 #
 # Adding `~mne.io.Raw` data


Test patch: