Skip to content
2 changes: 1 addition & 1 deletion src/metro/devices/abstract/single_plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@


import metro
from metro.external import pyqtgraph
import pyqtgraph


class Device(metro.WidgetDevice, metro.DisplayDevice):
Expand Down
95 changes: 71 additions & 24 deletions src/metro/devices/display/_fast_plot_native.pyx
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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 = <int>(((<double>x - s.start_x) * s.div_x) + s.offset_x)
out_y = <int>(((<double>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.
Expand All @@ -83,7 +89,11 @@ 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 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

Expand All @@ -99,7 +109,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

Expand All @@ -111,15 +121,59 @@ 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.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 = (<double>y2 - <double>y1) / (<double>x2 - <double>x1)
cdef double slope_x = (<double>x2 - <double>x1) / (<double>y2 - <double>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 * <double>(s.min_x - x1))
elif res_x[i] > s.max_x:
res_x[i] = s.max_x
res_y[i] = (y1 + slope_y * <double>(s.max_x - x1))
if res_y[i] < s.min_y:
res_y[i] = s.min_y
res_x[i] = (x1 + slope_x * <double>(s.min_y - x1))
elif res_y[i] > s.max_y:
res_y[i] = s.max_y
res_x[i] = (x1 + slope_x * <double>(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 = <int>res_x[0]
x2 = <int>res_x[1]
y1 = <int>res_y[0]
y2 = <int>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:
Expand All @@ -146,7 +200,7 @@ cdef void draw_line(surface* s, int x1, int y1, int x2, int y2, int c) nogil:
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:
Expand All @@ -156,7 +210,7 @@ cdef void draw_line(surface* s, int x1, int y1, int x2, int y2, int c) nogil:
x2 = y2

for x from x1 <= x <= x2 by 1:
draw_pixel(s, x, y1, c)
draw_pixel_unsafe(s, x, y1, c)

return

Expand All @@ -173,7 +227,7 @@ cdef void draw_line(surface* s, int x1, int y1, int x2, int y2, int c) nogil:
dx <<= 1

while x != x2:
draw_pixel(s, x, y, c)
draw_pixel_unsafe(s, x, y, c)

if error >= 0:
y += iy
Expand All @@ -182,14 +236,14 @@ cdef void draw_line(surface* s, int x1, int y1, int x2, int y2, int c) nogil:
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
Expand All @@ -198,7 +252,7 @@ cdef void draw_line(surface* s, int x1, int y1, int x2, int y2, int c) nogil:
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,
Expand Down Expand Up @@ -276,7 +330,7 @@ def plot(uintptr_t s_bits,
raise ValueError('len(x) != len(y)')

cdef surface* s = <surface*>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
Expand All @@ -285,20 +339,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 = <int>((<double>data_x[0] - s.start_x) * s.div_x) + s.offset_x
last_y = <int>((<double>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 = <int>((<double>data_x[i] - s.start_x) * s.div_x) \
+ s.offset_x
cur_y = <int>((<double>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
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)
Expand All @@ -308,12 +358,9 @@ def plot(uintptr_t s_bits,

else:
for i in range(1, N):
cur_x = <int>((<double>data_x[i] - s.start_x) * s.div_x) \
+ s.offset_x
cur_y = <int>((<double>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
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)

Expand Down
31 changes: 21 additions & 10 deletions src/metro/devices/display/fast_plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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(
Expand All @@ -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(
Expand All @@ -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(
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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"]
Comment on lines +1238 to +1242
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One could also allow specifying and utilizing the various dropna / fillna etc. available for DataArray. They don't handle inf, though. Also, dropping is tricky.


if self.idx_data.ndim == 1:
self.idx_data = self.idx_data[None, :]
Expand All @@ -1261,13 +1264,20 @@ 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,
posinf=1e10,
neginf=-1e10)

self._notifyFittingCallbacks(self.x, self.idx_data[0])

if self.stacking > 0.0:
Expand Down Expand Up @@ -1297,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

Expand Down
32 changes: 16 additions & 16 deletions src/metro/devices/display/hist2d.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -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)

Expand Down Expand Up @@ -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))

Expand Down Expand Up @@ -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

Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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]))

Expand Down
Loading