semantic¶
Semantic segmentation — mIoU, fwIoU, mAcc, aAcc, mDice — against mmsegmentation's protocol, and boundary IoU against its paper's.
from hotcoco import semantic
use hotcoco::semantic::SemanticEval;
The semantic guide covers what the metric measures, the two input forms, and how the numbers were checked. This page is the signatures and return shapes.
SemanticEval¶
SemanticEval(
classes: int | Sequence[str],
*,
ignore_index: int | None = 255,
reduce_zero_label: bool = False,
boundary_dilation: float | None = 0.02,
)
| Parameter | Description |
|---|---|
classes |
The number of classes, labeled 0..classes - 1, or their names in class-id order |
ignore_index |
Ground-truth label of an unlabeled pixel, left out on both sides. None counts every pixel |
reduce_zero_label |
mmseg's rule for files with "other" at 0: ground-truth 0 becomes 255 and every other ground-truth label shifts down by one. Needs ignore_index=255; the constructor raises ValueError otherwise |
boundary_dilation |
The boundary IoU band as a share of the image diagonal, the paper's 2%. None leaves boundary IoU out, so mBIoU reads -1.0. A negative value raises ValueError |
SemanticEval::new(num_classes: usize) -> SemanticEval // names class_<id>, ignore 255, boundary band 0.02
SemanticEval::with_class_names(names: Vec<String>) -> SemanticEval
SemanticEval::ignore_index(self, Option<u64>) -> SemanticEval
SemanticEval::reduce_zero_label(self, bool) -> SemanticEval
SemanticEval::boundary_dilation(self, Option<f64>) -> SemanticEval
SemanticEval::check_options(&self) -> Result<()> // the builder cannot fail; this is the error the first update would raise
Methods¶
| Method | Returns | Description |
|---|---|---|
update(gt, pred) |
None |
Count one ground-truth map against one prediction: integer arrays of the same shape, (H, W) or (N, H, W), any integer dtype; the last two axes are the map. ValueError on a shape mismatch or a label out of range; nothing from the pair is counted then |
update_png(gt, pred) |
None |
Count one pair of PNG label maps — 8- or 16-bit grayscale, or a palette image whose index is the class. OSError for a file that cannot be read; ValueError for one that is not a label map, two maps of different sizes, or a label out of range |
update_folders(gt_folder, pred_folder, *, recursive=False, gt_suffix=".png", pred_suffix=".png") |
int |
Count every ground-truth file under gt_folder against its prediction under pred_folder, in parallel; returns the pairs counted. A ground-truth file is one at the top of the folder, or anywhere below it with recursive, whose name ends in gt_suffix; its key is the name minus the suffix, and its prediction is key + pred_suffix anywhere in the prediction tree under the same walk. The defaults pair the .png files at the top of each folder by name. OSError for a folder that cannot be listed; ValueError if no ground-truth file ends in the suffix, if one has no prediction, or if two predictions share a key; or whatever update_png raises on the first pair it rejects. Nothing is counted then |
merge(other) |
None |
Add another evaluator's counts — the shards of a distributed run. ValueError if it was built with different classes or options, boundary_dilation included |
confusion() |
ndarray[uint64] |
The (num_classes, num_classes) matrix behind every number but boundary IoU: row the ground-truth class, column the predicted class |
summarize() |
None |
Print one row per class with IoU, Acc, Dice, and BIoU in percent, then the six means. Before anything is counted, prints nothing and emits a CallOrderWarning |
summary_lines() |
list[str] |
The same table as strings, without printing. Empty, with a CallOrderWarning, before anything is counted |
stats |
list[float] |
The six means as fractions, in METRIC_NAMES order; -1.0 where nothing was counted, and for mBIoU when boundary IoU is off |
class_names, num_classes, n_images |
The configuration and how many updates were counted: one per update call, so an (N, H, W) batch is one, and one per PNG pair |
|
results() |
dict |
The results shape, below |
report() |
dict |
The EvalReport every family produces |
reference_deviations() |
list[str] |
Why this run is not comparable to mmseg; empty today, since every option is a reference option |
provenance() |
str |
"parity_verified" or "extension" |
An evaluator pickles; pickle.loads(pickle.dumps(ev)) carries the
configuration, the matrix, and the boundary counts, which is what
torch.distributed's all_gather_object needs. In Rust the same names
return Result where Python raises, update is generic over any integer
slice type on either side and takes the map's height and width (a batch is
several maps one after another), update_maps takes two LabelMaps of
run-time width, results() returns a SemanticResults with to_json() and
save(), confusion() returns the metrics::semantic::PixelConfusion,
boundary() the BoundaryCounts beside it, boundary_scores() their
BoundaryScores, update_folders takes the three pairing options as a
semantic::FolderPairing (Default is the Python defaults), and
summary_lines() is spelled summarize_lines().
results()¶
{
"mIoU": 0.7642, "fwIoU": 0.9210, "mAcc": 0.8407, "aAcc": 0.9588, "mDice": 0.8531, "mBIoU": 0.5870,
"boundary_dilation": 0.02,
"per_class": [
{"name": "road", "IoU": 0.9781, "Acc": 0.9892, "Dice": 0.9889, "BIoU": 0.8120,
"tp": 3_204_118, "gt_area": 3_239_101, "pred_area": 3_250_777},
...
],
"n_images": 500,
"n_pixels": 1_046_249_374,
"provenance": "parity_verified",
"reference_deviations": [],
"hotcoco_version": "1.4.0",
}
Scores are fractions. per_class is a list in class-id order covering every
class; a class on neither side reports -1.0 for IoU and Dice, one with
no ground truth -1.0 for Acc, and one with no boundary on either side
-1.0 for BIoU, beside the counts that say so. With boundary IoU off,
boundary_dilation is None and every BIoU and mBIoU is -1.0.
n_pixels counts after ignoring.
report()¶
| Key | Contents |
|---|---|
task |
"semantic" |
provenance |
"parity_verified" |
metrics |
mIoU, fwIoU, mAcc, aAcc, mDice — -1.0 when nothing was counted — and mBIoU when boundary IoU is on |
per_class |
class name → {"IoU", "Acc", "Dice", "BIoU"}, computed values only |
per_group |
empty; there are no splits |
curves |
empty; mIoU has no curve |
params |
n_images, n_pixels, num_classes, ignore_index, reduce_zero_label, boundary_dilation |
METRIC_NAMES¶
["mIoU", "fwIoU", "mAcc", "aAcc", "mDice", "mBIoU"]
The order of stats and the keys of report()["metrics"]. In Rust,
metrics::semantic::METRIC_NAMES is the first five — what a confusion matrix
yields — and semantic::METRIC_NAMES adds mBIoU.
The layers underneath¶
The driver composes the shared layers, as detection and panoptic do. There is no matcher: pixels pair by position.
| Python | Rust | |
|---|---|---|
| The confusion matrix | metrics.pixel_confusion |
metrics::semantic::PixelConfusion — new, with_unlabeled_pred, update, update_with, update_maps, add_count, add, from_counts, class_counts |
| A map of run-time width | — | metrics::semantic::{LabelMap, LabelPairOp} — a map as U8, U16, or I64, and LabelMap::with_pair to run an op generic over both widths |
| The numbers | metrics.mean_iou |
metrics::semantic::{semantic_scores, SemanticScores, ClassCounts, ClassScores} |
| The boundary band | — | metrics::semantic::boundary::{boundary_mask, dilation, BoundaryCounts, BoundaryScores} — the band of every class in one pass, and ClassCounts over boundary pixels |
| The IoU kernel | — | primitives::sim::iou_from_counts(intersection, a, b) |
PixelConfusion::update takes any integer slice type on either side (every
dtype numpy stores a label map in) and the ignore index; update_with takes
a closure over the ground-truth label instead, which is how the driver
applies reduce_zero_label; update_maps, on PixelConfusion and on
SemanticEval, takes two LabelMaps, the form a decoded file or a numpy
array arrives in. with_unlabeled_pred adds a column for a
prediction that names no class — a panoptic void — which counts as a miss and
never as a class; PanopticEval fills it through add_count. ClassCounts::iou(), acc(), and dice() return None
where the reference has NaN; semantic_scores turns a class list into the
five means with the -1.0 sentinel filled in. boundary_mask marks the
pixels of an h × w map whose (2d + 1)-square holds another label or
leaves the map, which is the authors' mask_to_boundary for every class at
once; BoundaryCounts holds a class's band counts as ClassCounts, so its
iou() per class is the boundary IoU.