neu_glance.serving

Host arrays in this process and open a real neuroglancer on them.

What is hosted is a ServedLayer; this module is the server around it.

Runs the viewer’s server inside the calling process, so an array that exists only in a notebook (a model’s probability map, a relabelled crop) can be viewed without writing a volume. neuroglancer is imported only inside functions (the serve extra).

  • A served source is not portable. Its URL is python://volume/<token>, valid only while this process runs, so Server.state() is for inspection, not for sharing.

  • The chunk encoding stays ``npz``. In neuroglancer 2.41.2 the raw encoder calls ndarray.tostring, removed in NumPy 2, and fails on the first chunk requested, after the viewer has opened.

Server.on_click(), Server.boxes(), Server.points() and Server.selected_segments() read the browser’s state back into the notebook; Server.annotate() adds a layer to draw in.

Attributes

Classes

Click

Where a click landed, and what was under it.

Server

A running viewer, and the handle the notebook keeps on it.

Functions

serve(layers, *[, bind, port, into, annotations, ...]) → Server

Host layers and return a Server carrying the viewer URL.

stop_serving() → bool

Stop the viewer server, whatever started it. True if one was running.

Module Contents

class neu_glance.serving.Click

Where a click landed, and what was under it.

position is in the served frame’s voxels, zyx, as floats — the cursor is not on a voxel centre. values maps layer name to the value there, which for a segmentation is the label id.

position: tuple[float, ...]
state: Any = None
values: dict[str, Any]
property voxel: tuple[int, ...]

position floored to whole voxels, which is the voxel actually clicked.

class neu_glance.serving.Server(viewer, volumes: dict[str, Any], annotations: str | None)

A running viewer, and the handle the notebook keeps on it.

Reads the browser’s state back without needing neuroglancer’s API. .viewer is the underlying neuroglancer.Viewer, for anything not wrapped.

add_layer(layer: Any, *, name: str | None = None, color: Any = None) → Self

Host one more array in this viewer, live. Returns the server, so calls chain.

The new layer appears in the tab already open, with no new URL:

srv = (serve([em])
       .add_layer(ServedLayer.from_hdf5("gt.h5", "/vol_03700", "segmentation"))
       .add_layer(ServedLayer.from_array(pred, "probability",
                                         voxel_size=(40, 8, 8)),
                  name="prediction"))

Takes the same inputs as serve(). A name already in the viewer gets a numeric suffix; list(srv.volumes)[-1] is the name actually used. The view does not move, so a layer elsewhere may be off screen (see bounds()). color overrides the layer’s own, e.g. to add one array twice in two colours:

srv.add_layer(pred, color="#00ff00")
annotate(name: str | None = None, *, tool: str | None = 'box', select: bool = True) → str

Add a layer to draw in, arm a tool, and return the layer’s name.

The layer boxes() and points() read, added to a viewer already open:

srv = serve([em, gt])
srv.annotate()              # draw boxes; ctrl+mousedown0 places one
srv.boxes()                 # -> [((lo), (hi)), ...] in zyx voxels
srv.annotate(tool="point")  # same layer, now placing points
srv.points()

tool is what a click places: "box", "point", or None for nothing. select makes it the active layer, without which the armed tool does nothing until the layer is picked by hand.

Calling it again with the same name keeps what is already drawn and re-arms the tool. A name taken by a volume layer raises. The name (default: the current annotation layer, else ANNOTATE_LAYER) becomes the default for boxes() and points().

bounds(layer: str | None = None) → Any

Where the served arrays are, as a neu_lib.BBox in viewer voxels.

The extent of the arrays themselves, offset included: one layer by name, or the union of all. What enclose(clip="volume") clips to.

boxes(layer: str | None = None) → list[tuple[tuple[int, ...], ...]]

Boxes drawn in the viewer, as (lo, hi) pairs in whole voxels, zyx.

Ready for --crop-bbox, neu_vol.extract_roi() or neu-vol write. Corners are sorted per axis, since a box drawn up and to the left has them reversed.

data_bounds(layer: str | None = None) → Any

Where the non-zero voxels are, as a neu_lib.BBox in viewer voxels.

Usually much smaller than bounds() for a segmentation. One layer by name, or the union of all. Reads every voxel (in memory). A layer of all zeros raises.

