neu_morpho.measure.sweep

Per-body volume and local calibre, by counting voxels against a published centreline.

The measurement is V/L: a body’s voxel count gives its volume, and dividing by the cable length of the centreline through it gives the mean cross-sectional area. Binning the voxels by nearest centreline node turns the same pass into a length-weighted distribution of local cross-section rather than one number per body.

The defaults (read block, the 32 nm level, dropping zeros before counting) were each chosen by measurement; re-measure before changing one.

  • ``V/L`` is a TUBE measure and under-reads blobs (a sphere comes back at sqrt(2/3) of its radius); on a body with a soma the thick tail is “V/L over a blob”, not a diameter. blob_signal() flags it per body.

  • Per-node values are noisy (±40%): this is an instrument for distributions.

Attributes

Classes

SweepTotals

Running per-body and per-node counts across blocks. The driver owns one.

Functions

bin_to_nodes(block, origin_vox, voxel_nm, ...[, node_ids]) → dict

Voxels of body_id assigned to their nearest centreline node.

blob_signal(node_counts, node_dist_sum, cable_share_nm) → float

Share of cable whose voxels are spread more widely than a tube can explain.

blocks_from_mask(grid, block, shape) → list[tuple]

[(z0, y0, x0), …] block origins, in voxels, clipped to shape.

cable_shares(vertices_zyx_nm, edges) → numpy.ndarray

Cable length attributable to each node: half of every incident edge.

count_labels(block[, background]) → dict[int, int]

{label: voxel_count} for one block, background excluded.

log_bin_edges(lo_nm, hi_nm, nbins) → numpy.ndarray

nbins + 1 log-spaced edges: datasets compared on one axis differ by decades.

mean_cross_section(volume_nm3, cable_nm) → tuple[float, float]

(area_nm2, equivalent_radius_nm) from a volume and the cable through it.

node_radii(node_counts, cable_share_nm[, voxel_nm]) → tuple[numpy.ndarray, numpy.ndarray]

(radii_nm, weights_nm) per node — the local V/L and its cable weight.

roi_block_mask(roi, labels, factor[, dilate]) → numpy.ndarray

Boolean grid of blocks intersecting labels, from a coarse ROI volume.

weighted_histogram(values, weights, edges) → numpy.ndarray

nbins + 2 bins: one underflow, the nbins interior, one overflow.

Module Contents

class neu_morpho.measure.sweep.SweepTotals

Running per-body and per-node counts across blocks. The driver owns one.

add_block(per_label: Mapping[int, int]) → None
add_nodes(body_id: int, binned: Mapping[str, Mapping[int, float]]) → None
volume_nm3(body_id: int) → float
blocks: int = 0
body_voxels: dict[int, int]
node_dist_sum: dict[int, dict[int, float]]
node_voxels: dict[int, dict[int, int]]
voxel_nm: float = 32.0
property voxel_volume_nm3: float
neu_morpho.measure.sweep.bin_to_nodes(block: numpy.ndarray, origin_vox: Sequence[int], voxel_nm: float, nodes_zyx_nm: numpy.ndarray, body_id: int, node_ids: Sequence[int] | None = None) → dict

Voxels of body_id assigned to their nearest centreline node.

Returns {"counts": {node: n}, "dist_sum": {node: nm}}; the summed voxel-to-node distance feeds blob_signal(). Coarse node spacing (to ~800 nm) leaves the distribution unchanged.

neu_morpho.measure.sweep.blob_signal(node_counts: Mapping[int, int], node_dist_sum: Mapping[int, float], cable_share_nm: Mapping[int, float], voxel_nm: float = DEFAULT_VOXEL_NM, tolerance: float = 1.25) → float

Share of cable whose voxels are spread more widely than a tube can explain.

A soma, a bulb, or two merged branches put voxels further from their node than a cylinder would. The comparison is against a cylinder’s expected mean distance, which has an axial part from the node spacing: for radius r and node cable share s,

E[dist] ~ sqrt(E[rho^2] + E[zeta^2]) = sqrt(r^2/2 + s^2/12)

Comparing against r alone flags ordinary tubes whenever s is comparable to it.

neu_morpho.measure.sweep.blocks_from_mask(grid: numpy.ndarray, block: int, shape: Sequence[int]) → list[tuple]

[(z0, y0, x0), …] block origins, in voxels, clipped to shape.

neu_morpho.measure.sweep.cable_shares(vertices_zyx_nm: numpy.ndarray, edges: numpy.ndarray) → numpy.ndarray

Cable length attributable to each node: half of every incident edge.

neu_morpho.measure.sweep.count_labels(block: numpy.ndarray, background: int = 0) → dict[int, int]

{label: voxel_count} for one block, background excluded.

Zeros are dropped before np.unique, which is 17x faster on mostly empty volumes.

neu_morpho.measure.sweep.log_bin_edges(lo_nm: float, hi_nm: float, nbins: int) → numpy.ndarray

nbins + 1 log-spaced edges: datasets compared on one axis differ by decades.

neu_morpho.measure.sweep.mean_cross_section(volume_nm3: float, cable_nm: float) → tuple[float, float]

(area_nm2, equivalent_radius_nm) from a volume and the cable through it.

NaN for zero cable rather than infinity, which would propagate into every aggregate.

neu_morpho.measure.sweep.node_radii(node_counts: Mapping[int, int], cable_share_nm: Mapping[int, float], voxel_nm: float = DEFAULT_VOXEL_NM) → tuple[numpy.ndarray, numpy.ndarray]

(radii_nm, weights_nm) per node — the local V/L and its cable weight.

Weights are cable length: node density is a property of the skeletonizer, not the neuron.

neu_morpho.measure.sweep.roi_block_mask(roi: numpy.ndarray, labels: Sequence[int], factor: int, dilate: int = 1) → numpy.ndarray

Boolean grid of blocks intersecting labels, from a coarse ROI volume.

factor is how many ROI voxels span one block on a side. Reduced by any: a block holding one ROI voxel still holds tissue.

Dilated by one block by default: a process near the ROI boundary has voxels just outside, and a missed block silently truncates a body while an extra one costs a read. The dilation does not wrap around the grid.

neu_morpho.measure.sweep.weighted_histogram(values: numpy.ndarray, weights: numpy.ndarray, edges: numpy.ndarray) → numpy.ndarray

nbins + 2 bins: one underflow, the nbins interior, one overflow.

The catch-alls make hist.sum() == weights.sum() hold exactly, so no cable is lost to clipping and the total checks the bin range.

neu_morpho.measure.sweep.DEFAULT_BLOCK = 512
neu_morpho.measure.sweep.DEFAULT_VOXEL_NM = 32.0