From 5db5e228af7bfd36309f5cdbcc931bb29280c4f8 Mon Sep 17 00:00:00 2001 From: semantic-release Date: Tue, 8 Sep 2026 13:01:08 +0000 Subject: [PATCH] 0.23.1 Automatically generated by python-semantic-release --- CHANGELOG.md | 148 +++++++++++++++++++++++++++++++++++++++++++++++++ pyproject.toml | 2 +- 2 files changed, 149 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c8a65ff..25439e79 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,154 @@ # CHANGELOG +## v0.23.1 (2026-09-08) + +### Bug Fixes + +- Bound the sample camera SUB receive queue + ([`4c3b0ce`](https://gitea.psi.ch/mx/AareDAQ/commit/4c3b0ce64d59fa401913158a15d6a2c33232d56d)) + +The socket used library defaults, so a GUI that stalls builds its own private FIFO of up to 1000 + messages — 20 s of video at 50 Hz — plus whatever the autotuned kernel receive buffer holds on + top. PUB/SUB pipes are per-subscriber, which is why only aareGUI lagged while the other receivers + stayed current. + +RCVHWM=4 makes the producer drop frames for this subscriber once it is muted, instead of queueing + them, and a fixed RCVBUF stops the kernel hiding a further few dozen frames behind the HWM. + Measured against a flooding publisher, the buffered backlog drops from 1000 frames to 4. + +Co-Authored-By: Claude Opus 5 (1M context) + +Claude-Session: https://claude.ai/code/session_01DVqbXPoeHyqrQq5Vc8EcYP + +- Build the sample camera QPixmap on the GUI thread + ([`c842856`](https://gitea.psi.ch/mx/AareDAQ/commit/c842856fe0482e93374c561f3fb83de7d39e59fe)) + +QPixmap is a GUI-thread-only class in Qt; the worker was constructing one per frame via + QPixmap.fromImage. It happens to work with the raster backend, which is why this has not bitten + us, but it is not supported and the guarantee is not ours to rely on. + +The subscriber now emits QImage — which is explicitly safe to build and move between threads — and + _on_sample_camera_frame converts once on the GUI thread before handing the pixmap to the visible + view. No extra copy: the .copy() that detaches the QImage from the numpy buffer was already there. + +Verified end to end against a real PUB socket: the payload delivered is QImage, the slot runs only + on the main thread, and frames still decode. + +Co-Authored-By: Claude Opus 5 (1M context) + +Claude-Session: https://claude.ai/code/session_01DVqbXPoeHyqrQq5Vc8EcYP + +- Drain the sample camera SUB socket to the newest frame + ([`d63b4ad`](https://gitea.psi.ch/mx/AareDAQ/commit/d63b4ad1799c5c97eb6992eebeb56b77b23f7253)) + +The SUB socket is strictly FIFO, so once the GUI falls behind at 50 Hz it stays behind: every recv + returns the oldest queued frame and the lag never recovers on its own. zmq.CONFLATE cannot fix + this because it keeps only the last *part* of a message, which shreds the multipart header+payload + frames the producer sends. + +recv_multipart is atomic, though — a non-blocking recv either yields a whole message or raises Again + — so draining in a loop is a multipart-safe conflate. The drain runs after the previous frame's + decode, so it discards exactly the backlog that built up while we were busy, and the socket is + consumed at line rate regardless of how long a frame takes to decode. + +Verified against a real PUB/SUB pair: 100 queued multipart messages collapse to the newest one with + both parts intact, and RCVTIMEO is still honoured when the queue is empty. + +Co-Authored-By: Claude Opus 5 (1M context) + +Claude-Session: https://claude.ai/code/session_01DVqbXPoeHyqrQq5Vc8EcYP + +- Pace sample camera frames to what the GUI can paint + ([`a4a8b80`](https://gitea.psi.ch/mx/AareDAQ/commit/a4a8b8029abdb14d1aafd380486562216af369e4)) + +image is a queued cross-thread signal and Qt's event queue has no high-water mark, so this queue + grew without bound independently of ZMQ: with a 50 Hz feed and a GUI painting slower, the + displayed frame fell further behind for as long as the stream ran. Measured on a stand-in at 15 ms + decode / 25 ms paint, the displayed frame aged from 40 ms to 2010 ms over three seconds and kept + going. + +Two changes: + +- Emit once and fan out on the GUI thread to whichever view is actually on screen, rather than + connecting all three SampleCameraImageLabels. Two of the three are always hidden (tab / compact + page / portrait page), so this is one repaint per frame instead of three. + +- Cap the frames in flight and drop rather than queue past it. The cap is 2, not 1, so a frame can + be queued while the GUI paints the other: at 1 the GUI idles waiting for the next decode (33.3 fps + displayed), at 2 it does not (40.0 fps) for one extra frame of latency. Both stay bounded — 40 ms + and 62 ms respectively, flat over the run. + +Co-Authored-By: Claude Opus 5 (1M context) + +Claude-Session: https://claude.ai/code/session_01DVqbXPoeHyqrQq5Vc8EcYP + +- Satisfy basedpyright on the Optional subscriber and socket + ([`c0b0994`](https://gitea.psi.ch/mx/AareDAQ/commit/c0b0994feddc8a51d49fef66f601a183d16b7f4d)) + +CI lint failed on reportOptionalMemberAccess: prediction_thread is None when the GUI runs without a + sample feed, and _sock is nulled by run()'s cleanup, so both attributes are Optional to the + checker even though the flagged call sites cannot see None at runtime. Guard the slot and bind the + socket to a local asserted non-None in _recv_latest. + +Co-Authored-By: Claude Fable 5.1 + +### Code Style + +- One PSI red for every busy camera badge, blue only for robot cooling + ([`7c1efe4`](https://gitea.psi.ch/mx/AareDAQ/commit/7c1efe43847d5e0216b8889b84f82eb39a77551a)) + +AUTO CENTERING, ROBOT MOUNTING/UNMOUNTING/DRYING and BEAMLINE BUSY each had their own accent + (purple, red, orange, yellow, maroon), so the sample camera read as a per-activity rainbow instead + of one "hands off" signal. All busy states now share BUSY_PSI_RED; ROBOT COOLING keeps blue + because it is the long, harmless phase the operator should read as "wait". + +The five near-identical BusyOverlayStyle blocks collapse into one _animated() helper plus a text + lookup, and the now-unused purple/orange/ red-badge constants are dropped from styles.py. + +Co-Authored-By: Claude Fable 5.1 + +- Paint the unknown-session badge with the viewing-mode style + ([`dbd5b90`](https://gitea.psi.ch/mx/AareDAQ/commit/dbd5b9078f6223ff81bc5af1448fa952a09ea141)) + +The sample camera drew its own gold/orange/red "Session Vacant / Baton Requested / Guest Mode" box + whenever the busy overlay had nothing to show. Since the busy-style builder already receives the + session state, that box only ever appeared while the session was still unknown (no status yet, or + one without a session), so it flashed a different color and shape than the yellow "Viewing mode, + click here to grab the baton" badge every known not-owned state gets. + +Hoist that badge into VIEWING_MODE_STYLE and paint it from both paths; the hover fill is shared + through _badge_fill. The three MARK_TOOLTIP constants had no other users and are dropped. + +Co-Authored-By: Claude Fable 5.1 + +### Testing + +- Cover the sample camera pacing, the newest-frame drain and the frame slot + ([`c3e7b9a`](https://gitea.psi.ch/mx/AareDAQ/commit/c3e7b9a2cc2670e6a4cf83e5e00f6aabc0a5918b)) + +CI's diff-coverage gate sat at 29% because the ZMQ fixes live in code no test reached: the + subscriber's socket setup, _emit_image/notify_frame_ displayed, _recv_latest and the run() frame + path, plus the main window's frame slot. The subscriber now runs against a real inproc socket (the + drain and the in-flight cap are the fix, so a mock would prove nothing); run() is driven with a + stubbed _recv_latest that hands over one JPEG and then stops. The busy overlay gets a check that + cooling is the only blue busy state. + +The subscriber fixture takes qapp so no QImage is built before the QApplication exists. + +Co-Authored-By: Claude Fable 5.1 + +- Format the mocked token and cast the tell-state stand-in for basedpyright + ([`ccb71bf`](https://gitea.psi.ch/mx/AareDAQ/commit/ccb71bf43fb511eed4bde5d594a032075cc48734)) + +Co-Authored-By: Claude Fable 5.1 + +- Route the tell-state stand-in through object for basedpyright + ([`a56457d`](https://gitea.psi.ch/mx/AareDAQ/commit/a56457d03bb6fe7f4f1e3bef0fec64703f46a226)) + +Co-Authored-By: Claude Fable 5.1 + + ## v0.23.0 (2026-09-08) ### Bug Fixes diff --git a/pyproject.toml b/pyproject.toml index 124fa5cb..63e7fb8c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "aaredaq" -version = "0.23.0" +version = "0.23.1" description = "AareDAQ (with GUI)" readme = "README.md" requires-python = ">=3.11"