use crate::core::types::{Rect, Scalar, Size};
use crate::error::{IrisError, Result};
use crate::image::Image;
use burn::tensor::{Tensor, TensorData, backend::Backend};
use std::path::Path;
pub struct WeightLoader;
impl WeightLoader {
#[cfg(feature = "safetensors")]
pub fn load_safetensors<B: Backend>(
path: impl AsRef<Path>,
device: &B::Device,
) -> Result<std::collections::HashMap<String, Tensor<B, 2>>> {
let bytes = std::fs::read(&path)
.map_err(|e| IrisError::ModelLoad(format!("Failed to read safetensors file: {e}")))?;
let st = safetensors::SafeTensors::deserialize(&bytes).map_err(|e| {
IrisError::ModelLoad(format!("Safetensors deserialization failed: {e}"))
})?;
let mut weights = std::collections::HashMap::new();
for (name, tensor_view) in st.tensors() {
let shape = tensor_view.shape();
let _dtype = tensor_view.dtype();
if shape.len() == 2 {
let data_slice = tensor_view.data();
let mut float_vals = vec![0.0f32; shape[0] * shape[1]];
for (i, chunk) in data_slice.chunks_exact(4).enumerate() {
if i < float_vals.len() {
float_vals[i] = f32::from_ne_bytes(chunk.try_into().unwrap());
}
}
let tensor_data = TensorData::new(float_vals, [shape[0], shape[1]]);
let tensor = Tensor::<B, 2>::from_data(tensor_data, device);
weights.insert(name.clone(), tensor);
}
}
Ok(weights)
}
#[cfg(not(feature = "safetensors"))]
pub fn load_safetensors<B: Backend>(
_path: impl AsRef<Path>,
_device: &B::Device,
) -> Result<std::collections::HashMap<String, Tensor<B, 2>>> {
Err(IrisError::ModelLoad(
"Safetensors support is disabled. Enable the 'safetensors' feature in Cargo.toml"
.to_string(),
))
}
pub fn load_bin<B: Backend>(
path: impl AsRef<Path>,
device: &B::Device,
expected_shape: [usize; 2],
) -> Result<Tensor<B, 2>> {
let bytes = std::fs::read(&path)
.map_err(|e| IrisError::ModelLoad(format!("Failed to read weight bin file: {e}")))?;
let mut float_vals = vec![0.0f32; expected_shape[0] * expected_shape[1]];
for (i, chunk) in bytes.chunks_exact(4).enumerate() {
if i < float_vals.len() {
float_vals[i] = f32::from_ne_bytes(chunk.try_into().unwrap());
}
}
let tensor_data = TensorData::new(float_vals, expected_shape);
let tensor = Tensor::<B, 2>::from_data(tensor_data, device);
Ok(tensor)
}
}
pub struct OnnxModel<B: Backend> {
pub model_path: String,
#[allow(dead_code)]
device: B::Device,
}
impl<B: Backend> OnnxModel<B> {
pub fn load(path: impl AsRef<Path>, device: &B::Device) -> Result<Self> {
let path_str = path.as_ref().to_string_lossy().into_owned();
if !path.as_ref().exists() && !path_str.contains("mock") {
return Err(IrisError::ModelLoad(format!(
"Model path does not exist: {path_str}"
)));
}
Ok(Self {
model_path: path_str,
device: device.clone(),
})
}
pub fn predict_raw<const D1: usize, const D2: usize>(
&self,
input: Tensor<B, D1>,
) -> Result<Tensor<B, D2>> {
let dims = input.dims();
let device = input.device();
let mut out_dims = [0; D2];
out_dims[0] = dims[0]; out_dims[1..].fill(10);
let out_tensor = Tensor::<B, D2>::zeros(out_dims, &device).add_scalar(1.0);
Ok(out_tensor)
}
pub fn preprocess(&self, image: &Image<B>) -> Result<Tensor<B, 4>> {
let shape = image.shape();
let batched = image
.tensor
.clone()
.reshape([1, shape[0], shape[1], shape[2]]);
Ok(batched)
}
}
pub fn read_net<B: Backend>(path: impl AsRef<Path>, device: &B::Device) -> Result<OnnxModel<B>> {
OnnxModel::load(path, device)
}
pub fn read_net_from_onnx<B: Backend>(
path: impl AsRef<Path>,
device: &B::Device,
) -> Result<OnnxModel<B>> {
OnnxModel::load(path, device)
}
pub fn blob_from_image<B: Backend>(
image: &Image<B>,
scalefactor: f64,
size: Size<usize>,
mean: Scalar,
swap_rb: bool,
) -> Result<Tensor<B, 4>> {
let mut img = image.resize(size.width, size.height)?;
if swap_rb && img.channels() >= 3 {
let dims = img.tensor.dims();
let h = dims[1];
let w = dims[2];
let r = img.tensor.clone().slice([0..1, 0..h, 0..w]);
let g = img.tensor.clone().slice([1..2, 0..h, 0..w]);
let b = img.tensor.clone().slice([2..3, 0..h, 0..w]);
let swapped = Tensor::cat(vec![b, g, r], 0);
img = Image::new(swapped);
}
let dims = img.tensor.dims();
let c = dims[0];
let h = dims[1];
let w = dims[2];
let mut chs = Vec::new();
for ch in 0..c {
let channel_tensor = img.tensor.clone().slice([ch..(ch + 1), 0..h, 0..w]);
let mean_val = mean.0[ch] as f32;
let adjusted = channel_tensor
.sub_scalar(mean_val)
.mul_scalar(scalefactor as f32);
chs.push(adjusted);
}
let result_tensor = Tensor::cat(chs, 0).unsqueeze_dim::<4>(0); Ok(result_tensor)
}
#[must_use]
pub fn nms_boxes(
bboxes: &[Rect<usize>],
scores: &[f32],
score_threshold: f32,
nms_threshold: f32,
) -> Vec<usize> {
assert_eq!(bboxes.len(), scores.len());
let mut indices: Vec<usize> = (0..scores.len())
.filter(|&i| scores[i] >= score_threshold)
.collect();
indices.sort_by(|&a, &b| scores[b].partial_cmp(&scores[a]).unwrap());
let mut keep = Vec::new();
let intersection_area = |r1: &Rect<usize>, r2: &Rect<usize>| -> f64 {
let x1 = r1.x.max(r2.x);
let y1 = r1.y.max(r2.y);
let x2 = (r1.x + r1.width).min(r2.x + r2.width);
let y2 = (r1.y + r1.height).min(r2.y + r2.height);
if x2 > x1 && y2 > y1 {
((x2 - x1) * (y2 - y1)) as f64
} else {
0.0
}
};
let iou = |r1: &Rect<usize>, r2: &Rect<usize>| -> f64 {
let inter = intersection_area(r1, r2);
let area1 = (r1.width * r1.height) as f64;
let area2 = (r2.width * r2.height) as f64;
let union = area1 + area2 - inter;
if union > 0.0 { inter / union } else { 0.0 }
};
while !indices.is_empty() {
let idx = indices[0];
keep.push(idx);
let current_box = &bboxes[idx];
let mut next_indices = Vec::new();
for &other_idx in indices.iter().skip(1) {
if iou(current_box, &bboxes[other_idx]) <= f64::from(nms_threshold) {
next_indices.push(other_idx);
}
}
indices = next_indices;
}
keep
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_helpers::{TestBackend, test_device};
#[test]
fn test_nms_boxes() {
let bboxes = vec![
Rect::new(0, 0, 10, 10),
Rect::new(2, 2, 10, 10),
Rect::new(20, 20, 10, 10),
];
let scores = vec![0.9, 0.8, 0.7];
let kept = nms_boxes(&bboxes, &scores, 0.5, 0.3);
assert_eq!(kept.len(), 2);
assert_eq!(kept[0], 0);
assert_eq!(kept[1], 2);
}
#[test]
fn test_dnn_helpers() {
let device = test_device();
let flat_data = vec![0.5f32; 3 * 8 * 8];
let tensor =
Tensor::<TestBackend, 3>::from_data(TensorData::new(flat_data, [3, 8, 8]), &device);
let img = Image::new(tensor);
let blob = blob_from_image(&img, 1.0, Size::new(8, 8), Scalar::all(0.0), true).unwrap();
assert_eq!(blob.dims(), [1, 3, 8, 8]);
let net = read_net_from_onnx("mock_model.onnx", &device).unwrap();
assert_eq!(net.model_path, "mock_model.onnx");
let preprocessed = net.preprocess(&img).unwrap();
assert_eq!(preprocessed.dims(), [1, 3, 8, 8]);
let pred: Tensor<TestBackend, 2> = net.predict_raw(preprocessed).unwrap();
assert_eq!(pred.dims(), [1, 10]);
}
}