neu_morpho.measure.paths

Distances ALONG a skeleton from a reference compartment, for points attached to it.

For each synapse, a signed path length in nm: positive outside the region, negative inside it (depth from the nearest boundary crossing). Four pure steps:

  1. bridge_gaps() reconnects pieces across breaks below max_gap_nm. Bridges are cable nobody traced, so they are flagged and every result says how much it crossed.

  2. neu_lib.Skeleton.split_at() adds a vertex where each edge crosses the boundary.

  3. attach_points() snaps each synapse to the nearest point on an edge (vertices can be hundreds of nm apart) and reports the snap distance.

  4. path_distances(): one multi-source shortest-path pass from every boundary vertex.

Nothing is dropped: an unreachable synapse is unreachable, a body that never enters the region no_compartment. Normalisation is left to aggregation; distances are absolute.

Attributes

Functions

attach_points(skeleton, points_zyx_nm) → dict[str, numpy.ndarray]

The nearest point on any edge, for each point.

bridge_gaps(skeleton, max_gap_nm) → tuple[neu_lib.Skeleton, numpy.ndarray]

Join the skeleton's pieces wherever two come within max_gap_nm of each other.

compartment_graph(skeleton, inside, *, max_gap_nm[, ...]) → dict[str, Any]

The bridged, boundary-split skeleton and which of its cable is the compartment.

components(skeleton) → tuple[int, numpy.ndarray]

(n_components, label_per_vertex). An isolated vertex is its own component.

dvid_roi_region(source, rois, *, voxel_size_nm[, ...])

(inside, roi_volume) for the union of named DVID ROIs.

near_points_region(points_zyx_nm, radius_nm) → neu_lib.InsideFn

inside() for "within radius_nm of any of these points".

path_distances(skeleton, sources[, is_bridge]) → dict[str, numpy.ndarray]

Shortest path length from the nearest of sources, for every vertex.

skeleton_graph(vertices, edges)

Symmetric csr_matrix adjacency weighted by edge length.

synapse_distance_table(skeletons, synapses, inside, *, ...)

Per-synapse and per-body tables over a set of bodies.

synapse_distances(skeleton, points_zyx_nm, inside, *, ...) → tuple[dict[str, numpy.ndarray], dict[str, Any]]

Signed path length from the reference compartment, for each point on one body.

volume_roi_region(volume, labels, *, level)

(inside, piece) for labels of a precomputed ROI volume, read whole at level.

Module Contents

neu_morpho.measure.paths.attach_points(skeleton: neu_lib.Skeleton, points_zyx_nm: Any) → dict[str, numpy.ndarray]

The nearest point on any edge, for each point.

Returns {"edge", "t", "position", "snap_nm"}: the edge index, the fraction along it from edges[:, 0] to edges[:, 1], the attached position (zyx nm) and the distance to it. Exact: every edge is tested, in bounded chunks. Isolated vertices are not targets.

neu_morpho.measure.paths.bridge_gaps(skeleton: neu_lib.Skeleton, max_gap_nm: float) → tuple[neu_lib.Skeleton, numpy.ndarray]

Join the skeleton’s pieces wherever two come within max_gap_nm of each other.

Returns (skeleton, is_bridge): the original vertices and edges, unchanged and in order, with bridge edges appended, and one bool per edge marking the bridges.

A minimum spanning tree over pieces, using the nearest vertex pair between each two and keeping links no longer than max_gap_nm: the result of a greedy closest-pair merge, with vertex indices stable and bridges marked. Never adds a cycle; max_gap_nm = 0 bridges nothing. Cost grows with vertex density times the gap (fine up to ~a micron).

neu_morpho.measure.paths.compartment_graph(skeleton: neu_lib.Skeleton, inside: neu_lib.InsideFn, *, max_gap_nm: float, tolerance_nm: float = 8.0, region_pieces: str = 'largest', merge_nm: float = 0.0) → dict[str, Any]

The bridged, boundary-split skeleton and which of its cable is the compartment.

The graph synapse_distances() measures on, exposed so a renderer can draw exactly the cable that was treated as the compartment. Returns skeleton (bridged and split), per-edge is_bridge / in_region / in_compartment, per-vertex inside / boundary / in_compartment, the sources (boundary vertices of the compartment), edge lengths, and the piece counts. See synapse_distances() for region_pieces and merge_nm.

neu_morpho.measure.paths.components(skeleton: neu_lib.Skeleton) → tuple[int, numpy.ndarray]

(n_components, label_per_vertex). An isolated vertex is its own component.

neu_morpho.measure.paths.dvid_roi_region(source: Any, rois: Sequence[str] | str, *, voxel_size_nm: Sequence[float], origin_nm: Sequence[float] = (0.0, 0.0, 0.0))

(inside, roi_volume) for the union of named DVID ROIs.

source is a dvid:// URL or spec for any instance on the node. voxel_size_nm / origin_nm are the node’s scale-0 grid — the frame its synapse coordinates are in (neu_draw.sources.volume_frame(volume, 0) for the exported segmentation).

