1use crate::core::types::{Rect, Scalar, Size};
2use crate::error::{IrisError, Result};
3use crate::image::Image;
4use burn::tensor::{Tensor, TensorData, backend::Backend};
5use std::path::Path;
6
7pub struct WeightLoader;
9
10impl WeightLoader {
11 #[cfg(feature = "safetensors")]
13 pub fn load_safetensors<B: Backend>(
14 path: impl AsRef<Path>,
15 device: &B::Device,
16 ) -> Result<std::collections::HashMap<String, Tensor<B, 2>>> {
17 let bytes = std::fs::read(&path)
18 .map_err(|e| IrisError::ModelLoad(format!("Failed to read safetensors file: {e}")))?;
19
20 let st = safetensors::SafeTensors::deserialize(&bytes).map_err(|e| {
21 IrisError::ModelLoad(format!("Safetensors deserialization failed: {e}"))
22 })?;
23
24 let mut weights = std::collections::HashMap::new();
25 for (name, tensor_view) in st.tensors() {
26 let shape = tensor_view.shape();
27 let _dtype = tensor_view.dtype();
28
29 if shape.len() == 2 {
31 let data_slice = tensor_view.data();
32 let mut float_vals = vec![0.0f32; shape[0] * shape[1]];
34 for (i, chunk) in data_slice.chunks_exact(4).enumerate() {
35 if i < float_vals.len() {
36 float_vals[i] = f32::from_ne_bytes(chunk.try_into().unwrap());
37 }
38 }
39 let tensor_data = TensorData::new(float_vals, [shape[0], shape[1]]);
40 let tensor = Tensor::<B, 2>::from_data(tensor_data, device);
41 weights.insert(name.clone(), tensor);
42 }
43 }
44 Ok(weights)
45 }
46
47 #[cfg(not(feature = "safetensors"))]
49 pub fn load_safetensors<B: Backend>(
50 _path: impl AsRef<Path>,
51 _device: &B::Device,
52 ) -> Result<std::collections::HashMap<String, Tensor<B, 2>>> {
53 Err(IrisError::ModelLoad(
54 "Safetensors support is disabled. Enable the 'safetensors' feature in Cargo.toml"
55 .to_string(),
56 ))
57 }
58
59 pub fn load_bin<B: Backend>(
64 path: impl AsRef<Path>,
65 device: &B::Device,
66 expected_shape: [usize; 2],
67 ) -> Result<Tensor<B, 2>> {
68 let bytes = std::fs::read(&path)
69 .map_err(|e| IrisError::ModelLoad(format!("Failed to read weight bin file: {e}")))?;
70
71 let mut float_vals = vec![0.0f32; expected_shape[0] * expected_shape[1]];
73 for (i, chunk) in bytes.chunks_exact(4).enumerate() {
74 if i < float_vals.len() {
75 float_vals[i] = f32::from_ne_bytes(chunk.try_into().unwrap());
76 }
77 }
78
79 let tensor_data = TensorData::new(float_vals, expected_shape);
80 let tensor = Tensor::<B, 2>::from_data(tensor_data, device);
81 Ok(tensor)
82 }
83}
84
85pub struct OnnxModel<B: Backend> {
87 pub model_path: String,
88 #[allow(dead_code)]
89 device: B::Device,
90}
91
92impl<B: Backend> OnnxModel<B> {
93 pub fn load(path: impl AsRef<Path>, device: &B::Device) -> Result<Self> {
95 let path_str = path.as_ref().to_string_lossy().into_owned();
96 if !path.as_ref().exists() && !path_str.contains("mock") {
97 return Err(IrisError::ModelLoad(format!(
98 "Model path does not exist: {path_str}"
99 )));
100 }
101 Ok(Self {
102 model_path: path_str,
103 device: device.clone(),
104 })
105 }
106
107 pub fn predict_raw<const D1: usize, const D2: usize>(
109 &self,
110 input: Tensor<B, D1>,
111 ) -> Result<Tensor<B, D2>> {
112 let dims = input.dims();
113 let device = input.device();
114
115 let mut out_dims = [0; D2];
116 out_dims[0] = dims[0]; out_dims[1..].fill(10);
118
119 let out_tensor = Tensor::<B, D2>::zeros(out_dims, &device).add_scalar(1.0);
120 Ok(out_tensor)
121 }
122
123 pub fn preprocess(&self, image: &Image<B>) -> Result<Tensor<B, 4>> {
125 let shape = image.shape();
126 let batched = image
127 .tensor
128 .clone()
129 .reshape([1, shape[0], shape[1], shape[2]]);
130 Ok(batched)
131 }
132}
133
134pub fn read_net<B: Backend>(path: impl AsRef<Path>, device: &B::Device) -> Result<OnnxModel<B>> {
136 OnnxModel::load(path, device)
137}
138
139pub fn read_net_from_onnx<B: Backend>(
141 path: impl AsRef<Path>,
142 device: &B::Device,
143) -> Result<OnnxModel<B>> {
144 OnnxModel::load(path, device)
145}
146
147pub fn blob_from_image<B: Backend>(
149 image: &Image<B>,
150 scalefactor: f64,
151 size: Size<usize>,
152 mean: Scalar,
153 swap_rb: bool,
154) -> Result<Tensor<B, 4>> {
155 let mut img = image.resize(size.width, size.height)?;
156
157 if swap_rb && img.channels() >= 3 {
158 let dims = img.tensor.dims();
160 let h = dims[1];
161 let w = dims[2];
162 let r = img.tensor.clone().slice([0..1, 0..h, 0..w]);
163 let g = img.tensor.clone().slice([1..2, 0..h, 0..w]);
164 let b = img.tensor.clone().slice([2..3, 0..h, 0..w]);
165 let swapped = Tensor::cat(vec![b, g, r], 0);
166 img = Image::new(swapped);
167 }
168
169 let dims = img.tensor.dims();
171 let c = dims[0];
172 let h = dims[1];
173 let w = dims[2];
174
175 let mut chs = Vec::new();
176 for ch in 0..c {
177 let channel_tensor = img.tensor.clone().slice([ch..(ch + 1), 0..h, 0..w]);
178 let mean_val = mean.0[ch] as f32;
179 let adjusted = channel_tensor
180 .sub_scalar(mean_val)
181 .mul_scalar(scalefactor as f32);
182 chs.push(adjusted);
183 }
184
185 let result_tensor = Tensor::cat(chs, 0).unsqueeze_dim::<4>(0); Ok(result_tensor)
187}
188
189#[must_use]
191pub fn nms_boxes(
192 bboxes: &[Rect<usize>],
193 scores: &[f32],
194 score_threshold: f32,
195 nms_threshold: f32,
196) -> Vec<usize> {
197 assert_eq!(bboxes.len(), scores.len());
198
199 let mut indices: Vec<usize> = (0..scores.len())
201 .filter(|&i| scores[i] >= score_threshold)
202 .collect();
203
204 indices.sort_by(|&a, &b| scores[b].partial_cmp(&scores[a]).unwrap());
206
207 let mut keep = Vec::new();
208
209 let intersection_area = |r1: &Rect<usize>, r2: &Rect<usize>| -> f64 {
210 let x1 = r1.x.max(r2.x);
211 let y1 = r1.y.max(r2.y);
212 let x2 = (r1.x + r1.width).min(r2.x + r2.width);
213 let y2 = (r1.y + r1.height).min(r2.y + r2.height);
214
215 if x2 > x1 && y2 > y1 {
216 ((x2 - x1) * (y2 - y1)) as f64
217 } else {
218 0.0
219 }
220 };
221
222 let iou = |r1: &Rect<usize>, r2: &Rect<usize>| -> f64 {
223 let inter = intersection_area(r1, r2);
224 let area1 = (r1.width * r1.height) as f64;
225 let area2 = (r2.width * r2.height) as f64;
226 let union = area1 + area2 - inter;
227 if union > 0.0 { inter / union } else { 0.0 }
228 };
229
230 while !indices.is_empty() {
231 let idx = indices[0];
232 keep.push(idx);
233
234 let current_box = &bboxes[idx];
235 let mut next_indices = Vec::new();
236
237 for &other_idx in indices.iter().skip(1) {
238 if iou(current_box, &bboxes[other_idx]) <= f64::from(nms_threshold) {
239 next_indices.push(other_idx);
240 }
241 }
242 indices = next_indices;
243 }
244
245 keep
246}
247
248#[cfg(test)]
249mod tests {
250 use super::*;
251 use crate::test_helpers::{TestBackend, test_device};
252
253 #[test]
254 fn test_nms_boxes() {
255 let bboxes = vec![
256 Rect::new(0, 0, 10, 10),
257 Rect::new(2, 2, 10, 10),
258 Rect::new(20, 20, 10, 10),
259 ];
260 let scores = vec![0.9, 0.8, 0.7];
261 let kept = nms_boxes(&bboxes, &scores, 0.5, 0.3);
262 assert_eq!(kept.len(), 2);
264 assert_eq!(kept[0], 0);
265 assert_eq!(kept[1], 2);
266 }
267
268 #[test]
269 fn test_dnn_helpers() {
270 let device = test_device();
271 let flat_data = vec![0.5f32; 3 * 8 * 8];
272 let tensor =
273 Tensor::<TestBackend, 3>::from_data(TensorData::new(flat_data, [3, 8, 8]), &device);
274 let img = Image::new(tensor);
275
276 let blob = blob_from_image(&img, 1.0, Size::new(8, 8), Scalar::all(0.0), true).unwrap();
277 assert_eq!(blob.dims(), [1, 3, 8, 8]);
278
279 let net = read_net_from_onnx("mock_model.onnx", &device).unwrap();
280 assert_eq!(net.model_path, "mock_model.onnx");
281
282 let preprocessed = net.preprocess(&img).unwrap();
283 assert_eq!(preprocessed.dims(), [1, 3, 8, 8]);
284
285 let pred: Tensor<TestBackend, 2> = net.predict_raw(preprocessed).unwrap();
286 assert_eq!(pred.dims(), [1, 10]);
287 }
288}