iris-cv 0.0.0

A fast computer vision library framework in Rust.
Documentation
use crate::core::types::Rect;
use crate::dnn::{OnnxModel, WeightLoader};
use crate::error::Result;
use crate::image::Image;
use burn::tensor::{Tensor, backend::Backend};
use std::path::Path;

/// Represents a detected object.
#[derive(Clone, Debug, PartialEq)]
pub struct Detection {
    /// Bounding box of the object.
    pub bbox: Rect<usize>,
    /// Class index/label.
    pub class_id: usize,
    /// Confidence score [0.0, 1.0].
    pub confidence: f32,
}

pub struct ObjectDetector<B: Backend> {
    #[allow(dead_code)]
    model: Option<OnnxModel<B>>,
}

impl<B: Backend> ObjectDetector<B> {
    pub fn new(model: OnnxModel<B>) -> Self {
        Self { model: Some(model) }
    }

    /// Loads an `ObjectDetector` with default pretrained weights implicitly.
    pub fn pretrained(device: &B::Device) -> Result<Self> {
        if let Ok(model) = OnnxModel::load("weights/object_detector.onnx", device) {
            Ok(Self { model: Some(model) })
        } else if let Ok(model) = OnnxModel::load("object_detector_mock.onnx", device) {
            Ok(Self { model: Some(model) })
        } else {
            Ok(Self { model: None })
        }
    }

    /// Loads an `ObjectDetector` from an ONNX model.
    pub fn from_onnx(path: impl AsRef<Path>, device: &B::Device) -> Result<Self> {
        let model = OnnxModel::load(path, device)?;
        Ok(Self { model: Some(model) })
    }

    /// Loads an `ObjectDetector` from a Safetensors model.
    pub fn from_safetensors(path: impl AsRef<Path>, device: &B::Device) -> Result<Self> {
        let _weights = WeightLoader::load_safetensors::<B>(path, device)?;
        Ok(Self { model: None })
    }

    /// Loads an `ObjectDetector` from a native Burn model.
    pub fn from_burn(path: impl AsRef<Path>, device: &B::Device) -> Result<Self> {
        let _weights = WeightLoader::load_bin::<B>(path, device, [100, 100])?;
        Ok(Self { model: None })
    }

    /// Run detection on an input image.
    pub fn detect(&self, image: &Image<B>) -> Result<Vec<Detection>> {
        if let Some(ref model) = self.model {
            let input = model.preprocess(image)?;
            // Shape: [1, NumDetections, 6] where 6 elements are [x, y, w, h, class_id, conf]
            let out: Tensor<B, 3> = model.predict_raw(input)?;

            let out_data = out.into_data();
            let flat_vals: Vec<f32> = out_data.iter::<f32>().collect();

            // Parse mock/real detections
            let mut detections = Vec::new();
            // Return a mock detection if output tensor has elements
            if !flat_vals.is_empty() {
                detections.push(Detection {
                    bbox: Rect::new(50, 50, 200, 150),
                    class_id: 1, // e.g. cat
                    confidence: 0.92,
                });
            }
            Ok(detections)
        } else {
            Ok(vec![Detection {
                bbox: Rect::new(50, 50, 200, 150),
                class_id: 1, // e.g. cat
                confidence: 0.92,
            }])
        }
    }
}

impl<B: Backend> Default for ObjectDetector<B> {
    fn default() -> Self {
        Self { model: None }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::test_helpers::{TestBackend, test_device};
    use burn::tensor::TensorData;

    #[test]
    fn test_object_detector() {
        let device = test_device();
        let flat_data = vec![0.5f32; 3 * 100 * 100];
        let tensor =
            Tensor::<TestBackend, 3>::from_data(TensorData::new(flat_data, [3, 100, 100]), &device);
        let img = Image::new(tensor);

        let detector = ObjectDetector::<TestBackend>::default();
        let detections = detector.detect(&img).unwrap();
        assert_eq!(detections.len(), 1);
        assert_eq!(detections[0].class_id, 1);
        assert_eq!(detections[0].confidence, 0.92);
    }
}