Skip to main content

vision_rs/detect/
mod.rs

1/*
2 * Copyright 2026 Teenygrad
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *     http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17
18//! Object detection interface.
19//!
20//! # Example
21//!
22//! ```rust,no_run
23//! use vision_rs::detect::{DetectorConfig, ObjectDetector, Yolo26DetectorConfig};
24//! use vision_rs::models::yolo::yolo26::Yolo26Variant;
25//!
26//! # #[tokio::main]
27//! # async fn main() -> anyhow::Result<()> {
28//! let config = DetectorConfig::Yolo26(Yolo26DetectorConfig::new(
29//!     Yolo26Variant::N,
30//!     "weights/yolo26n.bin",
31//!     vec!["car".into(), "truck".into(), "person".into()],
32//! ));
33//!
34//! let detector = ObjectDetector::new(config)?;
35//! let image_bytes = std::fs::read("frame.jpg")?;
36//! let detections = detector.detect(&image_bytes).await?;
37//!
38//! for d in &detections {
39//!     println!("{} {:.2} {:?}", d.class, d.confidence, d.bbox);
40//! }
41//! # Ok(())
42//! # }
43//! ```
44
45use std::path::PathBuf;
46
47use crate::models::yolo::yolo26::Yolo26Variant;
48
49// ── Detection result ──────────────────────────────────────────────────────────
50
51/// A single detected object.
52#[derive(Clone, Debug)]
53pub struct Detection {
54    /// Bounding box as `[cx, cy, w, h]` normalised to `[0, 1]`.
55    pub bbox: [f32; 4],
56    /// Class label resolved from the detector's `class_names` list.
57    pub class: String,
58    /// Confidence score in `[0, 1]`.
59    pub confidence: f32,
60}
61
62// ── YOLO26 config ─────────────────────────────────────────────────────────────
63
64/// Configuration for a YOLO26-backed [`ObjectDetector`].
65#[derive(Clone, Debug)]
66pub struct Yolo26DetectorConfig {
67    /// Which YOLO26 model size to use (N/S/M/L/X).
68    pub variant: Yolo26Variant,
69    /// Path to the model weights file.
70    pub weights: PathBuf,
71    /// Class labels, indexed by the model's output class index.
72    pub class_names: Vec<String>,
73    /// Minimum confidence to retain a detection. Default: `0.25`.
74    pub conf_threshold: f32,
75    /// IoU threshold for non-maximum suppression. Default: `0.45`.
76    pub nms_iou_threshold: f32,
77    /// Square input resolution the model was trained at (e.g. `640`).
78    pub img_size: usize,
79}
80
81impl Yolo26DetectorConfig {
82    /// Creates a config with default `conf_threshold` (`0.25`), `nms_iou_threshold`
83    /// (`0.45`), and `img_size` (`640`).
84    pub fn new(
85        variant: Yolo26Variant,
86        weights: impl Into<PathBuf>,
87        class_names: Vec<String>,
88    ) -> Self {
89        Self {
90            variant,
91            weights: weights.into(),
92            class_names,
93            conf_threshold: 0.25,
94            nms_iou_threshold: 0.45,
95            img_size: 640,
96        }
97    }
98}
99
100// ── Top-level config enum ─────────────────────────────────────────────────────
101
102/// Detector configuration, tagged by model family.
103#[derive(Clone, Debug)]
104pub enum DetectorConfig {
105    /// Use a YOLO26 model.
106    Yolo26(Yolo26DetectorConfig),
107}
108
109// ── ObjectDetector ────────────────────────────────────────────────────────────
110
111/// A model-agnostic object detector, dispatching to the configured model family.
112pub struct ObjectDetector {
113    inner: DetectorInner,
114}
115
116impl ObjectDetector {
117    /// Build a detector from the given config.
118    pub fn new(config: DetectorConfig) -> anyhow::Result<Self> {
119        let inner = match config {
120            DetectorConfig::Yolo26(cfg) => DetectorInner::Yolo26(Yolo26Detector { config: cfg }),
121        };
122        Ok(Self { inner })
123    }
124
125    /// Run inference on raw JPEG or PNG `image_bytes`.
126    ///
127    /// Returns every detection that clears the config's `conf_threshold`,
128    /// after non-maximum suppression.
129    pub async fn detect(&self, image_bytes: &[u8]) -> anyhow::Result<Vec<Detection>> {
130        match &self.inner {
131            DetectorInner::Yolo26(d) => d.detect(image_bytes).await,
132        }
133    }
134}
135
136// ── Private per-model implementations ────────────────────────────────────────
137
138enum DetectorInner {
139    Yolo26(Yolo26Detector),
140}
141
142struct Yolo26Detector {
143    config: Yolo26DetectorConfig,
144}
145
146impl Yolo26Detector {
147    async fn detect(&self, _image_bytes: &[u8]) -> anyhow::Result<Vec<Detection>> {
148        let _ = &self.config;
149        todo!("YOLO26 inference")
150    }
151}