Repository: mne-tools/mne-python

Issue:
raw browser size gets mess up and becomes too small for working
#### Describe the bug

While trying to look into issue #9616, I ran into a problem with the sample dataset which could not be plotted using `raw.plot()`.
This problem occurs on both the version 0.23 and on the latest commit version using the develop mode.

#### Steps to reproduce

```
import mne
from pathlib import Path

data_folder = Path(mne.datasets.sample.data_path())
raw_file = data_folder / 'MEG' / 'sample' / 'sample_audvis_filt-0-40_raw.fif'
raw = mne.io.read_raw_fif(raw_file)
raw.plot()
```

#### Actual results

```
Opening raw data file C:\Users\Mathieu\mne_data\MNE-sample-data\MEG\sample\sample_audvis_filt-0-40_raw.fif...
    Read a total of 4 projection items:
        PCA-v1 (1 x 102)  idle
        PCA-v2 (1 x 102)  idle
        PCA-v3 (1 x 102)  idle
        Average EEG reference (1 x 60)  idle
    Range : 6450 ... 48149 =     42.956 ...   320.665 secs
Ready.
Traceback (most recent call last):

  File "C:\Users\Mathieu\AppData\Local\Temp/ipykernel_20280/2983236992.py", line 9, in <module>
    raw.plot()

  File "c:\users\mathieu\documents\git\mne-python\mne\io\base.py", line 1520, in plot
    return plot_raw(self, events, duration, start, n_channels, bgcolor,

  File "<decorator-gen-165>", line 24, in plot_raw

  File "c:\users\mathieu\documents\git\mne-python\mne\viz\raw.py", line 350, in plot_raw
    fig = _browse_figure(**params)

  File "c:\users\mathieu\documents\git\mne-python\mne\viz\_figure.py", line 2353, in _browse_figure
    fig = _figure(inst=inst, toolbar=False, FigureClass=MNEBrowseFigure,

  File "c:\users\mathieu\documents\git\mne-python\mne\viz\_figure.py", line 2341, in _figure
    fig = figure(FigureClass=FigureClass, **kwargs)

  File "C:\Users\Mathieu\Documents\pyvenv\mne\lib\site-packages\matplotlib\pyplot.py", line 797, in figure
    manager = new_figure_manager(

  File "C:\Users\Mathieu\Documents\pyvenv\mne\lib\site-packages\matplotlib\pyplot.py", line 316, in new_figure_manager
    return _backend_mod.new_figure_manager(*args, **kwargs)

  File "C:\Users\Mathieu\Documents\pyvenv\mne\lib\site-packages\matplotlib\backend_bases.py", line 3544, in new_figure_manager
    fig = fig_cls(*args, **kwargs)

  File "c:\users\mathieu\documents\git\mne-python\mne\viz\_figure.py", line 410, in __init__
    self.subplotpars.update(left=left, bottom=bottom, top=top, right=right)

  File "C:\Users\Mathieu\Documents\pyvenv\mne\lib\site-packages\matplotlib\figure.py", line 166, in update
    raise ValueError('bottom cannot be >= top')

ValueError: bottom cannot be >= top
```

#### Additional information

> 
> Platform:      Windows-10-10.0.19042-SP0
> Python:        3.8.10 (tags/v3.8.10:3d8993a, May  3 2021, 11:48:03) [MSC v.1928 64 bit (AMD64)]
> Executable:    C:\Users\Mathieu\Documents\pyvenv\mne\Scripts\python.exe
> CPU:           AMD64 Family 25 Model 33 Stepping 0, AuthenticAMD: 12 cores
> Memory:        31.9 GB
> 
> mne:           0.24.dev0
> numpy:         1.21.0 {blas=D:\\a\\1\\s\\numpy\\build\\openblas_info, lapack=D:\\a\\1\\s\\numpy\\build\\openblas_lapack_info}
> scipy:         1.7.0
> matplotlib:    3.4.2 {backend=Qt5Agg}
> 
> sklearn:       0.24.2
> numba:         0.53.1
> nibabel:       3.2.1
> nilearn:       0.8.0
> dipy:          1.4.1
> cupy:          Not found
> pandas:        1.3.0
> mayavi:        4.7.3
> pyvista:       0.31.3 {pyvistaqt=0.5.0, OpenGL 4.5.0 NVIDIA 471.41 via NVIDIA GeForce RTX 3070/PCIe/SSE2}
> vtk:           9.0.1
> PyQt5:         5.15.4



Discussion:
Hello! 👋 Thanks for opening your first issue here! ❤️ We will try to get back to you soon. 🚴🏽‍♂️

Perhaps your raw browse size got messed up, what do you get for:
```
>>> mne.get_config('MNE_BROWSE_RAW_SIZE')
'14.4,8.42'
```
These are in inches, if yours are too small then bad things happen with the matplotlib layout.
@larsoner That's definitely it. I get `'1.82,0.53'`. When I tried to run the code from issue #9616 it crashed but created a relatively large window with a tiny matplotlib figure/raw browser in the top left. I guess it somehow changed this config value.

I changed it to your values in the .json and my raw browser works again.
And by the way, why is it allowed to change this value, and to change it to small values which then create problems? Shouldn't it default to e.g. the monitor size?
> why is it allowed to change this value

