GUI: Improved performance of GUI with large reaster grids by throttle Raster grid drawing to 25 Hz, change geometry of the drawn grid only to imrpove speed - draw less lines particulalrly when cells are less than 2 px.
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import math
|
||||
from enum import Enum
|
||||
|
||||
from PySide6.QtCore import QObject, Signal, Slot, QPointF
|
||||
from PySide6.QtCore import QObject, Signal, Slot, QPointF, QRectF, QLineF
|
||||
from PySide6.QtGui import QPainter, QPen, QColor, QBrush
|
||||
from PySide6.QtCore import Qt, QRect
|
||||
from typing import List, Tuple
|
||||
@@ -74,6 +74,7 @@ def normalize_angle(angle_deg: float) -> float:
|
||||
normalized_angle = math.fmod(angle_deg + 180, 360) - 180.0
|
||||
return normalized_angle
|
||||
|
||||
|
||||
class RasterGridManager(QObject):
|
||||
viewer_track_online = Signal()
|
||||
|
||||
@@ -133,6 +134,54 @@ class RasterGridManager(QObject):
|
||||
return True
|
||||
return False
|
||||
|
||||
def _grid_pixel_geometry(self, grid: RasterGridRequest) -> tuple[float, float, float, float] | None:
|
||||
if not self._is_grid_visible(grid):
|
||||
return None
|
||||
|
||||
start_anchor = grid.start_pxl(self.__geom)
|
||||
start_picture = self.__geom.sample_to_picture(start_anchor)
|
||||
cell = grid.grid_size_pxl(self.__geom)
|
||||
|
||||
cell_w = float(cell.x)
|
||||
cell_h = float(cell.y)
|
||||
|
||||
if cell_w <= 0 or cell_h <= 0:
|
||||
return None
|
||||
|
||||
return float(start_picture.x), float(start_picture.y), cell_w, cell_h
|
||||
|
||||
def _grid_bounds_rect(self, grid: RasterGridRequest) -> QRectF | None:
|
||||
geo = self._grid_pixel_geometry(grid)
|
||||
if geo is None:
|
||||
return None
|
||||
|
||||
start_x, start_y, cell_w, cell_h = geo
|
||||
return QRectF(
|
||||
start_x,
|
||||
start_y,
|
||||
cell_w * grid.n_x,
|
||||
cell_h * grid.n_y,
|
||||
)
|
||||
|
||||
def _visible_index_range(
|
||||
self,
|
||||
grid: RasterGridRequest,
|
||||
visible_rect: QRectF | None,
|
||||
start_x: float,
|
||||
start_y: float,
|
||||
cell_w: float,
|
||||
cell_h: float,
|
||||
) -> tuple[int, int, int, int]:
|
||||
if visible_rect is None or visible_rect.isEmpty():
|
||||
return 0, grid.n_x, 0, grid.n_y
|
||||
|
||||
min_x = max(0, int(math.floor((visible_rect.left() - start_x) / cell_w)) - 1)
|
||||
max_x = min(grid.n_x, int(math.ceil((visible_rect.right() - start_x) / cell_w)) + 1)
|
||||
min_y = max(0, int(math.floor((visible_rect.top() - start_y) / cell_h)) - 1)
|
||||
max_y = min(grid.n_y, int(math.ceil((visible_rect.bottom() - start_y) / cell_h)) + 1)
|
||||
|
||||
return min_x, max_x, min_y, max_y
|
||||
|
||||
@Slot(RasterGridRequest)
|
||||
def update_active_grid_request(self, grid: RasterGridRequest):
|
||||
self.__active_grid.smargon_top_left = grid.smargon_top_left
|
||||
@@ -140,10 +189,12 @@ class RasterGridManager(QObject):
|
||||
self.__active_grid.grid_size_mm = grid.grid_size_mm
|
||||
self.__active_grid.n_x = grid.n_x
|
||||
self.__active_grid.n_y = grid.n_y
|
||||
self.grid_scan_size_changed.emit(self.__active_grid.n_x,
|
||||
self.__active_grid.n_y,
|
||||
self.__active_grid.grid_size_mm.x,
|
||||
self.__active_grid.grid_size_mm.y)
|
||||
self.grid_scan_size_changed.emit(
|
||||
self.__active_grid.n_x,
|
||||
self.__active_grid.n_y,
|
||||
self.__active_grid.grid_size_mm.x,
|
||||
self.__active_grid.grid_size_mm.y,
|
||||
)
|
||||
|
||||
@Slot(DAQStatusModel)
|
||||
def update_daq_status(self, s: DAQStatusModel):
|
||||
@@ -186,17 +237,18 @@ class RasterGridManager(QObject):
|
||||
)
|
||||
|
||||
self.__active_grid.omega_deg = self.__geom.omega_deg
|
||||
self.grid_scan_size_changed.emit(self.__active_grid.n_x,
|
||||
self.__active_grid.n_y,
|
||||
self.__active_grid.grid_size_mm.x,
|
||||
self.__active_grid.grid_size_mm.y)
|
||||
self.grid_scan_size_changed.emit(
|
||||
self.__active_grid.n_x,
|
||||
self.__active_grid.n_y,
|
||||
self.__active_grid.grid_size_mm.x,
|
||||
self.__active_grid.grid_size_mm.y,
|
||||
)
|
||||
|
||||
def move_active_grid(self, delta: QPointF):
|
||||
if not self._is_grid_visible(self.__active_grid):
|
||||
return
|
||||
|
||||
delta_pxl = Coordinate(x=delta.x(), y=delta.y())
|
||||
|
||||
delta_mm = self.__geom.smargon_nudge(delta_pxl * self.__geom.pixel_in_mm)
|
||||
|
||||
self.__active_grid.smargon_top_left = SmargonCoordinate(
|
||||
@@ -209,9 +261,12 @@ class RasterGridManager(QObject):
|
||||
def clear_active_grid(self):
|
||||
self.__active_grid.n_x = 0
|
||||
self.__active_grid.n_y = 0
|
||||
self.grid_scan_size_changed.emit(0,0,
|
||||
self.__active_grid.grid_size_mm.x,
|
||||
self.__active_grid.grid_size_mm.y)
|
||||
self.grid_scan_size_changed.emit(
|
||||
0,
|
||||
0,
|
||||
self.__active_grid.grid_size_mm.x,
|
||||
self.__active_grid.grid_size_mm.y,
|
||||
)
|
||||
|
||||
def get_grid_coord(self, grid: RasterGridRequest, point: QPointF) -> Tuple[int, int]:
|
||||
point_bl = self.__geom.picture_to_sample(Coordinate(x=point.x(), y=point.y()))
|
||||
@@ -363,22 +418,29 @@ class RasterGridManager(QObject):
|
||||
return obj.spots_ice / obj.spots_low_res
|
||||
|
||||
def draw_grid(self, painter: QPainter, alpha: int = 127):
|
||||
self._draw_grid(painter, self.__active_grid)
|
||||
if alpha < 0 or alpha > 255:
|
||||
return
|
||||
|
||||
visible_rect = painter.clipBoundingRect()
|
||||
if visible_rect.isEmpty():
|
||||
visible_rect = QRectF()
|
||||
|
||||
self._draw_grid(painter, self.__active_grid, None, alpha, visible_rect)
|
||||
|
||||
for i in self.__completed_grids:
|
||||
v = None
|
||||
match self.__metric:
|
||||
case RasterGridMetric.BKG:
|
||||
v = [obj.bkg for obj in i.result.images]
|
||||
case RasterGridMetric.SPOTS_LOW_RES:
|
||||
v = [obj.spots_low_res for obj in i.result.images]
|
||||
case RasterGridMetric.SPOTS:
|
||||
v = [obj.spots for obj in i.result.images]
|
||||
case RasterGridMetric.BKG:
|
||||
v = [obj.bkg for obj in i.result.images]
|
||||
case RasterGridMetric.INDEXING:
|
||||
v = [obj.index for obj in i.result.images]
|
||||
case RasterGridMetric.PR:
|
||||
v = [obj.pr for obj in i.result.images]
|
||||
v = [obj.index / max(obj.spots_low_res, 1) for obj in i.result.images]
|
||||
case RasterGridMetric.BFACTOR:
|
||||
v = [obj.b for obj in i.result.images]
|
||||
case RasterGridMetric.SPOTS_LOW_RES:
|
||||
v = [obj.spots_low_res for obj in i.result.images]
|
||||
case RasterGridMetric.RES:
|
||||
v = [obj.res for obj in i.result.images]
|
||||
case RasterGridMetric.SPOTS_ICE:
|
||||
@@ -387,43 +449,172 @@ class RasterGridManager(QObject):
|
||||
v = [self.spot_ice_ratio(obj) for obj in i.result.images]
|
||||
case RasterGridMetric.SPOTS_INDEXED:
|
||||
v = [obj.spots_indexed for obj in i.result.images]
|
||||
self._draw_grid(painter, i.request, v, alpha)
|
||||
self._draw_grid(painter, i.request, v, alpha, visible_rect)
|
||||
|
||||
def _draw_grid(self, painter: QPainter, grid: RasterGridRequest, values: List[float] | List[int] | None = None,
|
||||
alpha : int = 127):
|
||||
def _draw_grid(
|
||||
self,
|
||||
painter: QPainter,
|
||||
grid: RasterGridRequest,
|
||||
values: List[float] | List[int] | None = None,
|
||||
alpha: int = 127,
|
||||
visible_rect: QRectF | None = None,
|
||||
):
|
||||
if not self._is_grid_visible(grid):
|
||||
return
|
||||
if alpha < 0 or alpha > 255:
|
||||
return
|
||||
|
||||
if values is None:
|
||||
if self._draw_active_grid_fast(painter, grid, visible_rect):
|
||||
return
|
||||
|
||||
geo = self._grid_pixel_geometry(grid)
|
||||
if geo is None:
|
||||
return
|
||||
|
||||
start_x, start_y, cell_w, cell_h = geo
|
||||
bounds = self._grid_bounds_rect(grid)
|
||||
if bounds is None:
|
||||
return
|
||||
|
||||
if visible_rect is not None and not visible_rect.isEmpty() and not bounds.intersects(visible_rect):
|
||||
return
|
||||
|
||||
painter.save()
|
||||
painter.setRenderHint(QPainter.RenderHint.Antialiasing, False)
|
||||
|
||||
if values is not None:
|
||||
painter.setPen(Qt.PenStyle.NoPen)
|
||||
min_value = min((x for x in values if x is not None and not math.isnan(x)), default=0)
|
||||
max_value = max((x for x in values if x is not None and not math.isnan(x)), default=1)
|
||||
if min_value == max_value:
|
||||
diff = 1
|
||||
else:
|
||||
diff = max_value - min_value
|
||||
|
||||
diff = 1 if min_value == max_value else (max_value - min_value)
|
||||
else:
|
||||
painter.setPen(QPen(QColor(114, 159, 207), 2, Qt.PenStyle.SolidLine))
|
||||
painter.setPen(QPen(QColor(114, 159, 207), 1, Qt.PenStyle.SolidLine))
|
||||
min_value = 0
|
||||
diff = 1
|
||||
|
||||
g = grid.grid_size_pxl(self.__geom)
|
||||
c0 = grid.start_pxl(self.__geom)
|
||||
for x in range(grid.n_x):
|
||||
for y in range(grid.n_y):
|
||||
pxl = x + y * grid.n_x
|
||||
if values is None or len(values) <= pxl or values[pxl] is None or math.isnan(values[pxl]) or values[pxl] < 0:
|
||||
painter.setBrush(Qt.BrushStyle.NoBrush)
|
||||
else:
|
||||
painter.setBrush(QBrush(float_to_viridis_brush((values[pxl] - min_value) / diff, alpha=alpha)))
|
||||
c = self.__geom.sample_to_picture(c0 + Coordinate(x=x * grid.grid_size_mm.x, y=y * grid.grid_size_mm.y))
|
||||
painter.drawRect(QRect(round(c.x), round(c.y), round(g.x), round(g.y)))
|
||||
min_x, max_x, min_y, max_y = self._visible_index_range(
|
||||
grid, visible_rect, start_x, start_y, cell_w, cell_h
|
||||
)
|
||||
|
||||
stride_x = 1
|
||||
stride_y = 1
|
||||
if values is not None:
|
||||
min_fill_px = 2.0
|
||||
stride_x = max(1, int(math.ceil(min_fill_px / max(cell_w, 1e-9))))
|
||||
stride_y = max(1, int(math.ceil(min_fill_px / max(cell_h, 1e-9))))
|
||||
|
||||
for y in range(min_y, max_y, stride_y):
|
||||
py = start_y + y * cell_h
|
||||
draw_h = max(1.0, cell_h * stride_y)
|
||||
|
||||
for x in range(min_x, max_x, stride_x):
|
||||
px = start_x + x * cell_w
|
||||
draw_w = max(1.0, cell_w * stride_x)
|
||||
|
||||
if values is None:
|
||||
painter.setBrush(Qt.BrushStyle.NoBrush)
|
||||
painter.drawRect(QRect(round(px), round(py), round(draw_w), round(draw_h)))
|
||||
continue
|
||||
|
||||
value = self._block_value(
|
||||
values,
|
||||
grid.n_x,
|
||||
x,
|
||||
min(x + stride_x, grid.n_x),
|
||||
y,
|
||||
min(y + stride_y, grid.n_y),
|
||||
)
|
||||
if value is None:
|
||||
continue
|
||||
|
||||
brush = float_to_viridis_brush((value - min_value) / diff, alpha=alpha)
|
||||
painter.fillRect(QRectF(px, py, draw_w, draw_h), brush)
|
||||
|
||||
if values is None:
|
||||
painter.setBrush(Qt.BrushStyle.NoBrush)
|
||||
|
||||
painter.restore()
|
||||
|
||||
def _draw_active_grid_fast(
|
||||
self,
|
||||
painter: QPainter,
|
||||
grid: RasterGridRequest,
|
||||
visible_rect: QRectF | None,
|
||||
) -> bool:
|
||||
geo = self._grid_pixel_geometry(grid)
|
||||
if geo is None:
|
||||
return False
|
||||
|
||||
start_x, start_y, cell_w, cell_h = geo
|
||||
bounds = self._grid_bounds_rect(grid)
|
||||
if bounds is None:
|
||||
return False
|
||||
|
||||
if visible_rect is not None and not visible_rect.isEmpty() and not bounds.intersects(visible_rect):
|
||||
return True
|
||||
|
||||
painter.save()
|
||||
painter.setRenderHint(QPainter.RenderHint.Antialiasing, False)
|
||||
painter.setPen(QPen(QColor(114, 159, 207), 1, Qt.PenStyle.SolidLine))
|
||||
painter.setBrush(Qt.BrushStyle.NoBrush)
|
||||
|
||||
painter.drawRect(bounds)
|
||||
|
||||
min_spacing_px = 4.0
|
||||
stride_x = max(1, int(math.ceil(min_spacing_px / max(cell_w, 1e-9))))
|
||||
stride_y = max(1, int(math.ceil(min_spacing_px / max(cell_h, 1e-9))))
|
||||
|
||||
min_ix, max_ix, min_iy, max_iy = self._visible_index_range(
|
||||
grid, visible_rect, start_x, start_y, cell_w, cell_h
|
||||
)
|
||||
|
||||
vertical_lines: list[QLineF] = []
|
||||
horizontal_lines: list[QLineF] = []
|
||||
|
||||
x_start = max(1, min_ix)
|
||||
x_end = min(grid.n_x, max_ix)
|
||||
for x in range(x_start, x_end, stride_x):
|
||||
px = start_x + x * cell_w
|
||||
vertical_lines.append(QLineF(px, bounds.top(), px, bounds.bottom()))
|
||||
|
||||
y_start = max(1, min_iy)
|
||||
y_end = min(grid.n_y, max_iy)
|
||||
for y in range(y_start, y_end, stride_y):
|
||||
py = start_y + y * cell_h
|
||||
horizontal_lines.append(QLineF(bounds.left(), py, bounds.right(), py))
|
||||
|
||||
if vertical_lines:
|
||||
painter.drawLines(vertical_lines)
|
||||
if horizontal_lines:
|
||||
painter.drawLines(horizontal_lines)
|
||||
|
||||
painter.restore()
|
||||
return True
|
||||
|
||||
def _block_value(
|
||||
self,
|
||||
values: List[float] | List[int],
|
||||
grid_nx: int,
|
||||
x0: int,
|
||||
x1: int,
|
||||
y0: int,
|
||||
y1: int,
|
||||
) -> float | None:
|
||||
best = None
|
||||
for y in range(y0, y1):
|
||||
row_offset = y * grid_nx
|
||||
for x in range(x0, x1):
|
||||
idx = row_offset + x
|
||||
if idx >= len(values):
|
||||
continue
|
||||
value = values[idx]
|
||||
if value is None or math.isnan(value) or value < 0:
|
||||
continue
|
||||
if best is None or value > best:
|
||||
best = float(value)
|
||||
return best
|
||||
|
||||
@Slot(float)
|
||||
def update_exposure_time(self, exp_time_s: float):
|
||||
self.__active_grid.exp_time_s = exp_time_s
|
||||
@@ -460,11 +651,8 @@ class RasterGridManager(QObject):
|
||||
if last_raster is not None and last_raster.result.file_prefix is not None:
|
||||
com = last_raster.centre_of_mass
|
||||
logger.info(f"COM: {com}")
|
||||
if com:
|
||||
cell = com.max_image
|
||||
else:
|
||||
cell = 0
|
||||
self.image_selected.emit("http://sls-gpu-001:8080",cell) #last_raster.result.file_prefix, 0)
|
||||
cell = com.max_image if com else 0
|
||||
self.image_selected.emit("http://sls-gpu-001:8080", cell)
|
||||
self.completed_grid_updated.emit()
|
||||
|
||||
@Slot(int, bool)
|
||||
|
||||
@@ -74,6 +74,8 @@ class SampleCameraImageLabel(QGraphicsView):
|
||||
self.__sam_cam = SampleCameraSettings(exposure=0.1, gain=100.0)
|
||||
self.__is_daq_busy = False
|
||||
self.__camera_available = True
|
||||
self.__last_grid_update_ts = 0.0
|
||||
self.__grid_update_min_interval_s = 1.0 / 25.0
|
||||
|
||||
self.__geom = geom
|
||||
self.__bookmarks: SmargonBookmarkList = SmargonBookmarkList()
|
||||
@@ -118,7 +120,7 @@ class SampleCameraImageLabel(QGraphicsView):
|
||||
self.scene.addItem(self.pixmap_item)
|
||||
|
||||
self.setFrameShape(QFrame.Shape.NoFrame)
|
||||
self.setRenderHints(QPainter.RenderHint.Antialiasing)
|
||||
self.setRenderHints(QPainter.RenderHint.TextAntialiasing)
|
||||
|
||||
# Create a timer for throttling wheel events
|
||||
self.wheel_event_timer = QTimer()
|
||||
@@ -367,6 +369,11 @@ class SampleCameraImageLabel(QGraphicsView):
|
||||
|
||||
|
||||
def _update_grid(self):
|
||||
now = time.monotonic()
|
||||
if (now - self.__last_grid_update_ts) < self.__grid_update_min_interval_s:
|
||||
return
|
||||
self.__last_grid_update_ts = now
|
||||
|
||||
match self.__state:
|
||||
case SampleCameraImageState.DRAWING_RASTER_GRID:
|
||||
self.switch_raster_grid.emit()
|
||||
@@ -568,7 +575,7 @@ class SampleCameraImageLabel(QGraphicsView):
|
||||
# If no pixmap item exists (rare case), create one
|
||||
self.pixmap_item = QGraphicsPixmapItem(pixmap)
|
||||
self.scene.addItem(self.pixmap_item)
|
||||
self.update() # Request an update to redraw the view
|
||||
self.viewport().update() # Request an update to redraw the view
|
||||
|
||||
@Slot(DAQStatusModel)
|
||||
def update_daq_status(self, s: DAQStatusModel):
|
||||
|
||||
Reference in New Issue
Block a user