use crate::core::geometry::TBox;
use crate::core::image::OcrImage;
use anyhow::Result;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::Path;
use tokio::fs;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TrainingSample {
pub image: OcrImage,
pub text: String,
pub bounding_boxes: Vec<TBox>,
pub language: Option<String>,
pub metadata: HashMap<String, String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DatasetConfig {
pub dataset_path: String,
pub format: DatasetFormat,
pub train_split: f32,
pub val_split: f32,
pub test_split: f32,
pub max_samples: Option<usize>,
pub shuffle: bool,
pub seed: Option<u64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum DatasetFormat {
Synthetic,
Real,
Mixed,
Custom(String),
}
impl DatasetFormat {
pub fn from_string(s: &str) -> Self {
match s.to_lowercase().as_str() {
"synthetic" => DatasetFormat::Synthetic,
"real" => DatasetFormat::Real,
"mixed" => DatasetFormat::Mixed,
_ => DatasetFormat::Custom(s.to_string()),
}
}
}
#[derive(Debug, Clone)]
pub struct DatasetSplits {
pub train: Vec<TrainingSample>,
pub validation: Vec<TrainingSample>,
pub test: Vec<TrainingSample>,
}
pub struct DataLoader {
config: DatasetConfig,
splits: Option<DatasetSplits>,
}
impl DataLoader {
pub fn new(config: DatasetConfig) -> Self {
Self {
config,
splits: None,
}
}
pub async fn load_dataset(&mut self) -> Result<()> {
tracing::info!("Loading dataset from: {}", self.config.dataset_path);
match self.config.format {
DatasetFormat::Synthetic => self.load_synthetic_dataset().await,
DatasetFormat::Real => self.load_real_dataset().await,
DatasetFormat::Mixed => self.load_mixed_dataset().await,
DatasetFormat::Custom(ref loader_name) => {
let loader_name = loader_name.clone();
self.load_custom_dataset(&loader_name).await
}
}
}
pub fn get_splits(&self) -> Option<&DatasetSplits> {
self.splits.as_ref()
}
pub fn get_train_samples(&self) -> Option<&[TrainingSample]> {
self.splits.as_ref().map(|s| s.train.as_slice())
}
pub fn get_val_samples(&self) -> Option<&[TrainingSample]> {
self.splits.as_ref().map(|s| s.validation.as_slice())
}
pub fn get_test_samples(&self) -> Option<&[TrainingSample]> {
self.splits.as_ref().map(|s| s.test.as_slice())
}
async fn load_synthetic_dataset(&mut self) -> Result<()> {
tracing::info!("Loading synthetic dataset");
let samples = self.generate_synthetic_samples(1000).await?;
self.splits = Some(self.split_dataset(samples));
Ok(())
}
async fn load_real_dataset(&mut self) -> Result<()> {
tracing::info!("Loading real dataset");
let dataset_path = Path::new(&self.config.dataset_path);
if !dataset_path.exists() {
return Err(anyhow::anyhow!(
"Dataset path does not exist: {}",
self.config.dataset_path
));
}
if dataset_path.join("annotations.json").exists() {
self.load_json_dataset(dataset_path).await
} else if dataset_path.join("labels.txt").exists() {
self.load_text_dataset(dataset_path).await
} else {
Err(anyhow::anyhow!(
"Unknown dataset format in: {}",
self.config.dataset_path
))
}
}
async fn load_mixed_dataset(&mut self) -> Result<()> {
tracing::info!("Loading mixed dataset");
let mut synthetic_loader = DataLoader::new(DatasetConfig {
dataset_path: self.config.dataset_path.clone(),
format: DatasetFormat::Synthetic,
train_split: 0.5,
val_split: 0.25,
test_split: 0.25,
max_samples: self.config.max_samples.map(|n| n / 2),
shuffle: self.config.shuffle,
seed: self.config.seed,
});
let mut real_loader = DataLoader::new(DatasetConfig {
dataset_path: self.config.dataset_path.clone(),
format: DatasetFormat::Real,
train_split: 0.5,
val_split: 0.25,
test_split: 0.25,
max_samples: self.config.max_samples.map(|n| n / 2),
shuffle: self.config.shuffle,
seed: self.config.seed,
});
synthetic_loader.load_synthetic_dataset().await?;
real_loader.load_real_dataset().await?;
let synthetic_splits = synthetic_loader.splits.unwrap();
let real_splits = real_loader.splits.unwrap();
self.splits = Some(DatasetSplits {
train: [synthetic_splits.train, real_splits.train].concat(),
validation: [synthetic_splits.validation, real_splits.validation].concat(),
test: [synthetic_splits.test, real_splits.test].concat(),
});
Ok(())
}
async fn load_custom_dataset(&mut self, _loader_name: &str) -> Result<()> {
tracing::info!("Loading custom dataset");
Err(anyhow::anyhow!(
"Custom dataset loaders not implemented yet"
))
}
async fn load_json_dataset(&self, dataset_path: &Path) -> Result<()> {
let annotations_path = dataset_path.join("annotations.json");
let annotations_data = fs::read_to_string(annotations_path).await?;
let annotations: serde_json::Value = serde_json::from_str(&annotations_data)?;
tracing::info!(
"Loaded JSON annotations with {} entries",
annotations.as_object().unwrap().len()
);
Ok(())
}
async fn load_text_dataset(&self, dataset_path: &Path) -> Result<()> {
let labels_path = dataset_path.join("labels.txt");
let labels_data = fs::read_to_string(labels_path).await?;
let lines: Vec<&str> = labels_data.lines().collect();
tracing::info!("Loaded text labels with {} entries", lines.len());
Ok(())
}
async fn generate_synthetic_samples(&self, count: usize) -> Result<Vec<TrainingSample>> {
let mut samples = Vec::with_capacity(count);
for i in 0..count {
let image = self.generate_synthetic_image(i).await?;
let text = self.generate_synthetic_text(i).await?;
let bounding_boxes = self.generate_synthetic_boxes(&image, &text).await?;
samples.push(TrainingSample {
image,
text,
bounding_boxes,
language: Some("en".to_string()),
metadata: HashMap::new(),
});
}
Ok(samples)
}
async fn generate_synthetic_image(&self, _index: usize) -> Result<OcrImage> {
use image::{ImageBuffer, Rgb, RgbImage};
let width = 200;
let height = 50;
let mut img: RgbImage = ImageBuffer::new(width, height);
for pixel in img.pixels_mut() {
*pixel = Rgb([255, 255, 255]);
}
for y in 10..40 {
for x in 10..190 {
if (x + y) % 3 == 0 {
img.put_pixel(x, y, Rgb([0, 0, 0]));
}
}
}
Ok(OcrImage::new(img.into(), 300))
}
async fn generate_synthetic_text(&self, index: usize) -> Result<String> {
let texts = vec![
"Hello World",
"Sample Text",
"OCR Training",
"Neural Network",
"Machine Learning",
"Computer Vision",
"Text Recognition",
"Image Processing",
];
Ok(texts[index % texts.len()].to_string())
}
async fn generate_synthetic_boxes(&self, _image: &OcrImage, _text: &str) -> Result<Vec<TBox>> {
Ok(vec![TBox::new(10, 10, 190, 40)])
}
fn split_dataset(&self, mut samples: Vec<TrainingSample>) -> DatasetSplits {
if self.config.shuffle {
use rand::seq::SliceRandom;
use rand::SeedableRng;
let mut rng = if let Some(seed) = self.config.seed {
rand::rngs::StdRng::seed_from_u64(seed)
} else {
rand::rngs::StdRng::from_entropy()
};
samples.shuffle(&mut rng);
}
let total = samples.len();
let train_end = (total as f32 * self.config.train_split) as usize;
let val_end = train_end + (total as f32 * self.config.val_split) as usize;
DatasetSplits {
train: samples[..train_end].to_vec(),
validation: samples[train_end..val_end].to_vec(),
test: samples[val_end..].to_vec(),
}
}
}
#[derive(Debug, Clone)]
pub struct TrainingBatch {
pub images: Vec<OcrImage>,
pub texts: Vec<String>,
pub bounding_boxes: Vec<Vec<TBox>>,
pub languages: Vec<Option<String>>,
pub metadata: Vec<HashMap<String, String>>,
}
impl TrainingBatch {
pub fn new(batch_size: usize) -> Self {
Self {
images: Vec::with_capacity(batch_size),
texts: Vec::with_capacity(batch_size),
bounding_boxes: Vec::with_capacity(batch_size),
languages: Vec::with_capacity(batch_size),
metadata: Vec::with_capacity(batch_size),
}
}
pub fn add_sample(&mut self, sample: TrainingSample) {
self.images.push(sample.image);
self.texts.push(sample.text);
self.bounding_boxes.push(sample.bounding_boxes);
self.languages.push(sample.language);
self.metadata.push(sample.metadata);
}
pub fn len(&self) -> usize {
self.images.len()
}
pub fn is_empty(&self) -> bool {
self.images.is_empty()
}
}
pub struct BatchIterator {
samples: Vec<TrainingSample>,
batch_size: usize,
current_index: usize,
}
impl BatchIterator {
pub fn new(samples: Vec<TrainingSample>, batch_size: usize) -> Self {
Self {
samples,
batch_size,
current_index: 0,
}
}
}
impl Iterator for BatchIterator {
type Item = TrainingBatch;
fn next(&mut self) -> Option<Self::Item> {
if self.current_index >= self.samples.len() {
return None;
}
let end_index = (self.current_index + self.batch_size).min(self.samples.len());
let mut batch = TrainingBatch::new(self.batch_size);
for sample in &self.samples[self.current_index..end_index] {
batch.add_sample(sample.clone());
}
self.current_index = end_index;
Some(batch)
}
}