Having the browse window "remember" your previous window size setting is IMO a very useful feature.  It is updated in the config each time you close a data browser window (`raw.plot()`, `epochs.plot()`, or `ica.plot_sources()`).  

> and to change it to small values which then create problems? Shouldn't it default to e.g. the monitor size?

Often we don't know how/why it gets set to very small values.  But this error happens often enough that perhaps we should consider making fixed a minimum size?

> But this error happens often enough that perhaps we should consider making fixed a minimum size?

Sure, why not. We could also expose a `size` parameter, probably in pixels (?) for most generality (matches our 3D functions) and future compatibility with other backends. And maybe we should start storing that value in pixels, maybe by detecting float vs int in the values.
> But this error happens often enough that perhaps we should consider making fixed a minimum size?

+1
We should handle the race condition of the too-small window and fall back to a sane default.
So you mean we sanity check `MNE_BROWSE_RAW_SIZE` before creating the window? Good idea!
> So you mean we sanity check `MNE_BROWSE_RAW_SIZE` before creating the window? Good idea!

Yes, either that or we catch the exception and then apply the defaults. Both would yield the same results :)
I'd prefer sanity checking and correcting the values in the config.
> I'd prefer sanity checking and correcting the values in the config.

Yes, seems like the MNE way to do things :) +1

> Yes, seems like the MNE way to do things :) +1

If by that you are referring to EAFP vs. LBYL, I'm also fine with a try/catch block, but I'd definitely update the config in either case.
There is no need to update the config because it will automatically be updated with the window dimensions when the plot is closed. All we need to do is make sure the window successfully opens

Resolving patch:
diff --git a/mne/viz/_figure.py b/mne/viz/_figure.py
--- a/mne/viz/_figure.py
+++ b/mne/viz/_figure.py
@@ -2350,6 +2350,8 @@ def _browse_figure(inst, **kwargs):
     """Instantiate a new MNE browse-style figure."""
     from .utils import _get_figsize_from_config
     figsize = kwargs.pop('figsize', _get_figsize_from_config())
+    if figsize is None or np.any(np.array(figsize) < 8):
+        figsize = (8, 8)
     fig = _figure(inst=inst, toolbar=False, FigureClass=MNEBrowseFigure,
                   figsize=figsize, **kwargs)
     # initialize zen mode (can't do in __init__ due to get_position() calls)


Test patch:
diff --git a/mne/viz/tests/test_raw.py b/mne/viz/tests/test_raw.py
--- a/mne/viz/tests/test_raw.py
+++ b/mne/viz/tests/test_raw.py
@@ -6,7 +6,7 @@
 import itertools
 
 import numpy as np
-from numpy.testing import assert_allclose
+from numpy.testing import assert_allclose, assert_array_equal
 import pytest
 import matplotlib
 import matplotlib.pyplot as plt
@@ -15,7 +15,7 @@
 from mne.datasets import testing
 from mne.fixes import _close_event
 from mne.io import read_raw_fif, read_raw_ctf, RawArray
-from mne.utils import _dt_to_stamp, _click_ch_name
+from mne.utils import _dt_to_stamp, _click_ch_name, get_config, set_config
 from mne.viz.utils import _fake_click
 from mne.annotations import _sync_onset
 from mne.viz import plot_raw, plot_sensors
@@ -216,7 +216,6 @@ def test_scale_bar():
         y_lims = [y.min(), y.max()]
         bar_lims = bar.get_ydata()
         assert_allclose(y_lims, bar_lims, atol=1e-4)
-    plt.close('all')
 
 
 def test_plot_raw_selection(raw):
@@ -351,7 +350,6 @@ def test_plot_raw_child_figures(raw):
     # test resize of main window
     width, height = fig.canvas.manager.canvas.get_width_height()
     fig.canvas.manager.canvas.resize(width // 2, height // 2)
-    plt.close('all')
 
 
 def test_plot_raw_keypresses(raw):
@@ -361,8 +359,8 @@ def test_plot_raw_keypresses(raw):
     # test twice → once in normal, once in butterfly view.
     # NB: keys a, j, and ? are tested in test_plot_raw_child_figures()
     keys = ('pagedown', 'down', 'up', 'down', 'right', 'left', '-', '+', '=',
-            'd', 'd', 'pageup', 'home', 'end', 'z', 'z', 's', 's', 'f11', 'b',
-            't')
+            'd', 'd', 'pageup', 'home', 'end', 'z', 'z', 's', 's', 'f11', 't',
+            'b')
     # test for group_by='original'
     for key in 2 * keys + ('escape',):
         fig.canvas.key_press_event(key)
@@ -729,6 +727,17 @@ def test_plot_sensors(raw):
         raw.plot_sensors()
 
 
+@pytest.mark.parametrize('cfg_value', (None, '0.1,0.1'))
+def test_min_window_size(raw, cfg_value):
+    """Test minimum window plot size."""
+    old_cfg = get_config('MNE_BROWSE_RAW_SIZE')
+    set_config('MNE_BROWSE_RAW_SIZE', cfg_value)
+    fig = raw.plot()
+    # 8 × 8 inches is default minimum size
+    assert_array_equal(fig.get_size_inches(), (8, 8))
+    set_config('MNE_BROWSE_RAW_SIZE', old_cfg)
+
+
 def test_scalings_int():
     """Test that auto scalings access samples using integers."""
     raw = RawArray(np.zeros((1, 500)), create_info(1, 1000., 'eeg'))