1use crate::error::{IrisError, Result};
2use crate::image::Image;
3use burn::tensor::{Tensor, TensorData, backend::Backend};
4
5pub struct HogDescriptor {
10 cell_size: usize,
11 block_size: usize,
12 nbins: usize,
13}
14
15impl HogDescriptor {
16 pub fn new(cell_size: usize, block_size: usize, nbins: usize) -> Self {
22 Self {
23 cell_size,
24 block_size,
25 nbins,
26 }
27 }
28
29 pub fn compute<B: Backend>(&self, image: &Image<B>) -> Result<Tensor<B, 1>> {
35 let gray = image.grayscale()?;
36 let dims = gray.tensor.dims();
37 let h = dims[1];
38 let w = dims[2];
39
40 if h < self.cell_size || w < self.cell_size {
41 return Err(IrisError::InvalidParameter(format!(
42 "Image {}x{} too small for cell_size {}",
43 w, h, self.cell_size
44 )));
45 }
46
47 let device = gray.tensor.device();
48 let tensor_data = gray.tensor.clone().into_data();
49 let flat: Vec<f32> = tensor_data.iter::<f32>().collect();
50
51 let mut magnitude = vec![0.0f32; h * w];
53 let mut direction = vec![0.0f32; h * w];
54
55 for y in 0..h {
56 for x in 0..w {
57 let gx = if x == 0 {
59 flat[y * w + 1] - flat[y * w]
60 } else if x == w - 1 {
61 flat[y * w + w - 1] - flat[y * w + w - 2]
62 } else {
63 flat[y * w + x + 1] - flat[y * w + x - 1]
64 };
65
66 let gy = if y == 0 {
68 flat[w + x] - flat[x]
69 } else if y == h - 1 {
70 flat[(h - 1) * w + x] - flat[(h - 2) * w + x]
71 } else {
72 flat[(y + 1) * w + x] - flat[(y - 1) * w + x]
73 };
74
75 magnitude[y * w + x] = (gx * gx + gy * gy).sqrt();
76 direction[y * w + x] = gy.atan2(gx); }
78 }
79
80 let n_cells_y = h / self.cell_size;
82 let n_cells_x = w / self.cell_size;
83 let bin_width = std::f32::consts::PI / self.nbins as f32;
84
85 let mut cell_hists = vec![vec![vec![0.0f32; self.nbins]; n_cells_x]; n_cells_y];
87
88 for cy in 0..n_cells_y {
89 for cx in 0..n_cells_x {
90 let y_start = cy * self.cell_size;
91 let x_start = cx * self.cell_size;
92
93 for dy in 0..self.cell_size {
94 for dx in 0..self.cell_size {
95 let y = y_start + dy;
96 let x = x_start + dx;
97 let mag = magnitude[y * w + x];
98 let ang = direction[y * w + x]; let angle = if ang < 0.0 {
102 ang + std::f32::consts::PI
103 } else {
104 ang
105 };
106
107 let bin_f = angle / bin_width;
108 let bin0 = (bin_f as usize) % self.nbins;
109 let bin1 = (bin0 + 1) % self.nbins;
110 let frac = bin_f - bin0 as f32;
111
112 cell_hists[cy][cx][bin0] += mag * (1.0 - frac);
113 cell_hists[cy][cx][bin1] += mag * frac;
114 }
115 }
116 }
117 }
118
119 let n_blocks_y = n_cells_y.saturating_sub(self.block_size - 1);
121 let n_blocks_x = n_cells_x.saturating_sub(self.block_size - 1);
122 let block_desc_len = self.block_size * self.block_size * self.nbins;
123 let total_len = n_blocks_y * n_blocks_x * block_desc_len;
124
125 let mut descriptor = vec![0.0f32; total_len];
126
127 for by in 0..n_blocks_y {
128 for bx in 0..n_blocks_x {
129 let mut block_vec: Vec<f32> = Vec::with_capacity(block_desc_len);
130 for dy in 0..self.block_size {
131 for dx in 0..self.block_size {
132 let hist = &cell_hists[by + dy][bx + dx];
133 block_vec.extend_from_slice(hist);
134 }
135 }
136
137 let eps = 1e-6f32;
139 let norm: f32 = block_vec.iter().map(|v| v * v).sum::<f32>().sqrt() + eps;
140 for v in &mut block_vec {
141 *v /= norm;
142 }
143
144 let block_idx = (by * n_blocks_x + bx) * block_desc_len;
145 descriptor[block_idx..block_idx + block_desc_len].copy_from_slice(&block_vec);
146 }
147 }
148
149 let data = TensorData::new(descriptor, [total_len]);
150 let tensor = Tensor::<B, 1>::from_data(data, &device);
151 Ok(tensor)
152 }
153}
154
155#[cfg(test)]
156mod tests {
157 use super::*;
158 use crate::test_helpers::{TestBackend, test_device};
159
160 #[test]
161 fn test_hog_descriptor_computation() {
162 let device = test_device();
163 let flat_data = vec![0.5f32; 32 * 32];
165 let tensor =
166 Tensor::<TestBackend, 3>::from_data(TensorData::new(flat_data, [1, 32, 32]), &device);
167 let img = Image::new(tensor);
168
169 let hog = HogDescriptor::new(8, 2, 9);
170 let descriptor = hog.compute(&img).unwrap();
171
172 assert_eq!(descriptor.dims(), [324]);
176 }
177
178 #[test]
179 fn test_hog_too_small_image() {
180 let device = test_device();
181 let flat_data = vec![0.5f32; 4 * 4];
182 let tensor =
183 Tensor::<TestBackend, 3>::from_data(TensorData::new(flat_data, [1, 4, 4]), &device);
184 let img = Image::new(tensor);
185
186 let hog = HogDescriptor::new(8, 2, 9);
187 assert!(hog.compute(&img).is_err());
188 }
189}