use std::io;
use crate::error::CliError;
use crate::format::{EvalArtifact, FormatContext, FormatName, Formatter};
pub(crate) struct Text;
impl Formatter for Text {
fn name(&self) -> &'static str {
"text"
}
fn id(&self) -> FormatName {
FormatName::Text
}
fn render(
&self,
artifact: &EvalArtifact<'_>,
_ctx: &FormatContext<'_>,
out: &mut dyn io::Write,
) -> Result<(), CliError> {
match artifact {
EvalArtifact::Ap(summary) => {
for line in summary.pretty_lines() {
writeln!(out, "{line}")?;
}
Ok(())
}
EvalArtifact::Lrp(report) => render_lrp(report, out),
EvalArtifact::Partitioned { summary, label } => {
render_partitioned(summary, *label, out)
}
EvalArtifact::PartitionedLrp { summary, label } => {
render_partitioned_lrp(summary, *label, out)
}
}
}
}
fn render_lrp(
report: &vernier_core::lrp::LrpReport,
out: &mut dyn io::Write,
) -> Result<(), CliError> {
writeln!(out, "oLRP = {olrp:.4}", olrp = report.olrp,)?;
writeln!(out, "oLRP_Loc = {v:.4}", v = report.olrp_loc)?;
writeln!(out, "oLRP_FP = {v:.4}", v = report.olrp_fp)?;
writeln!(out, "oLRP_FN = {v:.4}", v = report.olrp_fn)?;
writeln!(out, "n_empty_classes = {n}", n = report.n_empty_classes,)?;
writeln!(
out,
"config: kernel={kernel} tp_threshold={tp:.3} tau_grid_len={g}",
kernel = report.config.kernel.as_str(),
tp = report.config.tp_threshold,
g = report.config.tau_grid_len,
)?;
if !report.per_class.is_empty() {
writeln!(
out,
"{:>10} {:>8} {:>8} {:>8} {:>8} {:>6}",
"cat_id", "oLRP", "Loc", "FP", "FN", "tau"
)?;
for entry in &report.per_class {
writeln!(
out,
"{:>10} {:>8} {:>8} {:>8} {:>8} {:>6}",
entry.category_id,
fmt_opt(entry.olrp),
fmt_opt(entry.olrp_loc),
fmt_opt(entry.olrp_fp),
fmt_opt(entry.olrp_fn),
fmt_opt(entry.tau),
)?;
}
}
Ok(())
}
fn fmt_opt(v: Option<f64>) -> String {
match v {
Some(x) => format!("{x:.4}"),
None => "NaN".to_string(),
}
}
fn render_partitioned(
summary: &vernier_core::partition::PartitionedSummary,
label: Option<&str>,
out: &mut dyn io::Write,
) -> Result<(), CliError> {
write_partitioned_preamble(
out,
label,
summary.overall_n_images,
summary.overall_n_detections,
)?;
for line in summary.overall.pretty_lines() {
writeln!(out, "{line}")?;
}
for sr in &summary.slices {
writeln!(out)?;
write_slice_header(
out,
&sr.slice.axis,
&sr.slice.value,
sr.n_images,
sr.n_detections,
)?;
for line in sr.summary.pretty_lines() {
writeln!(out, "{line}")?;
}
}
Ok(())
}
fn write_partitioned_preamble(
out: &mut dyn io::Write,
label: Option<&str>,
overall_n_images: u64,
overall_n_detections: u64,
) -> Result<(), CliError> {
if let Some(label) = label {
writeln!(out, "label = {label}")?;
}
writeln!(
out,
"overall (n_images={overall_n_images}, n_detections={overall_n_detections})"
)?;
Ok(())
}
fn write_slice_header(
out: &mut dyn io::Write,
axis: &str,
value: &str,
n_images: u64,
n_detections: u64,
) -> Result<(), CliError> {
writeln!(
out,
"==> {axis} = {value} (n_images={n_images}, n_detections={n_detections})"
)?;
Ok(())
}
fn render_partitioned_lrp(
summary: &vernier_core::partition::PartitionedLrpReport,
label: Option<&str>,
out: &mut dyn io::Write,
) -> Result<(), CliError> {
write_partitioned_preamble(
out,
label,
summary.overall_n_images,
summary.overall_n_detections,
)?;
writeln!(out, "oLRP = {olrp:.4}", olrp = summary.overall.olrp)?;
writeln!(out, "oLRP_Loc = {v:.4}", v = summary.overall.olrp_loc)?;
writeln!(out, "oLRP_FP = {v:.4}", v = summary.overall.olrp_fp)?;
writeln!(out, "oLRP_FN = {v:.4}", v = summary.overall.olrp_fn)?;
writeln!(
out,
"n_empty_classes = {n}",
n = summary.overall.n_empty_classes
)?;
writeln!(
out,
"config: kernel={kernel} tp_threshold={tp:.3} tau_grid_len={g}",
kernel = summary.overall.config.kernel.as_str(),
tp = summary.overall.config.tp_threshold,
g = summary.overall.config.tau_grid_len,
)?;
for sr in &summary.slices {
writeln!(out)?;
write_slice_header(
out,
&sr.slice.axis,
&sr.slice.value,
sr.n_images,
sr.n_detections,
)?;
writeln!(out, "oLRP = {v:.4}", v = sr.report.olrp)?;
writeln!(out, "oLRP_Loc = {v:.4}", v = sr.report.olrp_loc)?;
writeln!(out, "oLRP_FP = {v:.4}", v = sr.report.olrp_fp)?;
writeln!(out, "oLRP_FN = {v:.4}", v = sr.report.olrp_fn)?;
writeln!(out, "n_empty_classes = {n}", n = sr.report.n_empty_classes)?;
}
Ok(())
}