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, plus native AFNI HEAD/BRIK volume loading. - 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
tctimecourse mode for MNE-style 3D+time GIFTI overlays, with playback, baseline correction, response windows, peak/mean/AUC maps, and activation thresholds. - A
--volumemode that renders orthogonal NIfTI slice planes in the 3D scene. - AFNI/FATCAT
.niml.tractobjects with adjustable screen-space ribbon width, per-bundle visibility/opacity, and SUMA-style local-orientation, tract-orientation, or bundle coloring. - AFNI
Graph_Bucket .niml.dsetobjects with shaded 3D node glyphs, straight or tract-bundle edges, and an interactive matrix view; full, triangular, and sparse edge encodings are parsed. - 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 -- tc -i /path/to/underlay-lh.gii --overlay /path/to/stc-lh.gii
cargo run -- -spec /path/to/subj_rh.spec
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 -- --volume /path/to/anat+tlrc.
cargo run -- --tract /path/to/network.niml.tract
cargo run -- --volume /path/to/anat.nii.gz --tract /path/to/network.niml.tract
cargo run -- --graph /path/to/network.niml.dset
cargo run -- --vol /path/to/anat+orig. --gdset /path/to/network.niml.dset
cargo run -- -vol /path/to/anat+orig. -gdset /path/to/network.grid
cargo run -- inspect /path/to/file.nii.gzFor SUMA compatibility, --vol aliases --volume, while --gdset and
--grid alias --graph. The single-dash SUMA spellings -vol, -gdset, and
-grid are accepted as well.
Tracts and graphs are independent scene objects: several may be loaded at
once, either alone or beside surfaces and volume slices. Use the
TRACTS / GRAPHS controls to select an object, show or hide it, adjust ribbon
width and opacity, change tract coloring, control individual bundles, select a
graph edge measure and threshold, resize graph nodes, or remove it. Reopening
an already loaded path refreshes and activates that object.
Right-click a visible tract, graph node, or displayed graph edge to inspect it.
Graph node labels can be toggled per object. Tract hit-testing uses a compact
streamline bounding-volume hierarchy so large whole-brain datasets do not scan
every segment for each click.
Choose Open matrix view in a graph's inspector for a floating, scrollable
heatmap. It shares the 3D graph's selected measure, signed/magnitude coloring,
and absolute-value threshold; thresholded cells are dimmed rather than removed.
The view can show the full matrix or its lower triangle, and clicking a cell
selects that edge in the normal graph selection readout.
When a Graph_Bucket contains a network_file link, Sumaru resolves and loads
the associated .niml.tract network. The graph defaults to straight edges like
SUMA; choose linked tract bundles in the graph inspector to replace each
edge with the bundle identified by its Bundle_Tag or reciprocal
Bundle_Alt_Tag. Thresholding, coloring, and picking continue to use the
selected graph measure, and unmatched edges retain the straight-edge fallback.
The 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. Embedded GIFTI FDR curves are
used automatically. If a statistical .gii.dset has no embedded curve, set
GIFTI_DSET_AUTO_QCALC = YES in ~/.sumaru (or enable the matching preference)
to reconstruct q-values while loading; the default is NO. Treat this as a viewer
convenience rather than a statistics package: it is meant to help inspect the
data you already understand.
Opening another overlay adds it to the overlay workbench and makes it active. Use the dataset selector or Control-PageUp/Control-PageDown to switch between loaded overlays; only the active overlay is rendered. Reopening an existing source refreshes its existing list entry rather than adding a duplicate, and a left/right dataset pair is represented by one entry.
The Settings > Preferences panel controls how thresholds move between overlays.
The default keeps the current numeric slider value. The alternatives match the
p-value when both datasets carry compatible stat metadata, or remember a
separate threshold for every overlay. Preferences are saved automatically in
~/.sumaru; on first launch Sumaru creates the fully documented default file
if it does not already exist. The file follows the self-documenting AFNI/SUMA ~/.sumarc
convention: it has an ***ENVIRONMENT section, stable preference keys, and
an adjacent description, allowed values, and default for every setting. The
current threshold policy is stored as SUMARU_OverlayThresholdSync; optional
GIFTI q-value reconstruction is stored as GIFTI_DSET_AUTO_QCALC. Older
minimal files using overlay_threshold_sync remain readable and are migrated
to the documented form the next time Preferences saves the file.
Launch a surface timecourse directly with:
sumaru tc -i underlay-lh.gii --overlay stc-lh.giiUse --overlay-multi to compare several conditions on the same surfaces:
sumaru tc -i underlay-lh.gii \
--overlay-multi condition-a-lh.time.gii condition-b-lh.time.giiRepeating --overlay once per condition is equivalent.
If matching -rh files exist beside the supplied -lh files, Sumaru loads
both hemispheres automatically. --surface-rh and --overlay-rh can specify
the partners explicitly; repeat --overlay-rh in the same condition order.
A single hemisphere works normally. -onestate controls how multiple surface
geometry files are grouped and is not needed for multiple timecourse
conditions.
The bottom timecourse dock drives the existing surface renderer. Move the time slider, click the graph, or press Play to display each sample's vertex activation map. Auto robust (99.5%) uses one fixed color scale across the full timecourse so playback does not rescale or flicker; Full range includes the most extreme sample. These are display mappings and do not alter the data. Select a baseline and choose raw values, baseline subtraction, or baseline z-scores. Select a response window and show its mean, positive peak, negative peak, signed absolute peak, or trapezoidal area under the curve on the surface. The baseline and response fields are times in milliseconds and snap to the nearest sample. Dragging a baseline previews it immediately; the robust scale is recomputed when the drag ends. The activation threshold can be signed or absolute and uses the normal overlay masking path. Right-click any surface node to add its full timecourse to the dock. Every loaded condition is drawn on the same graph with a stable color; the active condition is emphasized and is the one mapped on the brain. Use the existing overlay Dataset selector (or its previous/next buttons) to change the active condition. Blue and orange bands mark the baseline and response windows, and the white cursor line is the time currently displayed on the surface. When the activation threshold is enabled, dashed gold lines show its boundary in the selected vertex's graph (two lines for an absolute threshold). Click the graph to move the cursor, drag across it to select the response window, or Shift-drag to select the baseline window. Map window summary switches away from the current time point and maps the chosen response-window measure across vertices; moving the time slider or clicking a time point switches back to the time-point map.
MNE-style GIFTI metadata keys TimeStart and TimeStep are read in seconds.
When absent, Sumaru falls back to a zero start and one-second spacing so the
data remain inspectable, but correctly exported metadata are required for
meaningful ERP/ERF timing and AUC units.
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.
Sumaru can listen for the NIML EngineCommand messages produced by AFNI's
DriveSuma program. There are two intentionally different command modes:
-niml/--nimluses Sumaru's native key meanings. For example, remoteControl+Ropens the ROI controller andRsaves a montage.-niml-suma/--niml-sumatranslates the supported SUMA subset.-niml-compat/--niml-compatis an alias. In this mode SUMA-style dataset controls, key translations, clustering, andkill_sumaare enabled.
Start Sumaru first, using the same port bloc that will be passed to DriveSuma:
sumaru -spec Demo.spec -sv Demo_SurfVol+orig. -niml-suma -npb 1000
DriveSuma -npb 1000 \
-com surf_cont -surf_label Net_000.gii \
-load_dset Net_000.cols.niml.dset \
-switch_cmap ROI_i32 -Dim 0.3Compatibility mode currently supports selecting an already loaded surface, loading and switching overlay datasets, intensity/threshold/brightness sub-bricks and ranges, numeric/p-value/percentile thresholds, brightness scale, dim and opacity, dataset visibility and display modes, zero masking, and SUMA-style cluster settings. The translated key subset covers camera movement and presets, render style and opacity, background, screenshots, graph opening, component visibility, and surface/state cycling.
Sumaru deliberately uses one view per process instead of SUMA's several
lettered viewers in one application. Start separate Sumaru processes with
different -npb values for independently scripted views. Likewise, Sumaru
renders only the active overlay: 1_only=y is its natural behavior, while
1_only=n is accepted but does not enable simultaneous overlay compositing.
See DriveSuma compatibility for the full command table, key translations, cluster sign conventions, native-versus-compatibility differences, examples, diagnostics, and known limitations.
- 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.
- With a surface time-series overlay active, press
Dto open or toggle InstaCorr. Its floating panel exposes a SUMA preprocessing switch plus editable TR, polort, and bandpass settings; press Recalculate to apply changes. Turning preprocessing off disables those dependent settings and computes a raw dot product, matching SUMA'snormalize_dsetgate. Ordinary right-clicks then update the seed using the applied settings. Shift-right-click moves the crosshair without recalculating InstaCorr. - Scroll to zoom.
- Press Space to reset the camera.
- Press
Cto switch camera mode betweenorbitandturntable. - Press Shift-
Vto toggle a loaded overlay on or off. - Press Control-PageUp or Control-PageDown to move through the ordered overlay list. The same actions are available beside the dataset selector in the overlay workbench.
- 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, or pass an AFNI .HEAD, .BRIK, .BRIK.gz, or dataset prefix such as
--volume path/to/anat+tlrc.. The volume is rendered 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/DRIVESUMA.mddocuments the inbound DriveSuma listener, native and SUMA compatibility modes, supported controller commands, and intentional architectural differences from SUMA.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, accepts optional-svsurface-volume context for AFNI/NIML communication, 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/preferences.rsloads and atomically saves the human-readable~/.sumarupreferences file.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/overlay_stack.rsowns ordered overlay switching, source deduplication, removal, and preference-driven threshold transfer.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.




