Skip to main content

reflex-niivue

A Reflex component wrapping NiiVue, the WebGL2 viewer for medical images: NIfTI, NRRD, MHA/MHD, MGH, DICOM, meshes (GIfTI, MZ3, OBJ, ...), tractography, connectomes and drawings.

  • One general-purpose niivue.view(...) component: volumes and overlays, label maps, meshes with layers, multiplanar and 3D render views, clip planes, drawing, synced viewers.
  • Props map 1:1 to NiiVue methods; volumes and meshes are diffed by url, so changing an opacity or a colormap never downloads the image again.
  • NiiVue events (on_location, on_image_loaded, ...) arrive in your Reflex state.
  • Imperative actions (save_image, undo_draw, move_crosshair_to_mm, ...) are events you return or yield from a handler.
  • NiiVue itself (@niivue/niivue 0.69.0) comes from npm when your app builds; nothing is vendored. No image data passes through Reflex state: NiiVue fetches the files itself.

Requires Python 3.11+, Reflex 0.10+ and a browser with WebGL2.

Install

uv add reflex-niivue    # or: pip install reflex-niivue

Quick start

import reflex as rx
from reflex_niivue import label_colormap, niivue

IMAGES = "https://niivue.com/demos/images/"


class State(rx.State):
    opacity: float = 0.7
    mm: list[float] = []

    @rx.event
    def located(self, mm: list[float], vox: list[int], values: list[dict], frac: list[float]):
        self.mm = mm

    @rx.event
    def save(self):
        yield niivue.save_image("mask.nii.gz")  # the drawing, as NIfTI

    @rx.var
    def volumes(self) -> list[dict]:
        return [
            {"url": IMAGES + "mni152.nii.gz", "name": "t1"},
            {
                "url": IMAGES + "hippo.nii.gz",
                "name": "hippocampus",
                "opacity": self.opacity,
                "colormap_label": label_colormap({9: ("hippocampus", (255, 70, 70))}),
            },
        ]


def index():
    return rx.vstack(
        niivue.view(
            volumes=State.volumes,
            slice_type="multiplanar",
            drawing_enabled=True,
            pen_value=1,
            on_location=State.located,
            height="500px",
        ),
        rx.text(State.mm.to_string()),
        rx.hstack(
            rx.button("Undo", on_click=niivue.undo_draw()),
            rx.button("Save drawing", on_click=State.save),
        ),
    )


app = rx.App()
app.add_page(index)

Props

Prop Type NiiVue
volumes list[dict] loadVolumes / addVolumeFromUrl / removeVolume, setColormap, setOpacity, cal_min/cal_max, setColormapLabel
meshes list[dict] addMeshFromUrl / removeMesh, setMeshProperty, setMeshLayerProperty, setMeshShader
slice_type "axial", "coronal", "sagittal", "multiplanar", "render" setSliceType
multiplanar_layout "auto", "column", "grid", "row" setMultiplanarLayout
show_3d_crosshair bool opts.show3Dcrosshair
crosshair_color, crosshair_width RGBA 0..1, float setCrosshairColor, setCrosshairWidth
back_color RGBA 0..1 opts.backColor
drag_mode "none", "contrast", "measurement", "pan", "slicer3d", "angle", "roi_selection", "crosshair", "windowing", "callback_only" setDragMode
is_colorbar, is_orient_cube bool opts.isColorbar, opts.isOrientCube
is_radiological bool setRadiologicalConvention
clip_plane [depth, azimuth, elevation] (depth ≥ 2 = off) setClipPlane
render_azimuth_elevation [azimuth, elevation] setRenderAzimuthElevation
selected_volume int the layer whose colorbar shows; default target of set_frame_4d / save_image
drawing_enabled bool setDrawingEnabled
pen_value int (0 erases) setPenValue
draw_opacity float setDrawOpacity
drawing_fill_overwrites bool drawFillOverwrites
options dict (camelCase NVConfigOptions) merged into new Niivue(options); the props above win
sync_group str viewers in the same group broadcastTo each other (crosshair, pan/zoom, 3D camera)
sync_opts dict what a group shares, default {"2d": True, "3d": True}
height, width, id default 480px × 100%; id names the viewer for the helpers

niivue_options(**props) shows the constructor options a set of literal props produces.

Volume descriptors

{
    "url": "https://.../t1.nii.gz",  # required (NIfTI, NRRD, MHA, MGH, ...)
    "name": "t1",  # display name (shown in on_location values)
    "colormap": "gray",  # see colormaps()
    "opacity": 1.0,
    "visible": True,  # False = opacity 0, the volume stays loaded
    "cal_min": 0,
    "cal_max": 100,  # display range
    "colormap_label": label_colormap({1: ("liver", (255, 0, 0))}),  # label map
    # any other NiiVue ImageFromUrlOptions, camelCase or snake_case:
    "colormap_negative": "winter",
    "frame_4d": 0,
    "headers": {"Authorization": "..."},
}

The first volume is the background. Volumes are matched by url (or name): changing a property updates the loaded volume; adding volumes after the existing ones appends them; replacing the background or reordering reloads the list.

Mesh descriptors

{
    "url": "https://.../BrainMesh_ICBM152.lh.mz3",
    "name": "lh",
    "rgba255": [200, 200, 200, 255],
    "opacity": 1.0,
    "visible": True,
    "layers": [
        {"url": ".../lh.motor.mz3", "colormap": "warm", "cal_min": 2, "cal_max": 5, "opacity": 0.7}
    ],
}

