Skip to content

Viewer: shareable links, measurement flyout, jump-to-organ + upload preview fix#32

Merged
aperson30 merged 3 commits into
mainfrom
aditya/viewer-enhancements
Jun 23, 2026
Merged

Viewer: shareable links, measurement flyout, jump-to-organ + upload preview fix#32
aperson30 merged 3 commits into
mainfrom
aditya/viewer-enhancements

Conversation

@aperson30

Copy link
Copy Markdown
Collaborator

Bundles three viewer features plus one upload-preview fix. All additive; no backend/API changes.

1. Shareable view links

Encodes the current viewer state into URL params so a link reproduces the exact view: orientation (MPR/axial/sagittal/coronal/3D), window/level, segmentation opacity, hidden organs, crosshair focal point, and the HD flag. A Share button copies the link (with a prompt fallback if the clipboard is blocked); an apply-on-load effect restores the state once the volume is ready.

  • New pure helper viewerShareState.ts (encode/decode) — omits defaults so plain links stay clean, ignores invalid params so a stale/hand-edited link can't crash the viewer. 11 unit tests.
  • getCrosshairMm() reads the focal point.

2. Measurement-tools flyout (toolbar declutter)

Collapses the four measurement tools (Distance / HU at point / ROI / Clear) into a single Measure button that opens a labeled dropdown — taking the toolbar from 9 icons to 6, matching the split-button pattern OHIF/radiology viewers use. The menu renders in a portal so the scrollable settings panel can't clip it.

3. Jump-to-organ

A per-organ target button in the Organs sidebar (revealed on row hover) recenters all three MPR planes on that organ and makes it visible if hidden.

  • getOrganCentroids() computes each label's centroid in one pass over the segmentation labelmap (cached per case, reset on load); jumpToOrgan() moves the crosshair there. No-ops for organs absent from a scan.

4. Upload preview windowing fix

The upload preview (CtPreview) trusted a narrow window baked into some file headers, clipping all but the brightest voxels to black. Now passes trustCalMinMax: false so it computes a robust window from the data — legible for any uploaded scan.

Testing

  • Type-check (tsc -b) ✓
  • Vitest 43/43 ✓ (11 new share-state tests; smoke-test mocks updated)
  • Production build ✓
  • Manually verified in the viewer: share round-trip, measure flyout, jump-to-organ across the body, and the preview fix.

NiiVue was trusting a narrow window/level baked into some file headers, which
clipped everything but the brightest voxels (e.g. contrast-filled vessels) to
black. Pass trustCalMinMax:false so the preview computes a robust window from the
data instead, keeping the scan legible for any uploaded volume.
- Shareable links: encode the viewer state (orientation, window/level, segmentation
  opacity, hidden organs, crosshair focal point, HD flag) into URL params via a new
  pure helper (viewerShareState, unit-tested), with a Share button that copies the
  link and an apply-on-load effect that restores the view. getCrosshairMm() reads
  the focal point.
- Toolbar declutter: collapse the four measurement tools (Distance/HU/ROI/Clear)
  into a single "Measure" flyout (rendered in a portal so the panel can't clip it),
  matching the split-button pattern radiology viewers use.
- Jump-to-organ: a per-organ target button in the sidebar recenters the MPR planes
  on that organ. getOrganCentroids() computes each label's centroid in one pass over
  the labelmap (cached per case); jumpToOrgan() moves the crosshair there.
- Replace redundant `as any` casts on getToolInstance with minimal typed casts
  (getCrosshairMm and moveCornerstoneCrosshairToMm).
- Document the window-apply effect's intentional deps and silence the
  exhaustive-deps warning, matching the other effects.
@aperson30 aperson30 merged commit 2981676 into main Jun 23, 2026
6 checks passed
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