From 29ae65a100bc8a4f676ae77cec406a4606895385 Mon Sep 17 00:00:00 2001 From: Dawn Date: Mon, 14 Sep 2026 10:28:50 +0200 Subject: [PATCH] feat: show the mounted sample name on the camera view A "Currently mounted: " HUD line in the top-left corner of the sample camera, driven by the DAQ status (clears when nothing is mounted). Painted like the other camera HUDs and sized through the text zoom, which QSS cannot reach here. Co-Authored-By: Claude Fable 5 --- src/aare/gui/widgets/camera_image.py | 34 ++++++++++++++++++++++++++++ tests/unit/gui/test_camera_image.py | 27 ++++++++++++++++++++++ 2 files changed, 61 insertions(+) diff --git a/src/aare/gui/widgets/camera_image.py b/src/aare/gui/widgets/camera_image.py index 03454282..04705f8b 100644 --- a/src/aare/gui/widgets/camera_image.py +++ b/src/aare/gui/widgets/camera_image.py @@ -59,6 +59,7 @@ from aare.gui.styles import ( THEME_SUNSET, TOOLTIP_TEXT, WHITE, + font_scale, qcolor, ) from aare.gui.widgets.busy_overlay import ( @@ -130,6 +131,7 @@ class SampleCameraImageLabel(QGraphicsView): self._tell_state = None self._auto_centering = False self._busy_overlay_style: BusyOverlayStyle | None = None + self._mounted_sample_name: str | None = None self._geom = geom self._bookmarks: SmargonBookmarkList = SmargonBookmarkList() @@ -459,6 +461,7 @@ class SampleCameraImageLabel(QGraphicsView): self._draw_detections(painter, rect) self._draw_target_point(painter) self._draw_overlay_legend(painter) + self._draw_mounted_sample(painter) self._draw_hover_hud(painter) self._draw_help_overlay(painter) @@ -803,6 +806,11 @@ class SampleCameraImageLabel(QGraphicsView): self._bounding_box = s.box self._tell_state = s.tell_state + new_name = s.sample.sample_name if s.sample is not None else None + if new_name != self._mounted_sample_name: + self._mounted_sample_name = new_name + self.viewport().update() + new_session_state = s.session.session if hasattr(s, "session") else None if new_session_state != self._session_state: self._session_state = new_session_state @@ -1269,6 +1277,32 @@ class SampleCameraImageLabel(QGraphicsView): label = f"{bar_um / 1000.0:g} mm" if bar_um >= 1000.0 else f"{bar_um:g} µm" return bar_um, label + def _draw_mounted_sample(self, painter: QPainter): + # Top-left HUD line (legend sits bottom-left, scale bar bottom-right): + # which sample is on the gonio, readable without leaving the camera. + if not self._mounted_sample_name: + return + + painter.save() + painter.resetTransform() + painter.setRenderHint(QPainter.RenderHint.Antialiasing, True) + + font = QFont() + # Follows the Ctrl+plus/minus text zoom — painted HUDs bypass QSS. + font.setPointSize(round(10 * font_scale())) + font.setBold(True) + painter.setFont(font) + fm = QFontMetrics(font) + + margin = 18 + text = f"Currently mounted: {self._mounted_sample_name}" + baseline = margin + fm.ascent() + painter.setPen(QPen(qcolor(SHADOW, 200))) + painter.drawText(QPointF(margin + 1, baseline + 1), text) + painter.setPen(QPen(qcolor(WHITE))) + painter.drawText(QPointF(margin, baseline), text) + painter.restore() + def _draw_hover_hud(self, painter: QPainter): # Bottom-right HUD: grey scale bar over the hovered pixel coordinates. if self._hover_pos is None or self.pixmap_item is None: diff --git a/tests/unit/gui/test_camera_image.py b/tests/unit/gui/test_camera_image.py index be91b41a..bf28c75e 100644 --- a/tests/unit/gui/test_camera_image.py +++ b/tests/unit/gui/test_camera_image.py @@ -297,3 +297,30 @@ def test_unknown_session_paints_the_viewing_mode_badge(camera): assert camera._busy_overlay_style is None camera.grab() assert camera._session_badge_rect is not None + + +def test_mounted_sample_hud_follows_status(camera): + from aarecommon.models.models import DewarAddress, SampleShortInfo + + # No sample in the status -> no HUD line, draw path skips cleanly. + assert camera._mounted_sample_name is None + camera.grab() + + sample = SampleShortInfo( + db_id=7, + puck_name="puck1", + dewar_name="dewar", + sample_name="lyso_007", + run_number=1, + user="p123", + pin=1, + location=DewarAddress(segment="A", pos=1), + ) + status = _status(busy=False, session=SessionsStateEnum.OwnedByYou) + camera.update_daq_status(status.model_copy(update={"sample": sample})) + assert camera._mounted_sample_name == "lyso_007" + camera.grab() # paints the top-left "Currently mounted:" line + + # Unmount (sample gone from the status) clears the line again. + camera.update_daq_status(status) + assert camera._mounted_sample_name is None