1#![doc = include_str!("../README.md")]
2#![no_std]
3
4pub mod regression;
5
6use regression::RegressionModel;
7
8pub const STANDARD_MODEL: RegressionModel = RegressionModel {
11 weights: [
12 -0.08685173,
13 -0.041326188,
14 -0.09524358,
15 -0.16007994,
16 -0.8713319,
17 0.08773025,
18 -0.00017104072,
19 -0.00025994,
20 -1.0389963,
21 -0.08185195,
22 -0.050398663,
23 0.30969876,
24 0.11085072,
25 -0.008571971,
26 ],
27 bias: 6.0718517,
28 lr: 0.01,
29 lambda: 0.01,
30};
31
32#[derive(Debug)]
35pub struct Analysis {
36 pub rg_cov: f32,
38 pub gb_cov: f32,
40 pub br_cov: f32,
42 pub entropy: f32,
44 pub low_energy: f32,
47 pub high_energy: f32,
49 pub spectral_energy_ratio: f32,
51 pub spectral_mean: f32,
53 pub spectral_mean_square: f32,
54 pub spectral_variance: f32,
56 pub spectral_skewness: f32,
58 pub spectral_kurtosis: f32,
60 pub peak_amplitude: f32,
62 pub peak_frequency: u8,
64 pub peak_to_avg_ratio: f32,
66}
67
68impl Analysis {
69 pub fn std_dev(&self) -> f32 {
70 self.spectral_variance.sqrt()
71 }
72
73 pub fn raw_is_vector(&self, model: &RegressionModel) -> f32 {
75 if self.peak_frequency > 24 || self.spectral_kurtosis < 0.0 || self.spectral_skewness < 0.0
77 {
78 return 1.0;
79 }
80
81 model.predict(self)
82 }
83
84 pub fn raw_is_photo(&self, model: &RegressionModel) -> f32 {
86 1.0 - self.raw_is_vector(model).min(1.0)
87 }
88
89 pub fn is_photo(&self, model: &RegressionModel) -> bool {
91 !self.is_vector(model)
92 }
93
94 pub fn is_vector(&self, model: &RegressionModel) -> bool {
96 self.raw_is_vector(model) > 0.5
97 }
98}
99
100use image::{GenericImageView, Pixel};
101
102#[rustfmt::skip]
154#[inline]
155fn luma([r, g, b]: [f32; 3]) -> f32 {
156 0.212671 * r +
157 0.715160 * g +
158 0.072169 * b
159}
160
161pub fn analyze<I, P>(img: &I) -> Option<Analysis>
165where
166 I: GenericImageView<Pixel = P>,
167 P: Pixel<Subpixel = u8>,
168{
169 use easyfft::{const_size::FftMut, num_complex::Complex};
170
171 let (width, height) = img.dimensions();
172 let num_pixels = width as u64 * height as u64;
173
174 if num_pixels < 2 {
175 return None;
176 }
177
178 let inverse_n = 1.0 / num_pixels as f32;
179
180 let mut histogram = [Complex::<f32>::ZERO; 256];
187
188 let mut red = 0.0;
189 let mut green = 0.0;
190 let mut blue = 0.0;
191
192 let mut rg_cov = 0.0;
193 let mut gb_cov = 0.0;
194 let mut br_cov = 0.0;
195
196 let [r0, g0, b0] = img.get_pixel(0, 0).to_rgb().0.map(|c| c as f32 / 255.0);
197
198 for (_, _, pixel) in img.pixels() {
199 let [r, g, b] = pixel.to_rgb().0.map(|c| c as f32 / 255.0);
200
201 histogram[(luma([r, g, b]) * 255.0) as usize].re += 1.0;
202
203 let dr = r - r0;
204 let dg = g - g0;
205 let db = b - b0;
206
207 red += dr;
208 green += dg;
209 blue += db;
210
211 rg_cov += dr * dg;
212 gb_cov += dg * db;
213 br_cov += db * dr;
214 }
215
216 rg_cov = (rg_cov - red * green * inverse_n) * inverse_n;
217 gb_cov = (gb_cov - green * blue * inverse_n) * inverse_n;
218 br_cov = (br_cov - blue * green * inverse_n) * inverse_n;
219
220 let mut entropy = 0.0;
222 for h in &mut histogram {
223 h.re *= inverse_n; if h.re > 0.0 {
226 entropy -= h.re * h.re.ln();
227 }
228 }
229
230 histogram.fft_mut();
232
233 let mut n = 0.0;
234 let mut mean = 0.0;
235 let mut m2 = 0.0;
236 let mut m3 = 0.0;
237 let mut m4 = 0.0;
238
239 let mut peak = (0, 0.0);
240
241 let mut low_sum = 0.0;
242 let mut high_sum = 0.0;
243
244 for (idx, s) in histogram[1..128].iter().enumerate() {
246 let x = s.norm();
247
248 if x >= peak.1 {
250 peak = (idx, x);
251 }
252
253 if idx >= 32 {
254 high_sum += x;
255 } else {
256 low_sum += x;
257 }
258
259 let n1 = n;
260 n += 1.0;
261
262 let delta = x - mean;
263 let delta_n = delta / n;
264 let delta_n2 = delta_n * delta_n;
265 let term1 = delta * delta_n * n1;
266
267 mean += delta_n;
268 m4 += term1 * delta_n2 * (n * n - 3.0 * n + 3.0) + 6.0 * delta_n2 * m2 - 4.0 * delta_n * m3;
269 m3 += term1 * delta_n * (n - 2.0) - 3.0 * delta_n * m2;
270 m2 += term1;
271 }
272
273 let variance = m2 / (n - 1.0);
274 let skewness = n.sqrt() * m3 / (m2 * m2 * m2).sqrt();
275 let kurtosis = (n * m4) / (m2 * m2) - 3.0;
276
277 Some(Analysis {
278 rg_cov,
279 gb_cov,
280 br_cov,
281 spectral_mean: mean,
282 spectral_mean_square: m2,
283 spectral_variance: variance,
284 entropy,
285 low_energy: low_sum * inverse_n,
287 high_energy: high_sum * inverse_n,
288 spectral_energy_ratio: low_sum / high_sum,
289 spectral_skewness: skewness,
290 spectral_kurtosis: kurtosis,
291 peak_amplitude: peak.1,
292 peak_frequency: peak.0 as u8,
293 peak_to_avg_ratio: peak.1 / mean,
294 })
295}