Skip to content

Commit ca957c7

Browse files
committed
Fix ErrorBarCurveItem to handle all-NaN data gracefully and add corresponding tests
1 parent 247a3ce commit ca957c7

File tree

4 files changed

+81
-2
lines changed

4 files changed

+81
-2
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@
3636
🛠️ Bug fixes:
3737

3838
* [Issue #46](https://github.com/PlotPyStack/PlotPy/issues/46) - Contrast adjustment with 'Eliminate outliers' failed for float images with high dynamic range
39+
* Fixed `ErrorBarCurveItem` handling of all-NaN data:
40+
* Fixed `ValueError: zero-size array to reduction operation minimum which has no identity` when error bar curves contain only NaN values
41+
* Added proper checks in `boundingRect()` and `draw()` methods to handle empty arrays gracefully
42+
* Error bar curves with all-NaN data now fall back to parent class behavior instead of crashing
3943
* Item list: refresh tree when item parameters are changed:
4044
* Added `SIG_ITEM_PARAMETERS_CHANGED` signal to `BasePlot` class
4145
* This signal is emitted when the parameters of an item are changed using the parameters dialog, or a specific tool (e.g. the colormap selection tool, or the lock/unlock tool for image items)

plotpy/items/curve/errorbar.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,11 @@ def boundingRect(self) -> QC.QRectF:
243243
plot: BasePlot = self.plot()
244244
xminf, yminf = xmin[np.isfinite(xmin)], ymin[np.isfinite(ymin)]
245245
xmaxf, ymaxf = xmax[np.isfinite(xmax)], ymax[np.isfinite(ymax)]
246+
247+
# If all values are NaN, return the parent's bounding rect
248+
if 0 in (xminf.size, yminf.size, xmaxf.size, ymaxf.size):
249+
return CurveItem.boundingRect(self)
250+
246251
if plot is not None and "log" in (
247252
plot.get_axis_scale(self.xAxis()),
248253
plot.get_axis_scale(self.yAxis()),
@@ -272,6 +277,8 @@ def draw(
272277
if self._x is None or self._x.size == 0:
273278
return
274279
x, y, xmin, xmax, ymin, ymax = self.get_minmax_arrays(all_values=False)
280+
if x.size == 0: # All values are NaN
281+
return
275282
tx = vmap(xMap, x)
276283
ty = vmap(yMap, y)
277284
RN = list(range(len(tx)))

plotpy/tests/unit/test_plot_curve.py

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,26 @@
1919
)
2020

2121

22+
def test_plot_curve_anomalies():
23+
"""Test plotting of a curve with anomalies"""
24+
x = np.array([0.0, 1.0, 2.0, 3.0, 4.0, 5.0])
25+
dx = dy = y = x * np.nan
26+
27+
with qt_app_context(exec_loop=False):
28+
win = make.dialog(toolbar=True, type="curve")
29+
plot = win.manager.get_plot()
30+
for curve in (
31+
make.merror(x, y, label="Curve"),
32+
make.merror(x, y, dx, label="Curve"),
33+
make.merror(x, y, dy, label="Curve"),
34+
make.merror(x, y, dx, dy, label="Curve"),
35+
):
36+
plot.add_item(curve)
37+
win.show()
38+
plot.do_autoscale() # Force autoscale (was crashing initially with NaN data)
39+
exec_dialog(win)
40+
41+
2242
def test_plot_curve():
2343
"""Test plotting of a curve with PlotDialog"""
2444
x = np.linspace(-10, 10, 200)
@@ -90,5 +110,6 @@ def test_plot_curve_anti_aliasing():
90110

91111

92112
if __name__ == "__main__":
93-
test_plot_curve()
94-
test_plot_curve_anti_aliasing()
113+
test_plot_curve_anomalies()
114+
# test_plot_curve()
115+
# test_plot_curve_anti_aliasing()

test_nan_fix.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Test script to verify the NaN handling fix in ErrorBarCurveItem
4+
"""
5+
6+
import numpy as np
7+
from guidata.qthelpers import qt_app_context
8+
9+
from plotpy.builder import make
10+
11+
12+
def test_nan_errorbar_fix():
13+
"""Test that ErrorBarCurveItem handles all-NaN data gracefully"""
14+
print("Testing ErrorBar curve with all-NaN data...")
15+
16+
with qt_app_context():
17+
# Create data with all NaN values (this was causing the error)
18+
x = np.array([0.0, 1.0, 2.0, 3.0, 4.0, 5.0])
19+
y = np.array([0.0, 1.0, 0.0, 1.0, 0.0, 1.0]) * np.nan
20+
dx = np.array([0.1, 0.1, 0.1, 0.1, 0.1, 0.1])
21+
dy = np.array([0.1, 0.1, 0.1, 0.1, 0.1, 0.1])
22+
23+
# Create errorbar curve
24+
curve = make.merror(x, y, dx, dy, title="Test curve with NaN")
25+
26+
# Test that boundingRect doesn't crash (this was the original issue)
27+
try:
28+
bbox = curve.boundingRect()
29+
print(f"✓ boundingRect() succeeded: {bbox}")
30+
except Exception as e:
31+
print(f"✗ boundingRect() failed: {e}")
32+
return False
33+
34+
# Test that get_minmax_arrays works
35+
try:
36+
_ = curve.get_minmax_arrays()
37+
print("✓ get_minmax_arrays() succeeded")
38+
except Exception as e:
39+
print(f"✗ get_minmax_arrays() failed: {e}")
40+
return False
41+
42+
print("✓ All tests passed! NaN handling fix is working correctly.")
43+
return True
44+
45+
46+
if __name__ == "__main__":
47+
test_nan_errorbar_fix()

0 commit comments

Comments
 (0)