neu_eval.voxel

Voxel-level agreement metrics. Every one is a pure function of a Contingency.

No arrays and no I/O, so computing several metrics is cheap; Plaza & Funke advise reading several together rather than trusting one.

Which half of VOI is “split” and which is “merge” depends on which side is the reference, so nothing here returns a bare pair. With side a as the reference: if b fuses two a bodies, a voxel’s b label no longer tells you its a body, so H(a|b) grows, and that is voi_merge; if one a body is spread over several b segments, H(b|a) grows, and that is voi_split. Plaza & Funke call these under-segmentation H(G|S) and over-segmentation H(S|G).

Classes

Rand

Adapted Rand, as a precision/recall pair plus its F-score and the error.

Voi

Variation of information, decomposed, in bits.

Functions

pair_voi(c) → tuple[numpy.ndarray, numpy.ndarray]

Per-pair VOI contributions, parallel to c's arrays.

per_label_voi(c) → tuple[dict[int, float], dict[int, float]]

Each label's contribution to the two halves of voi().

rand(c) → Rand

Adapted Rand precision, recall and F-score for the two sides of c.

summary(c) → dict[str, float | int]

Every scalar in this module, plus what was scored, as one flat row (so many

voi(c) → Voi

Variation of information between the two sides of c, in bits.

Module Contents

class neu_eval.voxel.Rand

Adapted Rand, as a precision/recall pair plus its F-score and the error.

precision: of the voxel pairs b puts in one segment, the fraction a also puts together. recall: of the pairs a puts together, the fraction b keeps together. error = 1 - f_score is the usual “adapted Rand error”.

as_dict() → dict[str, float]
property error: float
property f_score: float
precision: float
recall: float
class neu_eval.voxel.Voi

Variation of information, decomposed, in bits.

Named with side a as the reference: merge = H(a|b) (b fusing a bodies), split = H(b|a) (b fragmenting one). total is their sum.

as_dict() → dict[str, float]
merge: float
split: float
property total: float
neu_eval.voxel.pair_voi(c: neu_eval.overlap.Contingency) → tuple[numpy.ndarray, numpy.ndarray]

Per-pair VOI contributions, parallel to c’s arrays.

(merge_term, split_term) for each (a_id, b_id) row, so a caller can rank pairs without re-deriving the entropy. Their sums are voi()’s two fields.

neu_eval.voxel.per_label_voi(c: neu_eval.overlap.Contingency) → tuple[dict[int, float], dict[int, float]]

Each label’s contribution to the two halves of voi().

(merge_by_a, split_by_b): each a id’s share of H(a|b) and each b id’s share of H(b|a); each sums to the matching field of voi(). VOI contribution ranks errors better than voxel counts do: halving a large body and shaving a thousand voxels off it score the same in voxels.

neu_eval.voxel.rand(c: neu_eval.overlap.Contingency) → Rand

Adapted Rand precision, recall and F-score for the two sides of c.

Counts of pairs of voxels, computed from the sums of squares rather than by enumeration: sum_ij n_ij^2 for pairs the two sides agree on, against sum_j n_j^2 and sum_i n_i^2 for the pairs each side claims.

neu_eval.voxel.summary(c: neu_eval.overlap.Contingency) → dict[str, float | int]

Every scalar in this module, plus what was scored, as one flat row (so many comparisons make a DataFrame). frac_scored is included because a VOI over 3% of a box is not comparable to one over all of it.

neu_eval.voxel.voi(c: neu_eval.overlap.Contingency) → Voi

Variation of information between the two sides of c, in bits.

Zero when the labelings agree up to renaming. An empty table also scores zero; check frac_scored, which shows when nothing was scored.