neu_morpho.measure.paths.near_points_region(points_zyx_nm: Any, radius_nm: float) → neu_lib.InsideFn

inside() for “within radius_nm of any of these points”.

For a compartment the cell itself marks where no ROI separates it, such as an axon terminal marked by the cell’s own presynaptic sites when dendrite and terminal share a neuropil. radius_nm must reach the skeleton from the membrane; gaps between sites become separate pieces, which merge_nm rejoins.

neu_morpho.measure.paths.path_distances(skeleton: neu_lib.Skeleton, sources: Sequence[int], is_bridge: numpy.ndarray | None = None) → dict[str, numpy.ndarray]

Shortest path length from the nearest of sources, for every vertex.

Returns {"distance_nm", "n_bridges", "bridge_nm", "source"} per vertex: the bridge edges on that shortest path and their length, and which source vertex it starts from. Unreachable vertices get inf, zero bridges and source -1.

neu_morpho.measure.paths.skeleton_graph(vertices: numpy.ndarray, edges: numpy.ndarray)

Symmetric csr_matrix adjacency weighted by edge length.

Two scipy behaviours are silent here and both are handled: an explicit zero weight is no edge (floored at _MIN_EDGE_NM), and duplicate entries are summed on conversion (duplicates are removed first, so a doubly-listed edge is not twice as long). Self-loops are dropped.

neu_morpho.measure.paths.synapse_distance_table(skeletons: Mapping[int, neu_lib.Skeleton], synapses: Any, inside: neu_lib.InsideFn | Mapping[int, neu_lib.InsideFn], *, frame: Any, max_gap_nm: float, body_column: str = 'post_body', prefix: str = 'post_', tolerance_nm: float = 8.0, region_pieces: str = 'largest', merge_nm: float = 0.0, dataset: str | None = None)

Per-synapse and per-body tables over a set of bodies.

synapses is any table with {prefix}z/y/x columns and body_column — e.g. neu-mark’s connections filtered to the partners wanted, where post_* is the postsynaptic site on the body being measured. Its coordinates are in frame’s voxels (scale 0 for DVID). Every input column is carried through, so pre_body or a partner cell type stays on each row.

inside is one region for every body (an ROI), or a mapping {body: inside} for a compartment each cell marks itself (near_points_region() over its own outputs). A body the mapping lacks gets status == "no_compartment".

Returns (rows, bodies). rows adds distance_nm (signed; negative inside the compartment — see synapse_distances() on region_pieces), inside, in_roi, snap_nm, n_bridges, bridge_nm, on_bridge, status, radius_nm, attached_z/y/x_nm, entry_z/y/x_nm (the boundary crossing measured from) and the max_gap_nm / region_pieces / merge_nm it was measured with. A body in the table with no skeleton gets status == "no_skeleton" rather than vanishing. bodies holds synapse_distances()’ summary per body.

neu_morpho.measure.paths.synapse_distances(skeleton: neu_lib.Skeleton, points_zyx_nm: Any, inside: neu_lib.InsideFn, *, max_gap_nm: float, tolerance_nm: float = 8.0, region_pieces: str = 'largest', merge_nm: float = 0.0) → tuple[dict[str, numpy.ndarray], dict[str, Any]]

Signed path length from the reference compartment, for each point on one body.

The compartment is a connected piece of in-region cable, not the region itself. region_pieces="largest" (default) takes the piece with the most cable; "all" takes every piece. "all" lets a coarse ROI’s brush with neighbouring arbor (a dendrite dipping briefly into the region) become the reference, giving short distances with nothing raised. "largest" instead under-counts a terminal that weaves across the boundary, adding the weave’s length to every distance.

merge_nm (with "largest") repeatedly absorbs every piece within that much cable of the compartment, so a weaving terminal is one piece again while a far dip is not. It is a parameter to sweep, not a constant.

Returns (per_point, summary). per_point holds arrays, one entry per point: distance_nm (negative inside the compartment; NaN unless status == "ok"), inside (in the compartment), in_roi (in the region at all), snap_nm, n_bridges, bridge_nm, on_bridge (attached to a bridge edge itself), status, position (attached point, zyx nm), radius_nm (interpolated; NaN without radii) and entry (the boundary crossing measured from, zyx nm). summary describes the body’s graph: pieces before and after bridging, bridges and their length, total, in-region and compartment cable, piece and crossing counts, and n_entries_used (more than one: the region is entered at several places).

neu_morpho.measure.paths.volume_roi_region(volume: str, labels: Sequence[int | str] | int | str, *, level: int)

(inside, piece) for labels of a precomputed ROI volume, read whole at level.

Labels may be ids or names; names resolve through the volume’s own segment_properties and are never guessed. Pick a coarse level — the whole level is read into memory.

neu_morpho.measure.paths.NO_COMPARTMENT = 'no_compartment'
neu_morpho.measure.paths.NO_SKELETON = 'no_skeleton'
neu_morpho.measure.paths.OK = 'ok'
neu_morpho.measure.paths.REGION_PIECES = ('largest', 'all')
neu_morpho.measure.paths.STATUSES
neu_morpho.measure.paths.UNREACHABLE = 'unreachable'