render_niivue

render_niivue class

Reactively render a NiiVue neuroimaging viewer (volumes, overlays, and meshes), via ipyniivue.

Usage Source

render_niivue(
    _fn: Optional[ValueFn[ValueT]] = None,
    *,
    width: Optional[str] = None,
    height: Optional[str] = None,
    fill: Optional[bool] = None,
    fillable: Optional[bool] = None,
    rate_policy: Optional[RatePolicy] = None,
    rate_delay: Optional[float] = None,
)

@render_niivue is a decorator that creates a render function – a function that returns the viewer to display and re-runs when the reactive values it reads change:

from ipyniivue import NiiVue, SliceType, Volume

from shinywidgets import render_niivue


@render_niivue
def nv():
    nv = NiiVue(slice_type=SliceType.MULTIPLANAR)
    nv.add_volume(Volume(path="mni152.nii.gz", name="mni152"))
    return nv

The render function should return an ipyniivue.NiiVue widget with volumes and/or meshes loaded (requires pip install 'shinywidgets[niivue]'). In a filling layout, the viewer grows or shrinks to fit its container (tracking resizes, including full_screen); an explicit output_widget(height=...) is respected too. Don’t pass height= to NiiVue() itself – it sets the canvas’s pixel height, which disagrees with the output’s height and causes clipping or dead space.

Note

Accessing the rendered widget: the render function gains two read-only attributes: .value, the value it most recently returned, and .widget, the rendered NiiVue instance.

Responding to user interaction: nv.location() reads the crosshair position, much like input.my_slider() reads an input. Call it in a reactive function (a @render.* function, @reactive.calc, or @reactive.effect), which then re-runs whenever the crosshair moves. It attaches at render, seeded with the position the viewer has already synced to Python, so a crosshair move made before its first read (e.g., behind a button or chat handler) is reflected in that read instead of lost. A re-render re-seeds from the new viewer at attach (a freshly rendered viewer starts at the volume center, [0.5, 0.5, 0.5]). Debounced (250 ms) by default; see rate_policy and rate_delay. For every event un-debounced, register a callback with @nv.on_location_change inside the render function and have it set a session-scoped reactive.Value.

Reading the crosshair: location() (above) and the on_location_change callback are the ways to read the crosshair reactively. The position does sync back to nv.widget.scene.crosshair_pos (location()’s seed reads it), but ipyniivue applies that sync silently, so reactive_read(nv.widget.scene, "crosshair_pos") never re-runs. (Beware: exceptions raised inside on_location_change callbacks are swallowed by ipywidgets, so a failing callback just looks like “no updates”.) Similarly, nv.frac2mm()/nv.mm2frac() don’t work on Shiny (the coordinate matrices exist only in the browser); use the millimeter coordinates from the location() payload instead.

Mutating the widget: use .widget to update the viewer in place without a re-render: .widget.set_colormap(volume_id, name), .widget.set_opacity(index, value), .widget.set_gamma(value), .widget.set_slice_type(SliceType.RENDER), and .widget.move_crosshair_in_vox(dx, dy, dz) all apply live (trait changes on the widget are synced to the browser).

Data size: each render sends the viewer’s JavaScript bundle plus every volume to the browser, so re-renders are expensive for large volumes. Prefer Volume(url=...) for remote data (the browser fetches it directly), and prefer in-place .widget.set_* updates over re-rendering.

Parameters

width : Optional[str] = None

A CSS length unit (e.g., "400px" or "100%") for the output’s width.

height : Optional[str] = None

A CSS length unit for the output’s height. When None (the default), the output is designed to fill the height of its parent container (see fill and fillable).

fill : Optional[bool] = None

Whether the output should grow or shrink to fit its parent container. When None (the default), this is True if height is not specified.

fillable : Optional[bool] = None

Whether the output container should fill available space (scrolling its contents on overflow). When None (the default), this is True if height is not specified.

rate_policy : Optional[RatePolicy] = None

How to rate-limit location() (debounced by default) and reactive_read() on the widget: "debounce" or "throttle". None (the default) keeps the debounce, and debounces reactive_read() only if rate_delay is given.

rate_delay : Optional[float] = None

The rate-limit window in seconds (default 0.25). 0 disables rate limiting.

Examples

from ipyniivue import NiiVue, Volume
from shiny import App, reactive, render, ui
from shinywidgets import output_widget, render_niivue

app_ui = ui.page_fluid(
    output_widget("brain"),
    ui.output_text_verbatim("crosshair"),
)


def server(input, output, session):
    location = reactive.Value("Click the brain to inspect a voxel")

    @render_niivue
    def brain():
        nv = NiiVue()
        nv.load_volumes(
            [Volume(url="https://niivue.com/demos/images/mni152.nii.gz")]
        )

        @nv.on_location_change
        def _(loc):
            location.set(loc["string"])

        return nv

    @render.text
    def crosshair():
        return location()


app = App(app_ui, server)

Each accessor can also be read by name – out.read("location") – which also accepts the widget’s bare synced-trait names.

See also

output_widget(): in Shiny Core, the UI needs this to hold the output (Shiny Express displays it where the render function is defined).

Methods

location() The most recent crosshair position, as a dict with keys ax_cor_sag (the view index), frac (fractional volume coordinates), mm (millimeter coordinates), vox (voxel coordinates), values (voxel intensities under the crosshair, one per volume), string (a formatted summary), and xy (canvas coordinates).

location()

render_niivue.location()

The most recent crosshair position, as a dict with keys ax_cor_sag (the view index), frac (fractional volume coordinates), mm (millimeter coordinates), vox (voxel coordinates), values (voxel intensities under the crosshair, one per volume), string (a formatted summary), and xy (canvas coordinates).

The accessor attaches at render, seeded with the position the viewer has already synced to Python (nv.widget.scene.crosshair_pos), so a crosshair move made before its first read is reflected in that read instead of lost, and a re-render re-seeds from the new viewer’s position at attach. Only frac syncs back, so a seeded value carries just frac plus a "seeded": True marker; the other keys (mm, vox, values, …) appear with the next crosshair move, which reports the full dict. Debounced (250 ms) by default; see the rate_policy and rate_delay parameters.