enclose(layer: str | None = None, *, margin: Any = 0, replace: bool = False, clip: Any = None, description: str | None = None) → Any

Replace what is drawn with the one box containing it. Returns a neu_lib.BBox.

Place points around a region, then call this to get a box annotation in the viewer and the box in whole voxels, zyx, for --crop-bbox, neu_vol.extract_roi() or neu-vol write:

srv.annotate(tool="point")            # click a point at each extreme
box = srv.enclose(margin=16, clip="volume", replace=True)
lo, hi = box                          # a BBox unpacks like the pair it is

Every annotation in the layer counts, of any type.

margin grows the box on every side, in voxels: a scalar, or one value per axis (zyx). A negative margin shrinks it; one that empties an axis raises.

clip bounds the result:

  • "volume": the served arrays’ extent (bounds());

  • "data": their non-zero voxels (data_bounds());

  • a neu_lib.BBox or (lo, hi) pair, e.g. srv.bounds("seg");

  • None (default): no clipping, with a warning if the box starts below zero.

A clip that leaves nothing raises. replace=True deletes the annotations that went into the box, leaving only the result. With replace=False the new box is itself an annotation, so calling again with a margin grows it each time.

on_change(callback: Callable[[], None]) → None

Call callback() whenever the browser changes the state.

The callback takes no arguments, as in neuroglancer; it should re-read what it needs.

on_click(callback: Callable[[Click], None], *, button: int = 0, modifiers: str = 'control', scope: str = 'data_view', name: str | None = None) → str

Call callback(click) when the user clicks in the data.

Returns the action name. The default binding is control+mousedown0, like neuroglancer’s own tools, since a bare click navigates. The callback is not called for a click outside the data. It runs on the server’s event-loop thread, and an exception in it is logged rather than lost.

points(layer: str | None = None) → list[tuple[int, ...]]

Points drawn in the viewer, in whole voxels, zyx.

screenshot(path: str | None = None)

A PNG of what the browser is showing. Blocks until it replies.

With no browser connected this blocks forever: neuroglancer waits for the reply with no timeout. neu_glance.movie.rendering.wait_for_browser() is the bounded form.

selected_segments(layer: str | None = None) → set[int]

The label ids currently shown in a segmentation layer.

state() → dict

The viewer’s current state as JSON.

For inspection only: its sources are valid only in this process. To share a link, use neu-glance gen on a published volume.

stop() → None

Stop the server, invalidating every viewer URL in this process.

There is one server per process, shared by every viewer, so this stops them all (like stop_serving()).

property position: tuple[float, ...] | None

Where the viewer is looking, in the served frame’s voxels (zyx).

property url: str
viewer
volumes
neu_glance.serving.serve(layers: Sequence[neu_glance.served_layer.ServedLayer], *, bind: str | None = None, port: int = 0, into: dict | None = None, annotations: str | bool | None = None, position: Sequence[float] | None = None, layout: str = '4panel', fit: float = 1.0) → Server

Host layers and return a Server carrying the viewer URL.

Each entry is a ServedLayer, a neu_lib.Piece (which arrives already named and knowing its kind), or the dict form of a ServedLayer.

bind / port take effect only before the first viewer in the process (neuroglancer reads them at server start); a later different address is ignored with a warning. bind="0.0.0.0" puts the host’s name in the URL, for a browser elsewhere.

into merges the layers into an existing state (from neu_glance.load_state() or neu_glance.parse_url()), keeping its view. annotations (off by default) adds an empty annotation layer for Server.boxes(): True for the default name, or a name. Server.annotate() does the same later.

By default the view opens centred on the data and zoomed to fit it in each panel of layout; position overrides the centre, and fit above 1 shows more around it.

neu_glance.serving.stop_serving() → bool

Stop the viewer server, whatever started it. True if one was running.

For when the Server handle is lost (a re-run cell, an unassigned result). There is one server per process, a daemon thread, and every viewer URL dies with it. The library never blocks a notebook; a stuck cell is running the neu-glance serve command, which runs until interrupted.

neu_glance.serving.ANNOTATE_LAYER = 'annotations'
neu_glance.serving.ANNOTATION_TOOLS
neu_glance.serving.ENCODING = 'npz'
neu_glance.serving.logger