mod apply;
pub mod dewarpa;
pub mod io;
mod model;
mod single_page;
mod textline;
mod types;
pub use apply::{
apply_disparity, apply_horizontal_disparity, apply_vertical_disparity,
estimate_disparity_magnitude,
};
pub use dewarpa::Dewarpa;
pub use model::{build_horizontal_disparity, build_vertical_disparity, populate_full_resolution};
pub use single_page::{dewarp_single_page_init, dewarp_single_page_run};
pub use textline::{
find_textline_centers, is_line_coverage_valid, pix_find_textline_flow_direction,
remove_short_lines, sort_lines_by_y,
};
pub use types::{Dewarp, DewarpOptions, DewarpResult, TextLine};
use crate::color::{
AdaptiveThresholdOptions, adaptive_threshold, pix_convert_to_gray, threshold_to_binary,
};
use crate::core::{Pix, PixelDepth};
use crate::recog::{RecogError, RecogResult};
pub fn dewarp_single_page(pix: &Pix, options: &DewarpOptions) -> RecogResult<DewarpResult> {
let pix_binary = get_binary_image(pix, options)?;
let lines = find_textline_centers(&pix_binary)?;
if lines.len() < options.min_lines as usize {
return Err(RecogError::NoContent(format!(
"not enough text lines found: {} (need at least {})",
lines.len(),
options.min_lines
)));
}
let mut dewarp = Dewarp::new(pix.width(), pix.height(), 0, options);
build_vertical_disparity(&mut dewarp, &lines, options)?;
if !dewarp.v_success {
return Err(RecogError::NoContent(
"failed to build vertical disparity model".to_string(),
));
}
let mut h_applied = false;
if options.use_both && build_horizontal_disparity(&mut dewarp, &lines, options).is_ok() {
h_applied = dewarp.h_valid;
}
populate_full_resolution(&mut dewarp)?;
let dewarped = apply_disparity(pix, &dewarp, options.gray_in)?;
Ok(DewarpResult::new(dewarped, dewarp, true, h_applied))
}
fn get_binary_image(pix: &Pix, _options: &DewarpOptions) -> RecogResult<Pix> {
match pix.depth() {
PixelDepth::Bit1 => {
Ok(pix.deep_clone())
}
PixelDepth::Bit8 => {
let opts = AdaptiveThresholdOptions::default();
match adaptive_threshold(pix, &opts) {
Ok(binary) => Ok(binary),
Err(_) => {
threshold_to_binary(pix, 128).map_err(|e| e.into())
}
}
}
PixelDepth::Bit32 => {
let gray = pix_convert_to_gray(pix)?;
let opts = AdaptiveThresholdOptions::default();
match adaptive_threshold(&gray, &opts) {
Ok(binary) => Ok(binary),
Err(_) => threshold_to_binary(&gray, 128).map_err(|e| e.into()),
}
}
_ => {
let gray = pix_convert_to_gray(pix)?;
threshold_to_binary(&gray, 128).map_err(|e| e.into())
}
}
}
pub fn needs_dewarping(pix: &Pix) -> RecogResult<bool> {
let options = DewarpOptions::default().with_min_lines(6);
let pix_binary = get_binary_image(pix, &options)?;
let lines = find_textline_centers(&pix_binary)?;
if lines.len() < 6 {
return Ok(false);
}
let magnitude = estimate_disparity_magnitude(&lines);
Ok(magnitude > 5.0)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_dewarp_options_default() {
let options = DewarpOptions::default();
assert_eq!(options.sampling, 30);
assert_eq!(options.min_lines, 15);
assert!(options.use_both);
}
#[test]
fn test_dewarp_single_page_empty_image() {
let pix = Pix::new(100, 100, PixelDepth::Bit1).unwrap();
let options = DewarpOptions::default();
let result = dewarp_single_page(&pix, &options);
assert!(result.is_err());
}
#[test]
fn test_needs_dewarping_empty_image() {
let pix = Pix::new(100, 100, PixelDepth::Bit1).unwrap();
let result = needs_dewarping(&pix);
assert!(result.is_ok());
assert!(!result.unwrap()); }
}