pineapple_core/cv/
features.rs1use std::ops::Deref;
5
6use num::{FromPrimitive, ToPrimitive};
7
8use crate::constant::{GLCM_ARRAY_SIZE, GLCM_LEVELS};
9use crate::im::PineappleViewBuffer;
10
11#[derive(Debug, Clone)]
12pub struct GLCM {
13 data: [f32; GLCM_ARRAY_SIZE],
14 rows: usize,
15 cols: usize,
16}
17
18impl GLCM {
19 pub fn new<T>(
39 pixels: &[T],
40 width: usize,
41 height: usize,
42 channel: usize,
43 channels: usize,
44 angle: f32,
45 distance: f32,
46 ) -> GLCM
47 where
48 T: ToPrimitive,
49 {
50 let radians = angle.to_radians();
51
52 let mut min_val = f32::MAX;
53 let mut max_val = f32::MIN;
54
55 let pixel_vec: Vec<f32> = pixels
56 .iter()
57 .skip(channel)
58 .step_by(channels)
59 .map(|p| {
60 let value = p.to_f32().unwrap();
61 min_val = min_val.min(value);
62 max_val = max_val.max(value);
63 value
64 })
65 .collect();
66
67 let (sa, sb, sc) = if max_val != GLCM_LEVELS as f32 - 1.0 || min_val != 0.0 {
68 (min_val, max_val, GLCM_LEVELS as f32 - 1.0)
69 } else if min_val == max_val {
70 (0.0, 1.0, 0.0)
72 } else {
73 (0.0, 1.0, 1.0)
74 };
75
76 let (w, h) = (width as i32, height as i32);
77 let offset_x = (radians.cos() * distance).round() as i32;
78 let offset_y = (radians.sin() * distance).round() as i32;
79
80 let mut comatrix = [0.0; GLCM_ARRAY_SIZE];
81
82 let scale_pixel =
83 |pixel: f32| -> usize { ((pixel - sa) / (sb - sa) * sc).round() as usize };
84
85 let mut comatrix_sum = 0f32;
86
87 for y in 0..h {
88 for x in 0..w {
89 let idx = (y * w + x) as usize;
90 let root = pixel_vec[idx];
91
92 let i_offset = x + offset_x;
93 let j_offset = y + offset_y;
94
95 if i_offset >= w || i_offset < 0 || j_offset >= h || j_offset < 0 {
96 continue;
97 }
98
99 let neighbour_idx = (j_offset * w + i_offset) as usize;
100 let neighbour = pixel_vec[neighbour_idx];
101
102 let root_scaled = scale_pixel(root);
103 let neighbour_scaled = scale_pixel(neighbour);
104
105 comatrix[root_scaled * GLCM_LEVELS + neighbour_scaled] += 1.0;
106 comatrix[neighbour_scaled * GLCM_LEVELS + root_scaled] += 1.0;
107
108 comatrix_sum += 2.0;
109 }
110 }
111
112 comatrix.iter_mut().for_each(|v| *v /= comatrix_sum);
113
114 GLCM {
115 data: comatrix,
116 rows: GLCM_LEVELS,
117 cols: GLCM_LEVELS,
118 }
119 }
120
121 pub fn new_from_object<T, Container>(
137 object: &PineappleViewBuffer<T, Container>,
138 channel: usize,
139 angle: f32,
140 distance: f32,
141 ) -> GLCM
142 where
143 T: ToPrimitive + FromPrimitive,
144 Container: Deref<Target = [T]>,
145 {
146 let radians = angle.to_radians();
147
148 let mut min_val = f32::MAX;
149 let mut max_val = f32::MIN;
150
151 let pixel_vec: Vec<f32> = object
152 .iter()
153 .skip(channel)
154 .step_by(object.channels())
155 .map(|p| {
156 let value = p.to_f32().unwrap();
157 min_val = min_val.min(value);
158 max_val = max_val.max(value);
159 value
160 })
161 .collect();
162
163 let (sa, sb, sc) = if max_val != GLCM_LEVELS as f32 - 1.0 || min_val != 0.0 {
164 (min_val, max_val, GLCM_LEVELS as f32 - 1.0)
165 } else if min_val == max_val {
166 (0.0, 1.0, 0.0)
168 } else {
169 (0.0, 1.0, 1.0)
170 };
171
172 let (w, h) = (object.width() as i32, object.height() as i32);
173 let offset_x = (radians.cos() * distance).round() as i32;
174 let offset_y = (radians.sin() * distance).round() as i32;
175
176 let mut comatrix = [0.0; GLCM_ARRAY_SIZE];
177
178 let scale_pixel =
179 |pixel: f32| -> usize { ((pixel - sa) / (sb - sa) * sc).round() as usize };
180
181 let mut comatrix_sum = 0f32;
182
183 for y in 0..h {
184 for x in 0..w {
185 let idx = (y * w + x) as usize;
186 let root = pixel_vec[idx];
187
188 let i_offset = x + offset_x;
189 let j_offset = y + offset_y;
190
191 if i_offset >= w || i_offset < 0 || j_offset >= h || j_offset < 0 {
192 continue;
193 }
194
195 let neighbour_idx = (j_offset * w + i_offset) as usize;
196 let neighbour = pixel_vec[neighbour_idx];
197
198 let root_scaled = scale_pixel(root);
199 let neighbour_scaled = scale_pixel(neighbour);
200
201 comatrix[root_scaled * GLCM_LEVELS + neighbour_scaled] += 1.0;
202 comatrix[neighbour_scaled * GLCM_LEVELS + root_scaled] += 1.0;
203
204 comatrix_sum += 2.0;
205 }
206 }
207
208 comatrix.iter_mut().for_each(|v| *v /= comatrix_sum);
209
210 GLCM {
211 data: comatrix,
212 rows: GLCM_LEVELS,
213 cols: GLCM_LEVELS,
214 }
215 }
216
217 pub fn rows(&self) -> usize {
218 self.rows
219 }
220
221 pub fn cols(&self) -> usize {
222 self.cols
223 }
224
225 pub fn iter(&self) -> impl Iterator<Item = (usize, usize, f32)> + '_ {
226 self.data.iter().enumerate().map(|(index, &value)| {
227 let i = index / self.cols;
228 let j = index % self.cols;
229 (i, j, value)
230 })
231 }
232
233 pub fn margin_sums(&self) -> (Vec<f32>, Vec<f32>) {
234 let mut row_sums = vec![0.0; self.rows];
235 let mut col_sums = vec![0.0; self.cols];
236
237 for (i, j, value) in self.iter() {
238 row_sums[i] += value;
239 col_sums[j] += value;
240 }
241
242 (row_sums, col_sums)
243 }
244}
245
246pub fn glcm_multichannel<T>(
265 pixels: &[T],
266 width: usize,
267 height: usize,
268 channels: usize,
269 angle: f32,
270 distance: f32,
271) -> Vec<GLCM>
272where
273 T: ToPrimitive,
274{
275 (0..channels)
276 .map(|channel| GLCM::new(pixels, width, height, channel, channels, angle, distance))
277 .collect()
278}
279
280pub fn glcm_multichannel_object<T, Container>(
291 object: &PineappleViewBuffer<T, Container>,
292 angle: f32,
293 distance: f32,
294) -> Vec<GLCM>
295where
296 T: ToPrimitive + FromPrimitive,
297 Container: Deref<Target = [T]>,
298{
299 (0..object.channels())
300 .map(|channel| GLCM::new_from_object(object, channel, angle, distance))
301 .collect()
302}