LocalNeuroglancer — Python API

LocalNeuroglancer runs a local Neuroglancer instance that can load .zarrvectors stores directly from disk or remote object stores. It manages both a local file server (for serving local data) and a Neuroglancer viewer tab in your browser. This tutorial covers the Python API for loading stores, configuring layer rendering, and managing the viewer state programmatically.

For the interactive shell console, see Shell console.


Launching the viewer

from ngtools.local.viewer import LocalNeuroglancer

viewer = LocalNeuroglancer()
# Output:
# fileserver:   http://127.0.0.1:9123/
# neuroglancer: http://127.0.0.1:9321/v/1/

A Neuroglancer tab opens in your default browser automatically. If it does not open (e.g. on a remote server), navigate to the printed URL manually.

The viewer runs in the background as a Tornado server. Your Python session remains interactive.

Controlling the port

viewer = LocalNeuroglancer(
    fileserver_port=9123,       # local file server port
    neuroglancer_port=9321,     # Neuroglancer viewer port
    open_browser=True,          # set False to suppress auto-open
)

Using as a context manager

with LocalNeuroglancer() as viewer:
    viewer.add("scan.zarrvectors")
    input("Press Enter to close…")
# Viewer and server are shut down on exit

Loading a ZVF store

From local disk

# Infer type from .zarrvectors extension
viewer.add("scan.zarrvectors")

# Explicit layer name
viewer.add("scan.zarrvectors", name="synchrotron_scan")

# Explicit scheme
viewer.add("zarr_vectors:///path/to/scan.zarrvectors")

add() reads the root .zattrs of the store, determines the geometry type, and registers the appropriate Neuroglancer layer type. The store is not read in full at this point — data is fetched on demand as the viewer pans and zooms.

From S3

viewer.add("zarr_vectors://s3://open-neuro/datasets/scan.zarrvectors")

For private S3 buckets, credentials must be available via the standard AWS credential chain (~/.aws/credentials, environment variables, or IAM role). zv-ngtools uses s3fs under the hood.

From GCS

viewer.add("zarr_vectors://gs://my-bucket/tracts.zarrvectors")

Loading multiple layers

# Load a registered OME-Zarr image volume (upstream ngtools feature)
viewer.add("zarr:///path/to/em_volume.zarr", name="EM")

# Overlay ZVF layers on the same coordinate space
viewer.add("neurons.zarrvectors", name="neurons")
viewer.add("tracts.zarrvectors",  name="tracts")
viewer.add("vessels.zarrvectors", name="vessels")

All layers must share the same physical coordinate system. If your ZVF store is in voxel space and the image is in RAS mm, supply a transform (see Coordinate transforms below).


Layer listing

layers = viewer.list_layers()
print(layers)
# ['EM', 'neurons', 'tracts', 'vessels']

Configuring layer rendering

Point cloud rendering

viewer.add("scan.zarrvectors", name="scan")

# Point size in screen pixels
viewer.set_point_size("scan", size=3)

# Colour by attribute value (maps float32 to a colour ramp)
viewer.set_shader("scan", """
void main() {
    float val = prop_intensity();
    emitRGB(vec3(val, 0.2, 1.0 - val));
}
""")

# Fixed colour
viewer.set_shader("scan", color="#ff2d9a")

Streamline rendering

viewer.add("tracts.zarrvectors", name="tracts")

# Line width in screen pixels
viewer.set_line_width("tracts", width=1.5)

# Colour by per-vertex FA (custom GLSL shader)
viewer.set_shader("tracts", """
void main() {
    float fa = prop_fa();
    emitRGB(colormapJet(fa));
}
""")

# Solid colour
viewer.set_shader("tracts", color="#4da6ff")

# Opacity
viewer.set_opacity("tracts", 0.7)

Skeleton rendering

viewer.add("neurons.zarrvectors", name="neurons")

