Skip to content

Add a run viewer to the scan browser, reading runs from Tiled - #96

Open
Anthony Sligar (sligara7) wants to merge 2 commits into
mainfrom
create-viewer
Open

Anthony Sligar (sligara7) wants to merge 2 commits into
mainfrom
create-viewer

Conversation

@sligara7

Copy link
Copy Markdown
Contributor

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.

  • 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.
  • Real data: tried by Anthony Sligar (@sligara7) after the branch was pushed: "seemed to work nicely".
  • 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.
  • Overlaying several runs in one plot.

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
Copilot AI balanced review requested due to automatic review settings October 16, 2026 20:45

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Stale frame results can initialize incorrect view settings, and nonnumeric columns cause unhandled plotting errors.

2 open findings
What changed in this PR

Adds a Tiled-backed run viewer to the Scan Browser for inspecting acquired data.

Changes:

  • Adds image slicing, analysis tools, scalar plots, and metadata browsing.
  • Adds viewer launch actions and detector-name filtering.
  • Adds mock/offscreen tests and an explicit plotting dependency.
File Description
tests/​gui/​test_run_viewer.py Tests data helpers, filtering, and viewer integration.
src/​hextools/​gui/​scan_browser.py Adds detector filtering and viewer launch actions.
src/​hextools/​gui/​run_viewer.py Implements asynchronous data loading and visualization.
pyproject.toml Adds pyqtgraph to GUI dependencies.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/hextools/gui/run_viewer.py Outdated
Comment thread src/hextools/gui/run_viewer.py Outdated
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
Copilot AI balanced review requested due to automatic review settings October 16, 2026 21:08

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Medium severity 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.

Medium severity 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.

Medium severity 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.

Medium severity 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.

2 resolved since last review

Review effort: Balanced

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants