argus / evaluate.py
phanerozoic's picture
Return maps at the input shape, batch NMS, ship ADE20K names, add compile() and evaluate.py
c9b60bb verified
Raw History Blame Contribute Delete
6.89 kB
"""Evaluate Argus's heads on their benchmarks.
python evaluate.py imagenet --root VAL_DIR # VAL_DIR/<wnid>/*.JPEG
python evaluate.py ade20k --root ADEChallengeData2016
python evaluate.py nyu --root NYU_TEST_DIR # rgb/<id>.* and depth/<id>.png
python evaluate.py coco --images val2017 --annotations instances_val2017.json
Every command takes --model (default phanerozoic/argus), --device, --batch
and --limit N to evaluate the first N items only.
"""
import argparse
import json
import os
import sys
import numpy as np
import torch
from PIL import Image
from transformers import AutoModel
IMAGE_EXTS = (".jpg", ".jpeg", ".png", ".JPEG", ".JPG", ".PNG")
def _batches(items, size):
for i in range(0, len(items), size):
yield items[i:i + size]
def _load(path):
return Image.open(path).convert("RGB")
def evaluate_imagenet(model, args):
"""Top-1 and top-5 accuracy with classify(), on an ImageNet validation
folder of <wnid>/<image> files."""
wnid_to_idx = {w: i for i, w in enumerate(model.class_ids)}
items = sorted(
(os.path.join(args.root, w, f), wnid_to_idx[w])
for w in os.listdir(args.root) if w in wnid_to_idx
for f in os.listdir(os.path.join(args.root, w)) if f.endswith(IMAGE_EXTS)
)[: args.limit]
top1 = top5 = 0
for chunk in _batches(items, args.batch):
results = model.classify([_load(p) for p, _ in chunk], top_k=5)
for (_, label), result in zip(chunk, results):
predicted = [wnid_to_idx[r["class_id"]] for r in result]
top1 += predicted[0] == label
top5 += label in predicted
return {"images": len(items), "top1": top1 / len(items), "top5": top5 / len(items)}
def evaluate_ade20k(model, args):
"""Mean IoU over the 150 classes with segment(), on the ADEChallengeData2016
validation split, whose annotations store class k as k + 1 and 0 as
unlabeled."""
image_dir = os.path.join(args.root, "images", "validation")
label_dir = os.path.join(args.root, "annotations", "validation")
names = sorted(f for f in os.listdir(image_dir) if f.endswith(IMAGE_EXTS))[: args.limit]
n = model.config.num_seg_classes
intersection = np.zeros(n, dtype=np.int64)
union = np.zeros(n, dtype=np.int64)
for chunk in _batches(names, args.batch):
preds = model.segment([_load(os.path.join(image_dir, f)) for f in chunk])
for f, pred in zip(chunk, preds):
label = np.asarray(Image.open(os.path.join(label_dir, os.path.splitext(f)[0] + ".png")), dtype=np.int64) - 1
pred = pred.cpu().numpy()
valid = label >= 0
p, t = pred[valid], label[valid]
intersection += np.bincount(t[p == t], minlength=n)[:n]
union += np.bincount(p, minlength=n)[:n] + np.bincount(t, minlength=n)[:n] - np.bincount(t[p == t], minlength=n)[:n]
present = union > 0
return {"images": len(names), "miou": float((intersection[present] / union[present]).mean())}
def evaluate_nyu(model, args):
"""Depth error with depth(), on NYU Depth V2 test images and 16-bit depth
PNGs in millimetres, inside the Eigen crop and up to 10 m."""
rgb_dir, depth_dir = os.path.join(args.root, "rgb"), os.path.join(args.root, "depth")
stems = sorted(os.path.splitext(f)[0] for f in os.listdir(depth_dir) if f.endswith(".png"))[: args.limit]
rgb_files = {os.path.splitext(f)[0]: f for f in os.listdir(rgb_dir)}
abs_rel, sq_rmse, delta1, count = 0.0, 0.0, 0.0, 0
for chunk in _batches(stems, args.batch):
preds = model.depth([_load(os.path.join(rgb_dir, rgb_files[s])) for s in chunk])
for s, pred in zip(chunk, preds):
gt = np.asarray(Image.open(os.path.join(depth_dir, s + ".png")), dtype=np.float64) / 1000.0
pred = pred.cpu().numpy().astype(np.float64)
crop = np.zeros_like(gt, dtype=bool)
h, w = gt.shape
crop[int(0.4081 * h):int(0.9919 * h), int(0.0359 * w):int(0.9640 * w)] = True
valid = crop & (gt > 1e-3) & (gt <= 10.0)
g, p = gt[valid], np.clip(pred[valid], 1e-3, 10.0)
abs_rel += np.mean(np.abs(p - g) / g)
sq_rmse += np.sqrt(np.mean((p - g) ** 2))
delta1 += np.mean(np.maximum(p / g, g / p) < 1.25)
count += 1
return {"images": count, "abs_rel": abs_rel / count, "rmse": sq_rmse / count, "delta1": delta1 / count}
def evaluate_coco(model, args):
"""Box mAP with detect(), scored by pycocotools on COCO val2017."""
from pycocotools.coco import COCO
from pycocotools.cocoeval import COCOeval
gt = COCO(args.annotations)
name_to_cat = {c["name"]: c["id"] for c in gt.loadCats(gt.getCatIds())}
present = set(os.listdir(args.images))
image_ids = [i for i in sorted(gt.getImgIds()) if gt.imgs[i]["file_name"] in present][: args.limit]
detections = []
for chunk in _batches(image_ids, args.batch):
results = model.detect([_load(os.path.join(args.images, gt.imgs[i]["file_name"])) for i in chunk])
for image_id, result in zip(chunk, results):
for d in result:
x1, y1, x2, y2 = d["box"]
detections.append({
"image_id": image_id,
"category_id": name_to_cat[d["class_name"]],
"bbox": [x1, y1, x2 - x1, y2 - y1],
"score": d["score"],
})
evaluator = COCOeval(gt, gt.loadRes(detections), "bbox")
evaluator.params.imgIds = image_ids
evaluator.evaluate()
evaluator.accumulate()
evaluator.summarize()
s = evaluator.stats
return {"images": len(image_ids), "map": s[0], "map50": s[1], "map75": s[2], "map_small": s[3], "map_medium": s[4], "map_large": s[5]}
TASKS = {"imagenet": evaluate_imagenet, "ade20k": evaluate_ade20k, "nyu": evaluate_nyu, "coco": evaluate_coco}
def main(argv=None):
parser = argparse.ArgumentParser(description="Evaluate Argus's heads on their benchmarks.")
parser.add_argument("task", choices=sorted(TASKS))
parser.add_argument("--root", help="dataset root for imagenet, ade20k and nyu")
parser.add_argument("--images", help="COCO val2017 image directory")
parser.add_argument("--annotations", help="COCO instances_val2017.json")
parser.add_argument("--model", default="phanerozoic/argus")
parser.add_argument("--device", default="cuda" if torch.cuda.is_available() else "cpu")
parser.add_argument("--batch", type=int, default=8)
parser.add_argument("--limit", type=int, default=None)
args = parser.parse_args(argv)
model = AutoModel.from_pretrained(args.model, trust_remote_code=True).to(args.device).eval()
result = TASKS[args.task](model, args)
print(json.dumps({"task": args.task, **result}, indent=2))
return 0
if __name__ == "__main__":
sys.exit(main())