Proposal: DisplayXR for 3D Slicer (medical visualization, Leia SR tested) #2352
Replies: 5 comments
Final update: 3D SlicerCode: Status
Against the architecture direction
Runtime findings
Asks
🤖 Generated with Claude Code |
|
Thanks for the update. Putting each app into one of the two target classes (its own viewport, or a dedicated 3D window), with an explicit list of what it needs from a shared host library, is the right framing. It is the input we're using to design that library. The overlay PRs (#2332, #2350, #2353) stay as measurement rigs, as you say. We'll post the host-library API here for review when there's a first draft. |
|
The same in-viewport route now runs in 3D Slicer 5.12.4 and ParaView 6.2, on the texture target. There's no OpenXR render window and no SlicerVirtualReality: two off-axis eyes come from plain Measured with a simulated viewer; the seated check is still to come:
Your adapter logic carries over directly: the camera pin (as the model matrix), the true-geometry rig and the comfort solver. On Slicer, your screen-plane solver now drives the rig without ever moving Slicer's camera, and your calibrate-spheres scene is the test oracle. Two app-side lessons:
ParaView: |
|
Seated check: 3D Slicer in its own 3D view (texture target) 3D Slicer 5.12.4 now draws its 3D inside its own 3D view on the released runtime, and the seated check at the tested display (Leia SR) is a Pass.
|
|
Recheck on runtime 2.57.1: 3D Slicer in its own 3D view (texture target) Seated check: Pass. Conditions: installed DisplayXR 2.57.1 + Leia plug-in 2.19.1 (LeiaSR 1.38, so anchor fallback, state 0x2), the repo's default launcher, 3D Slicer 5.12.4, CT chest volume rendering at Depth 100 %, ~27 eye pairs/s, view 1689×1815 drawn pixel-exact. Measured (simulated display, CT chest,
No regressions from 2.55.0 + 2.18.3. |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Proposal: DisplayXR for 3D Slicer (medical visualization, Leia SR tested)
I've built a 3D Slicer module, DisplayXR 3D, that shows Slicer's 3D view in glasses-free 3D on DisplayXR displays. The 3D sits inside Slicer's own 3D view, next to the 2D axial / coronal / sagittal slices, and weaves at the same time as other 3D apps on the panel. It's tested end to end on a Leia SR laptop. I'd like the maintainers to review it, and to add 3D Slicer to DisplayXR's compatible applications: for example as
DisplayXR/displayxr-slicer(experimental), and in a displayxr.org listing once that's hosted and tagged, as with Omniverse in #2331.Code:
joehillthunder/displayxr-slicer(private for now, Apache-2.0 + NOTICE). @dfattal is invited as a collaborator; personal repos can only grant write. I can add anyone else on the maintainers team.Why Slicer
3D Slicer is the most widely used open-source medical imaging platform (CT/MRI volume rendering, segmentation, surgical planning). Clinicians and researchers already read anatomy in 3D, and a desk-sized glasses-free display fits that workflow better than a headset: the slice views and the rest of the UI stay readable, and nothing goes on anyone's head.
What it does
Slicer gets OpenXR from the SlicerVirtualReality extension (VTK
vtkOpenXRRenderWindow, OpenGL,XR_KHR_opengl_enable), so there's no renderer fork. Slicer runs as a hosted legacy app. The module adds Start 3D; the status reads "3D active".XR_RUNTIME_JSONand leaves the system OpenXR runtime alone.DXR_HOSTED_WINDOW=auto(Proposal: embed hosted windows in the app's own window (DXR_HOSTED_WINDOW, viewer inset, F11) #2332) embeds the woven window over Slicer. The module publishes Slicer's 3D view rect asDXR_HostedViewRectevery 100 ms, so the 3D fits the 3D view exactly at any layout or size (measured: 1782×1815 at the view's client rect). F11 or the "Full screen" button toggles the whole panel through the runtime's own F11.DXR_HOSTED_INPUT=appthe runtime posts the mouse over the 3D to Slicer (branchhosted/input-passthrough, from the ParaView integration; PR to follow). On older runtimes the module makes the woven window click-through instead. The 3D follows Slicer's 3D camera every frame.vtkOpenXRRenderWindow.SetPhysicalToWorldMatrixwith columnss·[right, up, −dop]of Slicer's camera, mapping the rig's nominal eye (0, 1.6, 0) onto the camera, pitch included. No pitch/pullback profile is needed. Plain SlicerVirtualReality on DisplayXR leaves the data out of frame.DXR_LEGACY_CAMERA_RIG= glass at the 0.6 m nominal viewer, vertical FOV = the 3D view's physical height at 0.6 m,ipdFactor = K · view width / 0.063. Virtual metres map 1:1 onto the panel, so parallax is predictable:p(z) = K·(1 − g/z)as a fraction of view width, with g the glass distance.DXR_HostedDepthRel, d3d11: live depth for embedded apps via DXR_HostedDepth (slider-ready; stacked on #2332) #2350) and Screen plane (moves the glass in-module, with no size change). While 3D runs, the module also starts the shared DisplayXR depth slider with-ProcessId, docked in Slicer's menu bar; both stay in sync throughDXR_HostedDepthRelNow..usda(UsdPreviewSurface, vertex normals) in Kit's default layout (Y-up, cm). The scaling is baked in because Kit doesn't convert units on scripted references. Checked in USD Composer: life size and upright.Tested
3D Slicer 5.12.4 + SlicerVirtualReality c9179ed on DisplayXR 2.53.0 (dev build with #2332 / #2350 / input pass-through), Leia SR plug-in 2.18.0, RTX 5070 Ti, Leia SR laptop panel 3840 × 2400.
displayxr_atlas_trigger.SlicerApp-real.projection) on sim-display (DXR_PLUGIN_EXCLUSIVE), with spheres at known depths:DXR_HostedDepthRelwrite of 0.5 from outside gives 0.50×, and the panel follows.Runtime-side findings (for discussion)
xrLocateViewsreturns eye0 == eye1 (3D-GATE … eye0=(0,0.1,0.6) eye1=(0,0.1,0.6),tracking=0), so every capture shows zero disparity. That's probably intended (fade to 2D), but it makes on-panel measurement impossible without a seated viewer. A documented "fixed nominal eyes" switch for leia-sr, or a note in the capture docs, would help integrators. sim-display is the workaround.displayxr_atlas_trigger.<exe>is keyed by exe name, so with twoSlicerApp-realprocesses whichever polls first answers. A PID-suffixed variant would make concurrent-app testing reliable.Suggested next step: a
displayxr-vendor-templatenote on what a display plug-in needs for apps like this (nominal viewing distance, panel size, and the comfort budget used here).Asks
DisplayXR/displayxr-slicer(experimental, me as maintainer), and a displayxr.org listing after a tagged release.All reactions