Skip to content

Commit

Permalink
BUG: Fix bug with themes on macOS (#10500)
Browse files Browse the repository at this point in the history
* BUG: Fix bug with themes on macOS

* TST: Add test

* FIX: Simplify

* FIX: Fine

* FIX: Rerun

* FIX: Simpler

* FIX: Better separators

* FIX: LRU

* FIX: Only on < 5.13
  • Loading branch information
larsoner authored Apr 10, 2022
1 parent 9b25b5d commit c276f61
Show file tree
Hide file tree
Showing 9 changed files with 110 additions and 17 deletions.
2 changes: 1 addition & 1 deletion doc/changes/latest.inc
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ Bugs

- Fix bug where plots produced using the ``'qt'`` / ``mne_qt_browser`` backend could not be added using :meth:`mne.Report.add_figure` (:gh:`10485` by `Eric Larson`_)

- Fix bug where ``theme`` was not handled properly in :meth:`mne.io.Raw.plot` (:gh:`10487` by `Mathieu Scheltienne`_)
- Fix bug where ``theme`` was not handled properly in :meth:`mne.io.Raw.plot` (:gh:`10487`, :gh:`10500` by `Mathieu Scheltienne`_ and `Eric Larson`_)

- Fix behavior for the ``pyvista`` 3D renderer's ``quiver3D`` function so that default arguments plot a glyph in ``arrow`` mode (:gh:`10493` by `Alex Rockhill`_)
Expand Down
Binary file added mne/icons/toolbar_move_horizontal@2x.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added mne/icons/toolbar_move_vertical@2x.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added mne/icons/toolbar_separator_horizontal.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added mne/icons/toolbar_separator_horizontal@2x.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added mne/icons/toolbar_separator_vertical@2x.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 2 additions & 9 deletions mne/viz/_scraper.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@

from contextlib import contextmanager

import numpy as np

from ..utils import _pl
from .backends._utils import _pixmap_to_ndarray


class _PyQtGraphScraper:
Expand Down Expand Up @@ -78,12 +77,6 @@ def _mne_qt_browser_screenshot(browser, inst=None, return_type='pixmap'):
pixmap = browser.grab()
assert return_type in ('pixmap', 'ndarray')
if return_type == 'ndarray':
img = pixmap.toImage()
img = img.convertToFormat(img.Format_RGBA8888)
ptr = img.bits()
ptr.setsize(img.height() * img.width() * 4)
data = np.frombuffer(ptr, dtype=np.uint8).copy()
data.shape = (img.height(), img.width(), 4)
return data / 255.
return _pixmap_to_ndarray(pixmap)
else:
return pixmap, inst
73 changes: 67 additions & 6 deletions mne/viz/backends/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import collections.abc
from colorsys import rgb_to_hls
from contextlib import contextmanager
import functools
import platform
import signal
import sys
Expand Down Expand Up @@ -75,6 +76,7 @@ def _alpha_blend_background(ctable, background_color):
return (use_table * alphas) + background_color * (1 - alphas)


@functools.lru_cache(1)
def _qt_init_icons():
from qtpy.QtGui import QIcon
icons_path = f"{Path(__file__).parent.parent.parent}/icons"
Expand Down Expand Up @@ -200,20 +202,69 @@ def _qt_detect_theme():


def _qt_get_stylesheet(theme):
from ..utils import logger, warn, _validate_type
from ...fixes import _compare_version
from ...utils import logger, warn, _validate_type
_validate_type(theme, ('path-like',), 'theme')
theme = str(theme)
system_theme = None
if theme == 'auto':
theme = _qt_detect_theme()
theme = system_theme = _qt_detect_theme()
if theme in ('dark', 'light'):
if theme == 'light':
stylesheet = ''
if system_theme is None:
system_theme = _qt_detect_theme()
if sys.platform == 'darwin' and theme == system_theme:
from qtpy import QtCore
try:
qt_version = QtCore.__version__ # PySide
except AttributeError:
qt_version = QtCore.QT_VERSION_STR # PyQt
if theme == 'dark' and _compare_version(qt_version, '<', '5.13'):
# Taken using "Digital Color Meter" on macOS 12.2.1 looking at
# Meld, and also adapting (MIT-licensed)
# https://github.com/ColinDuquesnoy/QDarkStyleSheet/blob/master/qdarkstyle/dark/style.qss # noqa: E501
# Something around rgb(51, 51, 51) worked as the bgcolor here,
# but it's easy enough just to set it transparent and inherit
# the bgcolor of the window (which is the same). We also take
# the separator images from QDarkStyle (MIT).
icons_path = _qt_init_icons()
stylesheet = """\
QStatusBar {
border: 1px solid rgb(76, 76, 75);
background: transparent;
}
QStatusBar QLabel {
background: transparent;
}
QToolBar {
background-color: transparent;
border-bottom: 1px solid rgb(99, 99, 99);
}
QToolBar::separator:horizontal {
width: 16px;
image: url("%(icons_path)s/toolbar_separator_horizontal@2x.png");
}
QToolBar::separator:vertical {
height: 16px;
image: url("%(icons_path)s/toolbar_separator_vertical@2x.png");
}
QToolBar::handle:horizontal {
width: 16px;
image: url("%(icons_path)s/toolbar_move_horizontal@2x.png");
}
QToolBar::handle:vertical {
height: 16px;
image: url("%(icons_path)s/toolbar_move_vertical@2x.png");
}
""" % dict(icons_path=icons_path)
else:
stylesheet = ''
else:
try:
import qdarkstyle
except ModuleNotFoundError:
logger.info('For Dark-Mode, "qdarkstyle" has to be installed! '
'You can install it with `pip install qdarkstyle`')
logger.info(
f'To use {theme} mode, "qdarkstyle" has to be installed! '
'You can install it with `pip install qdarkstyle`')
stylesheet = ''
else:
klass = getattr(getattr(qdarkstyle, theme).palette,
Expand Down Expand Up @@ -250,3 +301,13 @@ def _qt_is_dark(widget):
win = widget.window()
bgcolor = win.palette().color(win.backgroundRole()).getRgbF()[:3]
return rgb_to_hls(*bgcolor)[1] < 0.5


def _pixmap_to_ndarray(pixmap):
img = pixmap.toImage()
img = img.convertToFormat(img.Format_RGBA8888)
ptr = img.bits()
ptr.setsize(img.height() * img.width() * 4)
data = np.frombuffer(ptr, dtype=np.uint8).copy()
data.shape = (img.height(), img.width(), 4)
return data / 255.
41 changes: 40 additions & 1 deletion mne/viz/backends/tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,15 @@
#
# License: Simplified BSD

from colorsys import rgb_to_hls

import numpy as np
import pytest
from mne.viz.backends._utils import _get_colormap_from_array, _check_color

from mne import create_info
from mne.io import RawArray
from mne.viz.backends._utils import (_get_colormap_from_array, _check_color,
_qt_is_dark, _pixmap_to_ndarray)


def test_get_colormap_from_array():
Expand Down Expand Up @@ -39,3 +46,35 @@ def test_check_color():
_check_color(['foo', 'bar', 'foo'])
with pytest.raises(TypeError, match='Expected type'):
_check_color(None)


@pytest.mark.parametrize('theme', ('auto', 'light', 'dark'))
def test_theme_colors(pg_backend, theme, monkeypatch, tmp_path):
"""Test that theme colors propagate properly."""
darkdetect = pytest.importorskip('darkdetect')
monkeypatch.setenv('_MNE_FAKE_HOME_DIR', str(tmp_path))
monkeypatch.delenv('MNE_BROWSER_THEME', raising=False)
raw = RawArray(np.zeros((1, 1000)), create_info(1, 1000., 'eeg'))
fig = raw.plot(theme=theme)
is_dark = _qt_is_dark(fig)
if theme == 'dark':
assert is_dark, theme
elif theme == 'light':
assert not is_dark, theme
else:
got_dark = darkdetect.theme().lower() == 'dark'
assert is_dark is got_dark

def assert_correct_darkness(widget, want_dark):
__tracebackhide__ = True # noqa
# This should work, but it just picks up the parent in the errant case!
bgcolor = widget.palette().color(widget.backgroundRole()).getRgbF()[:3]
dark = rgb_to_hls(*bgcolor)[1] < 0.5
assert dark == want_dark, f'{widget} dark={dark} want_dark={want_dark}'
# ... so we use a more direct test
colors = _pixmap_to_ndarray(widget.grab())[:, :, :3]
dark = colors.mean() < 0.5
assert dark == want_dark, f'{widget} dark={dark} want_dark={want_dark}'

for widget in (fig.mne.toolbar, fig.statusBar()):
assert_correct_darkness(widget, is_dark)

0 comments on commit c276f61

Please sign in to comment.