neu_draw.scene

What to draw, described without reference to any renderer.

A Scene is data: a list of drawables, each with geometry, a colour, an alpha and a name, plus camera framing and legend settings. It imports nothing that renders, so what a figure will contain can be checked without a GPU: fetch with sources, build a Scene, then render it with neu_draw.show().

Attributes

Classes

Camera

How to frame the scene (intent, not a camera object).

Legend

Whether to draw a legend, where, and how it looks. Intent, not objects.

LinesDrawable

A skeleton, drawn as independent segments, one per edge.

MeshDrawable

A surface. mesh is an Mesh, already in nm/zyx.

Placed

Where a drawable is shown, as distinct from where its data says it is.

PointsDrawable

Synapses, or any other point set. zyx nm, like everything else.

Scene

Everything a backend needs, and nothing it does not.

VolumeDrawable

Reserved, not implemented: a future drawable for scalar fields (image slices,

Functions

anchor_of(box[, anchor]) → neu_lib.Vec3

The point of box an alignment refers to.

build_scene([meshes, skeletons, points, colors, ...]) → Scene

Assemble a scene and colour it in one call.

label_of(drawable) → Optional[str]

A drawable's legend row: its label, or failing that its name.

resolve_marker(marker) → str

Map a matplotlib shorthand to a pygfx marker name, and validate either way.

Module Contents

class neu_draw.scene.Camera

How to frame the scene (intent, not a camera object).

view_angle is the direction the camera looks from, in xyz like the renderer: the one non-zyx value in the package. target=None fits the scene’s bounding box.

target: neu_lib.BBox | None = None
up: tuple[float, float, float] = (0.0, 0.0, 1.0)
view_angle: tuple[float, float, float]
zoom: float = 1.0
class neu_draw.scene.Legend

Whether to draw a legend, where, and how it looks. Intent, not objects.

One row per distinct label_of(), in first-appearance order, hidden drawables included so they can be turned back on. Drawables sharing a label share a row.

Sizes are in logical pixels: the legend is a fixed-size panel beside the scene. width=None fits the longest label. A legend is shown by default; pass Legend(visible=False), or show(scene, legend=False), to omit it.

groups adds rows above the per-label ones, {row text: [drawable names]}. A drawable may be in any number of groups as well as its own row, so one click can hide every Mi1 synapse while each cell’s Mi1 set keeps its row. Members are names, so a relabel leaves groups intact and Scene.rename() updates them.

font_size: float = 13.0
group_row_color: neu_draw.colors.RGBA = (0.42, 0.42, 0.5, 0.75)
groups: Mapping[str, Sequence[str]] | None = None
highlight_color: neu_draw.colors.RGBA = (1.0, 1.0, 1.0, 1.0)
location: str = 'right'
panel_color: neu_draw.colors.RGBA | None = None
row_color: neu_draw.colors.RGBA = (0.3, 0.3, 0.34, 0.55)
text_color: neu_draw.colors.RGBA = (0.93, 0.93, 0.93, 1.0)
visible: bool = True
width: float | None = None
class neu_draw.scene.LinesDrawable

Bases: Placed

A skeleton, drawn as independent segments, one per edge.

baked() → LinesDrawable
alpha: float = 1.0
color: neu_draw.colors.RGBA = (0.5, 0.5, 0.5, 1.0)
property data_bbox: neu_lib.BBox
label: str | None = None
name: str | None = None
offset_zyx_nm: neu_lib.Vec3
skeleton: neu_lib.Skeleton
thickness: float = 1.5
visible: bool = True
class neu_draw.scene.MeshDrawable

Bases: Placed

A surface. mesh is an Mesh, already in nm/zyx.

baked() → MeshDrawable
alpha: float = 1.0
color: neu_draw.colors.RGBA = (0.5, 0.5, 0.5, 1.0)
property data_bbox: neu_lib.BBox
label: str | None = None
mesh: neu_lib.Mesh
name: str | None = None
offset_zyx_nm: neu_lib.Vec3
visible: bool = True
class neu_draw.scene.Placed

Where a drawable is shown, as distinct from where its data says it is.

The offset is a display transform; the geometry is never changed, so a mesh’s bbox still reports where the tissue is, and moving a large mesh copies nothing. Use bake() when the shifted coordinates themselves are the output.

aligned_to(other: Any, *, anchor: str = 'center') → Placed

A copy moved so its anchor coincides with other’s.

other is anything with a bbox: another drawable, or a whole Scene.

centered(at: Any = (0.0, 0.0, 0.0), *, anchor: str = 'center') → Placed

A copy moved so its anchor sits at at (the origin by default).

offset_by(delta: Any) → Placed

A copy shifted by delta, in addition to any existing offset.

placed_at(offset: Any) → Placed

A copy with the offset set outright, discarding any it had.

property bbox: neu_lib.BBox

Where it is drawn: the data box moved by the offset.

property data_bbox: neu_lib.BBox
Abstractmethod:

class neu_draw.scene.PointsDrawable

Bases: Placed

Synapses, or any other point set. zyx nm, like everything else.

baked() → PointsDrawable
alpha: float = 1.0
color: neu_draw.colors.RGBA = (0.5, 0.5, 0.5, 1.0)
property data_bbox: neu_lib.BBox
label: str | None = None
marker: str = 'circle'
name: str | None = None
offset_zyx_nm: neu_lib.Vec3
positions_zyx_nm: numpy.ndarray
size: float = 8.0
visible: bool = True
class neu_draw.scene.Scene

Everything a backend needs, and nothing it does not.

add(drawable: Drawable, rename: bool = False) → Scene

Append a drawable. A duplicate name raises, or is renamed if asked.

A name is identity, so two drawables may not share one; to share a legend row, give them the same label (see label_of()). rename=True appends a number instead ("1401 (2)"); a label is left alone. build_scene() avoids the common collision of a body’s mesh and skeleton by suffixing mesh and skeleton.

add_mesh(mesh: neu_lib.Mesh, *, rename: bool = False, **kwargs) → Scene
add_points(positions_zyx_nm: Any, *, rename: bool = False, **kwargs) → Scene
add_skeleton(skeleton: neu_lib.Skeleton, *, rename: bool = False, **kwargs) → Scene
aligned_to(other: Any, *, anchor: str = 'center') → Scene

Move the scene as a whole so its anchor coincides with other’s.

arrange(*, along: Any = 'x', anchor: str = 'center', spacing: float | None = None, gap: float = 0.1, wrap: int | None = None, down: Any = 'z', origin: Any = None, align_cross: bool = True, key=None) → Scene

Lay the drawables out along an axis, wrapping into a grid if wrap is given.

Packed by each object’s extent unless spacing gives a fixed pitch in nm. Combine with superimpose():

scene.superimpose(axes="z").arrange(along="x")

key sorts the drawables first; key=lambda d: -d.bbox.size puts the largest first.

bake() → Scene

Fold every offset into the geometry, leaving all offsets zero.

For when the shifted coordinates are the output, such as writing the arrangement as meshes. Once baked, the geometry no longer reports where the tissue is.

by_label(label: str) → list[Drawable]

Every drawable in one legend row.

centered(at: Any = (0.0, 0.0, 0.0), *, anchor: str = 'center') → Scene

Move the scene as a whole, keeping the arrangement, so its anchor sits at at.

get(name: str) → Drawable
group(text: str, members: Iterable[str] | Any) → Scene

Add (or replace) a legend group row: one click for many drawables.

members is drawable names, or a predicate on drawables, such as scene.group("Mi1", lambda d: d.name.endswith(": Mi1")), resolved to names now. A group matching nothing raises. See Legend.

of_kind(kind: str) → list[Drawable]

The drawables of one kind: "mesh", "skeleton" or "points".

offset_by(delta: Any) → Scene

Shift every drawable by delta, keeping their relative positions.

on_change(callback) → Scene

Call callback() whenever a mutating method here changes the scene.

The callback takes no arguments; the listener re-reads the scene. It fires for the mutating methods (add(), rename(), relabel(), set_color(), recolor(), …), not for a field set directly, such as scene.get("a").visible = False; the backend re-reads the scene each frame to cover that.

recolor(colors: Mapping[Hashable, Any] | Any | None = None, palette: Sequence[Any] | None = None, by: str = 'name') → Scene

Reassign every drawable’s colour in one pass.

Colours are chosen over the whole set at once, so the palette’s distinct colours go to distinct drawables. by="label" gives one colour per legend row, so a row’s swatch matches all its drawables; unlabelled drawables fall back to their name. colors is keyed by name or label accordingly.

relabel(names: str | Mapping[str, str], label: str | None = None) → Scene

Set drawables’ legend labels, keyed on their names.

Takes a mapping or one (name, label) pair. Giving two drawables the same label merges their legend rows:

scene.relabel({“1401 mesh”: “Tm2”, “1402 mesh”: “Tm2”})

A label of None puts a drawable back on a row of its own.

rename(old: str | Mapping[str, str], new: str | None = None) → Scene

Change drawables’ names (their unique identity).

Takes a mapping or one (old, new) pair. A mapping is checked as one batch, so a swap ({"a": "b", "b": "a"}) works. To change legend text, use relabel(); a rename changes a row only for a drawable without a label.

set_alpha(alpha: float, *, kind: str | None = None, names: Iterable[str] | None = None) → Scene

Set the opacity of every drawable of one kind, or of the names given.

kind is "mesh", "skeleton" or "points"; with neither argument, all drawables. Each is set to alpha outright, not scaled, and the change is stored on the drawables, so view.reset() and saved figures keep it.

set_color(name: str, color: Any) → Scene

Recolour one drawable, leaving every other colour unchanged (unlike recolor(), which reassigns the palette over the whole scene).

superimpose(*, anchor: str = 'center', axes: Any = None, at: Any = None, key=None) → Scene

Put every drawable’s anchor on one point, by default the first’s. axes="z" aligns depth only and leaves the other axes as the data put them.

axes_visible: bool = False
background: neu_draw.colors.RGBA | None = None
property bbox: neu_lib.BBox

Union over visible drawables, in nm, including their offsets.

camera: Camera
drawables: list[Drawable] = []
export_groups: Any | None = None
property labels: list[str]

The legend’s rows: each distinct label_of(), in first-appearance order.

legend: Legend
property names: list[str]

Every drawable’s name, in scene order. Unique by construction.

class neu_draw.scene.VolumeDrawable

Reserved, not implemented: a future drawable for scalar fields (image slices, probability maps). Constructing one raises.

neu_draw.scene.anchor_of(box: neu_lib.BBox, anchor: str = 'center') → neu_lib.Vec3

The point of box an alignment refers to.

neu_draw.scene.build_scene(meshes: Iterable[neu_lib.Mesh] = (), skeletons: Iterable[neu_lib.Skeleton] = (), points: Mapping[str, Any] | None = None, *, colors: Mapping[Hashable, Any] | Any | None = None, palette: Sequence[Any] | None = None, alpha: float | None = None, labels: Mapping[Hashable, str] | None = None, color_by: str | None = None, point_color: Any = None, **scene_kwargs) → Scene

Assemble a scene and colour it in one call.

alpha defaults to 1 for a single body and 0.8 for several, so overlapping surfaces do not hide each other.

A body’s mesh and skeleton are both named after the body; when a name occurs more than once, both get the representation appended ("1401 mesh", "1401 skeleton").

labels groups the legend, keyed on each item’s own name (the body id, before any suffix), matched by value or by str(). Unlisted items keep their own row:

build_scene(meshes=ms, skeletons=sks, labels={10014014: "Tm2", 10017394: "Tm2"})

Passing labels also colours by label (see Scene.recolor()); color_by="name" overrides that.

neu_draw.scene.label_of(drawable: Any) → str | None

A drawable’s legend row: its label, or failing that its name.

A name is unique identity (what Scene.get() and Scene.set_color() resolve). A label is a classification that many drawables may share: forty bodies of one cell type can share a label, one legend row and one colour, and still be addressed individually by name.

neu_draw.scene.resolve_marker(marker: str) → str

Map a matplotlib shorthand to a pygfx marker name, and validate either way.

neu_draw.scene.ANCHORS = ('center', 'min', 'max')
neu_draw.scene.DRAWABLE_KINDS
neu_draw.scene.Drawable
neu_draw.scene.LEGEND_LOCATIONS = ('right', 'left')
neu_draw.scene.MARKERS = ('circle', 'square', 'diamond', 'ring', 'cross', 'plus', 'tick', 'triangle_up', 'triangle_down',...
neu_draw.scene.MARKER_ALIASES