An experimental Rust viewer for SUMA-style neuroimaging data.
I have been working on sumaru as a way to try out some ideas about
3D neuroimaging viewers: native GPU rendering, a testable Rust data model, and
day-to-day compatibility with the kinds of files people already use with
AFNI/SUMA.
sumaru is not SUMA, and it is not trying to replace the full AFNI ecosystem.
It started as an experiment. The nice surprise is that it has become functional
enough to use like a partial SUMA-style drop-in for common 3D viewing tasks:
opening GIFTI surfaces, loading .niml.dset/.gii.dset overlays, stepping
through .spec scenes, drawing or reading .niml.roi regions, checking NIfTI
slice planes, and talking to a running AFNI/SUMA session over NIML.
If your current SUMA workflow is working, keep using it. This project may be worth considering when you want a small native viewer, a Rust codebase that is pleasant to experiment in, or a quick way to look at surface and volume data without moving it into a different ecosystem.
- It works with familiar files. The goal is to open the files scientists already have from AFNI/SUMA and related surface workflows, not ask for a new project format first.
- It is responsive enough for everyday inspection. Rendering goes through
wgpu, so surface rotation, overlays, ROIs, paired hemispheres, and volume slices can stay interactive while you are checking data. - Surfaces and volumes can share one scene. This is handy for sanity checks: Does the surface line up with the anatomical volume? Is the overlay sitting where expected? Are the slice planes telling the same story as the surface?
- It can sit next to AFNI. The NIML talk bridge is early, but it already supports the practical path of connecting to AFNI/SUMA and exchanging picks, geometry, and overlay colors.
- The internals are meant to be testable. Most of the behavior lives in the library crate rather than being buried inside the GUI. That matters for scientific software, where boring details like coordinates, thresholds, and file parsing deserve tests.
This is research software and still changing. It is quite functional for a viewer, but it is also an experiment: some SUMA features are missing, behavior may move around, and you should verify results with the same care you would give any new tool in an analysis workflow.
At the moment, the project is most useful for people who are comfortable trying a source build, comparing behavior against tools they already trust, and filing small, concrete issues when real datasets expose rough edges.
This is currently a source build:
git clone https://github.com/pmolfese/sumaru
cd sumaru
cargo build --release
Then copy the sumaru binary to somewhere on your path, or run it through
cargo run while experimenting.
![]() |
![]() |
| Volume mode: draggable axial / coronal / sagittal slice planes | Paired-hemisphere "acorn" layout from a both-hemisphere spec |
![]() |
![]() |
| Drawn ROIs with the ROI controller | Surface / overlay controller |
Screenshot files are kept in docs/images/ so they can be
refreshed as the viewer changes.
- GIFTI surface/shape/dataset I/O through
gifti-rsfromPennLINC/gifti-rs. - NIfTI volume I/O through
niftifromEnet4/nifti-rs. - SUMA
.specparsing for the common single-hemisphere and paired-hemisphere viewer cases I have been testing. - A surface viewer through
winit,wgpu, andegui, with overlays, drawn ROIs, and paired-hemisphere layouts. - A
--volumemode that renders orthogonal NIfTI slice planes in the 3D scene. - Headless file inspection for quick metadata checks.
Some useful ways to launch it:
cargo run
cargo run -- -i /path/to/surface.gii
cargo run -- --surface /path/to/surface.gii
cargo run -- --surface /path/to/surface.gii --overlay /path/to/overlay.shape.gii
cargo run -- --surface /path/to/surface.gii --overlay /path/to/stats.niml.dset
cargo run -- --surface /path/to/surface.gii --overlay /path/to/stats.gii.dset
cargo run -- --surface /path/to/surface.gii --overlay /path/to/stats.niml.dset --verbose
cargo run -- -spec /path/to/subj_rh.spec -sv /path/to/subj_SurfVol.nii
cargo run -- -spec /path/to/subj_rh.spec -sv /path/to/subj_SurfVol.nii --preload
cargo run -- --volume /path/to/subj_SurfVol.nii
cargo run -- inspect /path/to/file.nii.gzThe project defines a few Cargo aliases in .cargo/config.toml:
cargo check-all
cargo test-all
cargo fmt-all
cargo surface /path/to/surface.gii
cargo inspect -- /path/to/file.gii--overlay accepts a GIFTI file with one numeric value per surface vertex, an
AFNI/SUMA .niml.dset, or an AFNI-converted .gii.dset. Multi-column datasets
are parsed into the canonical Dataset table first; the controller can then
choose intensity, threshold, and brightness columns from the dataset. If the
selected threshold column carries an AFNI stat label such as Ttest(48), the
threshold control can operate in p-value mode. Treat this as a viewer
convenience rather than a statistics package: it is meant to help inspect the
data you already understand.
sumaru has an early AFNI/SUMA NIML talk layer in the library crate and a live
viewer bridge. The first useful AFNI-compatible message subset follows the same
practical path used by SUMA and PySuma:
SUMA_ixyz: surface node index and XYZ coordinates sent to AFNISUMA_node_normals: per-node normals sent to AFNISUMA_ijk: triangle indices sent to AFNISUMA_irgba: sparse node RGBA colors and optional threshold/function/volume metadata sent from AFNI back to the surface viewer
Launch with --talk-afni to connect on startup, or press T in the viewer to
toggle AFNI/SUMA NIML talk. Press Control+T to force-resend the active surface
geometry. Port selection follows AFNI/SUMA conventions: --afni-port PORT uses
an explicit port, while -np OFFSET/--np OFFSET and -npb BLOC/--npb BLOC
resolve the same AFNI-style port offsets. --afni-host defaults to 127.0.0.1.
AFNI must be listening for NIML before sumaru can connect: launch AFNI with
-niml (and usually -yesplugouts for SUMA-style sessions), or press the
NIML+PO button in the AFNI GUI after launch.
For a quick look at any supported file, use the generic inspector. It covers GIFTI, NIFTI, raw NIML datasets/ROIs/label tables, and recorded NIML traces:
cargo run -- inspect path/to/fileFor reproducible AFNI talk debugging, add
--niml-record path/to/session.nimlrec to a viewer launch. sumaru records each
sent and received NIML event with direction, timestamp, and the serialized
payload. Recording is intentionally plain .nimlrec for live-session speed;
gzip the file afterward if you want to archive or share it. The debug readers
accept both .nimlrec and .nimlrec.gz:
cargo run -- niml inspect path/to/session.nimlrec
cargo run -- niml replay path/to/session.nimlrec.gzSmall test messages can be sent directly to an AFNI/SUMA NIML socket:
cargo run -- --afni-port 53211 niml send raw path/to/message.niml
cargo run -- --afni-port 53211 niml send crosshair --surface-id SURF_ID --node 42 --xyz 1,2,3
cargo run -- --afni-port 53211 niml send command reset-cameraExample:
cargo run -- --spec path/to/fsaverage_lh.spec --sv path/to/SurfVol.nii --talk-afni --niml-record afni_session.nimlrecThe same module also defines sumaru-side NIML state messages for active surface,
crosshair and selected node/triangle, dataset loading, overlay/threshold
settings, controller commands, and ROI state. Those messages route through
shared controller/command state rather than directly mutating viewer-only
fields, so they can be tested without launching the GUI.
- Launch with
cargo runto open an empty viewer and a separate controls window, then use theOpen:buttons for a surface, overlay, spec, or surface volume. The controls window auto-fits to its current contents, capped by the monitor size. - Add
--verboseto print viewer status messages to the terminal. - Spec scenes load only the active display state by default. Add
--preloadto load all spec surfaces into memory before the viewer opens, so switching between surfaces is instant (at the cost of a longer startup). - Left-drag to orbit.
- Right-click the surface to inspect the nearest node, triangle, and loaded overlay value.
- Scroll to zoom.
- Press Space to reset the camera.
- Press
Cto switch camera mode betweenorbitandturntable. - Press
Oto toggle a loaded overlay on or off. - Press
.to advance to the next surface in a loaded single-hemisphere.specscene, or the next matched left/right state pair in abothscene. Press,to move backward. - In a
bothspec scene, useOpenandClosein the VIEW section to persistently switch between the closed and acorn paired-hemisphere layouts. Hold Control and left-drag in the viewer to fine-tune the pair: left/right adjusts the open angle, and up/down adjusts the gap between hemispheres. - In a
bothspec scene, press[to show/hide the left hemisphere and]to show/hide the right hemisphere. - Press
rto save the current view as a PNG, or Shift-Rto save a 1x4 montage. Single-surface scenes use left/right/top/bottom views;bothspec scenes use closed top, closed bottom, open medial-in, and open outer-out views. The VIEW section also hasSaveandMontagebuttons. When a thresholded overlay is active, a second_cmap-suffixed file is saved alongside the screenshot with the colorbar rendered on the right side. - Press
F5to switch the background between black and white. - Press
gto open or close the graph dock at the bottom of the view window. When open, right-click picks update the graph live. Drag the handle at the top of the dock to resize it; the 3D viewport adjusts to match. - Hold Option and press an arrow key for preset views:
- Option-Left: left side view
- Option-Right: right side view
- Option-Up: top-down view
- Option-Down: bottom-up view
- The view menu bar has two right-aligned icon buttons:
+launches a new blanksumaruwindow, and the copy icon duplicates the current surface/spec (no overlay) into a fresh window for a second analysis view.
Launch with --volume path/to/volume.nii (or .nii.gz) to render a NIfTI
volume as orthogonal slice planes inside the 3D scene. All three planes show by
default, color-coded by orientation: axial red, coronal green, sagittal
blue, each with a colored grab tab.
- Right-click a plane to select it (its border brightens).
- Left-drag the selected plane to scrub it along its axis; left-drag with no plane selected still orbits the camera.
- Right-click empty space to deselect.
- The Volume menu adds a second parallel slice of any orientation ("Add Axial/Coronal/Sagittal slice") or removes the selected one, so you can view two depths of the same orientation at once.
Slices share the surface camera and depth buffer; the slice shader reconstructs each fragment's voxel coordinate from the file's voxel↔world affine, so planes stay correct under any orientation.
The project is mostly a place to try viewer and data-model ideas in the open. The binary crate should stay thin. Most behavior should live in the library crate so future renderers, GUI experiments, batch tools, and tests can share the same data model.
That split is intentional: for scientific viewing software, the interesting parts are not only the pixels on screen. File parsing, coordinate transforms, overlay tables, ROI state, and AFNI interop should be understandable and testable without needing to launch the GUI.
See docs/ROADMAP.md for the active to-do plan and docs/COMPLETED.md for
the completed-work ledger.
Cargo.tomldefines thesumarupackage, Rust edition/toolchain floor, dependencies, and lint policy. This is where core libraries likegifti-rs,nifti,winit,wgpu,egui,rfd,clap, andglamare wired in.Cargo.lockrecords exact dependency versions so rebuilds use the same crate graph..cargo/config.tomldefines local Cargo aliases such ascargo check-all,cargo surface, andcargo inspect..gitignorekeeps Cargo build output intarget/out of version control.README.mdis the project-facing quickstart: scope, commands, controls, overlays, design direction, and this file guide.docs/ROADMAP.mdis the active to-do plan, grouped by shared foundations such as AFNI interop, command state, everyday viewer use, GPU work, and volume support.docs/COMPLETED.mdis the completed-work ledger for bootstrap, data model, geometry, viewer, ROI, spec, and rendering performance milestones.src/lib.rsis the library crate entry point. It exposes the reusable modules so the binary, tests, and future tools can share the same implementation.src/afni.rscontains the first AFNI/SUMA NIML talk layer. It resolves AFNI-style ports, buildsSUMA_ixyz/SUMA_node_normals/SUMA_ijksurface registration elements, parsesSUMA_irgbaoverlays, maps incoming NIML messages to shared controller actions, and emitssumarustate messages.src/command.rscontains the shared controller and command state used to route viewer menus, keyboard shortcuts, controller panels, and AFNI messages through the same non-wgpumodel. It owns the canonicalOverlayThresholdtype (used by both the render appearance and the controller command state) andValueRange(f32render range), keeping the render/domain boundary explicit.src/main.rsis the command-line entry point. It parsessumaruarguments, launches the viewer with an initial surface or.specscene, requires-svsurface-volume context for.speclaunches, handles--overlay, passes through--verboseterminal logging, controls spec preloading with--preload, opens a NIfTI volume in slice-plane mode with--volume, and runs theinspectsubcommand.src/color.rscontains shared RGBA, continuous color-map, and label-table models for scalar maps and integer label datasets, including GIFTI and FreeSurfer import helpers. Continuous colormaps include 13 byte-exact AFNI colorscales ported fromDC_spectrum_AJJ/DC_spectrum_ZSS(the same LUT engine SUMA uses):Spectrum:red_to_blue, both+gapvariants,Spectrum:yellow_to_red,Spectrum:yellow_to_cyanand its+gapvariant,color_circle_AJJ,color_circle_ZSS,Reds_and_Blues,Reds_and_Blues_w_Green,afni_p2spanned,bwr, andFire.src/dataset.rscontains the canonical domain-attached dataset table model. It supports dense and sparse row-to-node data, typed columns, column labels and roles, numeric ranges (ColumnRange, thef64domain range type), units, and parent/provenance ids.src/inspect.rscontains headless file inspection. It detects GIFTI/NIFTI paths, reads them through the current external crates, and prints concise metadata summaries.src/io/is the native AFNI/SUMA I/O layer, split into a thinmod.rsre-export facade over topical submodules:io/niml.rs(the NIML element model, ASCII/binary parser and serializer, label tables, and low-level text/number helpers),io/gifti.rs(GIFTI dataset loading and NIFTI-intent → column-role mapping), andio/roi.rs(.niml.roiread/write and the side/drawing-type code mappers). Callers keep usingcrate::io::*; the facade is the migration seam toward the externalafni_rustcrate (seedocs/ROADMAP.md).src/volume.rsloads a NIfTI volume into a dense scalar grid plus the voxel↔world transform (VolumeSpace) and intensity range, ready for the--volumeslice-plane renderer.src/overlay.rscontains display state layered on datasets. It selects intensity/threshold/brightness columns, stores color-map and range controls, and builds per-node RGBA color caches for rendering.src/roi.rscontains the shared ROI model for drawn, imported, dataset-born, and threshold-derived surface regions. It stores labels, styling, parent-surface/domain links, source/provenance, path history, domain validation, and conversion into sparse ROI datasets.src/spec.rsparses SUMA.specfiles into surface groups, states, hemisphere labels, resolved surface paths, local domain/curvature parents, anatomical flags, and label-dataset references.src/surface.rscontains the current surface data model and GIFTI surface adapter. It loads vertices/triangles, validates indices, computes bounds and normals, records SUMA-inspired domain/metadata/lineage, and stores scalar overlay values/ranges without depending on viewer rendering details.src/viewer/mod.rsis the viewer core. It sets up thewinitevent loop, owns the four windows (view, control, roi_control, graph) asWindowPanevalues, integrateseguiviaEguiPane, ownsViewerState, dispatchesViewerCommands, loads surfaces/overlays/volumes, and drives render/update. The feature-specific behavior lives in the topical submodules below (each animpl ViewerStateblock reached throughuse super::*), somod.rsreads as a table of contents rather than an encyclopedia.src/viewer/input.rsroutes window input: the view window's mouse/keyboard handling (camera, pair-drag, ROI picking, volume slice scrubbing, keyboard shortcuts) plus the egui passthrough for the control, ROI, and graph windows.src/viewer/ui.rsholds alleguipanel drawing: the view menu bar, the surface/overlay workbench, ROI/pick sections, and the graph window/dock.src/viewer/camera.rscontains the viewer camera model: orbit and turntable modes, preset orientations, scroll zoom, mouse drag handling, and camera uniform packing for the shader.src/viewer/scene.rsowns the multi-surface scene and GPU render set:SurfaceScene/SceneSurfaceand the resident vertex/index buffers.src/viewer/overlay_load.rsloads single and paired overlays and refreshes the overlay columns, appearance, and render model.src/viewer/roi.rsholds the drawn-ROI editing, fill, save, and load logic plus the ROI workspace/slot/draft types.src/viewer/pairing.rshandles paired-hemisphere drag, transform, and layout.src/viewer/afni.rsholds the viewer-side AFNI/SUMA NIML talk bridge.src/viewer/capture.rsandsrc/viewer/screenshot.rshandle screenshot and montage capture: camera framing,wgpureadback → RGBA, PNG writing, and preset-view montage stitching.src/viewer/graph.rsmanages the picked-node graph window and dock.src/viewer/transform.rsholds the paired-hemisphere layout math (matrices, auto-spread/clearance heuristics, open-book gesture bookkeeping).src/viewer/volume_view.rsis the--volumeGPU state: the 3D intensity texture, the orthogonal slice-plane pipeline, slice add/remove/select/drag, and theViewerStatehandlers that drive it.src/viewer/gpu.rscontains smallwgpusetup helpers such as surface format/present-mode selection and the depth-buffer texture.src/viewer/mesh.rsprepares durable surface/overlay data for the viewer. It normalizes positions, flattens triangle indices, assigns default or overlay colors, and packs vertex/index bytes for GPU upload.src/viewer/pick.rscontains right-click surface inspection and the shared camera-ray builder. It intersects the cursor ray with normalized triangles and reports the hit triangle, nearest node, and overlay value.src/viewer/shader.wgslcontains the lit surface rendering shader;src/viewer/volume_slice.wgslcontains the volume slice-plane shader (window/ level grayscale with per-fragment voxel reconstruction).target/is generated by Cargo when you build or run the project. It is not source code and can be regenerated at any time.




