use crate::api::error::{ApiError, ApiResult};
use crate::core::image::{ImageFormat, ImageStatistics, OcrImage};
use crate::utils::Result;
use image::DynamicImage;
use serde::{Deserialize, Serialize};
pub struct ImageProcessor;
impl ImageProcessor {
pub fn load_from_memory(data: &[u8]) -> ApiResult<DynamicImage> {
let img = image::load_from_memory(data)
.map_err(|e| ApiError::ImageProcessing(format!("Failed to load image: {}", e)))?;
Ok(img)
}
pub async fn load_from_file<P: AsRef<std::path::Path>>(path: P) -> ApiResult<DynamicImage> {
let data = tokio::fs::read(path).await?;
Self::load_from_memory(&data)
}
pub fn to_ocr_image(img: DynamicImage, dpi: u32) -> OcrImage {
OcrImage::new(img, dpi)
}
pub fn preprocess_for_ocr(img: &OcrImage) -> ApiResult<OcrImage> {
let gray_img = if img.format != ImageFormat::Grayscale {
img.to_grayscale()
} else {
img.clone()
};
let preprocessed = Self::apply_basic_preprocessing(&gray_img)?;
Ok(preprocessed)
}
fn apply_basic_preprocessing(img: &OcrImage) -> Result<OcrImage> {
Ok(img.clone())
}
pub fn enhance_contrast(img: &OcrImage, _factor: f32) -> ApiResult<OcrImage> {
Ok(img.clone())
}
pub fn reduce_noise(img: &OcrImage) -> ApiResult<OcrImage> {
Ok(img.clone())
}
pub fn sharpen(img: &OcrImage) -> ApiResult<OcrImage> {
Ok(img.clone())
}
pub fn deskew(img: &OcrImage) -> ApiResult<OcrImage> {
Ok(img.clone())
}
pub fn binarize(img: &OcrImage, threshold: u8) -> ApiResult<OcrImage> {
Ok(img.threshold(threshold))
}
pub fn resize(img: &OcrImage, width: u32, height: u32) -> OcrImage {
img.resize(width, height).unwrap_or_else(|_| img.clone())
}
pub fn crop(img: &OcrImage, x: u32, y: u32, width: u32, height: u32) -> ApiResult<OcrImage> {
img.crop(x, y, width, height)
.map_err(|e| ApiError::ImageProcessing(e.to_string()))
}
pub fn rotate(img: &OcrImage, angle: f32) -> OcrImage {
img.rotate(angle).unwrap_or_else(|_| img.clone())
}
pub fn get_statistics(img: &OcrImage) -> ImageStatistics {
img.statistics()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ImageEnhancementConfig {
pub enable_contrast_enhancement: bool,
pub contrast_factor: f32,
pub enable_noise_reduction: bool,
pub noise_reduction_strength: f32,
pub enable_sharpening: bool,
pub sharpening_strength: f32,
pub enable_deskewing: bool,
pub deskewing_threshold: f32,
}
impl Default for ImageEnhancementConfig {
fn default() -> Self {
Self {
enable_contrast_enhancement: true,
contrast_factor: 1.2,
enable_noise_reduction: true,
noise_reduction_strength: 0.5,
enable_sharpening: false,
sharpening_strength: 0.5,
enable_deskewing: true,
deskewing_threshold: 0.1,
}
}
}
pub struct ImagePreprocessingPipeline {
config: ImageEnhancementConfig,
}
impl ImagePreprocessingPipeline {
pub fn new(config: ImageEnhancementConfig) -> Self {
Self { config }
}
pub fn process(&self, img: &OcrImage) -> ApiResult<OcrImage> {
let mut processed = img.clone();
if self.config.enable_contrast_enhancement {
processed = ImageProcessor::enhance_contrast(&processed, self.config.contrast_factor)?;
}
if self.config.enable_noise_reduction {
processed = ImageProcessor::reduce_noise(&processed)?;
}
if self.config.enable_sharpening {
processed = ImageProcessor::sharpen(&processed)?;
}
if self.config.enable_deskewing {
processed = ImageProcessor::deskew(&processed)?;
}
Ok(processed)
}
}
pub struct ImageQualityAssessor;
impl ImageQualityAssessor {
pub fn assess_quality(img: &OcrImage) -> ImageQualityScore {
let stats = img.statistics();
let contrast = Self::calculate_contrast(&stats);
let sharpness = Self::calculate_sharpness(img);
let noise_level = Self::calculate_noise_level(&stats);
let resolution = Self::calculate_resolution_score(img);
let overall_score = (contrast + sharpness + (1.0 - noise_level) + resolution) / 4.0;
ImageQualityScore {
overall_score,
contrast,
sharpness,
noise_level,
resolution,
recommendations: Self::generate_recommendations(
overall_score,
contrast,
sharpness,
noise_level,
resolution,
),
}
}
fn calculate_contrast(stats: &ImageStatistics) -> f32 {
if stats.max == stats.min {
0.0
} else {
(stats.max - stats.min) as f32 / 255.0
}
}
fn calculate_sharpness(_img: &OcrImage) -> f32 {
0.5 }
fn calculate_noise_level(stats: &ImageStatistics) -> f32 {
if stats.pixel_count == 0 {
0.0
} else {
let variance = stats.mean * (1.0 - stats.mean / 255.0);
(variance / 255.0).min(1.0)
}
}
fn calculate_resolution_score(img: &OcrImage) -> f32 {
let pixel_count = img.width * img.height;
let dpi_score = (img.dpi as f32 / 300.0).min(1.0);
let size_score = (pixel_count as f32 / (1920.0 * 1080.0)).min(1.0);
(dpi_score + size_score) / 2.0
}
fn generate_recommendations(
overall_score: f32,
contrast: f32,
sharpness: f32,
noise_level: f32,
resolution: f32,
) -> Vec<String> {
let mut recommendations = Vec::new();
if overall_score < 0.5 {
recommendations.push(
"Image quality is poor. Consider using a higher resolution image.".to_string(),
);
}
if contrast < 0.3 {
recommendations.push("Low contrast detected. Consider enhancing contrast.".to_string());
}
if sharpness < 0.3 {
recommendations.push(
"Image appears blurry. Consider sharpening or using a higher resolution image."
.to_string(),
);
}
if noise_level > 0.7 {
recommendations
.push("High noise level detected. Consider applying noise reduction.".to_string());
}
if resolution < 0.5 {
recommendations
.push("Low resolution detected. Consider using a higher DPI image.".to_string());
}
if recommendations.is_empty() {
recommendations.push("Image quality is good for OCR processing.".to_string());
}
recommendations
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ImageQualityScore {
pub overall_score: f32,
pub contrast: f32,
pub sharpness: f32,
pub noise_level: f32,
pub resolution: f32,
pub recommendations: Vec<String>,
}
impl ImageQualityScore {
pub fn is_acceptable(&self, threshold: f32) -> bool {
self.overall_score >= threshold
}
pub fn get_grade(&self) -> QualityGrade {
match self.overall_score {
score if score >= 0.8 => QualityGrade::Excellent,
score if score >= 0.6 => QualityGrade::Good,
score if score >= 0.4 => QualityGrade::Fair,
score if score >= 0.2 => QualityGrade::Poor,
_ => QualityGrade::VeryPoor,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum QualityGrade {
Excellent,
Good,
Fair,
Poor,
VeryPoor,
}