Skip to main content

Overview

The run_stream() method allows you to receive real-time updates as each stage of the OCR pipeline completes. This is useful for:
  • Showing progress to users in long-running operations
  • Processing results incrementally
  • Building responsive UI applications
  • Debugging and monitoring the pipeline

OCR Pipeline Stages

Retto’s OCR pipeline consists of three stages:
  1. Detection (Det): Locates text regions in the image
  2. Classification (Cls): Determines text orientation (0° or 180°)
  3. Recognition (Rec): Recognizes actual text content
With run_stream(), you receive results as each stage completes instead of waiting for the entire pipeline.

Basic Streaming Example

use retto_core::prelude::*;
use std::sync::mpsc;
use std::fs;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Create configuration
    let cfg = RettoSessionConfig {
        worker_config: RettoOrtWorkerConfig::default(),
        ..Default::default()
    };
    let mut session = RettoSession::new(cfg)?;

    // Create a channel for receiving stage results
    let (sender, receiver) = mpsc::channel();

    // Load image
    let image_bytes = fs::read("input.png")?;

    // Start OCR in a separate thread
    let handle = std::thread::spawn(move || {
        session.run_stream(image_bytes, sender)
    });

    // Process results as they arrive
    while let Ok(stage_result) = receiver.recv() {
        match stage_result {
            RettoWorkerStageResult::Det(det_result) => {
                println!("✓ Detection complete: {} regions found", 
                    det_result.0.len());
                
                for (idx, det) in det_result.0.iter().enumerate() {
                    println!("  Region {}: score={:.2}", idx, det.score);
                }
            }
            RettoWorkerStageResult::Cls(cls_result) => {
                println!("✓ Classification complete");
                
                for (idx, cls) in cls_result.0.iter().enumerate() {
                    println!("  Region {}: angle={}°, confidence={:.2}", 
                        idx, cls.label.label, cls.label.score);
                }
            }
            RettoWorkerStageResult::Rec(rec_result) => {
                println!("✓ Recognition complete");
                
                for (idx, rec) in rec_result.0.iter().enumerate() {
                    println!("  Region {}: '{}' (confidence={:.2})", 
                        idx, rec.text, rec.score);
                }
                
                // Recognition is the final stage
                break;
            }
        }
    }

    // Wait for processing to complete
    handle.join().unwrap()?;
    
    println!("\n✓ All stages complete!");
    Ok(())
}

Understanding Stage Results

Each stage produces a specific result type:

Detection Stage

RettoWorkerStageResult::Det(det_result) => {
    // det_result: DetProcessorResult
    // Contains Vec<DetProcessorInnerResult>
    
    for det in det_result.0.iter() {
        // det.boxes: PointBox<OrderedFloat<f32>>
        // Four corner points of the detected text region
        let tl = det.boxes.tl();
        let tr = det.boxes.tr();
        let br = det.boxes.br();
        let bl = det.boxes.bl();
        
        // det.score: f32
        // Detection confidence (0.0 to 1.0)
        let confidence = det.score;
    }
}

Classification Stage

RettoWorkerStageResult::Cls(cls_result) => {
    // cls_result: ClsProcessorResult
    // Contains Vec<ClsProcessorSingleResult>
    
    for cls in cls_result.0.iter() {
        // cls.label.label: u16
        // Rotation angle: 0 or 180 degrees
        let angle = cls.label.label;
        
        // cls.label.score: f32
        // Classification confidence (0.0 to 1.0)
        let confidence = cls.label.score;
    }
}

Recognition Stage

RettoWorkerStageResult::Rec(rec_result) => {
    // rec_result: RecProcessorResult
    // Contains Vec<RecProcessorSingleResult>
    
    for rec in rec_result.0.iter() {
        // rec.text: String
        // Recognized text content
        let text = &rec.text;
        
        // rec.score: f32
        // Recognition confidence (0.0 to 1.0)
        let confidence = rec.score;
    }
}

Progress Monitoring Example

