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/Lover 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¶
Running per-body and per-node counts across blocks. The driver owns one. |
Functions¶
|
Voxels of |
|
Share of cable whose voxels are spread more widely than a tube can explain. |
|
|
|
Cable length attributable to each node: half of every incident edge. |
|
|
|
|
|
|
|
|
|
Boolean grid of blocks intersecting |
|
|
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_idassigned to their nearest centreline node.Returns
{"counts": {node: n}, "dist_sum": {node: nm}}; the summed voxel-to-node distance feedsblob_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
rand node cable shares,E[dist] ~ sqrt(E[rho^2] + E[zeta^2]) = sqrt(r^2/2 + s^2/12)
Comparing against
ralone flags ordinary tubes wheneversis 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 toshape.
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 + 1log-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 localV/Land 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.factoris how many ROI voxels span one block on a side. Reduced byany: 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 + 2bins: one underflow, thenbinsinterior, 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¶