Skip to content

Commit b8c2aaa

Browse files
committed
Merge branch 'master' into develop
2 parents ec6c8c4 + 81b28a0 commit b8c2aaa

File tree

6 files changed

+63
-61
lines changed

6 files changed

+63
-61
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,16 @@
1414

1515
## Version 2.6.3 ##
1616

17+
🧯 In this release, test coverage is 79%.
18+
1719
🛠️ Bug fixes:
1820

21+
* [Issue #25](https://github.com/PlotPyStack/PlotPy/issues/25) - `OverflowError` with Contrast Adjustment panel for constant images
22+
* When updating image parameters (`ImageParam`) from the associated item object:
23+
* If `xmin`, `xmax`, `ymin`, `ymax` attributes are not yet set (i.e. `None`), do not update them with the image data bounds
24+
* Previous behavior was to update them with the image data bounds, which was leading to breaking the automatic bounds update when the image data is updated
25+
* [Issue #24](https://github.com/PlotPyStack/PlotPy/issues/24) - Colormap: side effect on image axes when changing the colormap
26+
* [Issue #23](https://github.com/PlotPyStack/PlotPy/issues/23) - Windows: Image `_scaler` engine performance regression
1927
* PySide6 compatibility issues:
2028
* Fixed deprecated call to `QMouseEvent` in `tests/unit/utils.py`
2129
* Added workaround for `QPolygonF` shape point slicing

plotpy/items/shape/range.py

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@
2222
import qwt.scale_map
2323
from qtpy.QtCore import QPointF, QRectF
2424
from qtpy.QtGui import QPainter
25+
from qwt import QwtSymbol
2526

27+
from plotpy.plot import BasePlot
2628
from plotpy.styles.base import ItemParameters
2729

2830

@@ -137,34 +139,31 @@ def draw(
137139
yMap: Y axis scale map
138140
canvasRect: Canvas rectangle
139141
"""
140-
plot = self.plot()
142+
plot: BasePlot = self.plot()
141143
if not plot:
142144
return
143145
if self.selected:
144-
pen = self.sel_pen
145-
sym = self.sel_symbol
146+
pen: QG.QPen = self.sel_pen
147+
sym: QwtSymbol = self.sel_symbol
146148
else:
147-
pen = self.pen
148-
sym = self.symbol
149+
pen: QG.QPen = self.pen
150+
sym: QwtSymbol = self.symbol
149151

150-
rct = plot.canvas().contentsRect()
151-
rct2 = QC.QRectF(rct)
152-
rct2.setLeft(xMap.transform(self._min))
153-
rct2.setRight(xMap.transform(self._max))
152+
rct = QC.QRectF(plot.canvas().contentsRect())
153+
rct.setLeft(xMap.transform(self._min))
154+
rct.setRight(xMap.transform(self._max))
154155

155-
painter.fillRect(rct2, self.brush)
156+
painter.fillRect(rct, self.brush)
156157
painter.setPen(pen)
157-
painter.drawLine(rct2.topLeft(), rct2.bottomLeft())
158-
painter.drawLine(rct2.topRight(), rct2.bottomRight())
158+
painter.drawLine(rct.topLeft(), rct.bottomLeft())
159+
painter.drawLine(rct.topRight(), rct.bottomRight())
160+
159161
dash = QG.QPen(pen)
160162
dash.setStyle(QC.Qt.DashLine)
161163
dash.setWidth(1)
162164
painter.setPen(dash)
163-
164-
center_x = int(rct2.center().x())
165-
top = int(rct2.top())
166-
bottom = int(rct2.bottom())
167-
painter.drawLine(center_x, top, center_x, bottom)
165+
cx = rct.center().x()
166+
painter.drawLine(QC.QPointF(cx, rct.top()), QC.QPointF(cx, rct.bottom()))
168167

169168
painter.setPen(pen)
170169
x0, x1, y = self.get_handles_pos()

plotpy/styles/image.py

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -350,12 +350,12 @@ class ImageParam(RawImageParam):
350350
help=_("Locked images are not movable with the mouse"),
351351
)
352352
_xdata = BeginGroup(_("Image placement along X-axis"))
353-
xmin = FloatItem(_("x|min"), default=None)
354-
xmax = FloatItem(_("x|max"), default=None)
353+
xmin = FloatItem(_("x|min"), default=None, check=False)
354+
xmax = FloatItem(_("x|max"), default=None, check=False)
355355
_end_xdata = EndGroup(_("Image placement along X-axis"))
356356
_ydata = BeginGroup(_("Image placement along Y-axis"))
357-
ymin = FloatItem(_("y|min"), default=None)
358-
ymax = FloatItem(_("y|max"), default=None)
357+
ymin = FloatItem(_("y|min"), default=None, check=False)
358+
ymax = FloatItem(_("y|max"), default=None, check=False)
359359
_end_ydata = EndGroup(_("Image placement along Y-axis"))
360360

361361
def update_param(self, item: ImageItem) -> None:
@@ -366,21 +366,9 @@ def update_param(self, item: ImageItem) -> None:
366366
"""
367367
super().update_param(item)
368368
self.xmin = item.xmin
369-
if self.xmin is None:
370-
self.xmin = 0.0
371369
self.ymin = item.ymin
372-
if self.ymin is None:
373-
self.ymin = 0.0
374-
if item.is_empty():
375-
shape = (0, 0)
376-
else:
377-
shape = item.data.shape
378370
self.xmax = item.xmax
379-
if self.xmax is None:
380-
self.xmax = float(shape[1])
381371
self.ymax = item.ymax
382-
if self.ymax is None:
383-
self.ymax = float(shape[0])
384372

385373
def update_item(self, item: ImageItem) -> None:
386374
"""Update the given image item from the parameters.

plotpy/tests/features/test_image_data_update.py

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import pytest
2424
from guidata.qthelpers import exec_dialog, qt_app_context
2525
from qtpy import QtCore as QC
26+
from qtpy import QtGui as QG
2627
from qtpy import QtWidgets as QW
2728

2829
from plotpy.builder import make
@@ -31,39 +32,41 @@
3132
from plotpy.tools import LockLUTRangeTool
3233

3334

34-
def get_data() -> np.ndarray:
35+
def get_data(variable_size: bool) -> np.ndarray:
3536
"""Compute 2D Gaussian data and add a narrower Gaussian on top with a random
3637
position and amplitude."""
38+
size = np.random.randint(50, 200) if variable_size else 100
3739
dtype = np.uint16
3840
amp = np.iinfo(dtype).max * 0.3
39-
data = ptd.gen_2d_gaussian(100, dtype, sigma=10.0, x0=0.0, y0=0.0, amp=amp)
41+
data = ptd.gen_2d_gaussian(size, dtype, sigma=10.0, x0=0.0, y0=0.0, amp=amp)
4042
# Choose a random position: x0, y0 have to be in the range [-10.0, 10.0]
4143
x0 = np.random.uniform(-10.0, 10.0)
4244
y0 = np.random.uniform(-10.0, 10.0)
4345
# Choose a random amplitude: a has to be in the range [0.1, 0.5]
4446
a = np.random.uniform(0.1, 0.7) * np.iinfo(dtype).max
4547
# Add the narrower Gaussian on top
46-
data += ptd.gen_2d_gaussian(100, dtype, sigma=4.0, x0=x0, y0=y0, amp=a)
48+
data += ptd.gen_2d_gaussian(size, dtype, sigma=4.0, x0=x0, y0=y0, amp=a)
4749
return data
4850

4951

5052
class ImageUpdateDialog(PlotDialog):
5153
"""Dialog box for image update"""
5254

53-
def __init__(self, title):
55+
def __init__(self, title: str, variable_size: bool = False) -> None:
56+
self.variable_size = variable_size
5457
options = PlotOptions(title="-", show_contrast=True, type="image")
5558
super().__init__(title=title, toolbar=True, edit=False, options=options)
5659
self.resize(600, 600)
5760
self.timer = QC.QTimer()
58-
self.item = make.image(get_data(), interpolation="nearest")
61+
self.item = make.image(get_data(self.variable_size), interpolation="nearest")
5962
self.item.set_lut_range((15000, 28000))
6063
plot = self.get_plot()
6164
plot.add_item(self.item)
6265
plot.set_active_item(self.item, select=False)
6366
self.counter = 0
6467
self.keep_lut_cb: QW.QCheckBox | None = None
6568

66-
def populate_plot_layout(self):
69+
def populate_plot_layout(self) -> None:
6770
"""Populate the plot layout"""
6871
start_btn = QW.QPushButton("Start image update")
6972
start_btn.clicked.connect(self.start_image_update)
@@ -74,9 +77,17 @@ def populate_plot_layout(self):
7477
self.keep_lut_cb = QW.QCheckBox()
7578
self.keep_lut_cb.setChecked(False)
7679
self.add_widget(self.keep_lut_cb, 0, 2)
80+
variable_size_cb = QW.QCheckBox("Variable size")
81+
variable_size_cb.setChecked(self.variable_size)
82+
variable_size_cb.stateChanged.connect(self.toggle_variable_size)
83+
self.add_widget(variable_size_cb, 0, 3)
7784
self.add_widget(self.plot_widget, 1, 0, 1, 0)
7885

79-
def register_tools(self):
86+
def toggle_variable_size(self, state: int) -> None:
87+
"""Toggle the variable size of the image"""
88+
self.variable_size = state == QC.Qt.Checked
89+
90+
def register_tools(self) -> None:
8091
"""Reimplement to connect the Keep LUT range checkbox to the item"""
8192
mgr = self.get_manager()
8293
keep_lut_tool = mgr.add_tool(LockLUTRangeTool)
@@ -87,18 +98,18 @@ def register_tools(self):
8798
self.keep_lut_cb.setToolTip(keep_lut_tool.action.toolTip())
8899
self.keep_lut_cb.stateChanged.connect(keep_lut_tool.activate)
89100

90-
def start_image_update(self):
101+
def start_image_update(self) -> None:
91102
"""Start updating the image"""
92103
self.timer.timeout.connect(self.update_image)
93104
self.timer.start(500)
94105

95-
def stop_image_update(self):
106+
def stop_image_update(self) -> None:
96107
"""Stop updating the image"""
97108
self.timer.stop()
98109

99-
def update_image(self):
110+
def update_image(self) -> None:
100111
"""Update the image"""
101-
data = get_data()
112+
data = get_data(self.variable_size)
102113
self.counter += 1
103114
plot = self.get_plot()
104115

@@ -111,7 +122,7 @@ def update_image(self):
111122
plot.set_title(f"Image update {self.counter:03d}")
112123
plot.replot()
113124

114-
def closeEvent(self, event):
125+
def closeEvent(self, event: QG.QCloseEvent) -> None:
115126
"""Reimplement closeEvent to stop the timer before closing the dialog"""
116127
self.timer.stop()
117128
super().closeEvent(event)

plotpy/tests/items/test_transform.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,7 @@ def get_font_array(sz: int, chars: str = DEFAULT_CHARS) -> np.ndarray | None:
4848
metric = pnt.fontMetrics()
4949
rct = metric.boundingRect(chars)
5050
pnt.end()
51-
h = rct.height()
52-
w = rct.width()
51+
h, w = rct.height(), rct.width()
5352
img = QG.QImage(w, h, QG.QImage.Format_ARGB32)
5453
paint = QG.QPainter()
5554
paint.begin(img)
@@ -62,12 +61,11 @@ def get_font_array(sz: int, chars: str = DEFAULT_CHARS) -> np.ndarray | None:
6261
paint.drawText(0, paint.fontMetrics().ascent(), chars)
6362
paint.end()
6463
try:
65-
data = img.bits().asstring(img.sizeInBytes())
64+
data = img.bits().asstring(h * w * 4)
6665
except AttributeError:
6766
data = img.bits()
68-
npy = np.frombuffer(data, np.uint8)
69-
npy.shape = img.height(), img.bytesPerLine() // 4, 4
70-
return npy[:, :, 0]
67+
npy: np.ndarray = np.frombuffer(data, np.uint8)
68+
return npy.reshape(h, w, 4)[:, :, 0]
7169

7270

7371
def write_text_on_array(
@@ -119,7 +117,7 @@ def make_items(N: int) -> list[TrImageItem]:
119117
s = float((info.max - info.min))
120118
a1 = s * (data - m) / (M - m)
121119
img = np.array(a1 + info.min, dtype)
122-
write_text_on_array(img, 0, 0, int(N / 15.0), str(dtype))
120+
write_text_on_array(img, 0, 0, int(N / 15.0), dtype.__name__)
123121
items.append(make.trimage(img, colormap="jet"))
124122
nc = int(np.sqrt(len(items)) + 1.0)
125123
maxy, x, y = 0, 0, 0

plotpy/tools/image.py

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -642,18 +642,17 @@ def activate_cmap(self, cmap: str | EditableColormap) -> None:
642642
self.update_plot(self._active_colormap.name)
643643
self.update_status(plot)
644644

645-
def update_plot(self, cmap: str) -> None:
645+
def update_plot(self, cmap_name: str) -> None:
646646
"""Update the plot with the given colormap.
647647
648648
Args:
649-
cmap: Colormap name
649+
cmap_name: Colormap name
650650
"""
651651
plot: BasePlot = self.get_active_plot()
652652
items = get_selected_images(plot, IColormapImageItemType)
653653
for item in items:
654-
param: BaseImageParam = item.param
655-
param.colormap = cmap
656-
param.update_item(item)
654+
cmap = item.get_color_map()
655+
item.set_color_map(cmap_name, cmap.invert)
657656
plot.SIG_ITEM_PARAMETERS_CHANGED.emit(item)
658657
plot.invalidate()
659658

@@ -705,10 +704,9 @@ def activate_command(self, plot: BasePlot, checked: bool) -> None:
705704
if self._active_colormap is not None and plot is not None:
706705
items = get_selected_images(plot, IColormapImageItemType)
707706
for item in items:
708-
param: BaseImageParam = item.param
709-
param.invert_colormap = checked
710-
param.update_item(item)
711-
plot.SIG_ITEM_PARAMETERS_CHANGED.emit(item)
707+
cmap = item.get_color_map()
708+
item.set_color_map(cmap.name, invert=checked)
709+
plot.SIG_ITEM_PARAMETERS_CHANGED.emit(item)
712710
plot.invalidate()
713711
self.update_status(plot)
714712

0 commit comments

Comments
 (0)