Skip to content

Commit ecbf089

Browse files
Merge branch 'release' into develop
# Conflicts: # plotpy/locale/fr/LC_MESSAGES/plotpy.po
2 parents d819f8c + cd6b28f commit ecbf089

5 files changed

Lines changed: 129 additions & 18 deletions

File tree

plotpy/items/image/base.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,15 @@ def get_r_values(self, i0, i1, j0, j1, flag_circle=False):
340340
"""
341341
return self.get_x_values(i0, i1)
342342

343+
def _recompute_log_data(self) -> None:
344+
"""Refresh the cached log10 data from the current ``self.data``.
345+
346+
Used both when toggling the Z-axis log scale on and when the underlying
347+
data is replaced (e.g. via :meth:`set_data`) while the log scale is
348+
already active.
349+
"""
350+
self._log_data = np.array(np.log10(self.data.clip(1)), dtype=np.float64)
351+
343352
def set_data(
344353
self, data: np.ndarray, lut_range: tuple[float, float] | None = None
345354
) -> None:
@@ -353,9 +362,15 @@ def set_data(
353362
self.histogram_cache = None
354363
self.update_bounds()
355364
self.update_border()
365+
# Refresh the cached log10 data when log scale is active, otherwise the
366+
# display would keep using the previous (now stale) log data.
367+
if self.get_zaxis_log_state():
368+
self._recompute_log_data()
356369
if not self.param.keep_lut_range:
357370
if lut_range is not None:
358371
_min, _max = lut_range
372+
elif self.get_zaxis_log_state():
373+
_min, _max = get_nan_range(self._log_data)
359374
else:
360375
_min, _max = get_nan_range(data)
361376
self.set_lut_range((_min, _max))
@@ -574,7 +589,7 @@ def set_zaxis_log_state(self, state: bool) -> None:
574589
if state:
575590
self._lin_lut_range = self.get_lut_range()
576591
if self._log_data is None:
577-
self._log_data = np.array(np.log10(self.data.clip(1)), dtype=np.float64)
592+
self._recompute_log_data()
578593
self.set_lut_range(get_nan_range(self._log_data))
579594
dtype = self._log_data.dtype
580595
else:

plotpy/locale/fr/LC_MESSAGES/plotpy.po

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,9 @@ msgstr "Police des valeurs"
355355
msgid "Scale"
356356
msgstr "Échelle"
357357

358+
msgid "logarithmic"
359+
msgstr "logarithmique"
360+
358361
msgid "linear"
359362
msgstr "linéaire"
360363

@@ -649,12 +652,12 @@ msgstr "Classes"
649652
msgid "Number of bins"
650653
msgstr "Nombre de classes"
651654

652-
msgid "Minimum value"
653-
msgstr "Valeur minimale"
654-
655655
msgid "Min"
656656
msgstr "Min"
657657

658+
msgid "Minimum value"
659+
msgstr "Valeur minimale"
660+
658661
msgid "Maximum value"
659662
msgstr "Valeur maximale"
660663

@@ -1441,12 +1444,11 @@ msgid ""
14411444
"<b>Keyboard/mouse shortcuts:</b><br><br>\n"
14421445
" - <u>single left-click</u>: item (curve, image, ...) selection<br>\n"
14431446
" - <u>single right-click</u>: context-menu relative to selected item<br>\n"
1444-
" - <u>shift</u>: on-active-curve (or image) cursor (+ <u>control</u> to maintain\n"
1445-
"cursor visible)<br>\n"
1446-
" - <u>shift + control</u>: on-active-curve cursor (+ <u>control</u> to maintain\n"
1447-
"cursor visible)<br>\n"
1447+
" - <u>shift</u>: on-active-curve (or image) cursor<br>\n"
1448+
" - <u>shift + control</u>: on-active-curve cursor (maintained visible)<br>\n"
14481449
" - <u>alt</u>: free cursor<br>\n"
14491450
" - <u>left-click + mouse move</u>: move item (when available)<br>\n"
1451+
" - <u>control + left-click + mouse move</u>: move label on markers and range selections<br>\n"
14501452
" - <u>middle-click + mouse move</u>: pan<br>\n"
14511453
" - <u>right-click + mouse move</u>: zoom"
14521454
msgstr ""
@@ -1705,3 +1707,26 @@ msgstr "Rotation et rognage"
17051707

17061708
msgid "Show cropping rectangle"
17071709
msgstr "Afficher le rectangle de rognage"
1710+
1711+
msgid ""
1712+
"<b>Keyboard/mouse shortcuts:</b><br><br>\n"
1713+
" - <u>single left-click</u>: item (curve, image, ...) selection<br>\n"
1714+
" - <u>single right-click</u>: context-menu relative to selected item<br>\n"
1715+
" - <u>shift</u>: on-active-curve (or image) cursor (+ <u>control</u> to maintain\n"
1716+
"cursor visible)<br>\n"
1717+
" - <u>shift + control</u>: on-active-curve cursor (+ <u>control</u> to maintain\n"
1718+
"cursor visible)<br>\n"
1719+
" - <u>alt</u>: free cursor<br>\n"
1720+
" - <u>left-click + mouse move</u>: move item (when available)<br>\n"
1721+
" - <u>middle-click + mouse move</u>: pan<br>\n"
1722+
" - <u>right-click + mouse move</u>: zoom"
1723+
msgstr ""
1724+
"<b>Raccourcis clavier et souris :</b><br><br>\n"
1725+
" - <u>clique gauche</u> : sélection d'un objet (courbe, image, ...)<br>\n"
1726+
" - <u>clique droit</u> : menu contextuel relatif à l'objet sélectionné<br>\n"
1727+
" - <u>shift</u> : curseur sur la courbe (ou l'image) active (+ <u>control</u> pour maintenir le curseur visible)<br>\n"
1728+
" - <u>shift + control</u> : curseur sur la courbe (ou l'image) active (+ <u>control</u> pour maintenir le curseur visible)<br>\n"
1729+
" - <u>alt</u> : curseur libre<br>\n"
1730+
" - <u>clique gauche + déplacement souris</u> : déplacement de l'objet actif (si possible)<br>\n"
1731+
" - <u>clique du milieu + déplacement souris</u> : translation dans le plan ('pan')<br>\n"
1732+
" - <u>clique droit + déplacement souris</u> : agrandissement ('zoom')"
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# -*- coding: utf-8 -*-
2+
#
3+
# Licensed under the terms of the BSD 3-Clause
4+
# (see plotpy/LICENSE for details)
5+
6+
"""Regression tests for cached log10 data refresh in ``ImageItem.set_data``.
7+
8+
When the Z-axis is in logarithmic scale, ``ImageItem`` keeps a cached
9+
``_log_data`` array. Prior to the fix, calling ``set_data`` did not refresh
10+
that cache, so the displayed image kept reflecting the previous values until
11+
the user toggled the log scale off and on again.
12+
"""
13+
14+
from __future__ import annotations
15+
16+
import numpy as np
17+
from guidata.qthelpers import qt_app_context
18+
19+
from plotpy.builder import make
20+
21+
22+
def _make_item(data: np.ndarray):
23+
"""Return an ``ImageItem`` ready for log-scale tests."""
24+
return make.image(data, interpolation="nearest")
25+
26+
27+
def test_set_data_refreshes_log_data_when_log_scale_enabled() -> None:
28+
"""``set_data`` must recompute ``_log_data`` when log scale is active."""
29+
with qt_app_context(exec_loop=False):
30+
first = np.array([[1.0, 10.0], [100.0, 1000.0]])
31+
item = _make_item(first)
32+
item.set_zaxis_log_state(True)
33+
np.testing.assert_array_almost_equal(item._log_data, np.log10(first.clip(1)))
34+
35+
second = np.array([[10.0, 100.0], [1000.0, 10000.0]])
36+
item.set_data(second)
37+
38+
# The cache must reflect the new data, not the previous one.
39+
np.testing.assert_array_almost_equal(item._log_data, np.log10(second.clip(1)))
40+
# And the LUT range must be derived from the refreshed log data.
41+
lut_min, lut_max = item.get_lut_range()
42+
assert lut_min == np.log10(second.clip(1)).min()
43+
assert lut_max == np.log10(second.clip(1)).max()
44+
45+
46+
def test_set_data_keeps_lut_range_in_log_mode() -> None:
47+
"""``keep_lut_range`` must be honored even when log scale is active."""
48+
with qt_app_context(exec_loop=False):
49+
first = np.array([[1.0, 10.0], [100.0, 1000.0]])
50+
item = _make_item(first)
51+
item.set_zaxis_log_state(True)
52+
item.set_lut_range((0.5, 2.5))
53+
item.param.keep_lut_range = True
54+
55+
second = np.array([[10.0, 100.0], [1000.0, 10000.0]])
56+
item.set_data(second)
57+
58+
# Cache must still be refreshed (display correctness)…
59+
np.testing.assert_array_almost_equal(item._log_data, np.log10(second.clip(1)))
60+
# …but the LUT range must remain frozen as requested by the user.
61+
assert item.get_lut_range() == (0.5, 2.5)
62+
63+
64+
def test_set_data_does_not_create_log_data_when_log_scale_disabled() -> None:
65+
"""When log scale is off, ``set_data`` must not create ``_log_data``."""
66+
with qt_app_context(exec_loop=False):
67+
item = _make_item(np.array([[1.0, 2.0], [3.0, 4.0]]))
68+
assert item._log_data is None
69+
item.set_data(np.array([[5.0, 6.0], [7.0, 8.0]]))
70+
assert item._log_data is None

plotpy/tools/curve.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -142,13 +142,13 @@ class CurveStatsTool(BaseRangeCursorTool):
142142
TITLE = _("Signal statistics")
143143
ICON = "xrange.png"
144144
LABELFUNCS: tuple[tuple[str, Callable[..., Any]], ...] = (
145-
("%g &lt; x &lt; %g", lambda *args: (args[0].min(), args[0].max())),
146-
("%g &lt; y &lt; %g", lambda *args: (args[1].min(), args[1].max())),
147-
("∆x=%g", lambda *args: args[0].max() - args[0].min()),
148-
("∆y=%g", lambda *args: args[1].max() - args[1].min()),
149-
("&lt;y&gt;=%g", lambda *args: args[1].mean()),
150-
("σ(y)=%g", lambda *args: args[1].std()),
151-
("∑(y)=%g", lambda *args: np.sum(args[1])),
145+
("%g &lt; x &lt; %g", lambda *args: (np.nanmin(args[0]), np.nanmax(args[0]))),
146+
("%g &lt; y &lt; %g", lambda *args: (np.nanmin(args[1]), np.nanmax(args[1]))),
147+
("∆x=%g", lambda *args: np.nanmax(args[0]) - np.nanmin(args[0])),
148+
("∆y=%g", lambda *args: np.nanmax(args[1]) - np.nanmin(args[1])),
149+
("&lt;y&gt;=%g", lambda *args: np.nanmean(args[1])),
150+
("σ(y)=%g", lambda *args: np.nanstd(args[1])),
151+
("∑(y)=%g", lambda *args: np.nansum(args[1])),
152152
("∫ydx=%g", lambda *args: spt.trapezoid(args[1], args[0])),
153153
)
154154
SHAPECLASS = XRangeSelection
@@ -212,8 +212,8 @@ class YRangeCursorTool(BaseRangeCursorTool):
212212
TITLE = _("Y-range")
213213
ICON = "yrange.png"
214214
LABELFUNCS: tuple[tuple[str, Callable[..., Any]], ...] = (
215-
("%g &lt; y &lt; %g", lambda ymin, ymax: (ymin, ymax)),
216-
("∆y=%g", lambda ymin, ymax: ymax - ymin),
215+
("%g &lt; y &lt; %g", lambda ymin, ymax: (min(ymin, ymax), max(ymin, ymax))),
216+
("∆y=%g", lambda ymin, ymax: abs(ymax - ymin)),
217217
)
218218
SHAPECLASS = YRangeSelection
219219

scripts/reinstall_dev.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
Reinstall multiple local libraries in editable mode for development.
44
55
Workflow:
6-
1) Try to uninstall all target libraries in one command (ignore errors if some are not installed).
6+
1) Try to uninstall all target libraries in one command (ignore errors if some are
7+
not installed).
78
2) Reinstall each library in editable mode from a sibling folder: ../<library>.
89
910
This script uses the same Python interpreter that runs it (sys.executable),

0 commit comments

Comments
 (0)