Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BUG: Fix bug with themes on macOS #10500

Merged
merged 9 commits into from
Apr 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)