# Colour axons and dendrites differently
viewer.set_shader("neurons", """
void main() {
    int t = prop_swc_type();
    if (t == 2) emitRGB(vec3(0.2, 0.8, 1.0));       // axon: blue
    else if (t == 3) emitRGB(vec3(1.0, 0.5, 0.2));  // basal: orange
    else if (t == 4) emitRGB(vec3(0.2, 1.0, 0.5));  // apical: green
    else emitRGB(vec3(0.8, 0.8, 0.8));              // other: grey
}
""")

Mesh rendering

viewer.add("brain.zarrvectors", name="brain_surface")

# Transparency
viewer.set_opacity("brain_surface", 0.4)

# Colour by attribute (e.g. cortical thickness)
viewer.set_shader("brain_surface", """
void main() {
    float t = prop_thickness();
    emitRGB(colormapViridis(clamp((t - 1.5) / 3.0, 0.0, 1.0)));
}
""")

LOD and resolution level control

Automatic LOD (default)

By default, zv-ngtools selects the resolution level based on the Neuroglancer viewport’s screen resolution at the current zoom. As the user zooms out, coarser levels are served automatically.

The LOD decision uses:

# Pseudocode inside the zv-ngtools request handler
def select_level(store, requested_resolution_um):
    for level in reversed(store.levels):   # coarsest first
        if store.bin_shape_at(level).max() <= requested_resolution_um:
            return level
    return 0   # finest level if nothing coarser qualifies

Force a specific level

To lock the viewer to a specific resolution level (useful for debugging or when the automatic LOD selection is not appropriate):

viewer.set_level("tracts", level=2)   # always serve level 2
viewer.set_level("tracts", level=None) # restore automatic LOD

Coordinate transforms

If a ZVF store and an image volume are in different coordinate spaces, apply a transform when loading:

import numpy as np

# 4×4 affine (homogeneous): scales from voxel to µm and offsets origin
voxel_to_um = np.array([
    [4.0,  0,    0,    -500.0],   # x: 4 µm/vx, offset -500 µm
    [0,    4.0,  0,    -500.0],   # y
    [0,    0,    25.0, -250.0],   # z: 25 µm/vx
    [0,    0,    0,     1.0  ],
])

viewer.add(
    "scan.zarrvectors",
    name="scan",
    transform=voxel_to_um,
)

The transform is stored in the Neuroglancer layer state and applied when rendering. It does not modify the ZVF store.



Hiding and removing layers

# Hide a layer (keeps it loaded, stops rendering)
viewer.set_visible("tracts", visible=False)
viewer.set_visible("tracts", visible=True)

# Remove a layer entirely
viewer.remove("tracts")

Getting and setting viewer state

The Neuroglancer state is a JSON object that encodes all layers, positions, zoom, and rendering settings. It can be serialised and shared:

# Get current state as a Python dict
state = viewer.get_state()

# Save to file
import json
with open("session_state.json", "w") as f:
    json.dump(state, f, indent=2)

# Restore from file
with open("session_state.json") as f:
    saved_state = json.load(f)
viewer.set_state(saved_state)

Programmatic screenshot

# Save the current viewport as a PNG
viewer.screenshot("screenshot.png")

# Specify resolution
viewer.screenshot("hires.png", size=(3840, 2160))

Common patterns

Quick data inspection

from ngtools.local.viewer import LocalNeuroglancer

viewer = LocalNeuroglancer()
viewer.add("scan.zarrvectors")
# Browse the data manually, then close the terminal to stop the server

Scripted figure generation

from ngtools.local.viewer import LocalNeuroglancer
import time

viewer = LocalNeuroglancer(open_browser=False)

viewer.add("em_volume.zarr", name="EM")
viewer.add("neurons.zarrvectors", name="neurons")
viewer.set_shader("neurons", color="#22d97a")
viewer.set_layout("3d")
viewer.move_to([1000., 800., 400.])
viewer.zoom(2.0)

time.sleep(1)   # allow rendering to complete
viewer.screenshot("figure_3a.png", size=(1920, 1080))
viewer.close()