Guides

Evaluation

Public package status: the Python distributions and desktop installers are not released yet. These pages document an evolving interface, not currently usable install artifacts. View release status.

Two entry points cover both ends of the evaluation workflow: evaluate runs a model over a dataset and scores it in one call; score takes predictions you already have and scores them against a loaded dataset. Both return the same ScoreReport.

The Predictor protocol

evaluate drives any object satisfying this protocol (it’s runtime_checkable, so isinstance(model, mo.Predictor) works, but nothing needs to subclass it — duck typing is enough):

class Predictor(Protocol):
    def predict(self, image: np.ndarray, spacing=None) -> np.ndarray: ...

predict returns an integer label map matching image’s spatial shape, in the dataset’s native label space (see Sample). A bare callable model(image) — with no .predict method — also works with evaluate, called positionally.

Scoring predictions you already have: score

def score(predictions: dict, dataset, *, metrics=("dice", "iou"),
          labels=None, schema=None, per_case=False, present_only=False) -> ScoreReport
import medotter as mo

ds = mo.load_dataset("ACDC", split="test")
preds = {"patient001_frame01": my_prediction_array, ...}  # keyed like _case_key
report = mo.score(preds, ds, metrics=("dice", "iou"), per_case=True)

One call, model included: evaluate

def evaluate(model, name: str, *, split="test", type="image",
             metrics=("dice", "iou"), labels=None, schema=None,
             per_case=False, present_only=False, **load_kwargs) -> ScoreReport

evaluate loads name via load_dataset (forwarding split, type, and any **load_kwargs), runs model over every sample, and scores the predictions with the same logic score uses.

model = mo.load_model("sam2_hiera_small")
report = mo.evaluate(model, "ACDC", split="test", metrics=("dice", "iou"))
print(report)   # per-class dice/iou + macro average

Use evaluate when you want the model run for you; use score when the predictions already exist — computed separately, cached from a previous run, or produced by a model medotter doesn’t know how to load.

Label semantics: labels= vs schema=

By default, label semantics (which ids are background, which are ignored, what each foreground id is named) are resolved from the dataset’s catalog entry, or inferred from the ground-truth masks if the catalog has no class info. Two ways to override that:

Passing both labels and schema raises ValueError.

ScoreReport

@dataclass(frozen=True)
class ScoreReport:
    per_class: dict[str, dict[str, float | None]]
    macro: dict[str, float | None]
    n_cases: int
    metrics: tuple[str, ...]
    support: dict[str, int]
    per_case: dict[str, dict[str, dict[str, float]]] | None

per_class holds each foreground class’s mean per metric; macro averages those means over classes that are present in at least one ground-truth case (a class never present anywhere is reported in per_class/support but excluded from macro). support gives each class’s GT-present case count. per_case is populated only when per_case=True was passed.

Common errors