neu_eval.disagree¶
Where the two labelings disagree, ranked worst-first, with a place to look.
A score cannot say whether the reference or the segmentation was wrong; only looking at the voxels can. This module makes looking cheap: a short list of disagreements, ordered by what each costs, each with a coordinate to paste into a viewer.
Severity is the pair’s VOI contribution; rank="fraction" orders by how broken a body
or segment is instead (see rows()).
Specks are filtered asymmetrically. Bodies here are genuinely fragmented into many tiny pieces, and a one-voxel stray segment is 100% of itself, so a filter on “a real fraction of either side” would still call every body split. Whether a body is split is judged on fractions of that body, and whether a segment is a merge on fractions of that segment.
Attributes¶
Functions¶
|
How many rows of each kind, for the summary line. |
|
Add a world-coordinate click target to each row. |
|
One row per structurally significant pair, worst first. |
|
Add |
|
|
Module Contents¶
- neu_eval.disagree.counts_by_kind(report_rows: Sequence[dict]) dict[str, int]¶
How many rows of each kind, for the summary line.
- neu_eval.disagree.locate(report_rows: Sequence[dict], a: Any, b: Any, *, labels: Sequence[str] = ('a', 'b'), at: str = 'seam', ignore_a: Iterable[int] = (0,), ignore_b: Iterable[int] = (), crop: bool = True, max_edt_voxels: int = MAX_EDT_VOXELS, shapes: bool = False, anchor_radius: int = 24, gap: int = 2) list[dict]¶
Add a world-coordinate click target to each row.
aandbare the twoPieceobjects the table was built from.at="seam"(default) points at the error, the surface where the labelings part:split: the boundary of
a ∩ binside the body, the false cut;merge: the boundary of
a ∩ binside the segment, the false join;tangle: both, and the larger contact wins;
match: no seam; falls back to the overlap.
The point is the seam voxel deepest inside the body (split) or segment (merge), so it sits mid-cut.
at="overlap"instead gives the deepest voxel ofa ∩ b, the middle of what the two agree on. A pair with no seam (a disconnected fragment) falls back to the overlap, andpoint_atrecords which was used.``ignore_a`` / ``ignore_b`` must match the table’s. The seam is contact with another labelled thing, so if membrane is 0 and ignored but counted here, every split point lands on an ordinary cell boundary.
Cost is a few passes per row over the two labels’ bounding box, so bound it with
rows(..., top=N). Addsz_nm/y_nm/x_nm(viaPiece.to_nm) and the voxel index.shapes=Truealso adds a drawable description of the error underrow["shapes"](see_shapes()), since a single seam voxel can sit on an incidental contact.
- neu_eval.disagree.rows(c: neu_eval.overlap.Contingency, *, min_frac: float = 0.05, min_voxels: int = 1, top: int | None = None, include_matches: bool = False, rank: str = 'severity', max_per_group: int | None = None, exclude: Iterable[str] = ()) list[dict]¶
One row per structurally significant pair, worst first.
A pair counts toward “this body is split” only if it holds at least
min_fracof the body, and toward “this segment is a merge” only if it holdsmin_fracof the segment (see the module docstring).min_voxelsis a floor on the pair’s size.ranksets the order:"severity"(default): the pair’s VOI contribution, what to fix first to move the number. It inherits VOI’s size weighting: a 600-voxel body cut in half scores 0.003, a 100,000-voxel body losing a 6% sliver 0.242."fraction": the larger of the two fractions, so a badly broken small body ranks high. Both values are in every row.
One error can be many rows: a segment merging twenty bodies gives twenty pairs. Each row has
group(the segment for a merge or tangle, the body for a split),group_sizeandgroup_rank;max_per_groupkeeps each group’s worst n, after ranking. The rows are not duplicates, since a segment often merges bodies at several places.include_matcheskeeps pairs where the two sides agree.excludeis a set ofpair_keyvalues treated as not there at all, neither reported nor counted as a partner, asthin_pairs()needs. Returns plain dicts; seeneu_eval.tables.
- neu_eval.disagree.sample_ids(report_rows: Sequence[dict], pieces: Mapping[str, Any], *, ignore: Mapping[str, Iterable[int]] | None = None) list[dict]¶
Add
shape["ids"] = {name: [ids at p0 and p1]}for every labelling inpieces.So an annotation names the objects at its two ends in every layer: the reference bodies, the merging segment, and whatever another segmentation has there.
piecesmust be on the gridlocatewas given (p0_vox/p1_voxindex it) and should hold the ids a viewer shows, even if the table was built from connected-component ids. Ignored values are left out.
- neu_eval.disagree.thin_pairs(c: neu_eval.overlap.Contingency, a: Any, b: Any, *, radius: float = 3.0, min_frac: float = 0.05, min_voxels: int = 1, ignore_a: Iterable[int] = (0,), ignore_b: Iterable[int] = ()) dict[str, float]¶
{pair_key: interior radius}for the significant pairs with NO real interior.Two labelings draw a cell boundary a voxel or two apart, so each body carries a thin strip of its neighbour’s segment, which can be a large enough fraction to read as a merge or split. Such a strip has no interior: its largest inscribed radius is a voxel or two. A pair is thin when that radius is at most
radiusvoxels.Only pairs that could become
rows()are measured. Pass the keys torows(..., exclude=...).
- neu_eval.disagree.FRAGMENT_SHARE = 0.25¶
- neu_eval.disagree.KINDS = ('match', 'split', 'merge', 'tangle')¶
- neu_eval.disagree.MAX_EDT_VOXELS = 16777216¶