From 6a6d1832848542f81c974fce69ae5fb452d293da Mon Sep 17 00:00:00 2001 From: appleb_m Date: Wed, 22 Apr 2026 11:35:11 +0200 Subject: [PATCH] GUI: improved performance for dense compelted heat maps --- .../gui/scan_logic/raster_grid_manager.py | 114 ++++++++++++++---- 1 file changed, 88 insertions(+), 26 deletions(-) diff --git a/src/aare/gui/scan_logic/raster_grid_manager.py b/src/aare/gui/scan_logic/raster_grid_manager.py index 9f53564c..5e3c8fe0 100644 --- a/src/aare/gui/scan_logic/raster_grid_manager.py +++ b/src/aare/gui/scan_logic/raster_grid_manager.py @@ -138,16 +138,16 @@ class RasterGridManager(QObject): 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 + start_anchor = grid.start_pxl(self.__geom) + start_picture = self.__geom.sample_to_picture(start_anchor) + return float(start_picture.x), float(start_picture.y), cell_w, cell_h def _grid_bounds_rect(self, grid: RasterGridRequest) -> QRectF | None: @@ -480,6 +480,10 @@ class RasterGridManager(QObject): if visible_rect is not None and not visible_rect.isEmpty() and not bounds.intersects(visible_rect): return + if values is not None and (cell_w < 3.0 or cell_h < 3.0): + if self._draw_completed_grid_fast(painter, grid, values, alpha, visible_rect): + return + painter.save() painter.setRenderHint(QPainter.RenderHint.Antialiasing, False) @@ -497,42 +501,35 @@ class RasterGridManager(QObject): 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): + for y in range(min_y, max_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): + for x in range(min_x, max_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))) + painter.drawRect(QRect(round(px), round(py), round(cell_w), round(cell_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: + idx = x + y * grid.n_x + if ( + len(values) <= idx + or values[idx] is None + or math.isnan(values[idx]) + or values[idx] < 0 + ): continue - brush = float_to_viridis_brush((value - min_value) / diff, alpha=alpha) - painter.fillRect(QRectF(px, py, draw_w, draw_h), brush) + brush = float_to_viridis_brush((values[idx] - min_value) / diff, alpha=alpha) + painter.fillRect(QRectF(px, py, max(1.0, cell_w), max(1.0, cell_h)), brush) if values is None: painter.setBrush(Qt.BrushStyle.NoBrush) + else: + painter.setPen(QPen(QColor(114, 159, 207, min(255, alpha + 40)), 1, Qt.PenStyle.SolidLine)) + painter.setBrush(Qt.BrushStyle.NoBrush) + painter.drawRect(bounds) painter.restore() @@ -592,6 +589,71 @@ class RasterGridManager(QObject): painter.restore() return True + def _draw_completed_grid_fast( + self, + painter: QPainter, + grid: RasterGridRequest, + values: List[float] | List[int], + alpha: int, + 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 + + valid_values = [x for x in values if x is not None and not math.isnan(x) and x >= 0] + min_value = min(valid_values, default=0) + max_value = max(valid_values, default=1) + diff = 1 if min_value == max_value else (max_value - min_value) + + min_x, max_x, min_y, max_y = self._visible_index_range( + grid, visible_rect, start_x, start_y, cell_w, cell_h + ) + + min_fill_px = 3.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)))) + + painter.save() + painter.setRenderHint(QPainter.RenderHint.Antialiasing, False) + painter.setPen(Qt.PenStyle.NoPen) + + 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) + + 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) + + painter.setPen(QPen(QColor(114, 159, 207, min(255, alpha + 40)), 1, Qt.PenStyle.SolidLine)) + painter.setBrush(Qt.BrushStyle.NoBrush) + painter.drawRect(bounds) + + painter.restor + def _block_value( self, values: List[float] | List[int],