neu_morpho.measure.metrics

Per-body measurements from a skeleton. Pure: arrays in, numbers out.

Everything takes a neu_lib.Skeleton, so a published skeleton can be measured without rerunning anything, and so can an excluded one (skel.exclude(inside)).

Diameter statistics are weighted by edge length, never per vertex: node density is a property of the skeletonizer, and a densely sampled region would dominate a vertex mean.

The radii are INSCRIBED radii: exact at the node, but systematically small wherever a cross-section is not round. Volume goes as r^2, so frustum_volume_nm3() is a lower bound whose tightness depends on how round the neurites are.

Missing radii give NaN, not zero: an unknown calibre must not read as “infinitely thin” in an average.

Functions

cable_length_nm(skeleton) → float

Total edge length, in nm.

diameter_stats(skeleton) → dict

Edge-length-weighted diameter statistics, in nm.

frustum_area_nm2(skeleton) → float

Lateral surface area of that same tube: pi * (r0 + r1) * slant per edge.

frustum_volume_nm3(skeleton) → float

Volume of the tube the radii imply: one truncated cone per edge.

max_radius_nm(skeleton) → float

The largest vertex radius; NaN without radii.

measure_skeleton(skeleton, *[, variant, body_id, dataset]) → dict

Every skeleton-derived measurement for one body, as one flat row.

topology(skeleton) → dict

Tip, branch-point and connected-component counts from the edge list.

weighted_quantile(values, weights, q) → numpy.ndarray

Quantiles of values weighted by weights. Step, not interpolated.

Module Contents

neu_morpho.measure.metrics.cable_length_nm(skeleton: neu_lib.Skeleton) → float

Total edge length, in nm.

Sensitive to skeleton postprocessing (joins add cable, tick removal drops it), so it is only comparable across datasets skeletonized with the same settings.

neu_morpho.measure.metrics.diameter_stats(skeleton: neu_lib.Skeleton) → dict

Edge-length-weighted diameter statistics, in nm.

Each edge contributes the mean of its two endpoint radii, weighted by its length; NaN throughout without radii.

p10-p90 describes the twigs, not the trunk: the thick part of a cell is a small length fraction and sits above p90, so read p99 / max for the thick end. Percentiles are steps (weighted_quantile()), so where one edge dominates the length they collapse onto its diameter.

neu_morpho.measure.metrics.frustum_area_nm2(skeleton: neu_lib.Skeleton) → float

Lateral surface area of that same tube: pi * (r0 + r1) * slant per edge.

End caps are excluded (a neurite’s ends are cuts, not surface). NaN without radii.

neu_morpho.measure.metrics.frustum_volume_nm3(skeleton: neu_lib.Skeleton) → float

Volume of the tube the radii imply: one truncated cone per edge.

V = (pi/3) * L * (r0^2 + r0*r1 + r1^2) per edge, summed. Cones overlapping at a junction are double-counted: the opposite sign to the inscribed-radius bias, but not a cancellation of it. NaN without radii.

neu_morpho.measure.metrics.max_radius_nm(skeleton: neu_lib.Skeleton) → float

The largest vertex radius; NaN without radii.

Non-positive radii are skipped: kimimaro writes -1 where a radius is unknown.

neu_morpho.measure.metrics.measure_skeleton(skeleton: neu_lib.Skeleton, *, variant: str = 'all', body_id: int | None = None, dataset: str | None = None) → dict

Every skeleton-derived measurement for one body, as one flat row.

variant labels what was measured ("all", "minus_nucleus", "minus_soma_nucleus", "neurite_positive") and is carried into the row, since rows measured differently cannot be compared safely.

neu_morpho.measure.metrics.topology(skeleton: neu_lib.Skeleton) → dict

Tip, branch-point and connected-component counts from the edge list.

Segmented bodies are often genuinely fragmented, so more than one component is correct reporting, not a defect.

neu_morpho.measure.metrics.weighted_quantile(values: Any, weights: Any, q: Any) → numpy.ndarray

Quantiles of values weighted by weights. Step, not interpolated.

Each edge is a length of cable with one diameter, so the weighted median is “the diameter at which half the cable is thinner”: an empirical distribution over cable. Interpolating would let a few sparse samples drag the answer away from where the cable is. Weights of zero are allowed and do not count.