Spectrometer viewer: alignment & centroiding tabs, streaming reliability fixes, offline analysis tools#13
Merged
Merged
Conversation
At the end of each acquisition, save three additional files alongside
the existing PNG, CSV, and JSON:
- {stem}_energy.csv: 1D eV axis derived from the UI calibration
spinboxes (eV @ x=0, eV/pixel), one value per x-pixel.
- {stem}_time_ns.csv: 1D time axis in nanoseconds from the TDC trigger
(t=0), one value per time bin, using t_delta_ns from ZMQ metadata
with a fallback to reconstruct from tdc_frequency_hz and n_bins.
- {stem}_uuid.txt: 36-char UUID4 that matches the scan_name broadcast
by the streaming server via ZMQ, enabling downstream services to
correlate saved files with the data stream.
To support the UUID linkage, app.py now generates scan_name as a plain
uuid.uuid4() string (replacing the datetime+8-hex format) and includes
it in every flush's ZMQ metadata so the UI can read it back at save
time.
Simulator Start being sent when cli starts up Stop not being sent when DAQ time runs through Stop being sent in between event messages if buffer somewhat full during disconnect (stop trigger) Flush numbers not reset and scan UUID not updated during consecutive starts in the same cli session start N produced one fewer event message than expected ZMQ start message not sent in real-acquisition mode with zero counts
Factory fixture spins up the streaming server on dynamic ports with pre-subscribed ZMQ data + heartbeat sockets, and provides make_simulator() / spawn_simulator_cli() so TDC / cps / flush-interval live in one place and can't drift between server and simulator. Rewrites the four integration tests in test_main_workflow and test_start_stop_messages to use the rig, and tightens assertions: scan_name cohesion across start/event/stop, contiguous 1..N flush_number, stop.total_flushes == len(events), flush_number restart at 1 on reconnect.
Reproduce the Serval+luna-iterator upstream-batching symptom described in solution.md as a CI-only regression test. Adds an opt-in bolus- batching mode to the simulator (tcp_batch_interval_s, default 0) that buffers packets in-memory for N seconds then emits them as a single sendall, mimicking the way the production sorter delivers multi-second sorted boluses over TCP. - simulator.py / simulator_cli.py: new tcp_batch_interval_s knob, threaded through SimulatorSource and the Typer CLI as --tcp-batch-interval. Default 0 preserves the original per-packet send profile; all existing tests and production paths are unchanged. - tests/conftest.py: StreamingRig.spawn_simulator_cli gains a tcp_batch_interval_s kwarg that forwards to the CLI flag. - tests/test_simulator_batching.py (new): false-green guard proving the knob actually batches (multi-packet sendall payloads, pacing near the configured interval). Pure unit test, no subprocesses. - tests/test_flush_burstiness.py: add test_batched_wire_regression with pass/fail assertions on CV < 0.5, fraction_sub_50ms < 0.1, max_gap < 2x flush_interval, n_flushes within 30% of expected, flush_number sequence 1..N, total_cycles monotonic, sum(cycles_in_flush) == stop.total_cycles, and n_stops == 1. Writes a JSON artifact with per-combo metrics to /tmp/burstiness_latest.json for before/after diffing. Red state against the current (unfixed) server: CV 0.84-0.96 on all three combos, fraction_sub_50ms 42.9% on cps=1000/tdc=100, max_gap >= 2x flush_interval on the low-rate combo. These failures match the production trace in solution.md. The wall-clock flush gate fix (next commit) is expected to flip this test to green. Made-with: Cursor
Decouple flush emission from TDC cadence so that bursty TDC arrivals (e.g. when an upstream batches a 2-second window of packets in one TCP write) no longer translate into bursty ZMQ updates. Previously the gate fired on cycle_count % flush_every_n_cycles == 0, which is event-driven by definition and therefore inherits any burstiness in the wire. The new gate is wall-clock: emit_flush_if_due() checks time.monotonic() - last_flush_time >= flush_interval and resets the flush state atomically under xyt_lock. handle_tdc and the main loop's silence watchdog (after time.sleep(1)) both call emit_flush_if_due, so the wire being idle does not stop flushes from going out within ~flush_interval + 1s. cycles_since_last_flush and cycle_count are now incremented in lockstep (only when t_zero is already armed), which keeps the conservation invariant sum(event.cycles_in_flush) == stop.total_cycles intact. do_final_flush now reports cycles_since_last_flush as the residual count, which is the exact number of cycles still sitting in local_accumulator. The bursty-wire regression test (3 combos, batch=2.0s) goes from CV 0.84-0.96 / 7-43% sub-50ms gaps to CV 0.19-0.27 / 0% sub-50ms gaps. The 25-combo no-regression grid passes with CV <= 0.14 everywhere including the previously fragile low-TDC race zone. Made-with: Cursor
…respect zooming in heatmap
Trailing TDCs that arrived over TCP but had not yet been routed
through data_callback were silently dropped from stop.total_cycles
and from the published events. Sequence (pre-fix):
1. Producer disconnects; _handle_client flushes residual bytes
onto SocketDataServer.message_queue and exits.
2. app.py main loop sees client_disconnected_event and calls
do_final_flush() + _wait_for_xyt_drain().
_wait_for_xyt_drain() only waits for the *output* queue
(xyt_queue, events to ZMQ); it does not wait for the *input*
queue (message_queue, raw byte batches to the parser).
3. cycle_count is read into TimePixStop.total_cycles.
4. _data_processor continues to drain message_queue in the
background, but the stop has already been published and the
zmq_worker is shutting down, so any flushes triggered by
those late TDCs are also lost.
Net effect: ~flush_interval x tdc_frequency_hz cycles (and the
binned counts associated with them) silently disappear at every
acquisition end.
Fix:
- New SocketDataServer.wait_for_idle(timeout) helper that uses
queue.Queue's existing unfinished_tasks / all_tasks_done
machinery (populated by the task_done() calls already in
_data_processor) to block until every batch currently queued
has returned from data_callback.
- Three new wait_for_idle(5.0) calls in app.py, one before each
do_final_flush() site (exit_on_disconnect branch, natural
disconnect branch, finally block). Each logs a warning and
proceeds on timeout so a stuck producer cannot hang shutdown.
Empirical: 100 Hz x 10 s simulator run drops loss vs sim_pulses
from 5.1% (pre-fix) to ~1% (post-fix), and the gap shrinks
further with longer runs. Existing conservation invariant in
tests/test_flush_burstiness.py::test_batched_wire_regression
still holds; tests/test_main_workflow.py still passes.
A separate connect-time race (orphan flush published before the
main-loop reset) was found during validation but is not addressed
here; details in info/report04242026.md.
Made-with: Cursor
prevent user from openeing another instance of the UI if one is already running save user settings inside a json file and apply last settings when opening the UI
previously when starting a new DAQ it would only flush the average map (right) not the last flush (left)
remove old plans bug reports put all network sniffing / packet debugging into a new folder
saving logs improve logging ui
converting print statemetns to logs adding new logs
script for now, no UI Co-authored-by: Cursor <cursoragent@cursor.com>
tpx3dump labels the dispersive (physically horizontal) detector axis as y/cy, not x/cx. The centroider was histogramming cx/x, which is the non-dispersive axis and produces a single blob. Switching to cy/y restores the expected two-peak structure. Updated axis label in the summary plot and developer docs to document the naming convention.
Co-authored-by: Cursor <cursoragent@cursor.com>
gabrielgazollalbl
added a commit
that referenced
this pull request
Jul 22, 2026
Spectrometer viewer: alignment & centroiding tabs, streaming reliability fixes, offline analysis tools
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This branch turns the viewer into a full spectrometer operations tool: two new UI tabs (Alignment and Centroiding), a set of streaming/backend reliability fixes, richer end-of-scan outputs, and a suite of offline analysis and diagnostics tools under
tools/.UI
Streaming & acquisition reliability
cycle_counton stop, so final flush counts are accurate.info/UDP_RECEIVER_BUFFER_LIMITS.md).y/cy, notx/cx).End-of-scan outputs
Each acquisition now also saves an energy axis CSV (from UI calibration), a time-axis CSV in ns (from TDC metadata), and a UUID file matching the
scan_namebroadcast over ZMQ, so downstream services can correlate saved files with the data stream.scan_nameis now a plain UUID4.Tooling
tools/centroider/: offline centroiding pipeline (API, histogramming, parameter sweeps, runner) with a tutorial ininfo/centroider_tutorial.md.tools/sniffer/andtools/diagnostics/: flush-pacing listener and burstiness analysis for debugging wire-level timing.tools/dev/andtools/ui/.Tests
New
streaming_rigfixture spins up the streaming server on dynamic ports with pre-wired ZMQ sockets; integration tests were rewritten on top of it with tighter assertions (scan_name cohesion, contiguous flush numbers, reconnect behavior). New test suites cover burstiness, log manager, preferences, single-instance, and simulator batching.Misc
live-cliupdated to beta 0.4.3; simulator gained burst-mode support.