Skip to content

Inference and thresholds

The .joblib file stores the fitted detector, the preprocessing, the threshold, the extractor configuration and metadata (number of training images, date, threshold origin). Hugging Face weights are not stored: they are reloaded from the Hub (or the local HF cache) on load.

from imgad import AnomalyPredictor

predictor = AnomalyPredictor.load("bottle.joblib")   # device auto (cuda/mps/cpu); device="cpu" to force it
r = predictor.predict_one("new.png")                 # path, PIL.Image or numpy array
r.is_anomaly, r.score, r.normal_percentile

results = predictor.predict("folder/")               # folder, list of paths/images, or a mix
predictor.save_results(results, "preds.csv")         # .csv or .json
for r in predictor.predict_iter("huge_folder/", chunk_size=256): ...   # constant memory

Each Prediction contains:

Field Meaning
score raw score (detector-dependent scale; higher = more anomalous)
is_anomaly score > threshold
threshold threshold used
normal_percentile fraction of normal training images with a lower score (0–1). Comparable across detectors: ~0.5 = typical normal image, 1.0 = rarer than any normal image seen

Tune the threshold on a labelled validation set (no retraining):

pipe = AnomalyDetectionPipeline.load("bottle.joblib")
pipe.calibrate_threshold(val_paths, val_labels, method="f1")   # f1 | youden | fpr | tpr
pipe.save("bottle_cal.joblib")
# Normal images only: method="fpr", target_fpr=0.01  -> ≤1% false alarms

Or on the fly: AnomalyPredictor.load("bottle.joblib", threshold=0.02).

Shortcuts: the extractor can be an HF id ("facebook/dinov2-base") and the detector a name ("mahalanobis") or a dict ({"name": "knn", "k": 5}).