Events

Event Handler arguments
on_location mm: list[float], vox: list[int], values: list[dict] (name, id, value, vox, mm per volume), frac: list[float] — throttled to 20/s
on_image_loaded name: str, dims: list[int]
on_mesh_loaded name: str
on_frame_time_ms ms: float — mean redraw time, at most once per second
on_frame_change name: str, frame: int (4D volumes)
on_intensity_change volume: dict (name, id, index, cal_min, cal_max)
on_drawing_changed action: str ("draw", "undo", "load", "close")
on_volume_updated —
on_error message: str (no WebGL2, failed download, unreadable file)

Imperative helpers

Each returns an event: use it as an event trigger, or return/yield it from a handler. id picks the viewer (its id prop); without it the most recently mounted viewer is used.

rx.button("Undo", on_click=niivue.undo_draw(id="main"))


@rx.event
def save(self):
    yield niivue.save_image("mask.nii.gz", id="main")
Helper Does
move_crosshair_to_mm([x, y, z]) moves the crosshair (fires on_location)
set_frame_4d(frame, volume=None) shows a frame of a 4D volume
save_image(filename, drawing=None, volume=None) downloads the drawing (default when there is one) or a volume as NIfTI
save_scene(filename) downloads a PNG screenshot
set_slice_mm(True) world-space (True) or voxel-space slices
undo_draw() / clear_drawing() drawing undo / erase
load_drawing_from_url(url, binarize=False) loads a drawing on the background's grid
reset_view() resets pan/zoom, crosshair and 3D camera

Arguments may be state Vars: niivue.move_crosshair_to_mm(State.target).

Colormaps

from reflex_niivue import colormaps, label_colormap

colormaps()  # ["actc", ..., "gray", "hot", ..., "x_rain"]: NiiVue's built-in names

label_colormap({1: ("liver", (255, 0, 0)), 2: ("spleen", "#00ff0080")})
# {"R": [0, 255, 0], "G": [0, 0, 255], "B": [0, 0, 0], "A": [0, 255, 128],
#  "I": [0, 1, 2], "labels": ["background", "liver", "spleen"]}

Demo

git clone https://github.com/ecrespo/reflex-niivue && cd reflex-niivue
uv sync
cd niivue_demo && uv run reflex run

Tabs: volume + label overlay (colormap, opacity, label/heat), multiplanar vs 3D render (camera, clip plane), drawing (pen, undo, clear, save, load), meshes with a scalar layer, two synced viewers, 4D frames and the imperative helpers, and a panel showing on_location from Python state. It uses NiiVue's demo images and falls back to a synthetic phantom (numpy + nibabel, written to niivue_demo/assets/) when they cannot be downloaded; NIIVUE_DEMO_OFFLINE=1 forces the offline data.

Development

uv sync
uv run pre-commit run --all-files
uv run playwright install chromium   # once, for the browser checks
uv run pytest                        # or: uv run pytest -m "not browser"
uv build

Releasing

Releases are published from GitHub Actions with Trusted Publishing (no API tokens):

Event Result
push to main all checks, then the development pre-release 0.1.1.devN on PyPI (pip install --pre reflex-niivue) and an install check
tag v0.2.0rc1 (or a1, b1) on main all checks, then a pre-release on PyPI (pip install --pre reflex-niivue) and a GitHub pre-release
tag v0.2.0 on main all checks, then the release on PyPI and a GitHub release
git tag v0.1.0rc1 && git push origin v0.1.0rc1   # pre-release
git tag v0.1.0 && git push origin v0.1.0         # release

License

Apache-2.0 (see LICENSE and NOTICE). NiiVue is BSD-2-Clause and is installed from npm.

Metadata

Release files for reflex-niivue 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for reflex-niivue 0.1.0
File Size Uploaded Provenance Actions
reflex_niivue-0.1.0.tar.gz 100.7 kB With provenance Details for reflex_niivue-0.1.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for reflex-niivue 0.1.0
File Interpreter ABI Platform Actions
reflex_niivue-0.1.0-py3-none-any.whl Python 3 none any Details for reflex_niivue-0.1.0-py3-none-any.whl

Total release size: 130.4 kB

Release files / reflex_niivue-0.1.0.tar.gz

Download URL reflex_niivue-0.1.0.tar.gz
Size 100.7 kB
Tags Source
SHA-256 checksum
How to use checksums
55e8c4e4ec2f2a3810fa806b52b861bb9ea7d07ab47e167628b4e79cbe7125a0
BLAKE2b-256 checksum
How to use checksums
51d38cb27af5cc508d4ffef278278e9a3b07e0531f21bff6cf706be4372f8579
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 29, 2026.

Transparency log

Release files / reflex_niivue-0.1.0-py3-none-any.whl

Download URL reflex_niivue-0.1.0-py3-none-any.whl
Size 29.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
7f52e37892be6c6cd85527d53ac7bae4aad7cc0d61378d0057ccaf1dd910db42
BLAKE2b-256 checksum
How to use checksums
9e293c387832f10c0340c9e0797754c04b089c9f51e561d2207ac7d0eae18d18
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 29, 2026.

Transparency log

Security for reflex-niivue 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for reflex_niivue-0.1.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 29, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page