π Live demo: https://neuroanatomyandconnectivity.github.io/directionality-indicator-webgl/
Browser-based visualization of directional information on anatomical surfaces. A WebGL2 / TypeScript port of the C++/Qt/OpenGL 3.3 DirectionalityIndicator by Sebastian Eichelbaum and the Max Planck Research Group "Neuroanatomy and Connectivity". See NOTICE.md for credits and license.
The visualization pipeline is the original's Tier-1 behaviour: a Line Integral Convolution (LIC) flow rendered on the cortical surface, with directionality arrows overlaid following the parcellation-derived vector field.
cd web
npm install
npm run devOpen http://localhost:5173. The bundled demo dataset (label34.ply,
label34.labels, label34.labelorder) loads automatically.
- WebGL2 with the
EXT_color_buffer_floatextension. The app detects the extension at boot and shows a banner if it's missing. Chromium-based browsers, Firefox, and Safari 16+ all support this. - A modern JS engine (ES2022 features used).
The right-side panel is fully collapsible (click the chevron at its left edge or the corner button when closed). Sections collapse individually by clicking their headings; collapse state persists.
Pick a dataset triple β one .ply mesh, one .labels file, one
.labelorder file β via the file picker. The triple is parsed,
processed, and rendered. The dataset is cached in IndexedDB and
auto-restored on the next page load. Cache is cleared by the Reset
to defaults button at the bottom of the panel.
Six canonical anatomical view buttons: Lateral L/R, Anterior / Posterior, Dorsal / Ventral. Click to snap. Free rotation/pan/zoom with the mouse is also available; the camera position auto-saves 350 ms after each change and restores on next load.
Independent toggles for the three layers:
- LIC streamlines β the smeared noise pattern showing the directional field on the surface.
- Parcellation colors β the per-region colored shading. When off, the surface goes black so the LIC streaks read more clearly.
- Arrows β the overlay glyphs.
- Streamline length (iterations) β how far each LIC noise sample is advected along the field. Longer = more visible streaks.
- High contrast β boosts the LIC contribution to the composite.
- Emphasize singularities β paints weak-magnitude regions
(saddles, vortex centres) with white/black bands so field topology
reads at a glance. Mirrors the original C++
m_emphasizeSingularPointsEnable.
- Background β canvas clear color (used for both the page background and PNG screenshot transparency reference).
Phong parameters for the underlying parcellation surface:
- Ambient / Diffuse / Specular β standard material weights.
- Shininess β specular sharpness. Higher = smaller, sharper highlights. Defaults are softened from the original C++ to reduce glare.
- Borders only (M2 mode) β restrict arrow placement to parcellation-border vertices instead of the whole surface.
- Density (smaller = denser) β Poisson minimum spacing as a fraction of mesh radius. Coupled to arrow length so arrows can't visually overlap regardless of size.
- Size / Length / Head width / Body width β arrow geometry. Length and Size trigger a re-sample on release (debounced).
- Distance from surface β how far above the surface the arrow bodies float (in mesh units relative to arrow scale).
- Opacity β alpha-blend strength of the arrow over the LIC.
- Flip direction β instant uniform-side flip of all arrows.
- Color β picker; default black.
- Resolution (DPI) β output pixel scale. 72 β 1 Γ current canvas; 150 β ~1.5 Γ; 1200 β ~12.5 Γ. Hard-capped at 50 megapixels (a console warning fires if the requested resolution would exceed this).
- Current view β saves the current camera angle as a transparent- background PNG, auto-cropped to the brain extent.
- All canonical views β saves six PNGs (one per anatomical direction), each auto-cropped.
At the bottom of the panel. Clears all panel parameters, the camera state, the section-collapse state, and the IndexedDB dataset cache, then reloads.
| Gesture | Action |
|---|---|
| Drag | Rotate (trackball, around target) |
| Shift + drag | Pan (translate target on screen) |
| Wheel | Zoom (exponential, distance-clamped) |
Camera state (rotation, target, distance) persists 350 ms after each change.
The app expects three files per dataset, all referring to the same mesh by vertex order.
.ply β ASCII PLY 1.0. The current parser supports:
ply
format ascii 1.0
element vertex N
property float x
property float y
property float z
[property uchar red] (optional; alternative source of color)
[property uchar green]
[property uchar blue]
element face M
property list uchar int vertex_indices
end_header
...vertex data, one line per vertex...
...face data, one line per triangle...
Triangle faces only. Per-vertex colors in the PLY (the optional
red / green / blue columns) are used directly as the surface
palette when present. When the PLY has no color columns, the
surface falls back to a label-derived HSV-wheel palette keyed by
labelorder index. The labels file is always used to compute the
directionality field that the arrows follow, regardless of where
the surface colors come from.
TODO: decouple surface colors entirely from the PLY by reading a separate colormap file, so the PLY can be pure geometry.
.labels β one integer per line, one line per mesh vertex. The
integer is the region ID for that vertex.
.labelorder β one integer per line listing the region IDs in
display order. The number of entries determines how many regions
there are. Region IDs in .labels that are not in .labelorder
are treated as "ignored" / non-mesh (excluded from the directionality
field).
The bundled demo label34.* is the FreeSurfer 34-region cortical
parcellation on a single cortical hemisphere mesh (32,492 vertices,
64,980 triangles).
- Parse the three input files.
extractRegionsβ port of the originalExtractRegionsCase 2 algorithm. Computes a per-vertex unit direction vector from the parcellation: at parcel borders, the direction follows thelabelorder(from later-in-order to earlier-in-order); the field is then iteratively spread inward across each parcel by Laplacian- style propagation projected onto the surface tangent plane.- SurfaceLIC β four-pass FBO pipeline:
- Transform β render the mesh into G-buffers (color, view-space vec, noise sampled from a 3D noise volume by world position, normal, position).
- Edge β Laplacian on the depth buffer.
- Advect β iterated noise advection along the screen-space vec field (50 forward + 50 backward Euler steps), accumulating and averaging.
- Compose β combine color + LIC + edge mask + multi-LOD depth halo into the canvas.
- Arrows β streamline-based seeding (or border-restricted
Poisson-disk in M2 mode), then instanced quad rendering with the
parametric arrow shape from the original C++
Arrows-fragmentshader. Occlusion uses the LIC G-buffer position texture.
For deeper architectural notes see docs/superpowers/specs/ and
docs/superpowers/plans/.
web/
βββ README.md β this file
βββ NOTICE.md β credits and license info
βββ index.html β entry HTML + panel CSS
βββ package.json
βββ vite.config.ts
βββ tsconfig.json
βββ public/data/ β bundled demo dataset
βββ src/
βββ main.ts β boot
βββ app.ts β top-level pipeline orchestrator
βββ io/ β PLY / labels / labelorder parsers + IndexedDB cache
βββ algorithms/ β extractRegions, sampling, surfaceLIC, etc.
βββ gfx/ β view, camera, framebuffer, screen quad, program, buffer
βββ ui/ β panel
βββ shaders/ β all GLSL
npm test # vitest run
npm run build # tsc + vite build
npm run dev # vite dev server with HMRTests cover the IO parsers (plyReader, labelReader, labelOrderReader),
the regionColors palette, meshAdjacency, computeNormals,
extractRegions, seedArrows, and the Camera view math. GPU code
paths (the LIC pipeline, arrow rendering) are exercised by the dev
server's interactive UAT.