From daba1f9e2be97a7ee2eb10f4879aeb61f440da35 Mon Sep 17 00:00:00 2001 From: Philipp Schmidt Date: Tue, 10 Dec 2024 09:11:20 +0100 Subject: [PATCH 1/9] Fix compatibility with recent PyQt in display.fast_plot --- src/metro/devices/display/fast_plot.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/metro/devices/display/fast_plot.py b/src/metro/devices/display/fast_plot.py index f791d0b..dd2ab2a 100644 --- a/src/metro/devices/display/fast_plot.py +++ b/src/metro/devices/display/fast_plot.py @@ -542,7 +542,7 @@ def paintEvent(self, event): # Vertical annotation lines. if self.vlines is not None and self.show_annotations: div = self.plot_transform[0] - y_end = -self.plot_geometry[3] + y_end = int(-self.plot_geometry[3]) p.save() p.translate( @@ -558,8 +558,8 @@ def paintEvent(self, event): for x_data, text in vlines_it: if self.plot_axes[0] < x_data < self.plot_axes[1]: - x_widget = x_data * div - p.drawLine(x_widget, 0.0, x_widget, y_end) + x_widget = int(x_data * div) + p.drawLine(x_widget, 0, x_widget, y_end) if text is not None: p.drawText(p.boundingRect( @@ -571,7 +571,7 @@ def paintEvent(self, event): # Horizontal annotation lines. if self.hlines is not None and self.show_annotations: div = self.plot_transform[1] - x_end = self.plot_geometry[2] + x_end = int(self.plot_geometry[2]) p.save() p.translate( @@ -587,8 +587,8 @@ def paintEvent(self, event): for y_data, text in hlines_it: if self.plot_axes[2] < y_data < self.plot_axes[3]: - y_widget = -y_data * div - p.drawLine(0.0, y_widget, x_end, y_widget) + y_widget = int(-y_data * div) + p.drawLine(0, y_widget, x_end, y_widget) if text is not None: p.drawText(p.boundingRect( @@ -791,7 +791,7 @@ def on_render_completed(self): def _buildAxisLabel(self, value, x, y, p, flags): text = str(value) - r = p.boundingRect(x, y, 1, 1, flags, text) + r = p.boundingRect(int(x), int(y), 1, 1, flags, text) r._flags = flags r._str = text From f3a4a41e92e6063511085ca42a1181256860b553 Mon Sep 17 00:00:00 2001 From: Philipp Schmidt Date: Tue, 10 Dec 2024 09:18:32 +0100 Subject: [PATCH 2/9] Fix compatibility with recent PyQt in display.image --- src/metro/devices/display/image.py | 35 ++++++++++++++++-------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/src/metro/devices/display/image.py b/src/metro/devices/display/image.py index 163fdf7..e3b06d1 100644 --- a/src/metro/devices/display/image.py +++ b/src/metro/devices/display/image.py @@ -3,7 +3,6 @@ # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at https://mozilla.org/MPL/2.0/. - from itertools import repeat import math import time @@ -13,18 +12,18 @@ from PyQt5 import QtCore from PyQt5 import QtGui from PyQt5 import QtWidgets +import pyqtgraph +pyqtgraph.setConfigOptions(antialias=False) import metro -from metro.external import pyqtgraph from metro.devices.abstract import fittable_plot -pyqtgraph.setConfigOptions(antialias=False) - # Create a default gradient, which is almost viridis. It adds a new # lower bracket though for very small values (< 1e-3 relatively) which # is pure black. -from metro.external.pyqtgraph.graphicsItems.GradientEditorItem \ - import Gradients # noqa +from pyqtgraph.graphicsItems.GradientEditorItem import Gradients +#from metro.external.pyqtgraph.graphicsItems.GradientEditorItem \ +# import Gradients # noqa default_gradient = Gradients['viridis'].copy() default_gradient['ticks'][0] = (1e-3, default_gradient['ticks'][0][1]) default_gradient['ticks'].insert(0, (0.0, (0, 0, 0, 255))) @@ -78,7 +77,9 @@ def raiseContextMenu(self, ev): class DataImageItem(pyqtgraph.ImageItem): def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) + # ImageView requires the image to be array-like when constructed + # with an explicit (this) ImageItem. + super().__init__(*args, image=np.zeros((1, 1)), **kwargs) self._coords = None @@ -132,12 +133,15 @@ def _drawMarkers(self, p, view, markers): for label, pos in markers: m = view.mapViewToDevice(pos) - p.drawLine(m.x() - 20, m.y(), m.x() + 20, m.y()) - p.drawLine(m.x(), m.y() - 20, m.x(), m.y() + 20) + x = int(m.x()) + y = int(m.y()) + + p.drawLine(x - 20, y, x + 20, y) + p.drawLine(x, y - 20, x, y + 20) if label is not None: p.drawText(p.boundingRect( - m.x(), m.y() + 20, 1, 1, flags, label), flags, label) + x, y + 20, 1, 1, flags, label), flags, label) def paint(self, p, *args): # Verbatim copy of ImageItem.paint() except for the actual @@ -145,9 +149,9 @@ def paint(self, p, *args): if self.image is None: return - if self.qimage is None: + if self._renderRequired: self.render() - if self.qimage is None: + if self._unrenderable: return if self.paintMode is not None: p.setCompositionMode(self.paintMode) @@ -163,7 +167,6 @@ def paint(self, p, *args): if self.border is not None: p.setPen(self.border) p.drawRect(self.boundingRect()) - p.save() p.resetTransform() view = self.getViewBox() @@ -180,7 +183,7 @@ def paint(self, p, *args): vlines_it = zip(self.vlines, repeat(None)) for dx, text in vlines_it: - rx = view.mapViewToDevice(QtCore.QPointF(dx, 0)).x() + rx = int(view.mapViewToDevice(QtCore.QPointF(dx, 0)).x()) p.drawLine(rx, 0, rx, height) if text is not None: @@ -197,7 +200,7 @@ def paint(self, p, *args): hlines_it = zip(self.hlines, repeat(None)) for dy, text in hlines_it: - ry = view.mapViewToDevice(QtCore.QPointF(0, dy)).y() + ry = int(view.mapViewToDevice(QtCore.QPointF(0, dy)).y()) p.drawLine(0, ry, width, ry) if text is not None: @@ -222,7 +225,7 @@ def paint(self, p, *args): if text is not None: bl = rect.bottomLeft() p.drawText(p.boundingRect( - bl.x(), bl.y() + 1, 1, 1, flags, text + int(bl.x()), int(bl.y()) + 1, 1, 1, flags, text ), flags, text) if self.ellipses is not None: From cc7265d633b428dd2fde16b90347593bf8c4752d Mon Sep 17 00:00:00 2001 From: Philipp Schmidt Date: Mon, 16 Dec 2024 10:24:34 +0100 Subject: [PATCH 3/9] Fix compatibility with recent PyQt in display.hist2d --- src/metro/devices/display/hist2d.py | 32 ++++++++++++++--------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/metro/devices/display/hist2d.py b/src/metro/devices/display/hist2d.py index 8f1cb59..b823fb8 100755 --- a/src/metro/devices/display/hist2d.py +++ b/src/metro/devices/display/hist2d.py @@ -271,8 +271,9 @@ def paintEvent(self, event): polygon = self.x_spectrum_polygon for i in range(0, n_points): - polygon.setPoint(i, i / x_scale, - 145 - int(x_spectrum[x_min+i] / y_scale)) + polygon.setPoint( + i, int(i / x_scale), + 145 - int(x_spectrum[x_min+i] / y_scale)) qp.drawPolyline(polygon) @@ -291,14 +292,14 @@ def paintEvent(self, event): x_scale = y_spectrum_max / 85 y_scale = n_points / self.data_img_dest.height() - x_offset = 5 + self.data_img_dest.right() + x_offset = 5 + int(self.data_img_dest.right()) polygon = self.y_spectrum_polygon for i in range(0, n_points): polygon.setPoint( - i, x_offset + int(y_spectrum[y_min+i] / x_scale), - 150 + self.data_img_dest.height() - i / y_scale - ) + i, + x_offset + int(y_spectrum[y_min+i] / x_scale), + 150 + int(self.data_img_dest.height() - i / y_scale)) qp.drawPolyline(polygon) @@ -347,12 +348,12 @@ def paintEvent(self, event): for label, line in zip(self.axes_tick_x_labels, self.axes_tick_x_lines): - qp.drawText(line.x1()-40, line.y1()-20, 80, 20, + qp.drawText(int(line.x1()) - 40, int(line.y1()) - 20, 80, 20, QtCore.Qt.AlignCenter, str(label)) for label, line in zip(self.axes_tick_y_labels, self.axes_tick_y_lines): - qp.drawText(line.x1()+10, line.y1()-10, 80, 20, + qp.drawText(int(line.x1()) + 10, int(line.y1()) - 10, 80, 20, QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter, str(label)) @@ -524,8 +525,8 @@ def _rebuildStaticObjects(self, qp): coords = roi['coords'] - roi_x = s_x * (coords[0] - self.axes['x_min']) - roi_y = s_y * (self.axes['y_max'] - coords[3]) + roi_x = int(s_x * (coords[0] - self.axes['x_min'])) + roi_y = int(s_y * (self.axes['y_max'] - coords[3])) top_visible = bottom_visible = left_visible = right_visible = True @@ -543,8 +544,8 @@ def _rebuildStaticObjects(self, qp): top_visible = False roi_y = img_height - roi_width = s_x * (coords[2] - coords[0]) - roi_height = s_y * (coords[3] - coords[1]) + roi_width = int(s_x * (coords[2] - coords[0])) + roi_height = int(s_y * (coords[3] - coords[1])) if (roi_x + roi_width) > img_width: right_visible = False @@ -1343,8 +1344,8 @@ def _buildColorPalette(self): mx_60 = max_value * 0.6 + z_min mx_80 = max_value * 0.8 + z_min - color_table = [QtGui.qRgb(255.0, 255.0, 255.0)] * 256 - color_table[0] = QtGui.qRgb(0.0, 0.0, 0.0) + color_table = [QtGui.qRgb(255, 255, 255)] * 256 + color_table[0] = QtGui.qRgb(0, 0, 0) grad = QtGui.QLinearGradient() grad.setStart(0, 0) @@ -1382,8 +1383,7 @@ def _buildColorPalette(self): blue = (i - mx_80) / (max_value - mx_80) # rising color_table[i] = QtGui.qRgb( - red * 255.0, green * 255.0, blue * 255.0 - ) + int(red * 255.0), int(green * 255.0), int(blue * 255.0)) grad.setColorAt(1.0 - (i / max_value), QtGui.QColor.fromRgb(color_table[i])) From e3638fccb6db5cafd369ab682c6a0b456432134d Mon Sep 17 00:00:00 2001 From: Philipp Schmidt Date: Mon, 16 Dec 2024 10:29:37 +0100 Subject: [PATCH 4/9] Fix compatibility with recent PyQt in display.plot --- src/metro/devices/abstract/single_plot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/metro/devices/abstract/single_plot.py b/src/metro/devices/abstract/single_plot.py index a1bf541..b01ad0f 100755 --- a/src/metro/devices/abstract/single_plot.py +++ b/src/metro/devices/abstract/single_plot.py @@ -5,7 +5,7 @@ import metro -from metro.external import pyqtgraph +import pyqtgraph class Device(metro.WidgetDevice, metro.DisplayDevice): From 6aef942941c1e2e207e263e2ff06f7924dcff656 Mon Sep 17 00:00:00 2001 From: Philipp Schmidt Date: Mon, 16 Dec 2024 12:17:31 +0100 Subject: [PATCH 5/9] Fix compatibility with recent PyQt in util.simulate.scalar* --- src/metro/devices/util/simulate/scalar_abstract.py | 2 +- src/metro/devices/util/simulate/scalar_manual.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/metro/devices/util/simulate/scalar_abstract.py b/src/metro/devices/util/simulate/scalar_abstract.py index 240dab0..18ac725 100644 --- a/src/metro/devices/util/simulate/scalar_abstract.py +++ b/src/metro/devices/util/simulate/scalar_abstract.py @@ -18,7 +18,7 @@ def prepare(self, args): self.offset = args['offset'] self.timer = metro.QTimer(self) - self.timer.setInterval(args['interval'] * 1000) + self.timer.setInterval(int(args['interval'] * 1000)) self.timer.timeout.connect(self.tick) def finalize(self): diff --git a/src/metro/devices/util/simulate/scalar_manual.py b/src/metro/devices/util/simulate/scalar_manual.py index 7d95682..ac39601 100644 --- a/src/metro/devices/util/simulate/scalar_manual.py +++ b/src/metro/devices/util/simulate/scalar_manual.py @@ -24,7 +24,7 @@ def prepare(self, args, state): self.offset = args['offset'] self.timer = metro.QTimer(self) - self.timer.setInterval(args['interval'] * 1000) + self.timer.setInterval(int(args['interval'] * 1000)) self.timer.timeout.connect(self.tick) self.measure_connect(self.measuringStarted, self.measuringStopped) From 6202018c787d8b6c74a9e766b4f90b296c08959d Mon Sep 17 00:00:00 2001 From: David Hammer Date: Wed, 18 Dec 2024 18:46:16 +0100 Subject: [PATCH 6/9] Handle NaN in fast_plot and waveform --- src/metro/devices/display/fast_plot.py | 6 ++++++ src/metro/devices/display/waveform.py | 3 +++ 2 files changed, 9 insertions(+) diff --git a/src/metro/devices/display/fast_plot.py b/src/metro/devices/display/fast_plot.py index dd2ab2a..1a5a556 100644 --- a/src/metro/devices/display/fast_plot.py +++ b/src/metro/devices/display/fast_plot.py @@ -1235,8 +1235,11 @@ def dataSet(self, d): def dataAdded(self, d): self.ch_data = d + nan_replacement = 0 if isinstance(self.ch_data, xr.DataArray): self.idx_data = self.ch_data.data[self.index] + if "replace_nan" in self.ch_data.attrs: + nan_replacement = self.ch_data.attrs["replace_nan"] if self.idx_data.ndim == 1: self.idx_data = self.idx_data[None, :] @@ -1268,6 +1271,9 @@ def dataAdded(self, d): if self.idx_data.shape[1] == 0: return + self.idx_data = np.nan_to_num(self.idx_data, copy=True, + nan=nan_replacement) + self._notifyFittingCallbacks(self.x, self.idx_data[0]) if self.stacking > 0.0: diff --git a/src/metro/devices/display/waveform.py b/src/metro/devices/display/waveform.py index 67f632f..868aa43 100644 --- a/src/metro/devices/display/waveform.py +++ b/src/metro/devices/display/waveform.py @@ -237,6 +237,9 @@ def dataAdded(self, d): except TypeError: pass + if not numpy.isfinite(d): + return + try: self.y_data.append(d) self.ma_buffer.append(d) From b642530d0c7b568e3428d9a2451d665e17888891 Mon Sep 17 00:00:00 2001 From: David Hammer Date: Wed, 5 Mar 2025 12:30:17 +0100 Subject: [PATCH 7/9] Add noexcept, DRY data coord transformation This was just part of preparation before adding clipping to draw_line, but I got significant performance boost from, presumably, the noexcept. We can drop the C++ part if it's not wanted; I just found references easier to work with (syntactically) than more pointers. --- .../devices/display/_fast_plot_native.pyx | 47 +++++++++++-------- 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/src/metro/devices/display/_fast_plot_native.pyx b/src/metro/devices/display/_fast_plot_native.pyx index 87daaae..81faaeb 100644 --- a/src/metro/devices/display/_fast_plot_native.pyx +++ b/src/metro/devices/display/_fast_plot_native.pyx @@ -1,3 +1,4 @@ +# distutils: language=c++ # cython: boundscheck=False, wraparound=False, cdivision=True # This Source Code Form is subject to the terms of the Mozilla Public @@ -74,7 +75,12 @@ cdef struct surface: double div_y -cdef inline void draw_pixel(surface* s, int x, int y, int c) nogil: +cdef inline void data_coord_to_image_space(surface* s, x_t x, y_t y, int& out_x, int& out_y) noexcept nogil: + out_x = (((x - s.start_x) * s.div_x) + s.offset_x) + out_y = (((y - s.start_y) * s.div_y) + s.offset_y) + + +cdef inline void draw_pixel(surface* s, int x, int y, int c) noexcept nogil: # We currently do pixel-level culling since we lose so little # performance here. Sorting the set beforehand will probably # only scale for very high data sets. @@ -83,7 +89,7 @@ cdef inline void draw_pixel(surface* s, int x, int y, int c) nogil: s.ptr[(s.height_complement - y) * s.line_length + x] = c -cdef void draw_rect(surface* s, int x1, int y1, int w, int h, int c) nogil: +cdef void draw_rect(surface* s, int x1, int y1, int w, int h, int c) noexcept nogil: # Draw a rectangle with x1/y1 as its upper-left edge and size w/h in # color c @@ -99,7 +105,7 @@ cdef void draw_rect(surface* s, int x1, int y1, int w, int h, int c) nogil: draw_pixel(s, x2, y, c) -cdef void fill_rect(surface* s, int x1, int y1, int w, int h, int c) nogil: +cdef void fill_rect(surface* s, int x1, int y1, int w, int h, int c) noexcept nogil: # Draw a filled rectangle with x1/y1 as its upper-left edge and size w/h in # color c @@ -111,13 +117,23 @@ cdef void fill_rect(surface* s, int x1, int y1, int w, int h, int c) nogil: draw_pixel(s, x, y, c) -cdef inline void draw_square(surface* s, int x, int y, int l, int c) nogil: +cdef inline void draw_square(surface* s, int x, int y, int l, int c) noexcept nogil: # Draw a square centered around x/y with length l in color c draw_rect(s, x - ((l - 1) >> 1), y - ((l - 1) >> 1), l, l, c) -cdef void draw_line(surface* s, int x1, int y1, int x2, int y2, int c) nogil: +cdef char clip_line_segment(surface* s, int& x1, int& y1, int& x2, int& y2) noexcept nogil: + # Clips the line segment provided to bounding box of surface s + # Return value: 0 for line entirely segment outside bbox + if (x1 < s.offset_x and x2 < s.offset_x) or \ + (x1 > s.offset_x and x2 > s.offset_x) or \ + (y1 < s.offset_y and y2 < s.offset_y) or \ + (y1 > s.offset_y and y2 > s.offset_y): + return 0 + + +cdef void draw_line(surface* s, int x1, int y1, int x2, int y2, int c) noexcept nogil: cdef int x, y, dx, dy, ix, iy # Swap the coordinates in case x1/y1 is bigger than x2/y2. @@ -276,7 +292,7 @@ def plot(uintptr_t s_bits, raise ValueError('len(x) != len(y)') cdef surface* s = s_bits - cdef int N = data_y.shape[0], i, cur_x, cur_y, last_x, last_y + cdef int N = data_y.shape[0], i, cur_x = 0, cur_y = 0, last_x = 0, last_y = 0 cdef int line_color = (color_idx + 1) * 10 + 1 cdef int symbol_color = line_color + 1 @@ -285,21 +301,16 @@ def plot(uintptr_t s_bits, # is very misleading! Then we can also stop using s.x_max down there with nogil: - last_x = ((data_x[0] - s.start_x) * s.div_x) + s.offset_x - last_y = ((data_y[0] - s.start_y) * s.div_y) + s.offset_y + data_coord_to_image_space(s, data_x[0], data_y[0], last_x, last_y) if marker and last_x >= s.offset_x and last_x <= s.max_x: draw_square(s, last_x, last_y, 5, symbol_color) if marker: for i in range(1, N): - cur_x = ((data_x[i] - s.start_x) * s.div_x) \ - + s.offset_x - cur_y = ((data_y[i] - s.start_y) * s.div_y) \ - + s.offset_y + data_coord_to_image_space(s, data_x[i], data_y[i], cur_x, cur_y) - if not ((last_x < s.offset_x and cur_x < s.offset_x) or - (last_x > s.max_x and cur_x > s.max_x)): + if not ((last_x < s.offset_x and cur_x < s.offset_x) or (last_x > s.max_x and cur_x > s.max_x)): draw_line(s, last_x, last_y, cur_x, cur_y, line_color) draw_square(s, cur_x, cur_y, 5, symbol_color) @@ -308,13 +319,9 @@ def plot(uintptr_t s_bits, else: for i in range(1, N): - cur_x = ((data_x[i] - s.start_x) * s.div_x) \ - + s.offset_x - cur_y = ((data_y[i] - s.start_y) * s.div_y) \ - + s.offset_y + data_coord_to_image_space(s, data_x[i], data_y[i], cur_x, cur_y) - if not ((last_x < s.offset_x and cur_x < s.offset_x) or - (last_x > s.max_x and cur_x > s.max_x)): + if not ((last_x < s.offset_x and cur_x < s.offset_x) or (last_x > s.max_x and cur_x > s.max_x)): draw_line(s, last_x, last_y, cur_x, cur_y, line_color) last_x = cur_x From 6d9fecd942a5cc16e37123177fea0b40b6bed405 Mon Sep 17 00:00:00 2001 From: David Hammer Date: Wed, 5 Mar 2025 14:26:00 +0100 Subject: [PATCH 8/9] Add line segment - box clipping --- .../devices/display/_fast_plot_native.pyx | 64 +++++++++++++++---- 1 file changed, 52 insertions(+), 12 deletions(-) diff --git a/src/metro/devices/display/_fast_plot_native.pyx b/src/metro/devices/display/_fast_plot_native.pyx index 81faaeb..5b241ac 100644 --- a/src/metro/devices/display/_fast_plot_native.pyx +++ b/src/metro/devices/display/_fast_plot_native.pyx @@ -89,6 +89,10 @@ cdef inline void draw_pixel(surface* s, int x, int y, int c) noexcept nogil: s.ptr[(s.height_complement - y) * s.line_length + x] = c +cdef inline void draw_pixel_unsafe(surface* s, int x, int y, int c) noexcept nogil: + s.ptr[(s.height_complement - y) * s.line_length + x] = c + + cdef void draw_rect(surface* s, int x1, int y1, int w, int h, int c) noexcept nogil: # Draw a rectangle with x1/y1 as its upper-left edge and size w/h in # color c @@ -126,16 +130,50 @@ cdef inline void draw_square(surface* s, int x, int y, int l, int c) noexcept no cdef char clip_line_segment(surface* s, int& x1, int& y1, int& x2, int& y2) noexcept nogil: # Clips the line segment provided to bounding box of surface s # Return value: 0 for line entirely segment outside bbox - if (x1 < s.offset_x and x2 < s.offset_x) or \ - (x1 > s.offset_x and x2 > s.offset_x) or \ - (y1 < s.offset_y and y2 < s.offset_y) or \ - (y1 > s.offset_y and y2 > s.offset_y): + if (x1 < s.min_x and x2 < s.min_x) or \ + (x1 > s.max_x and x2 > s.max_x) or \ + (y1 < s.min_y and y2 < s.min_y) or \ + (y1 > s.max_y and y2 > s.max_y): + return 0 + cdef double res_x[2] + cdef double res_y[2] + cdef double slope_y = (y2 - y1) / (x2 - x1) + cdef double slope_x = (x2 - x1) / (y2 - y1) + res_x[0] = x1 + res_x[1] = x2 + res_y[0] = y1 + res_y[1] = y2 + cdef int i + for i in range(2): + if res_x[i] < s.min_x: + res_x[i] = s.min_x + res_y[i] = (y1 + slope_y * (s.min_x - x1)) + elif res_x[i] > s.max_x: + res_x[i] = s.max_x + res_y[i] = (y1 + slope_y * (s.max_x - x1)) + if res_y[i] < s.min_y: + res_y[i] = s.min_y + res_x[i] = (x1 + slope_x * (s.min_y - x1)) + elif res_y[i] > s.max_y: + res_y[i] = s.max_y + res_x[i] = (x1 + slope_x * (s.max_y - x1)) + # check if shifting put us outside bounds + if (res_x[0] < s.min_x and res_x[1] < s.min_x) or \ + (res_x[0] > s.max_x and res_x[1] > s.max_x): return 0 + x1 = res_x[0] + x2 = res_x[1] + y1 = res_y[0] + y2 = res_y[1] + return 1 cdef void draw_line(surface* s, int x1, int y1, int x2, int y2, int c) noexcept nogil: cdef int x, y, dx, dy, ix, iy + if not clip_line_segment(s, x1, y1, x2, y2): + return + # Swap the coordinates in case x1/y1 is bigger than x2/y2. if x2 >= x1: @@ -162,7 +200,7 @@ cdef void draw_line(surface* s, int x1, int y1, int x2, int y2, int c) noexcept y2 = x2 for y from y1 <= y <= y2 by 1: - draw_pixel(s, x1, y, c) + draw_pixel_unsafe(s, x1, y, c) return elif dy == 0: @@ -172,7 +210,7 @@ cdef void draw_line(surface* s, int x1, int y1, int x2, int y2, int c) noexcept x2 = y2 for x from x1 <= x <= x2 by 1: - draw_pixel(s, x, y1, c) + draw_pixel_unsafe(s, x, y1, c) return @@ -189,7 +227,7 @@ cdef void draw_line(surface* s, int x1, int y1, int x2, int y2, int c) noexcept dx <<= 1 while x != x2: - draw_pixel(s, x, y, c) + draw_pixel_unsafe(s, x, y, c) if error >= 0: y += iy @@ -198,14 +236,14 @@ cdef void draw_line(surface* s, int x1, int y1, int x2, int y2, int c) noexcept x += ix error += dy - draw_pixel(s, x, y, c) + draw_pixel_unsafe(s, x, y, c) else: dx <<= 1 error = dx - dy dy <<= 1 while y != y2: - draw_pixel(s, x, y, c); + draw_pixel_unsafe(s, x, y, c); if error >= 0: x += ix @@ -214,7 +252,7 @@ cdef void draw_line(surface* s, int x1, int y1, int x2, int y2, int c) noexcept y += iy error += dx - draw_pixel(s, x, y, c) + draw_pixel_unsafe(s, x, y, c) def stack(numpy.ndarray[y_t, ndim=2, mode="c"] inp, @@ -310,7 +348,8 @@ def plot(uintptr_t s_bits, for i in range(1, N): data_coord_to_image_space(s, data_x[i], data_y[i], cur_x, cur_y) - if not ((last_x < s.offset_x and cur_x < s.offset_x) or (last_x > s.max_x and cur_x > s.max_x)): + if not ((last_x < s.min_x and cur_x < s.min_x) or \ + (last_x > s.max_x and cur_x > s.max_x)): draw_line(s, last_x, last_y, cur_x, cur_y, line_color) draw_square(s, cur_x, cur_y, 5, symbol_color) @@ -321,7 +360,8 @@ def plot(uintptr_t s_bits, for i in range(1, N): data_coord_to_image_space(s, data_x[i], data_y[i], cur_x, cur_y) - if not ((last_x < s.offset_x and cur_x < s.offset_x) or (last_x > s.max_x and cur_x > s.max_x)): + if not ((last_x < s.min_x and cur_x < s.min_x) or \ + (last_x > s.max_x and cur_x > s.max_x)): draw_line(s, last_x, last_y, cur_x, cur_y, line_color) last_x = cur_x From 662881241561b7f0cc507647105982d3257887bc Mon Sep 17 00:00:00 2001 From: David Hammer Date: Wed, 5 Mar 2025 14:26:12 +0100 Subject: [PATCH 9/9] Uses nan_to_num, but scale y based on finite values only --- src/metro/devices/display/fast_plot.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/metro/devices/display/fast_plot.py b/src/metro/devices/display/fast_plot.py index 1a5a556..0fc2299 100644 --- a/src/metro/devices/display/fast_plot.py +++ b/src/metro/devices/display/fast_plot.py @@ -1264,15 +1264,19 @@ def dataAdded(self, d): self.idx_data = self.idx_data[1:] else: self.x = np.arange(self.idx_data.shape[1]) - + # TODO: option to drop instead of replace self.x_label = self.y_label = self.legend_entries = \ self.vlines = self.hlines = None if self.idx_data.shape[1] == 0: return + # infinity replaced with very positive / negative values + finite_mask = np.isfinite(self.idx_data) self.idx_data = np.nan_to_num(self.idx_data, copy=True, - nan=nan_replacement) + nan=nan_replacement, + posinf=1e10, + neginf=-1e10) self._notifyFittingCallbacks(self.x, self.idx_data[0]) @@ -1303,8 +1307,9 @@ def dataAdded(self, d): self.plot_axes[1] = x_max + x_pad if self.autoscale_y: - y_min = self.idx_data.min() - y_max = self.idx_data.max() + data_for_scale = self.idx_data[finite_mask] + y_min = data_for_scale.min() + y_max = data_for_scale.max() y_pad = (y_max - y_min) * 0.02