1pub mod butteraugli;
30pub mod dssim;
31pub mod ssimulacra2;
32
33use serde::{Deserialize, Serialize};
34
35#[derive(Debug, Clone, Default, Serialize, Deserialize)]
37pub struct MetricConfig {
38 pub dssim: bool,
40 pub ssimulacra2: bool,
42 pub butteraugli: bool,
44 pub psnr: bool,
46}
47
48impl MetricConfig {
49 #[must_use]
51 pub fn all() -> Self {
52 Self {
53 dssim: true,
54 ssimulacra2: true,
55 butteraugli: true,
56 psnr: true,
57 }
58 }
59
60 #[must_use]
62 pub fn fast() -> Self {
63 Self {
64 dssim: false,
65 ssimulacra2: false,
66 butteraugli: false,
67 psnr: true,
68 }
69 }
70
71 #[must_use]
73 pub fn perceptual() -> Self {
74 Self {
75 dssim: true,
76 ssimulacra2: true,
77 butteraugli: true,
78 psnr: false,
79 }
80 }
81
82 #[must_use]
84 pub fn ssimulacra2_only() -> Self {
85 Self {
86 dssim: false,
87 ssimulacra2: true,
88 butteraugli: false,
89 psnr: false,
90 }
91 }
92}
93
94#[derive(Debug, Clone, Default, Serialize, Deserialize)]
96pub struct MetricResult {
97 pub dssim: Option<f64>,
99 pub ssimulacra2: Option<f64>,
101 pub butteraugli: Option<f64>,
103 pub psnr: Option<f64>,
105}
106
107impl MetricResult {
108 #[must_use]
110 pub fn perception_level(&self) -> Option<PerceptionLevel> {
111 self.dssim.map(PerceptionLevel::from_dssim)
112 }
113
114 #[must_use]
116 pub fn perception_level_ssimulacra2(&self) -> Option<PerceptionLevel> {
117 self.ssimulacra2.map(PerceptionLevel::from_ssimulacra2)
118 }
119
120 #[must_use]
122 pub fn perception_level_butteraugli(&self) -> Option<PerceptionLevel> {
123 self.butteraugli.map(PerceptionLevel::from_butteraugli)
124 }
125}
126
127#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
129pub enum PerceptionLevel {
130 Imperceptible,
132 Marginal,
134 Subtle,
136 Noticeable,
138 Degraded,
140}
141
142impl PerceptionLevel {
143 #[must_use]
145 pub fn from_dssim(dssim: f64) -> Self {
146 if dssim < 0.0003 {
147 Self::Imperceptible
148 } else if dssim < 0.0007 {
149 Self::Marginal
150 } else if dssim < 0.0015 {
151 Self::Subtle
152 } else if dssim < 0.003 {
153 Self::Noticeable
154 } else {
155 Self::Degraded
156 }
157 }
158
159 #[must_use]
162 pub fn from_ssimulacra2(score: f64) -> Self {
163 if score > 90.0 {
164 Self::Imperceptible
165 } else if score > 80.0 {
166 Self::Marginal
167 } else if score > 70.0 {
168 Self::Subtle
169 } else if score > 50.0 {
170 Self::Noticeable
171 } else {
172 Self::Degraded
173 }
174 }
175
176 #[must_use]
179 pub fn from_butteraugli(score: f64) -> Self {
180 if score < 1.0 {
181 Self::Imperceptible
182 } else if score < 2.0 {
183 Self::Marginal
184 } else if score < 3.0 {
185 Self::Subtle
186 } else if score < 5.0 {
187 Self::Noticeable
188 } else {
189 Self::Degraded
190 }
191 }
192
193 #[must_use]
195 pub fn max_dssim(self) -> f64 {
196 match self {
197 Self::Imperceptible => 0.0003,
198 Self::Marginal => 0.0007,
199 Self::Subtle => 0.0015,
200 Self::Noticeable => 0.003,
201 Self::Degraded => f64::INFINITY,
202 }
203 }
204
205 #[must_use]
207 pub fn min_ssimulacra2(self) -> f64 {
208 match self {
209 Self::Imperceptible => 90.0,
210 Self::Marginal => 80.0,
211 Self::Subtle => 70.0,
212 Self::Noticeable => 50.0,
213 Self::Degraded => f64::NEG_INFINITY,
214 }
215 }
216
217 #[must_use]
219 pub fn max_butteraugli(self) -> f64 {
220 match self {
221 Self::Imperceptible => 1.0,
222 Self::Marginal => 2.0,
223 Self::Subtle => 3.0,
224 Self::Noticeable => 5.0,
225 Self::Degraded => f64::INFINITY,
226 }
227 }
228
229 #[must_use]
231 pub fn code(self) -> &'static str {
232 match self {
233 Self::Imperceptible => "IMP",
234 Self::Marginal => "MAR",
235 Self::Subtle => "SUB",
236 Self::Noticeable => "NOT",
237 Self::Degraded => "DEG",
238 }
239 }
240}
241
242impl std::fmt::Display for PerceptionLevel {
243 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
244 match self {
245 Self::Imperceptible => write!(f, "Imperceptible"),
246 Self::Marginal => write!(f, "Marginal"),
247 Self::Subtle => write!(f, "Subtle"),
248 Self::Noticeable => write!(f, "Noticeable"),
249 Self::Degraded => write!(f, "Degraded"),
250 }
251 }
252}
253
254#[must_use]
268pub fn calculate_psnr(reference: &[u8], test: &[u8], width: usize, height: usize) -> f64 {
269 assert_eq!(reference.len(), test.len());
270 assert_eq!(reference.len(), width * height * 3);
271
272 let mut mse_sum: f64 = 0.0;
273 let pixel_count = (width * height * 3) as f64;
274
275 for (r, t) in reference.iter().zip(test.iter()) {
276 let diff = f64::from(*r) - f64::from(*t);
277 mse_sum += diff * diff;
278 }
279
280 let mse = mse_sum / pixel_count;
281
282 if mse == 0.0 {
283 f64::INFINITY
284 } else {
285 10.0 * (255.0_f64 * 255.0 / mse).log10()
286 }
287}
288
289#[cfg(test)]
290mod tests {
291 use super::*;
292
293 #[test]
294 fn test_perception_level_thresholds() {
295 assert_eq!(
296 PerceptionLevel::from_dssim(0.0001),
297 PerceptionLevel::Imperceptible
298 );
299 assert_eq!(
300 PerceptionLevel::from_dssim(0.0003),
301 PerceptionLevel::Marginal
302 );
303 assert_eq!(
304 PerceptionLevel::from_dssim(0.0005),
305 PerceptionLevel::Marginal
306 );
307 assert_eq!(PerceptionLevel::from_dssim(0.0007), PerceptionLevel::Subtle);
308 assert_eq!(PerceptionLevel::from_dssim(0.001), PerceptionLevel::Subtle);
309 assert_eq!(
310 PerceptionLevel::from_dssim(0.0015),
311 PerceptionLevel::Noticeable
312 );
313 assert_eq!(
314 PerceptionLevel::from_dssim(0.002),
315 PerceptionLevel::Noticeable
316 );
317 assert_eq!(
318 PerceptionLevel::from_dssim(0.003),
319 PerceptionLevel::Degraded
320 );
321 assert_eq!(PerceptionLevel::from_dssim(0.01), PerceptionLevel::Degraded);
322 }
323
324 #[test]
325 fn test_psnr_identical() {
326 let data = vec![128u8; 100 * 100 * 3];
327 let psnr = calculate_psnr(&data, &data, 100, 100);
328 assert!(psnr.is_infinite());
329 }
330
331 #[test]
332 fn test_psnr_different() {
333 let reference = vec![100u8; 100 * 100 * 3];
334 let test = vec![110u8; 100 * 100 * 3];
335 let psnr = calculate_psnr(&reference, &test, 100, 100);
336 assert!(psnr > 28.0);
338 assert!(psnr < 29.0);
339 }
340
341 #[test]
342 fn test_metric_config_all() {
343 let config = MetricConfig::all();
344 assert!(config.dssim);
345 assert!(config.psnr);
346 }
347
348 #[test]
349 fn test_metric_config_fast() {
350 let config = MetricConfig::fast();
351 assert!(!config.dssim);
352 assert!(config.psnr);
353 }
354}