Instructions to use camstack/camstack-trainer with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- ultralytics
How to use camstack/camstack-trainer with ultralytics:
from huggingface_hub import hf_hub_download from ultralytics import YOLO # pick the weights file from this repo's "Files and versions" tab weights = hf_hub_download("camstack/camstack-trainer", "<weights>.pt") model = YOLO(weights) source = 'http://images.cocodataset.org/val2017/000000039769.jpg' model.predict(source=source, save=True) - Notebooks
- Google Colab
- Kaggle
File size: 6,752 Bytes
ebeed67 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 | """Run a YOLO detector (ONNX or OpenVINO IR) exactly as the inference pool does.
Nothing here re-implements the pipeline: the letterbox (grey 114 padding,
bilinear resize, /255) and the YOLO decode (sigmoid detection, confidence
floor, NMS) are IMPORTED from `packages/addon-pipeline/python`, so a change to
production preprocessing changes the evaluation with it. The pool floor (0.05)
and the NMS IoU (0.45) are the pool's.
An OpenVINO IR is compiled as the pool compiles it: a static 3-channel input
gets the pool's own PrePostProcessor (uint8 NHWC in, the /255 folded into the
graph — `inference_pool._build_ov_ppp_model`), a dynamic one is fed the float
letterbox. The device is the CPU at FP32 (`INFERENCE_PRECISION_HINT=f32`), the
x86 CPU default; an ARM CPU defaults to FP16, which is not what any node runs.
One difference stays and is stated: the live path first downscales a frame to
the model's content size on the Node side (sharp); here the PIL letterbox does
that resize itself. The comparison run of 2026-10-07 measured the same way.
"""
from __future__ import annotations
import sys
from pathlib import Path
from typing import Optional, Sequence
import numpy as np
from .metrics import Detection
REPO_ROOT = Path(__file__).resolve().parents[3]
PIPELINE_PYTHON = REPO_ROOT / "packages" / "addon-pipeline" / "python"
#: The inference pool's confidence floor and NMS IoU.
POOL_FLOOR = 0.05
POOL_NMS_IOU = 0.45
def _pipeline():
if str(PIPELINE_PYTHON) not in sys.path:
sys.path.insert(0, str(PIPELINE_PYTHON))
import inference_pool # noqa: E402 (path set above)
from postprocessors import POSTPROCESSORS # noqa: E402
return inference_pool, POSTPROCESSORS
class OnnxDetector:
"""A detector on ONNX Runtime (CPU unless ``providers`` says otherwise)."""
def __init__(
self,
model_path: str,
input_size: int,
labels: Optional[Sequence[str]] = None,
postprocessor: str = "yolo",
pool_floor: float = POOL_FLOOR,
nms_iou: float = POOL_NMS_IOU,
providers: Optional[Sequence[str]] = None,
) -> None:
import onnxruntime as ort
self._pool, postprocessors = _pipeline()
if postprocessor not in postprocessors:
raise ValueError(f"unknown postprocessor {postprocessor!r}")
self._postprocess = postprocessors[postprocessor]
from postprocessors.yolo import COCO_80
self.input_size = input_size
self._config = {
"inputSize": input_size,
"labels": list(labels) if labels is not None else list(COCO_80),
"confidence": pool_floor,
"nmsIouThreshold": nms_iou,
"postprocessor": postprocessor,
}
self._session = ort.InferenceSession(
model_path, providers=list(providers) if providers else ["CPUExecutionProvider"]
)
self._input_name = self._session.get_inputs()[0].name
self._output_names = [o.name for o in self._session.get_outputs()]
def detect(self, jpeg: bytes) -> list[Detection]:
img = self._pool.decode_jpeg(jpeg)
orig_w, orig_h = img.size
arr, scale, pad = self._pool.letterbox(img, self.input_size)
tensor = arr.transpose(2, 0, 1)[np.newaxis].astype(np.float32)
outputs = self._session.run(self._output_names, {self._input_name: tensor})
predictions = dict(zip(self._output_names, outputs))
result = self._postprocess(predictions, self._config, orig_w, orig_h, scale, pad)
return _to_detections(result, orig_w, orig_h)
def _to_detections(result: dict, orig_w: int, orig_h: int) -> list[Detection]:
out: list[Detection] = []
for d in result.get("detections", []):
x1, y1, x2, y2 = d["bbox"]
out.append(
Detection(
str(d["class"]),
float(d["score"]),
(x1 / orig_w, y1 / orig_h, (x2 - x1) / orig_w, (y2 - y1) / orig_h),
)
)
return out
class OpenVinoDetector:
"""A detector on an OpenVINO IR, on the CPU at FP32 (see the module doc)."""
def __init__(
self,
model_path: str,
input_size: int,
labels: Optional[Sequence[str]] = None,
postprocessor: str = "yolo",
pool_floor: float = POOL_FLOOR,
nms_iou: float = POOL_NMS_IOU,
device: str = "CPU",
precision: str = "f32",
) -> None:
import openvino as ov
self._pool, postprocessors = _pipeline()
if postprocessor not in postprocessors:
raise ValueError(f"unknown postprocessor {postprocessor!r}")
self._postprocess = postprocessors[postprocessor]
from postprocessors.yolo import COCO_80
self.input_size = input_size
self._config = {
"inputSize": input_size,
"labels": list(labels) if labels is not None else list(COCO_80),
"confidence": pool_floor,
"nmsIouThreshold": nms_iou,
"postprocessor": postprocessor,
"preprocessMode": "letterbox",
}
core = ov.Core()
model, self._ppp = self._pool._build_ov_ppp_model(core, model_path, self._config)
properties = {"INFERENCE_PRECISION_HINT": precision, "PERFORMANCE_HINT": "LATENCY"}
self._compiled = core.compile_model(model if model is not None else model_path, device, properties)
self._request = self._compiled.create_infer_request()
self._output_names = [o.get_any_name() for o in self._compiled.outputs]
@property
def folded(self) -> bool:
"""True when the pool's PrePostProcessor was attached (static input)."""
return self._ppp is not None
def detect(self, jpeg: bytes) -> list[Detection]:
img = self._pool.decode_jpeg(jpeg)
orig_w, orig_h = img.size
if self._ppp is not None:
tensor, scale, pad = self._pool._preprocess_ov_uint8(img, self._ppp)
else:
arr, scale, pad = self._pool.letterbox(img, self.input_size)
tensor = arr.transpose(2, 0, 1)[np.newaxis].astype(np.float32)
self._request.infer({0: tensor})
predictions = {
name: self._request.get_output_tensor(i).data.copy()
for i, name in enumerate(self._output_names)
}
result = self._postprocess(predictions, self._config, orig_w, orig_h, scale, pad)
return _to_detections(result, orig_w, orig_h)
def make_detector(model_path: str, input_size: int):
"""An ``OpenVinoDetector`` for an ``.xml`` IR, an ``OnnxDetector`` otherwise."""
if model_path.endswith(".xml"):
return OpenVinoDetector(model_path, input_size)
return OnnxDetector(model_path, input_size)
|