use std::path::PathBuf;
use clap::Parser;
use rapid_structure_rs::evaluation::charbox::run::{
CharBoxEvalConfig, CharBoxEvent, DEFAULT_DATASET_RELATIVE, run_charbox_evaluation,
};
use rapid_structure_rs::{RapidOcrError, WordOutputMode};
fn parse_mode(raw: &str) -> Result<WordOutputMode, String> {
match raw.trim() {
"off" => Ok(WordOutputMode::Off),
"words" => Ok(WordOutputMode::Words),
"chars" => Ok(WordOutputMode::Chars),
other => Err(format!(
"unknown words mode `{other}`; expected off, words or chars"
)),
}
}
fn require_chars(modes: &[WordOutputMode]) -> Result<(), String> {
if modes.contains(&WordOutputMode::Chars) {
Ok(())
} else {
Err(
"`--modes` must include `chars`: this benchmark scores character geometry, \
and without per-char boxes there is nothing to measure"
.to_string(),
)
}
}
#[derive(Debug, Parser)]
#[command(
name = "charbox_eval",
about = "Measure per-character box geometry (IoU, boundary/center error, selection P/R) and the cost of words=words|chars"
)]
struct Cli {
#[arg(long, default_value = DEFAULT_DATASET_RELATIVE)]
dataset: PathBuf,
#[arg(long, default_value = "models/configs/test-config.yaml")]
config: PathBuf,
#[arg(long, default_value = "Temp/charbox-baseline.json")]
report: PathBuf,
#[arg(long, default_value_t = 3)]
repetitions: usize,
#[arg(long, default_value_t = rapid_structure_rs::evaluation::charbox::run::DEFAULT_LINE_IOU)]
line_iou: f32,
#[arg(long, default_value_t = 0)]
limit: usize,
#[arg(
long,
default_value = "off,words,chars",
value_delimiter = ',',
value_parser = parse_mode
)]
modes: Vec<WordOutputMode>,
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let cli = Cli::parse();
require_chars(&cli.modes).map_err(RapidOcrError::InvalidInput)?;
let config = CharBoxEvalConfig {
dataset: cli.dataset,
engine_config: cli.config,
repetitions: cli.repetitions,
line_iou: cli.line_iou,
limit: cli.limit,
modes: cli.modes,
};
let outcome = run_charbox_evaluation(&config, &mut |event| match event {
CharBoxEvent::Message(text) => eprintln!("{text}"),
CharBoxEvent::Progress { done, total } => eprintln!(" {done}/{total}"),
})?;
if let Some(parent) = cli.report.parent()
&& !parent.as_os_str().is_empty()
{
std::fs::create_dir_all(parent)?;
}
std::fs::write(&cli.report, serde_json::to_vec_pretty(&outcome.report)?)?;
eprintln!(
"wrote {} ({} page(s), {} line(s) matched, {} char(s) scored, {} gate failure(s))",
cli.report.display(),
outcome.pages,
outcome.lines_matched,
outcome.chars_scored,
outcome.gate_failures
);
Ok(())
}