New 'Current energy / lambda' row above the setpoint, fed per DAQ
tick from diffraction.energy_keV; the setpoint row is renamed
'Set Energy (keV)' so 'what is' and 'what to set' are distinct.
Guards energy 0.0 (server's detector-unavailable fallback) because
the wavelength property divides by it. The copied energy row in the
experiment configuration is renamed to match.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Complement of Measured (rotation count <= 1): one click shows
everything still to be done, so it can be selected wholesale and
queued. Plain chip like All - unmeasured rows carry no tint, so
there is no legend color to wear; cross-status marks (queued/
flagged) show like in the Measured view.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
While Moving, the target state sat solid blue and read as already
reached. An opacity pulse (QGraphicsOpacityEffect + QPropertyAnimation)
marks it as in progress; a stylesheet animation would repolish the
button every frame, which the guarded updates in _apply_highlight
exist to avoid.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Per-window installs of the two QApplication-level filters (cursor,
wheel guard) stacked one stale copy per MainWindow; now installed once
per process, parented to the app. closeEvent overrides in the baton
dialogs replaced by finished.connect(timer.stop); keyPressEvent in
VideoGraphicsView replaced by WidgetShortcut QShortcuts; hover
enter/leave overrides in PlayPauseButton replaced by underMouse();
ValueLabel inherits ClickableLabel instead of duplicating its
mousePressEvent. Dead raster-timer branch and a PyQt5-era
event-position fallback deleted; redundant try/except and getattr
guards dropped now that class-level defaults cover pre-init reads.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The locked-banner and Beamline-tab-bar filters were installed mid
__init__, but MainWindow.eventFilter reads sample_lists_tabs, which is
created later - every event delivered in between raised AttributeError
inside the filter ('Error calling Python override of eventFilter()'
spam) and broke widget teardown, cascading errors across CI tests and
wedging the pxiii_bec job. Filters now install late, next to
installEventFilter(self), and the filter guards its attribute reads so
construction/teardown-time events can never raise.
Also fold the staff-gate assertions into test_main_window_init: every
extra MainWindow construction raises the odds of the pre-existing
PySide SystemError flake, so don't build a window just for them.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Diff coverage vs main now 100 percent from the gui suite alone:
non-staff locked banners and both gated-tab popups, remote-close
bookkeeping in update_daq_status, smargon staged moves, camera More
link and Alt-wheel axis fallback, number box relimits, energy row
emit, and the gui.main() banner/graceful-exit path.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Promote the illumination panel's __main__ self-check to a real pytest
file so CI counts it, add busy-overlay render checks (status text
paints, video view routes through it), and cover the motor group's
incomplete-entry and below-min Enter paths. Local diff coverage from
the gui suite alone: 82 percent.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Rename NumberLineEdit.validator to range_validator: the instance attr
shadowed QLineEdit.validator() and broke type checking at call sites.
Annotate the smargon _move_axes dict and cast the stand-in self in the
prediction subscriber test.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
CI / test-with-beamline-plugins (pxi_bec) (push) Skipped
CI / test-with-beamline-plugins (pxii_bec) (push) Skipped
CI / test-with-beamline-plugins (pxiii_bec) (push) Skipped
Chi/Phi boxes in the Smargon tab no longer move on Enter: edits stage
the target (orange), the Move button sends all staged axes at once
(green), and boxes return to neutral when the motor reports arrival.
Out-of-range entries stay red and are never sent; limit tips pop as
tooltips. MotorMoveGroup is opt-in per box so other panels can adopt
the same logic later.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
CI / test-with-beamline-plugins (pxi_bec) (push) Skipped
CI / test-with-beamline-plugins (pxii_bec) (push) Skipped
CI / test-with-beamline-plugins (pxiii_bec) (push) Skipped
Sniff for '{' before JSON-parsing zmq parts: JPEG frames start with 0xff
and raised UnicodeDecodeError on every frame. Stop closing the socket
from the GUI thread in stop(): zmq sockets are not thread-safe and the
concurrent close/recv tripped the signaler.cpp POLLIN assertion (core
dump); run() closes it in its own thread via the 500ms RCVTIMEO.
uv.lock: sync version to 0.8.3.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
draw_busy_badge in busy_overlay renders the title+subtext pill once;
the sample camera and axis video overlays both delegate to it instead
of keeping diverging copies. The axis panel's dot+label status pill and
its per-theme QSS go away with it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The two tabified bottom docks left their switcher tabs stranded at the
window bottom; one Information dock with proper tabs mirrors the Sample
List dock's layout. LogDock becomes the LogPanel widget with mirror
views for the pop-out and a reveal_requested signal so the owner
controls visibility (Ctrl+Shift+L, notifications).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Diff coverage vs main was 77%, under the 80% CI gate; the new camera
help/error/badge code and the theme+layout main_window paths were the
uncovered bulk. Locally: 82%.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The Optional _popout_view is assert-narrowed once after opening; the
unguarded accesses were the last three diff-gate violations.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
New tests for what the port added: status tint priority and
context-dependent chip filters on the dewar/queue model, the
right-button wheel guard (motor protection), PopoutWindow edge maths,
manual-resize fallback, close-hides behavior and DockTitleBar, the
chip drag-drop relabeling, and the console-log pop-out mirror. The
widgets' __main__ self-checks are superseded by these and removed.
Brings PR diff coverage from 70% to 81% (gate: 80%).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The frozen #+status column shifted data columns +1, so the User-column
model tests move from index 5 to 6; ruff import-sort fixes for
main_window and log_panel ride along.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Mechanical swap of hardcoded color literals for styles.py constants and
qcolor() across panels, models, and scan_logic. portrait_mode.py drops
its private palette block; beamline_controls and data_collection_settings
gain object names so the new $border QSS rules can target them. The
status-panel test asserts the palette hex instead of 'red'.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Availability sets (routes + menu shortcuts, Moving/unknown), signal
gating, palette and cursors per availability, red/blue active states,
adaptive label wrapping, left-click hint paths, hover-hint timer
lifecycle, and pending-target clearing. Lifts the PR diff coverage back
over the 80 percent gate (measured 88 percent locally).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A fresh GUI shows only the banners; the Beamline state panel keeps its
own expanded default. A user's saved per-title states still override.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- ruff format and import ordering (main_window, data_collection_settings,
beam_center_panel, beamline_state_panel)
- rename unused unpacked variable in test_title_label
- replace the mousePressEvent monkeypatch on the beamline state title
with a proper eventFilter so basedpyright accepts the diff
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
TitleLabel gains an optional collapsible mode: whole-banner click target,
bare +/- glyph (no pill background), per-title collapsed state persisted
in QSettings, and an expand() helper. Banners are halved to 25px with
plain text + QSS font (H3 margins would clip vertically).
- convert Zoom, Light, Omega, Smargon, Sample camera, Monochromator,
ABR meas. pos., Beam mark, Beam center, Beam size, Dataset path and
Loop centering to collapsible banners
- new collapsible Exp. Config. section wrapping the scan tabs
- move Manual sample from a bottom dock into the left column between
Dataset path and Exp. Config.; Ctrl+M now expands it instead of
raising the dock
- Beamline state banner restyled to match (25px, plain font, bare
glyph, whole banner clickable)
- universal 11px banner gap via tighten_column() shared by both panel
columns and the left column stack; ABR banner spans all grid columns
and file path / manual sample drop side margins so widths align
- abort button sits under the tabs instead of pinned to the bottom
- unit tests for the collapse behavior
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Make the first-grid y padding asymmetric: the bottom (far end of the n_y scan)
can be padded more than the top via daq.auto_raster.grid_padding_fraction_y_
bottom (defaults to grid_padding_fraction_y, so symmetric unless set higher).
The top padding still shifts smargon_top_left; the extra bottom padding only
grows n_y.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the loop_face-only box padding with a single grid padding applied to
the first auto-center grid scan for any loop box (loop_all or loop_face). It
pads each side by a fraction of the grid size (daq.auto_raster.grid_padding_
fraction_x / _y, default 0.15), with a minimum of one cell per side, and
extends *before* cell 0 too so smargon_top_left shifts outward. Manual grid
(get_ml_bounding_box) is unchanged (grid_padding defaults off).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Before the first auto-center raster, zoom so the loop fills at most 1/3 of the
frame, pad loop_face grids, and optionally extend the grid to cover crystals
outside the loop.
- ml_bounding_box: add build_ml_raster_plan() (single prediction via
predict_all_best) returning the grid request + raw loop_all/loop_face boxes +
image size. Grid box is loop_face (else loop_all); loop_face is padded by
daq.auto_raster.loop_face_padding_fraction (15% default); when
daq.auto_raster.include_crystal is set, the grid is unioned with any crystal
boxes lying beyond the loop. get_ml_bounding_box (manual button / endpoint)
is unchanged.
- config: add zoom_for_pixel_to_mm(), the inverse of pixel_to_mm.
- raster service execute_auto_center: after the first detection (reused, no
re-predict), pick loop_all if detected and not clipped at the frame edge else
loop_face, compute the zoom that makes its larger side 1/3 of the frame, clamp
to daq.hardware.zoom_min/max, set zoom and re-expose.
- x06da.yaml: zoom_min/max + daq.auto_raster block.
- tests for box selection, padding, crystal union and the clipped-loop_all rule.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- promote crystal score to its own RasterGridMetric.RASTER_SCORE; first and
default metric in the GUI dropdown; restore PR to original "Profile Radius"
- send per-cell raster score to AareDB via RasterPayloadModel
- split raster_highest_score (compute_crystal_score_array) from
raster_centre_of_mass (restored to ndimage.center_of_mass); use score-based
targeting for where-to-collect in both simulation and real mode
- add tests covering compute_crystal_score_array and raster_highest_score
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>