1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180
|
import platform
from unittest.mock import patch
import numpy as np
import pytest
from qtpy import API_NAME
try:
from cmap import Colormap
except ImportError:
pytest.skip("cmap not installed", allow_module_level=True)
from qtpy.QtCore import QRect
from qtpy.QtGui import QPainter, QPixmap
from qtpy.QtWidgets import QStyleOptionViewItem, QWidget
from superqt import QColormapComboBox
from superqt.cmap import (
CmapCatalogComboBox,
QColormapItemDelegate,
QColormapLineEdit,
_cmap_combo,
draw_colormap,
)
from superqt.utils import qimage_to_array
def test_draw_cmap(qtbot):
# draw into a QWidget
wdg = QWidget()
qtbot.addWidget(wdg)
draw_colormap(wdg, "viridis")
# draw into any QPaintDevice
draw_colormap(QPixmap(), "viridis")
# pass a painter an explicit colormap and a rect
draw_colormap(QPainter(), Colormap(("red", "yellow", "blue")), QRect())
# test with a border
draw_colormap(wdg, "viridis", border_color="red", border_width=2)
with pytest.raises(TypeError, match="Expected a QPainter or QPaintDevice instance"):
draw_colormap(QRect(), "viridis") # type: ignore
with pytest.raises(TypeError, match="Expected a Colormap instance or something"):
draw_colormap(QPainter(), "not a recognized string or cmap", QRect())
def test_cmap_draw_result():
"""Test that the image drawn actually looks correct."""
# draw into any QPaintDevice
w = 100
h = 20
pix = QPixmap(w, h)
cmap = Colormap("viridis")
draw_colormap(pix, cmap)
ary1 = cmap(np.tile(np.linspace(0, 1, w), (h, 1)), bytes=True)
ary2 = qimage_to_array(pix.toImage())
# there are some subtle differences between how qimage draws and how
# cmap draws, so we can't assert that the arrays are exactly equal.
# they are visually indistinguishable, and numbers are close within 4 (/255) values
# and linux, for some reason, is a bit more different``
atol = 8 if platform.system() == "Linux" else 4
np.testing.assert_allclose(ary1, ary2, atol=atol)
cmap2 = Colormap(("#230777",), name="MyMap")
draw_colormap(pix, cmap2) # include transparency
def test_catalog_combo(qtbot):
wdg = CmapCatalogComboBox()
qtbot.addWidget(wdg)
wdg.show()
wdg.setCurrentText("viridis")
assert wdg.currentColormap() == Colormap("viridis")
@pytest.mark.parametrize("filterable", [False, True])
def test_cmap_combo(qtbot, filterable):
wdg = QColormapComboBox(allow_user_colormaps=True, filterable=filterable)
qtbot.addWidget(wdg)
wdg.show()
assert wdg.userAdditionsAllowed()
with qtbot.waitSignal(wdg.currentColormapChanged):
wdg.addColormaps([Colormap("viridis"), "magma", ("red", "blue", "green")])
assert wdg.currentColormap().name.split(":")[-1] == "viridis"
with pytest.raises(ValueError, match="Invalid colormap"):
wdg.addColormap("not a recognized string or cmap")
assert wdg.currentColormap().name.split(":")[-1] == "viridis"
assert wdg.currentIndex() == 0
assert wdg.count() == 4 # includes "Add Colormap..."
wdg.setCurrentColormap("magma")
assert wdg.count() == 4 # make sure we didn't duplicate
assert wdg.currentIndex() == 1
if API_NAME == "PySide2":
return # the rest fails on CI... but works locally
# click the Add Colormap... item
# NOTE: We wrap __init__ instead of patching exec directly because
# PySide6 6.10 crashes when MetaObjectBuilder inspects mocked methods
# during signal connection (parsePythonType segfault)
_original_init = _cmap_combo._CmapNameDialog.__init__
def _init_with_mock_exec(self, *args, **kwargs):
_original_init(self, *args, **kwargs)
self.exec = lambda: True
with qtbot.waitSignal(wdg.currentColormapChanged):
with patch.object(
_cmap_combo._CmapNameDialog, "__init__", _init_with_mock_exec
):
wdg._on_activated(wdg.count() - 1)
assert wdg.count() == 5
# this could potentially fail in the future if cmap catalog changes
# but mocking the return value of the dialog is also annoying
assert wdg.itemColormap(3).name.split(":")[-1] == "accent"
# click the Add Colormap... item, but cancel the dialog
def _init_with_mock_exec_false(self, *args, **kwargs):
_original_init(self, *args, **kwargs)
self.exec = lambda: False
with patch.object(
_cmap_combo._CmapNameDialog, "__init__", _init_with_mock_exec_false
):
wdg._on_activated(wdg.count() - 1)
def test_cmap_item_delegate(qtbot):
wdg = CmapCatalogComboBox()
qtbot.addWidget(wdg)
view = wdg.view()
delegate = view.itemDelegate()
assert isinstance(delegate, QColormapItemDelegate)
# smoke tests:
painter = QPainter()
option = QStyleOptionViewItem()
index = wdg.model().index(0, 0)
delegate._colormap_fraction = 1
delegate.paint(painter, option, index)
delegate._colormap_fraction = 0.33
delegate.paint(painter, option, index)
assert delegate.sizeHint(option, index) == delegate._item_size
def test_cmap_line_edit(qtbot, qapp):
wdg = QColormapLineEdit()
qtbot.addWidget(wdg)
wdg.show()
wdg.setColormap("viridis")
assert wdg.colormap() == Colormap("viridis")
wdg.setText("magma") # also works if the name is recognized
assert wdg.colormap() == Colormap("magma")
qapp.processEvents()
qtbot.wait(10) # force the paintEvent
wdg.setFractionalColormapWidth(1)
assert wdg.fractionalColormapWidth() == 1
wdg.update()
qapp.processEvents()
qtbot.wait(10) # force the paintEvent
wdg.setText("not-a-cmap")
assert wdg.colormap() is None
# or
wdg.setFractionalColormapWidth(0.3)
wdg.setColormap(None)
assert wdg.colormap() is None
qapp.processEvents()
qtbot.wait(10) # force the paintEvent
|