feat: show the mounted sample name on the camera view

A "Currently mounted: <sample_name>" 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 <noreply@anthropic.com>
This commit is contained in:
2026-09-14 10:29:04 +02:00
co-authored by Claude Fable 5
parent bd74ac47bc
commit 29ae65a100
2 changed files with 61 additions and 0 deletions
+34
View File
@@ -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:
+27
View File
@@ -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