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
camstack-train 0.1.0: YOLOv9 root detector fine-tune (job.json in, result.json + bundle.tar out)
ebeed67 verified Download camstack_train/_vendor/camstack_eval/pipeline.py from camstack/camstack-trainer: direct link, hf CLI and curl.
- Browser
- Download file 6.75 kB
-
https://huggingface.co/camstack/camstack-trainer/resolve/main/camstack_train/_vendor/camstack_eval/pipeline.py
- Command line
-
hf download hf://camstack/camstack-trainer/camstack_train/_vendor/camstack_eval/pipeline.py
-
curl -L -o pipeline.py https://huggingface.co/camstack/camstack-trainer/resolve/main/camstack_train/_vendor/camstack_eval/pipeline.py
6.75 kB
| """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] | |
| 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) | |