use image::RgbImage;
use serde::{Deserialize, Serialize};
use std::fmt;
use std::sync::Arc;
pub use oar_ocr_core::domain::TextRegion;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OAROCRResult {
pub input_path: Arc<str>,
pub index: usize,
#[serde(skip)]
pub input_img: Arc<RgbImage>,
pub text_regions: Vec<TextRegion>,
pub orientation_angle: Option<f32>,
#[serde(skip)]
pub rectified_img: Option<Arc<RgbImage>>,
}
impl OAROCRResult {
pub fn recognized_text_regions(&self) -> impl Iterator<Item = &TextRegion> {
self.text_regions.iter().filter(|region| region.has_text())
}
pub fn confident_text_regions(&self) -> impl Iterator<Item = &TextRegion> {
self.text_regions
.iter()
.filter(|region| region.has_confidence())
}
pub fn all_text(&self) -> Vec<&str> {
self.text_regions
.iter()
.filter_map(|region| region.text.as_ref().map(|s| s.as_ref()))
.collect()
}
pub fn concatenated_text(&self, separator: &str) -> String {
self.all_text().join(separator)
}
pub fn recognized_text_count(&self) -> usize {
self.text_regions
.iter()
.filter(|region| region.has_text())
.count()
}
pub fn average_confidence(&self) -> Option<f32> {
let confident_regions: Vec<_> = self.confident_text_regions().collect();
if confident_regions.is_empty() {
None
} else {
let sum: f32 = confident_regions
.iter()
.filter_map(|region| region.confidence)
.sum();
Some(sum / confident_regions.len() as f32)
}
}
}
impl fmt::Display for OAROCRResult {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "Input path: {}", self.input_path)?;
writeln!(f, "Page index: {}", self.index)?;
writeln!(
f,
"Image dimensions: [{}, {}]",
self.input_img.width(),
self.input_img.height()
)?;
if let Some(angle) = self.orientation_angle {
writeln!(f, "Orientation angle: {angle:.1}°")?;
} else {
writeln!(f, "Orientation angle: not detected")?;
}
writeln!(f, "Total text regions: {}", self.text_regions.len())?;
writeln!(f, "Recognized texts: {}", self.recognized_text_count())?;
if !self.text_regions.is_empty() {
writeln!(f, "Text regions (detection + recognition):")?;
for (region_index, region) in self.text_regions.iter().enumerate() {
write!(f, " Region {}: ", region_index + 1)?;
let bbox = ®ion.bounding_box;
if bbox.points.is_empty() {
write!(f, "[] (empty)")?;
} else {
write!(f, "[")?;
for (j, point) in bbox.points.iter().enumerate() {
if j == 0 {
write!(f, "[{:.0}, {:.0}]", point.x, point.y)?;
} else {
write!(f, ", [{:.0}, {:.0}]", point.x, point.y)?;
}
}
write!(f, "]")?;
}
match (®ion.text, region.confidence) {
(Some(text), Some(score)) => {
let orientation_str = match region.orientation_angle {
Some(angle) => format!(" (orientation: {angle:.1}°)"),
None => String::new(),
};
writeln!(f, " -> '{text}' (confidence: {score:.3}){orientation_str}")?;
}
_ => {
writeln!(f, " -> [no text recognized]")?;
}
}
}
}
if let Some(rectified_img) = &self.rectified_img {
writeln!(
f,
"Rectified image: available [{} x {}]",
rectified_img.width(),
rectified_img.height()
)?;
} else {
writeln!(
f,
"Rectified image: not available (document unwarping not enabled)"
)?;
}
Ok(())
}
}