Skip to content

Commit b499268

Browse files
FreezyLemonshssoichiro
authored andcommitted
implement basic accuracy testing binary
1 parent 153dcb8 commit b499268

File tree

2 files changed

+117
-1
lines changed

2 files changed

+117
-1
lines changed

Cargo.toml

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ exclude = ["test_data", "yuvxyb-dump"]
1313
[features]
1414
default = ["fastmath"]
1515
fastmath = []
16+
build-acctest = ["image", "plotters"]
1617

1718
[dependencies]
1819
av-data = "0.4.1"
@@ -23,12 +24,20 @@ paste = "1.0.9"
2324
thiserror = "1.0.40"
2425
v_frame = "0.3.0"
2526

27+
# acctest dependencies
28+
image = { version = "0.24.5", optional = true }
29+
plotters = { version = "0.3.1", optional = true }
30+
2631
[dev-dependencies]
2732
criterion = "0.4.0"
28-
image = "0.24.4"
33+
image = "0.24.5"
2934
interpolate_name = "0.2.3"
3035
rand = "0.8.5"
3136

3237
[[bench]]
3338
name = "benches"
3439
harness = false
40+
41+
[[bin]]
42+
name = "acctest"
43+
required-features = ["build-acctest"]

src/bin/acctest.rs

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
use std::path::PathBuf;
2+
use std::ops::{Range, Neg};
3+
4+
use anyhow::Result;
5+
use yuvxyb::{LinearRgb, TransferCharacteristic, ColorPrimaries, Rgb};
6+
7+
fn main() {
8+
compare("tank_srgb.png", "tank_rgb.png", "tank_comparison.png");
9+
compare("chimera_srgb.png", "chimera_rgb.png", "chimera_comparison.png");
10+
compare("chimera_srgb.png", "tank_rgb.png", "useless_comparison.png");
11+
}
12+
13+
fn compare(source_path: &str, expected_path: &str, graph_path: &str) {
14+
let source = struct_from_file(source_path, |d, w, h| Rgb::new(d, w, h, TransferCharacteristic::SRGB, ColorPrimaries::BT709)).unwrap();
15+
let expected = struct_from_file(expected_path, LinearRgb::new).unwrap();
16+
17+
let actual = LinearRgb::try_from(source).unwrap();
18+
19+
let deltas = compare_data(expected.data(), actual.data());
20+
draw_graph(deltas, ["R".to_string(), "G".to_string(), "B".to_string()], graph_path);
21+
}
22+
23+
fn struct_from_file<T>(path: &str, create_fn: fn(Vec<[f32; 3]>, usize, usize) -> Result<T>) -> Result<T> {
24+
let base_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("test_data");
25+
let img = image::open(base_path.join(path)).unwrap();
26+
27+
let w = img.width() as usize;
28+
let h = img.height() as usize;
29+
let data: Vec<[f32; 3]> = img
30+
.into_rgb32f()
31+
.chunks_exact(3)
32+
.map(|c| [c[0], c[1], c[2]])
33+
.collect();
34+
35+
create_fn(data, w, h)
36+
}
37+
38+
// return the delta for each channel/plane
39+
fn compare_data(expected: &[[f32; 3]], actual: &[[f32; 3]]) -> [Vec<f32>; 3] {
40+
let mut r = vec![0f32; expected.len()];
41+
let mut g = vec![0f32; expected.len()];
42+
let mut b = vec![0f32; expected.len()];
43+
44+
for (i, (pix_e, pix_a)) in expected.iter().zip(actual).enumerate() {
45+
r[i] = pix_a[0] - pix_e[0];
46+
g[i] = pix_a[1] - pix_e[1];
47+
b[i] = pix_a[2] - pix_e[2];
48+
}
49+
50+
[r, g, b]
51+
}
52+
53+
fn draw_graph(deltas: [Vec<f32>; 3], plane_labels: [String; 3], path: &str) {
54+
use plotters::prelude::*;
55+
use plotters::data::fitting_range;
56+
57+
let root = BitMapBackend::new(path, (640, 360)).into_drawing_area();
58+
root.fill(&WHITE).unwrap();
59+
let root = root.margin(5, 5, 5, 20); // leave some space on the right
60+
61+
let dataset: Vec<(String, Quartiles)> = deltas.iter().zip(&plane_labels).map(|(v, l)| (l.clone(), Quartiles::new(&v))).collect();
62+
let range = fitting_range(deltas.iter().flatten());
63+
let range = refit_range(range);
64+
65+
let mut cc = ChartBuilder::on(&root)
66+
.x_label_area_size(50)
67+
.y_label_area_size(25)
68+
.caption("Conversion errors per color plane", ("sans-serif", 20))
69+
.build_cartesian_2d(
70+
// -5e-3f32..5e-3f32,
71+
range,
72+
plane_labels.into_segmented(),
73+
)
74+
.unwrap();
75+
76+
cc.configure_mesh()
77+
.x_desc("Error")
78+
.x_label_style(("sans-serif", 20))
79+
.y_labels(plane_labels.len())
80+
.y_label_style(("sans-serif", 20))
81+
.y_label_formatter(&|v| match v {
82+
SegmentValue::Exact(l) => l.to_string(),
83+
SegmentValue::CenterOf(l) => l.to_string(),
84+
SegmentValue::Last => String::new(),
85+
})
86+
.light_line_style(&WHITE)
87+
.draw()
88+
.unwrap();
89+
90+
cc.draw_series(dataset.iter().map(|(l, q)| {
91+
Boxplot::new_horizontal(SegmentValue::CenterOf(l), q)
92+
.width(25)
93+
.whisker_width(0.5)
94+
}))
95+
.unwrap();
96+
97+
root.present().unwrap();
98+
}
99+
100+
// make "symmetric"/centered around zero
101+
fn refit_range<T: Neg<Output = T> + PartialOrd + Copy>(r: Range<T>) -> Range<T> {
102+
if r.end > -r.start {
103+
-r.end..r.end
104+
} else {
105+
r.start..-r.start
106+
}
107+
}

0 commit comments

Comments
 (0)