Here’s a more advanced example that shows progress with timing information:
use retto_core::prelude::*;
use std::sync::mpsc;
use std::time::Instant;
use std::fs;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let cfg = RettoSessionConfig {
        worker_config: RettoOrtWorkerConfig::default(),
        ..Default::default()
    };
    let mut session = RettoSession::new(cfg)?;

    let (sender, receiver) = mpsc::channel();
    let image_bytes = fs::read("document.png")?;

    let start_time = Instant::now();
    let mut stage_times = Vec::new();
    let mut last_time = start_time;

    // Spawn processing thread
    let handle = std::thread::spawn(move || {
        session.run_stream(image_bytes, sender)
    });

    println!("Starting OCR pipeline...\n");

    // Collect results from all stages
    let mut det_result = None;
    let mut cls_result = None;
    let mut rec_result = None;

    while let Ok(stage_result) = receiver.recv() {
        let elapsed = last_time.elapsed();
        stage_times.push(elapsed);
        last_time = Instant::now();

        match stage_result {
            RettoWorkerStageResult::Det(result) => {
                println!("[1/3] Detection: {} regions in {:?}", 
                    result.0.len(), elapsed);
                det_result = Some(result);
            }
            RettoWorkerStageResult::Cls(result) => {
                println!("[2/3] Classification: {} regions in {:?}", 
                    result.0.len(), elapsed);
                cls_result = Some(result);
            }
            RettoWorkerStageResult::Rec(result) => {
                println!("[3/3] Recognition: {} regions in {:?}", 
                    result.0.len(), elapsed);
                rec_result = Some(result);
                break;
            }
        }
    }

    handle.join().unwrap()?;

    // Print summary
    let total_time = start_time.elapsed();
    println!("\n" + "=".repeat(50));
    println!("Total time: {:?}", total_time);
    
    // Print final results
    if let (Some(det), Some(cls), Some(rec)) = (det_result, cls_result, rec_result) {
        println!("\nRecognized Text:");
        println!("=".repeat(50));
        for (idx, rec) in rec.0.iter().enumerate() {
            println!("{}. {} (confidence: {:.1}%)", 
                idx + 1, rec.text, rec.score * 100.0);
        }
    }

    Ok(())
}

Building a Progress Bar

Integrate with a progress bar library like indicatif:
use retto_core::prelude::*;
use std::sync::mpsc;
use std::fs;
use indicatif::{ProgressBar, ProgressStyle};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let cfg = RettoSessionConfig {
        worker_config: RettoOrtWorkerConfig::default(),
        ..Default::default()
    };
    let mut session = RettoSession::new(cfg)?;

    let (sender, receiver) = mpsc::channel();
    let image_bytes = fs::read("input.png")?;

    // Create progress bar
    let pb = ProgressBar::new(3);
    pb.set_style(
        ProgressStyle::default_bar()
            .template("{msg} [{bar:40}] {pos}/{len}")
            .unwrap()
            .progress_chars("#>-")
    );

    // Spawn processing
    let handle = std::thread::spawn(move || {
        session.run_stream(image_bytes, sender)
    });

    // Update progress as stages complete
    while let Ok(stage_result) = receiver.recv() {
        match stage_result {
            RettoWorkerStageResult::Det(_) => {
                pb.set_message("Detecting text regions...");
                pb.inc(1);
            }
            RettoWorkerStageResult::Cls(_) => {
                pb.set_message("Classifying orientations...");
                pb.inc(1);
            }
            RettoWorkerStageResult::Rec(_) => {
                pb.set_message("Recognizing text...");
                pb.inc(1);
                break;
            }
        }
    }

    handle.join().unwrap()?;
    pb.finish_with_message("OCR complete!");

    Ok(())
}

Comparison with run()

Here’s how run_stream() differs from the standard run() method:
let result = session.run(image_bytes)?;
// Wait for all stages to complete
// Get all results at once
println!("Found: {} regions", result.det_result.0.len());
println!("Text: {:?}", result.rec_result.0);

Error Handling

Handle errors in the streaming pipeline:
use retto_core::prelude::*;
use std::sync::mpsc;
use std::fs;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let cfg = RettoSessionConfig {
        worker_config: RettoOrtWorkerConfig::default(),
        ..Default::default()
    };
    let mut session = RettoSession::new(cfg)?;

    let (sender, receiver) = mpsc::channel();
    let image_bytes = fs::read("input.png")?;

    let handle = std::thread::spawn(move || {
        session.run_stream(image_bytes, sender)
    });

    // Process results with error handling
    let mut stage_count = 0;
    while let Ok(stage_result) = receiver.recv() {
        stage_count += 1;
        match stage_result {
            RettoWorkerStageResult::Det(result) => {
                println!("Stage 1/3 complete");
            }
            RettoWorkerStageResult::Cls(result) => {
                println!("Stage 2/3 complete");
            }
            RettoWorkerStageResult::Rec(result) => {
                println!("Stage 3/3 complete");
                break;
            }
        }
    }

    // Check if pipeline completed successfully
    match handle.join() {
        Ok(Ok(())) => {
            if stage_count == 3 {
                println!("✓ Pipeline completed successfully");
            } else {
                eprintln!("⚠ Pipeline incomplete: only {} stages", stage_count);
            }
        }
        Ok(Err(e)) => eprintln!("✗ Pipeline error: {:?}", e),
        Err(e) => eprintln!("✗ Thread panic: {:?}", e),
    }

    Ok(())
}

Next Steps

Basic OCR

Learn the fundamentals of OCR with Retto

Custom Configuration

Fine-tune processing parameters for your use case

Build docs developers (and LLMs) love