Skip to content

ViSR sprint: UI improvements - #227

Draft
hyperrealist wants to merge 9 commits into
DiamondLightSource:mainfrom
hyperrealist:visr-sprint
Draft

hyperrealist wants to merge 9 commits into
DiamondLightSource:mainfrom
hyperrealist:visr-sprint

Conversation

@hyperrealist

@hyperrealist hyperrealist commented Oct 14, 2026 •

Copy link
Copy Markdown
Collaborator

ViSR UI work for the sprint, collected in one draft. Each change is its own commit with its tests, so it can be reviewed, dropped or split out on its own. Server side: DiamondLightSource/dls-tiled#19.

current

atlas:main (top) against this PR (bottom), on the same data: a fly scan half-way, a 2×2 scan, and a single point.

New: choose how the scan is drawn

current

A toggle above the plots (a303d7b, 1738c92), all four in so we can try them and decide which to keep:

  • Setpoint grid: the spec's setpoint grid from /binned, as before.
  • Readbacks: a step-sized rectangle at each recorded position (Tiled's /render).
  • Gaussian: a beam-sized blob per point.
  • Footprints: each point's beam footprint, overlaps split between neighbours.

Where nothing was drawn, the empty stripes show through, so gaps the beam left stay visible. No run records its beam size yet, so Gaussian and Footprints use one scan step and the plot says so.

New: progress, region detail, resizable plots

  • Scan progress (d4d5164): a bar above the plots shows the points recorded against the scan's total ("120 of 289 points", or just the count without a spec), and the shift applied to put the readbacks on the setpoint grid, when there is one. Both come from what /binned and /render already return.
  • Pick a region (bcd6a45): "Pick region", then drag a rectangle on any plot; all three show just that part of the scan, drawn by /render for that viewport at full resolution in the current mode. A chip shows the region and goes back to the whole scan; a new scan does too. The region's colours are scaled over that region alone.
  • Resizable plots (a477abd): drag the line between the plots and the controls to set the plots' height; arrow keys move it, and a double click or Escape resets it. The height is remembered in the browser.

New: tomography reads reconstructions from Tiled

  • Reconstructions from Tiled (3a1ee52): the tomography view finds reconstructions in Tiled's analysis container (array nodes with metadata visr.kind: tomography-reconstruction), shows the newest and reads its bytes through /api/data, so no new proxy route is needed. A picker in the controls switches between them, and a status line shows while none is loaded. The viewer takes 3D uint8 volumes, the format the reconstruction workflow writes (as in public/test-data). The mocks serve that test volume through the same Tiled endpoints.

Fixes

  • Unmeasured cells are grey stripes instead of black, so a scan in progress no longer looks like dark data (37a70d2).
  • A single point (or all-equal values) is drawn at full brightness instead of black (37a70d2); with dls-tiled#19, 1×1 and 2×2 scans get their own grid.
  • Cells sit between their bin edges instead of half a cell right and down: the plot was given cell centres, which it reads as each pixel's leading edge (f8b6698).
  • The poll after a scan ends now happens, so the last image includes the last points; it was skipped before. A 503 (data file behind the catalog) is retried instead of logged as an error (ad0fea7).

Checked, no change needed: polling already gets 304 Not Modified when nothing changed, as the browser revalidates with the server's ETag.

Open questions

  • Stable colours: colours change during a scan whenever a new extreme arrives, since the range comes from the data so far. A fixed range (detector or user), percentile clipping, or the previous scan's range would avoid it.
  • Beam size: to be read from the run's metadata once plans record it (start.beam_size).
  • Which drawing modes to keep, and what to call them.

Testing: pnpm typecheck, pnpm turbo build lint test --filter=@atlas/visr...; checked in the dev server against the live b01-1 Tiled, replaying a real 17×17 fly scan. A test release from this branch goes on the beamline before it leaves draft.

🤖 Generated with Claude Code

Cells no point had landed in yet were drawn black, the same as a measured
low value, so a scan in progress looked like dark data. They are now grey
diagonal stripes: each cell is drawn as 4x4 pixels so the empty part can
carry the pattern, and the axes are split to match.

A flat image (a single point, or all values equal) was scaled to black,
since its range is zero; it is now drawn at full brightness.

Also imports NDT as a type only, so the module no longer loads davidia at
runtime and can be unit-tested.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fy4Rv41HcbMRMkxzmCCJga
hyperrealist and others added 8 commits October 14, 2026 14:07
The UI turned /binned's bin edges into cell centres for the plot, which reads
values of the same length as the image as each pixel's leading edge, so the
whole image sat half a cell right and down of where the scan measured it
(a 2x2 scan with edges -0.5, 0.5, 1.5 was drawn over 0..2). The plot is now
given the edges themselves, one more than the cells, which it places each
cell between.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BsMDFhupHcBFbVJPK8QAvR
The poll that should fetch a scan's last points once it finished never ran:
it was conditional on the poll interval still being set, but the previous
run of the effect had already cleared it. So the final image was whatever
the last regular poll got, up to half a second before the scan ended. It now
runs whenever a scan stops running.

/binned answers 503 with Retry-After while the data file is behind the
catalog. That was logged as a polling error; it is now treated as "not ready
yet": during a scan the next poll simply tries again, and the final poll,
which nothing follows, retries after the time the server asks for (up to 10
times).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BsMDFhupHcBFbVJPK8QAvR
…otprints

A toggle above the plots picks the drawing. Cells is /binned's grid as
before; the others come from Tiled's /render, drawn over the scan's extent at
200 px on its longer side: a step-sized rectangle on each recorded position,
a beam-sized Gaussian on each point, or each point's beam footprint with
overlaps split between neighbours. Each channel is coloured as before; where
/render drew nothing or covered a pixel only partly, it fades into the empty
stripes, so gaps the beam left stay visible, and the footprints' outlines
are drawn in white on top.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BsMDFhupHcBFbVJPK8QAvR
…des by source

Gaussian and footprints are drawn with the beam size the run records, but
no run records one yet, so /render falls back to one scan step. The plots
now say so ("not the real beamline footprint"), with the size used, whenever
/render reports that fallback.

The modes are now named for what they are drawn from: "Setpoint grid" (the
spec's setpoints, was "Cells") and "Readbacks" (the recorded positions, was
"Rectangles"). The mode list and the warning text move to plotKinds.ts, so
they can be tested without loading the plot library.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BsMDFhupHcBFbVJPK8QAvR
/binned and /render both say how many points the scan has recorded and how
many it will have. A bar above the plots now shows that ("120 of 289
points"), or just the count when the run has no spec. When the readbacks
were shifted onto the setpoint grid, the shift is shown next to it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BsMDFhupHcBFbVJPK8QAvR
"Pick region" lets a rectangle be dragged on any of the plots; all three
then show just that part of the scan, drawn by /render for that viewport at
its full resolution and scaled over that part's own range, in whichever
mode is chosen. The region is shown as a chip above the plots; removing it,
or a new scan starting, goes back to the whole scan. Polling carries on
over the region while the scan runs.

The rectangle is drawn with Davidia's own region selection, switched on
from inside the plot by RegionSelectMode (the plot's toolbar children,
which is the only place the plot's interaction mode can be set). The
toolbar itself stays hidden.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BsMDFhupHcBFbVJPK8QAvR
The line between the plots and the controls is now a handle: dragging it
sets the plots' height (150 px at least), arrow keys move it 20 px at a
time when it has focus, and a double click or Escape goes back to the
automatic height. The height chosen is remembered in the browser.

HeightHandle is a separate component, so other views can use it too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BsMDFhupHcBFbVJPK8QAvR
The tomography view now finds reconstructions in Tiled's `analysis`
container (array nodes with metadata `visr.kind` set to
`tomography-reconstruction`), shows the newest, and reads its bytes
through /api/data. A picker in the controls switches between them.
The viewer takes 3D uint8 volumes, as the reconstruction workflow writes.

The mocks serve the test volume through the same Tiled endpoints.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BsMDFhupHcBFbVJPK8QAvR
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.

1 participant