Object Detection
Transformers
Safetensors
detr
computer-vision
text-detection
historical-documents
Eval Results (legacy)
Instructions to use harness-race/opencode-r2 with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use harness-race/opencode-r2 with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("object-detection", model="harness-race/opencode-r2")# Load model directly from transformers import AutoImageProcessor, AutoModelForObjectDetection processor = AutoImageProcessor.from_pretrained("harness-race/opencode-r2") model = AutoModelForObjectDetection.from_pretrained("harness-race/opencode-r2", device_map="auto") - Notebooks
- Google Colab
- Kaggle
| # /// script | |
| # requires-python = ">=3.10" | |
| # dependencies = [ | |
| # "torch", | |
| # "transformers>=4.40", | |
| # "datasets>=2.20", | |
| # "pillow", | |
| # "accelerate", | |
| # "pycocotools", | |
| # "huggingface_hub", | |
| # "trackio", | |
| # "timm", | |
| # "scipy", | |
| # ] | |
| # /// | |
| import os | |
| import sys | |
| import copy | |
| import argparse | |
| import numpy as np | |
| import torch | |
| from torch.utils.data import DataLoader, Dataset | |
| from datasets import load_dataset | |
| from transformers import DetrImageProcessor, DetrForObjectDetection | |
| ID2LABEL = { | |
| 0: "Photograph", | |
| 1: "Illustration", | |
| 2: "Map", | |
| 3: "Comics/Cartoon", | |
| 4: "Editorial Cartoon", | |
| 5: "Headline", | |
| 6: "Advertisement", | |
| } | |
| LABEL2ID = {v: k for k, v in ID2LABEL.items()} | |
| CLASSES = [ID2LABEL[i] for i in sorted(ID2LABEL)] | |
| MODEL_ID = "facebook/detr-resnet-50" # Apache-2.0 | |
| REPO_ID = "harness-race/opencode-r2" | |
| # ----- trackio (best effort) ----- | |
| def setup_trackio(): | |
| if os.environ.get("OPENCODE_TRACKIO", "1") == "0": | |
| return (lambda **kw: None) | |
| try: | |
| import trackio | |
| r = trackio.init(project="opencode-r2", name=os.environ.get("JOB_NAME", "finetune"), private=True) | |
| def tlog(**kw): | |
| try: | |
| for k, v in kw.items(): | |
| trackio.log(f"metric/{k}", v) | |
| except Exception: | |
| pass | |
| return tlog | |
| except Exception as e: | |
| print("[trackio] unavailable:", e) | |
| return (lambda **kw: None) | |
| # ---------- dataset ---------- | |
| class DetrDataset(Dataset): | |
| def __init__(self, hf_ds, processor, split): | |
| self.ds = hf_ds | |
| self.processor = processor | |
| self.split = split | |
| def __len__(self): | |
| return len(self.ds) | |
| def __getitem__(self, idx): | |
| item = self.ds[idx] | |
| image = item["image"].convert("RGB") | |
| objs = item["objects"] # list of per-object dicts | |
| bboxes_xywh = [] | |
| cat_ids = [] | |
| for o in objs: | |
| box = o["bbox"] | |
| crowd = bool(o["iscrowd"]) if o.get("iscrowd") is not None else False | |
| if box[2] <= 0 or box[3] <= 0: | |
| continue | |
| if crowd and self.split == "train": | |
| continue | |
| bboxes_xywh.append(box) | |
| cat_ids.append(o["category_id"]) # int label 0..6 | |
| annotations = [ | |
| {"area": b[2] * b[3], "bbox": list(b), "category_id": c} | |
| for b, c in zip(bboxes_xywh, cat_ids) | |
| ] | |
| anno = {"image_id": int(item["image_id"]), "annotations": annotations} | |
| encoding = self.processor( | |
| images=image, | |
| annotations=anno, | |
| return_tensors="pt", | |
| ) | |
| if "labels" in encoding and isinstance(encoding["labels"], list) and len(encoding["labels"]) == 1: | |
| encoding["labels"] = encoding["labels"][0] | |
| for k, v in encoding.items(): | |
| if k == "labels": | |
| continue | |
| if isinstance(v, torch.Tensor) and len(v.shape) > 0: | |
| try: | |
| encoding[k] = v.squeeze(0) | |
| except Exception: | |
| pass | |
| encoding["image_id"] = int(item["image_id"]) | |
| encoding["image_size"] = [int(item["height"]), int(item["width"])] | |
| return encoding | |
| def collate_fn(batch): | |
| H = max(b["pixel_values"].shape[-2] for b in batch) | |
| W = max(b["pixel_values"].shape[-1] for b in batch) | |
| pixel_values = [] | |
| pixel_mask = [] | |
| for b in batch: | |
| im = torch.as_tensor(b["pixel_values"]) | |
| h, w = im.shape[-2:] | |
| if (h, w) != (H, W): | |
| im = torch.nn.functional.pad(im, (0, W - w, 0, H - h), value=0.0) | |
| m = torch.zeros((H, W), dtype=torch.int64) | |
| m[:h, :w] = 1 | |
| pixel_values.append(im) | |
| pixel_mask.append(m) | |
| pixel_values = torch.stack(pixel_values) | |
| pixel_mask = torch.stack(pixel_mask) | |
| labels = [] | |
| for b in batch: | |
| lb = None | |
| if "labels" in b and b["labels"] is not None and "boxes" in b["labels"]: | |
| lab = b["labels"] | |
| lb = { | |
| "class_labels": lab["class_labels"].clone() if isinstance(lab["class_labels"], torch.Tensor) else torch.tensor(lab["class_labels"], dtype=torch.long), | |
| "boxes": lab["boxes"].clone() if isinstance(lab["boxes"], torch.Tensor) else torch.tensor(lab["boxes"], dtype=torch.float32), | |
| } | |
| if lb["boxes"].numel() == 0: | |
| lb["boxes"] = torch.zeros((0, 4), dtype=torch.float32) | |
| else: | |
| lb = {"class_labels": torch.zeros((0,), dtype=torch.long), | |
| "boxes": torch.zeros((0, 4), dtype=torch.float32)} | |
| lb["image_id"] = b["image_id"] | |
| lb["image_size"] = b["image_size"] | |
| labels.append(lb) | |
| return {"pixel_values": pixel_values, "pixel_mask": pixel_mask, "labels": labels} | |
| # ---------- coco evaluation ---------- | |
| def to_coco(preds, gts, all_image_ids): | |
| """preds: list of {image_id, score, label, box_xyxy(pixels)} | |
| gts: list of {image_id, category_id, bbox_xywh, area, ann_id} | |
| """ | |
| cat_id_map = {i: i + 1 for i in range(7)} # 0..6 -> 1..7 | |
| all_image_ids = list(dict.fromkeys(all_image_ids)) | |
| data = { | |
| "images": [{"id": int(im)} for im in all_image_ids], | |
| "categories": [{"id": i + 1, "name": CLASSES[i]} for i in range(7)], | |
| "annotations": [ | |
| {"id": g["ann_id"], "image_id": g["image_id"], "category_id": cat_id_map[g["category_id"]], | |
| "bbox": g["bbox_xywh"], "area": g["area"], "iscrowd": 0} | |
| for g in gts | |
| ], | |
| } | |
| from pycocotools.coco import COCO | |
| from pycocotools.cocoeval import COCOeval | |
| coco_gt = COCO() | |
| coco_gt.dataset = data | |
| coco_gt.createIndex() | |
| res = [] | |
| for p in preds: | |
| res.append({ | |
| "image_id": p["image_id"], "category_id": cat_id_map[p["label"]], | |
| "bbox": [p["box_xyxy"][0], p["box_xyxy"][1], | |
| p["box_xyxy"][2] - p["box_xyxy"][0], p["box_xyxy"][3] - p["box_xyxy"][1]], | |
| "score": float(p["score"]), | |
| }) | |
| if not res: | |
| return {"mAP": 0.0, "AP50": 0.0, "AP75": 0.0, "AR1": 0.0, "AR10": 0.0, "AR100": 0.0} | |
| coco_dt = coco_gt.loadRes(res) | |
| e = COCOeval(coco_gt, coco_dt, "bbox") | |
| e.evaluate() | |
| e.accumulate() | |
| e.summarize() | |
| s = e.stats | |
| out = {"mAP": float(s[0]), "AP50": float(s[1]), "AP75": float(s[2]), | |
| "AR1": float(s[6]), "AR10": float(s[7]), "AR100": float(s[8])} | |
| prec = e.eval["precision"] # (T=10 IoU, R=101 rec, K cat, A=4 area, M=3 maxDet) | |
| per = {} | |
| for i in range(7): | |
| p = prec[:, :, i, 0, 2].flatten() # all IoU, all rec, class i, area=all, maxDet=100 | |
| p = p[p > -1] | |
| per[CLASSES[i]] = float(p.mean()) if p.size > 0 else 0.0 | |
| out["per_class_mAP"] = per | |
| p50 = {} | |
| for i in range(7): | |
| p = prec[0, :, i, 0, 2].flatten() # IoU=0.5 | |
| p = p[p > -1] | |
| p50[CLASSES[i]] = float(p.mean()) if p.size > 0 else 0.0 | |
| out["per_class_AP50"] = p50 | |
| return out | |
| def evaluate(model, processor, val_dl, device): | |
| model.eval() | |
| preds = [] | |
| gts = [] | |
| all_image_ids = [] | |
| ann_id = 1 | |
| for batch in val_dl: | |
| pixel_values = batch["pixel_values"].to(device) | |
| pixel_mask = batch["pixel_mask"].to(device) | |
| labels = batch["labels"] | |
| with torch.autocast(device_type="cuda", dtype=torch.float16): | |
| outputs = model(pixel_values=pixel_values, pixel_mask=pixel_mask) | |
| target_sizes = torch.tensor([[labels[bi]["image_size"][0], labels[bi]["image_size"][1]] for bi in range(len(labels))], device=device) | |
| results = processor.post_process_object_detection(outputs, target_sizes=target_sizes, threshold=0.0) | |
| for bi, r in enumerate(results): | |
| lab_gt = labels[bi] | |
| img_id = int(lab_gt["image_id"]) | |
| all_image_ids.append(img_id) | |
| scores = r["scores"] | |
| keep = scores > 0.0 | |
| boxes = r["boxes"][keep] | |
| scores = scores[keep] | |
| labels_ids = r["labels"][keep] | |
| for bx, sc, la in zip(boxes, scores, labels_ids): | |
| preds.append({"image_id": img_id, "label": int(la.item()), "score": float(sc.item()), | |
| "box_xyxy": [float(v) for v in bx]}) | |
| # gt boxes are normalized cxcywh in b['boxes']; convert to pixel xyxy | |
| lab = labels[bi] | |
| if lab["boxes"].numel() > 0: | |
| H, W = int(target_sizes[bi][0]), int(target_sizes[bi][1]) | |
| c = lab["boxes"].float() | |
| cx, cy = c[:, 0], c[:, 1] | |
| w2, h2 = c[:, 2], c[:, 3] | |
| x1 = (cx - w2 / 2) * W | |
| x2 = (cx + w2 / 2) * W | |
| y1 = (cy - h2 / 2) * H | |
| y2 = (cy + h2 / 2) * H | |
| for j in range(c.shape[0]): | |
| x1v, y1v, x2v, y2v = float(x1[j]), float(y1[j]), float(x2[j]), float(y2[j]) | |
| gts.append({"image_id": img_id, "category_id": int(lab["class_labels"][j].item()), | |
| "bbox_xywh": [x1v, y1v, x2v - x1v, y2v - y1v], | |
| "area": (x2v - x1v) * (y2v - y1v), "ann_id": ann_id}) | |
| ann_id += 1 | |
| return to_coco(preds, gts, all_image_ids) | |
| # ---------- main ---------- | |
| def main(): | |
| ap = argparse.ArgumentParser() | |
| ap.add_argument("--epochs", type=int, default=5) | |
| ap.add_argument("--batch", type=int, default=2) | |
| ap.add_argument("--size", type=int, default=560) | |
| ap.add_argument("--lr", type=float, default=1e-4) | |
| ap.add_argument("--workers", type=int, default=2) | |
| ap.add_argument("--skip_eval", action="store_true") | |
| ap.add_argument("--no_push", action="store_true") | |
| args = ap.parse_args() | |
| tlog = setup_trackio() | |
| torch.manual_seed(42) | |
| device = "cuda" if torch.cuda.is_available() else "cpu" | |
| print("device:", device, "gpus:", torch.cuda.device_count(), flush=True) | |
| processor = DetrImageProcessor.from_pretrained(MODEL_ID) | |
| processor.do_resize = True | |
| processor.size = {"shortest_edge": args.size, "longest_edge": 800} | |
| processor.do_rescale = True | |
| processor.do_normalize = True | |
| processor.do_rescale_deprecated = False | |
| hf_train = load_dataset("biglam/loc_beyond_words", split="train") | |
| hf_val = load_dataset("biglam/loc_beyond_words", split="validation") | |
| print(f"train={len(hf_train)} val={len(hf_val)}", flush=True) | |
| model = DetrForObjectDetection.from_pretrained( | |
| MODEL_ID, num_labels=7, ignore_mismatched_sizes=True, | |
| id2label=ID2LABEL, label2id=LABEL2ID, | |
| ).to(device) | |
| n_params = sum(p.numel() for p in model.parameters() if p.requires_grad) | |
| print("trainable params:", n_params, flush=True) | |
| train_ds = DetrDataset(hf_train, processor, "train") | |
| val_ds = DetrDataset(hf_val, processor, "val") | |
| train_dl = DataLoader(train_ds, batch_size=args.batch, shuffle=True, | |
| num_workers=args.workers, collate_fn=collate_fn, drop_last=True) | |
| val_dl = DataLoader(val_ds, batch_size=args.batch, shuffle=False, | |
| num_workers=args.workers, collate_fn=collate_fn) | |
| no_decay = ["bias", "LayerNorm.weight", "layer_norm.weight", "embed_positions.weight", "norm.weight"] | |
| opt = torch.optim.AdamW([ | |
| {"params": [p for n, p in model.named_parameters() if not any(nd in n for nd in no_decay)], | |
| "lr": args.lr, "weight_decay": 1e-4}, | |
| {"params": [p for n, p in model.named_parameters() if any(nd in n for nd in no_decay)], | |
| "lr": args.lr, "weight_decay": 0.0}, | |
| ]) | |
| steps_per_epoch = len(train_dl) | |
| total_steps = steps_per_epoch * args.epochs | |
| from transformers import get_linear_schedule_with_warmup | |
| sched = get_linear_schedule_with_warmup(opt, num_warmup_steps=int(0.1 * total_steps), | |
| num_training_steps=total_steps) | |
| global_step = 0 | |
| best_map = -1.0 | |
| for epoch in range(args.epochs): | |
| model.train() | |
| epoch_loss = 0.0 | |
| nb = 0 | |
| for step, batch in enumerate(train_dl): | |
| pixel_values = batch["pixel_values"].to(device) | |
| pixel_mask = batch["pixel_mask"].to(device) | |
| labels = [{k: (v.to(device) if isinstance(v, torch.Tensor) else v) | |
| for k, v in lab.items()} for lab in batch["labels"]] | |
| with torch.autocast(device_type="cuda", dtype=torch.float16): | |
| out = model(pixel_values=pixel_values, pixel_mask=pixel_mask, labels=labels) | |
| loss = sum(v for k, v in out.loss_dict.items() if v is not None) | |
| opt.zero_grad() | |
| loss.backward() | |
| torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0) | |
| opt.step() | |
| sched.step() | |
| epoch_loss += loss.item() | |
| nb += 1 | |
| global_step += 1 | |
| if step % 25 == 0: | |
| m = {("l_" + k): float(v.item()) for k, v in out.loss_dict.items() if v is not None} | |
| print(f"[ep{epoch} step{step}/{steps_per_epoch}] loss={loss.item():.3f} { {k: round(v,3) for k,v in m.items()} }", flush=True) | |
| tlog(step=global_step, loss=loss.item(), **m) | |
| avg = epoch_loss / max(nb, 1) | |
| print(f"==== EPOCH {epoch} DONE avg_loss={avg:.4f} ====", flush=True) | |
| tlog(epoch_loss=avg, epoch=epoch) | |
| # eval | |
| if not args.skip_eval: | |
| print("evaluating...", flush=True) | |
| metrics = evaluate(model, processor, val_dl, device) | |
| print("EVAL:", {k: (round(v, 4) if isinstance(v, float) else v) for k, v in metrics.items() if k != "per_class_mAP" and k != "per_class_AP50"}, flush=True) | |
| print("eval per-class mAP:", {k: round(v, 4) for k, v in metrics["per_class_mAP"].items()}, flush=True) | |
| print("eval per-class AP50:", {k: round(v, 4) for k, v in metrics["per_class_AP50"].items()}, flush=True) | |
| tlog(mAP=metrics["mAP"], AP50=metrics["AP50"], AR100=metrics["AR100"], epoch=epoch) | |
| if metrics["mAP"] > best_map: | |
| best_map = metrics["mAP"] | |
| save_dir = "/tmp/best_model" | |
| model.save_pretrained(save_dir) | |
| processor.save_pretrained(save_dir) | |
| else: | |
| save_dir = "/tmp/best_model" | |
| model.save_pretrained(save_dir) | |
| processor.save_pretrained(save_dir) | |
| print("best mAP:", best_map, flush=True) | |
| save_dir = "/tmp/final_model" | |
| model.save_pretrained(save_dir) | |
| processor.save_pretrained(save_dir) | |
| print("saved:", save_dir, flush=True) | |
| if not args.no_push: | |
| from huggingface_hub import HfApi | |
| api = HfApi() | |
| print("pushing model to", REPO_ID, flush=True) | |
| api.upload_folder(repo_id=REPO_ID, folder_path=save_dir, repo_type="model", commit_message="fine-tuned DETR on loc_beyond_words") | |
| tlog(best_mAP=best_map) | |
| try: | |
| import trackio | |
| trackio.finish(status=0) | |
| except Exception: | |
| pass | |
| print("DONE", flush=True) | |
| if __name__ == "__main__": | |
| main() | |