You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Add a run viewer to the scan browser, reading runs from Tiled - #96
A run viewer for the GUI's Plan Execution → Scan Browser. Double-click a search result (or use the new Open in viewer button or the right-click menu) and the run opens in its own window, read from Tiled. The feature set follows DatView, which HEX users already use on files; no DatView code is used.
Datasets: every stream's datasets with shape and type, listed with one request per stream; the run's metadata alongside.
Images and stacks: read one frame at a time from Tiled, sliced along a chosen axis (projections, or a row through the stack for a sinogram). Zoom box and reset, contrast (levels/histogram, Auto contrast clips at 1–99 %), horizontal or vertical line profile, histogram, percentile density and statistics.
Per-event values: plotted against the event index or another column of the same stream; time is shown as seconds since the first event.
Detector filter in the scan browser: keeps runs whose start document lists a detector containing the text, ignoring case, so kinetix matches kinetix-det1…4.
Files: src/hextools/gui/run_viewer.py (new), src/hextools/gui/scan_browser.py, tests/gui/test_run_viewer.py (new), pyproject.toml (pyqtgraph added to the gui extra; it was only installed through ntnda-qt-viewer, and pixi.lock already contains it).
Data is read off the GUI thread with the existing create_worker; a result that arrives after the user has moved to another dataset or frame is dropped, and while a frame is loading only the latest slider position waits.
How it was verified
Mock tests: 13 tests in tests/gui/test_run_viewer.py with fake Tiled clients: frame indexing (including Kinetix's (events, 1, rows, columns) layout), statistics, percentile density, dataset listing for both Tiled run layouts, the detector filter, and the viewer and scan browser offscreen (only single frames are requested, never the whole stack).
Local Tiled server: a throwaway tiled.server.SimpleTiledServer holding runs written by TiledWriter: an ophyd-async SimBlobDetector named kinetix-det1 writing HDF5 as (5, 1, 240, 320), and a motor count. The detector filter found only the detector run, frames were read singly, and the motor plotted against time.
Full suite (tests, minus test_dclm.py, which hangs when run with the others): 314 passed, 7 failed. The same 7 fail on main @ 3cf1786: Phantom ×2, test_log ×1, test_utils ×2, radiography ×2.
ruff, ruff format and ty are clean on the new files; scan_browser.py has the same ty findings as on main.
Not in this PR
Filtering by averaging: no plan records averaging in its start document yet.
Double-click a run in the Plan Execution tab's Scan Browser (or use its 'Open in viewer' button or right-click menu) to open it in a new window. The viewer lists the run's streams and datasets and reads them from Tiled: images and stacks are sliced along a chosen axis one frame at a time, with zoom, contrast, horizontal/vertical line profiles, histogram, percentile density and statistics; per-event columns are plotted against the index or another column (time as seconds since the first event); the run's metadata is shown alongside. Feature set follows DatView (algotom/datview); no DatView code is used.
The scan browser also gains a Detector filter that keeps runs whose start document lists a detector containing the text (e.g. 'kinetix' matches every Kinetix).
Verified with mock Tiled clients (tests/gui/test_run_viewer.py) and end to end against a local Tiled server (SimpleTiledServer) holding runs written by TiledWriter: an ophyd-async simulated area detector writing HDF5 as (5, 1, 240, 320), the same layout as Kinetix, and a motor count. Not yet tried at the beamline with real data and the facility Tiled.
Assisted-by: copilot:claude-opus-4-8
PR 96 review. (1) A frame was accepted if it came from the current array, so a read that finished after the slider or slice axis moved was shown: the image and statistics disagreed with the controls, and after an axis change the stale projection used up the first-frame zoom/contrast reset. A frame is now accepted only if it answers the latest request (array and index). (2) Per-event values were converted to float in the plotting callback without a check, so a text column such as a shutter's Open/Closed raised ValueError there and left the previous plot under the new label. Values are now checked once: a text Y clears the plot with a message; a text X falls back to the index with a message.
Four regression tests (a gated array delays a read on purpose; a text column as Y and as X) were observed failing on the previous commit and pass here, five runs in a row.
Assisted-by: copilot:claude-opus-4-8
The reason will be displayed to describe this comment to others. Learn more.
🔵 Needs a closer look
Dataset navigation and asynchronous loading still allow inaccessible frames, stale displays, and redundant reads.
No open issues 🎉
4 previously missed issues
In code that hasn't changed since last review
Leading stack axes are inaccessible
src/hextools/gui/run_viewer.py:124
For a stack shaped (3, 2, rows, columns) (multiple events with multiple frames each), this exposes only axes 1–3. frame_key() fixes axis 0 at zero for every available selection, so events after the first are inaccessible. Add navigation for the remaining non-singleton axes while retaining sliced reads, or explicitly report unsupported shapes rather than silently showing only the first event. Add a regression case with both leading dimensions greater than one.
Stale slicing controls can cause IndexError
src/hextools/gui/run_viewer.py:453
The previous dataset's slicing controls remain enabled while _fetch_array loads the new dataset. After switching from a 3D stack to a 2D array, choosing the old axis 2 during that interval makes _on_slice_axis_changed() evaluate the new shape's nonexistent shape[2], raising an uncaught IndexError. Disable the slicing controls on selection; the array-ready callbacks already re-enable them for the new shape.
Old plot persists after failed dataset load
src/hextools/gui/run_viewer.py:602
Selecting an uncached scalar dataset changes the Y-axis label before its data loads, leaving the previous curve and statistics visible. If the Tiled read fails, _on_error() only changes the status, so the old values remain displayed under the new dataset's name indefinitely. Clear the curve and analysis before loading the new selection, and add a failed-read regression test.
Column callbacks trigger duplicate dataset reads
src/hextools/gui/run_viewer.py:662
Every column result calls _plot_1d() for the current selection, even when it belongs to a previous selection. If A finishes while B is still loading, this starts another full read of B because B is not cached yet. Several outstanding selections can multiply these requests. Cache completed results, but only refresh from this callback when all columns needed by the current selection are available; selection changes can continue to initiate reads.
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
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.
What
A run viewer for the GUI's Plan Execution → Scan Browser. Double-click a search result (or use the new Open in viewer button or the right-click menu) and the run opens in its own window, read from Tiled. The feature set follows DatView, which HEX users already use on files; no DatView code is used.
timeis shown as seconds since the first event.kinetixmatcheskinetix-det1…4.Files:
src/hextools/gui/run_viewer.py(new),src/hextools/gui/scan_browser.py,tests/gui/test_run_viewer.py(new),pyproject.toml(pyqtgraphadded to theguiextra; it was only installed throughntnda-qt-viewer, andpixi.lockalready contains it).Data is read off the GUI thread with the existing
create_worker; a result that arrives after the user has moved to another dataset or frame is dropped, and while a frame is loading only the latest slider position waits.How it was verified
tests/gui/test_run_viewer.pywith fake Tiled clients: frame indexing (including Kinetix's(events, 1, rows, columns)layout), statistics, percentile density, dataset listing for both Tiled run layouts, the detector filter, and the viewer and scan browser offscreen (only single frames are requested, never the whole stack).tiled.server.SimpleTiledServerholding runs written byTiledWriter: an ophyd-asyncSimBlobDetectornamedkinetix-det1writing HDF5 as(5, 1, 240, 320), and a motor count. The detector filter found only the detector run, frames were read singly, and the motor plotted against time.tests, minustest_dclm.py, which hangs when run with the others): 314 passed, 7 failed. The same 7 fail onmain@3cf1786: Phantom ×2,test_log×1,test_utils×2, radiography ×2.ruff,ruff formatandtyare clean on the new files;scan_browser.pyhas the sametyfindings as onmain.Not in this PR