camstack-trainer / tests /conftest.py
apocaliss92's picture
camstack-train 0.1.0: YOLOv9 root detector fine-tune (job.json in, result.json + bundle.tar out)
ebeed67 verified
Raw History Blame Contribute Delete
3.7 kB
"""Fixtures: a synthetic hub export in the real manifest format, and a job."""
from __future__ import annotations
import copy
import hashlib
import io
import json
import tarfile
from pathlib import Path
from typing import Callable
import pytest
from PIL import Image
DAY = 86_400_000
T0 = 1_760_000_000_000 # a capture instant (epoch ms)
JOB = {
"schema": "camstack.training/v1",
"jobKey": "0b6c1f7e-6a4e-4a8e-9f0b-3c2a1d4e5f60",
"target": {"kind": "detector", "step": "object-detection"},
"base": {"modelId": "yolov9m-640", "weights": "yolov9m.pt", "imgsz": 640},
"baseline": {
"modelId": "yolov9m-640",
"onnxUrl": "https://huggingface.co/camstack/camstack-models/resolve/main/objectDetection/yolov9/onnx/camstack-yolov9m-640.onnx",
"imgsz": 640,
"floors": {"person": 0.6, "vehicle": 0.4, "animal": 0.55},
},
"dataset": {"versionId": "v1", "annotationHash": "abc", "sha256": "0" * 64},
"split": {"holdoutCameras": [], "holdoutLastDays": 0, "valFraction": 0.15, "seed": 1},
"hyper": {"epochs": 2, "batch": 4, "lr0": 0.002, "freeze": 10, "patience": 10, "seed": 1},
"replay": {"source": "coco2017", "count": 0},
"exports": ["onnx", "openvino-fp16", "openvino-int8", "coreml-fp16"],
"calibration": {"maxFrames": 300},
"floors": {"minSupport": 20, "clamp": 0.15},
"int8Gate": {"maxMap50Drop": 0.02},
"catalog": {"id": "custom-yolov9m-640-ab12cd34", "name": "Custom YOLOv9 M"},
}
@pytest.fixture
def job_dict() -> dict:
return copy.deepcopy(JOB)
def _jpeg() -> bytes:
buf = io.BytesIO()
Image.new("RGB", (64, 48), (90, 120, 150)).save(buf, "JPEG")
return buf.getvalue()
def subject(macro: str, label: str | None = None, x: float = 0.1, kind: str = "subject") -> dict:
a = {
"id": f"a-{macro}-{x}-{kind}", "macroClass": macro, "kind": kind, "source": "operator",
"bbox": {"x": x, "y": 0.2, "w": 0.3, "h": 0.4}, "derived": {},
}
if label is not None:
a["label"] = label
return a
def frame(fid: str, device: int, day: int, annotations: list[dict], complete: bool = True, night: bool = False) -> dict:
return {
"frameId": fid, "path": f"frames/{device}/t{fid}/{fid}.jpg", "deviceId": device,
"origin": "track", "trackId": f"t{fid}", "collectionIds": [], "width": 64, "height": 48,
"sizeBytes": 100, "sourceKind": "keyFrame", "copiedAt": T0, "capturedAt": T0 + day * DAY,
"complete": complete, "lighting": {"condition": "night" if night else "day", "night": night, "source": "clock"},
"annotations": annotations,
}
def write_export(path: Path, frames: list[dict], schema: str = "camstack.retrain-export/v3") -> str:
manifest = {
"schema": schema, "layout": "frames", "generatedAt": T0, "batchId": "b1",
"frameCount": len(frames), "annotationCount": 0, "modelErrorCount": 0,
"classifierCropPadding": 0.15, "frames": frames,
}
data = _jpeg()
with tarfile.open(path, "w") as tar:
body = json.dumps(manifest).encode()
info = tarfile.TarInfo("annotations.json")
info.size = len(body)
tar.addfile(info, io.BytesIO(body))
for f in frames:
info = tarfile.TarInfo(f["path"])
info.size = len(data)
tar.addfile(info, io.BytesIO(data))
return hashlib.sha256(path.read_bytes()).hexdigest()
@pytest.fixture
def make_export(tmp_path: Path) -> Callable[..., tuple[Path, str]]:
def build(frames: list[dict], schema: str = "camstack.retrain-export/v3") -> tuple[Path, str]:
path = tmp_path / "dataset.tar"
return path, write_export(path, frames, schema)
return build