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/run.py from camstack/camstack-trainer: direct link, hf CLI and curl.
- Browser
- Download file 7.41 kB
-
https://huggingface.co/camstack/camstack-trainer/resolve/main/camstack_train/run.py
- Command line
-
hf download hf://camstack/camstack-trainer/camstack_train/run.py
-
curl -L -o run.py https://huggingface.co/camstack/camstack-trainer/resolve/main/camstack_train/run.py
7.41 kB
| """One training job, end to end: prepare → replay → train → export → eval → package. | |
| Everything heavy is injected (:class:`Deps`) so the orchestration is tested | |
| without torch, Ultralytics or OpenVINO. ``result.json`` is written on every | |
| exit, success or failure, and the last line is always a ``done`` event. | |
| """ | |
| from __future__ import annotations | |
| from dataclasses import dataclass, field | |
| from pathlib import Path | |
| from typing import Optional | |
| from .dataset import coco_80, extract_archive, frames_from_manifest, verify_archive, write_layout | |
| from .errors import EXIT_INTERNAL, EXIT_OK, TrainerError | |
| from .evaluate import DetectorFactory, Evaluation, baseline_model, evaluate, harness_detector, holdout_frames, map50, policy_of, split_reports | |
| from .events import Emitter | |
| from .export import SubprocessToolchain, Toolchain, calibration_frames, export_all, remove_export | |
| from .floors import floors_block | |
| from .manifest import ( | |
| LICENCE, RESULT_SCHEMA, catalog_draft, failed_result, file_rows, toolchain_versions, | |
| trainer_block, write_bundle, write_result, | |
| ) | |
| from .replay import Fetch, http_fetch, prepare_replay | |
| from .spec import JobSpec | |
| from .split import SplitResult, assign | |
| from .train_det import Trainer, TrainOutcome, ultralytics_train, write_data_yaml | |
| class Deps: | |
| fetch: Fetch = http_fetch | |
| trainer: Trainer = ultralytics_train | |
| toolchain: Toolchain = field(default_factory=SubprocessToolchain) | |
| detector: DetectorFactory = harness_detector | |
| emitter: Emitter = field(default_factory=Emitter) | |
| class Paths: | |
| job: Path | |
| dataset: Path | |
| out: Path | |
| cache: Path | |
| work: Path | |
| def _int8_gate(job: JobSpec, evaluation: Evaluation, produced: bool) -> dict: | |
| fp16, int8 = map50(evaluation.candidate), map50(evaluation.candidate_int8) | |
| if not produced: | |
| return {"kept": False, "map50Fp16": fp16, "map50Int8": None, "reason": "not exported"} | |
| if fp16 is None or int8 is None: | |
| return {"kept": True, "map50Fp16": fp16, "map50Int8": int8, "reason": "no holdout to gate on"} | |
| drop = round(fp16 - int8, 4) | |
| if drop > job.int8_gate.max_map50_drop: | |
| return {"kept": False, "map50Fp16": fp16, "map50Int8": int8, "reason": f"mAP50 drop {drop} > {job.int8_gate.max_map50_drop}"} | |
| return {"kept": True, "map50Fp16": fp16, "map50Int8": int8, "reason": None} | |
| def _prepare(job: JobSpec, paths: Paths, emitter: Emitter) -> tuple[SplitResult, Path, Path]: | |
| emitter.stage("prepare") | |
| verify_archive(paths.dataset, job.dataset.sha256) | |
| source = paths.work / "export" | |
| manifest = extract_archive(paths.dataset, source) | |
| split = assign(frames_from_manifest(manifest), job.split) | |
| yolo = paths.work / "yolo" | |
| counts = write_layout({"train": list(split.train), "val": list(split.val)}, source, yolo) | |
| emitter.metric("frames.train", counts.frames.get("train", 0)) | |
| emitter.metric("frames.val", counts.frames.get("val", 0)) | |
| emitter.metric("frames.holdout", len(split.holdout)) | |
| return split, source, yolo | |
| def _dataset_block(job: JobSpec, split: SplitResult, replayed: int) -> dict: | |
| return { | |
| "versionId": job.dataset.version_id, | |
| "annotationHash": job.dataset.annotation_hash, | |
| "sha256": job.dataset.sha256, | |
| "frames": {"train": len(split.train), "val": len(split.val), "holdout": len(split.holdout)}, | |
| "holdoutCameras": list(split.holdout_cameras), | |
| "replayImages": replayed, | |
| } | |
| def _training_block(outcome: TrainOutcome) -> dict: | |
| return { | |
| "epochsRun": outcome.epochs_run, | |
| "bestEpoch": outcome.best_epoch, | |
| "bestMap50": outcome.best_map50, | |
| "bestMap50_95": outcome.best_map50_95, | |
| "seconds": outcome.seconds, | |
| } | |
| def execute(job: JobSpec, paths: Paths, deps: Deps, partial: dict) -> dict: | |
| emitter = deps.emitter | |
| split, source, yolo = _prepare(job, paths, emitter) | |
| emitter.stage("replay") | |
| names = coco_80() | |
| replayed = prepare_replay(job.replay.count, paths.cache / "coco", yolo, names, deps.fetch) | |
| partial["dataset"] = _dataset_block(job, split, replayed) | |
| emitter.stage("train") | |
| data_yaml = write_data_yaml(yolo, names, replayed > 0, len(split.val) > 0) | |
| outcome = deps.trainer(job, data_yaml, paths.work, paths.cache, emitter) | |
| partial["training"] = _training_block(outcome) | |
| emitter.stage("export") | |
| staging = paths.work / "artifacts" | |
| calib = calibration_frames(yolo / "images" / "train", job.calibration.max_frames) | |
| exported = export_all(list(job.exports), outcome.best_pt, job.catalog.id, job.base.imgsz, calib, staging, deps.toolchain) | |
| emitter.stage("eval") | |
| int8_path = staging / f"{job.catalog.id}-int8.xml" if "openvino-int8" in exported.produced else None | |
| frames = holdout_frames(source, {f.frame_id for f in split.holdout_eval}) | |
| baseline = baseline_model(job, paths.cache, deps.fetch) if frames else None | |
| evaluation = ( | |
| evaluate(job, frames, baseline, staging / f"{job.catalog.id}.onnx", int8_path, deps.detector) | |
| if baseline is not None else Evaluation(None, None, 0) | |
| ) | |
| gate = _int8_gate(job, evaluation, int8_path is not None) | |
| if int8_path is not None and not gate["kept"]: | |
| remove_export(staging, job.catalog.id, "openvino-int8") | |
| exported.drop("openvino-int8", gate["reason"]) | |
| floors = floors_block(evaluation.candidate, evaluation.candidate_int8, dict(job.baseline.floors), policy_of(job), gate["kept"] and int8_path is not None) | |
| emitter.stage("package") | |
| rows = file_rows(staging, job.catalog.id) | |
| for row in rows: | |
| emitter.artifact(row["path"], row["bytes"], row["sha256"]) | |
| bundle = write_bundle(staging, rows, paths.out / "bundle.tar") | |
| return { | |
| "schema": RESULT_SCHEMA, | |
| "jobKey": job.job_key, | |
| "status": "succeeded", | |
| "error": None, | |
| "trainer": trainer_block(), | |
| "toolchain": toolchain_versions(), | |
| **partial, | |
| "evaluation": split_reports(evaluation), | |
| "floors": floors, | |
| "int8": gate, | |
| "exportsDropped": exported.dropped, | |
| "files": rows, | |
| "bundle": bundle, | |
| "catalogDraft": catalog_draft(job, rows, floors), | |
| "licence": LICENCE, | |
| } | |
| def scrub(message: str, paths: Paths) -> str: | |
| """No frame path leaves the container: every one lives under the work dir.""" | |
| return message.replace(str(paths.work), "<work>").replace(str(paths.dataset), "<dataset>")[:300] | |
| def run_job(job: JobSpec, paths: Paths, deps: Optional[Deps] = None) -> int: | |
| deps = deps or Deps() | |
| partial: dict = {} | |
| try: | |
| result = execute(job, paths, deps, partial) | |
| code, status = EXIT_OK, "succeeded" | |
| except TrainerError as exc: | |
| deps.emitter.error(exc.stage, exc.code, exc.message) | |
| result, code, status = {**failed_result(job.job_key, exc.as_dict()), **partial}, exc.exit_code, "failed" | |
| except Exception as exc: # anything unforeseen: still a result and a done line | |
| message = scrub(f"{type(exc).__name__}: {exc}", paths) | |
| error = {"stage": "internal", "code": "internal", "message": message} | |
| deps.emitter.error("internal", "internal", message) | |
| result, code, status = {**failed_result(job.job_key, error), **partial}, EXIT_INTERNAL, "failed" | |
| digest = write_result(paths.out / "result.json", result) | |
| deps.emitter.done(status, digest) | |
| return code | |