# EasyOCR Tuning REST API

REST API service for EasyOCR hyperparameter evaluation. Keeps the model loaded in memory for fast repeated evaluations during hyperparameter search.

## Quick Start

### CPU Version

```bash
cd src/easyocr_service

# Build
docker build -t easyocr-api:cpu .

# Run
docker run -d -p 8002:8000 \
  -v $(pwd)/../dataset:/app/dataset:ro \
  -v easyocr-cache:/root/.EasyOCR \
  easyocr-api:cpu

# Test
curl http://localhost:8002/health
```

### GPU Version

```bash
# Build GPU image
docker build -f Dockerfile.gpu -t easyocr-api:gpu .

# Run with GPU
docker run -d -p 8002:8000 --gpus all \
  -v $(pwd)/../dataset:/app/dataset:ro \
  -v easyocr-cache:/root/.EasyOCR \
  easyocr-api:gpu
```

## Files

| File | Description |
|------|-------------|
| `easyocr_tuning_rest.py` | FastAPI REST service with 14 tunable hyperparameters |
| `dataset_manager.py` | Dataset loader (shared with other services) |
| `Dockerfile` | CPU-only image (amd64 + arm64) |
| `Dockerfile.gpu` | GPU/CUDA image (amd64 + arm64) |
| `requirements.txt` | Python dependencies |

## API Endpoints

### `GET /health`

Check if service is ready.

```json
{
  "status": "ok",
  "model_loaded": true,
  "dataset_loaded": true,
  "dataset_size": 24,
  "languages": ["es", "en"],
  "cuda_available": true,
  "device": "cuda",
  "gpu_name": "NVIDIA GB10"
}
```

### `POST /evaluate`

Run OCR evaluation with given hyperparameters.

**Request (14 tunable parameters):**
```json
{
  "pdf_folder": "/app/dataset",
  "text_threshold": 0.7,
  "low_text": 0.4,
  "link_threshold": 0.4,
  "slope_ths": 0.1,
  "ycenter_ths": 0.5,
  "height_ths": 0.5,
  "width_ths": 0.5,
  "add_margin": 0.1,
  "contrast_ths": 0.1,
  "adjust_contrast": 0.5,
  "decoder": "greedy",
  "beamWidth": 5,
  "min_size": 10,
  "rotation_info": null,
  "start_page": 5,
  "end_page": 10
}
```

**Response:**
```json
{"CER": 0.0234, "WER": 0.1156, "TIME": 45.2, "PAGES": 5, "TIME_PER_PAGE": 9.04}
```

## Debug Output (debugset)

The `debugset` folder allows saving OCR predictions for debugging and analysis. When `save_output=True` is passed to `/evaluate`, predictions are written to `/app/debugset`.

### Enable Debug Output

```json
{
  "pdf_folder": "/app/dataset",
  "save_output": true,
  "start_page": 5,
  "end_page": 10
}
```

### Output Structure

```
debugset/
├── doc1/
│   └── easyocr/
│       ├── page_0005.txt
│       ├── page_0006.txt
│       └── ...
├── doc2/
│   └── easyocr/
│       └── ...
```

Each `.txt` file contains the OCR-extracted text for that page.

### Docker Mount

Add the debugset volume to your docker run command:

```bash
docker run -d -p 8002:8000 \
  -v $(pwd)/../dataset:/app/dataset:ro \
  -v $(pwd)/../debugset:/app/debugset:rw \
  -v easyocr-cache:/root/.EasyOCR \
  easyocr-api:cpu
```

### Use Cases

- **Compare OCR engines**: Run same pages through PaddleOCR, DocTR, EasyOCR with `save_output=True`, then diff results
- **Debug hyperparameters**: See how different settings affect text extraction
- **Ground truth comparison**: Compare predictions against expected output

## Hyperparameters

### Detection (CRAFT Algorithm)

| Parameter | Default | Range | Description |
|-----------|---------|-------|-------------|
| `text_threshold` | 0.7 | 0.0-1.0 | Text confidence threshold |
| `low_text` | 0.4 | 0.0-1.0 | Text lower-bound score |
| `link_threshold` | 0.4 | 0.0-1.0 | Link confidence threshold |

### Bounding Box Merging

| Parameter | Default | Range | Description |
|-----------|---------|-------|-------------|
| `slope_ths` | 0.1 | 0.0-1.0 | Max slope for merging |
| `ycenter_ths` | 0.5 | 0.0-2.0 | Max vertical shift |
| `height_ths` | 0.5 | 0.0-2.0 | Max height variance |
| `width_ths` | 0.5 | 0.0-2.0 | Max horizontal distance |
| `add_margin` | 0.1 | 0.0-1.0 | Bounding box extension |

### Contrast

| Parameter | Default | Range | Description |
|-----------|---------|-------|-------------|
| `contrast_ths` | 0.1 | 0.0-1.0 | Contrast threshold for dual-pass |
| `adjust_contrast` | 0.5 | 0.0-1.0 | Target contrast level |

### Decoder

| Parameter | Default | Options | Description |
|-----------|---------|---------|-------------|
| `decoder` | "greedy" | greedy, beamsearch, wordbeamsearch | Decoding method |
| `beamWidth` | 5 | 1-20 | Beam width (for beam search) |

### Other

| Parameter | Default | Description |
|-----------|---------|-------------|
| `min_size` | 10 | Minimum text box pixels |
| `rotation_info` | null | Rotation angles to try: [90, 180, 270] |

## GPU Support

### Platform Support

| Platform | CPU | GPU |
|----------|-----|-----|
| Linux x86_64 (amd64) | ✅ | ✅ PyTorch CUDA |
| Linux ARM64 (GH200/GB200/DGX Spark) | ✅ | ✅ PyTorch CUDA (cu128 index) |
| macOS ARM64 (M1/M2) | ✅ | ❌ |

### PyTorch CUDA on ARM64

Unlike PaddlePaddle, PyTorch provides **official ARM64 CUDA wheels** on the cu128 index:

```bash
pip install torch torchvision --index-url https://download.pytorch.org/whl/cu128
```

This works on both amd64 and arm64 platforms with CUDA support.

### GPU Detection

EasyOCR automatically uses GPU when PyTorch CUDA is available:

```python
import torch
print(torch.cuda.is_available())  # True if GPU available
```

The `/health` endpoint shows GPU status:
```json
{
  "cuda_available": true,
  "device": "cuda",
  "gpu_name": "NVIDIA GB10",
  "gpu_memory_total": "128.00 GB"
}
```

## Environment Variables

| Variable | Default | Description |
|----------|---------|-------------|
| `EASYOCR_LANGUAGES` | `es,en` | Comma-separated language codes |
| `CUDA_VISIBLE_DEVICES` | `0` | GPU device selection |

## CI/CD

Built images available from registry:

| Image | Architecture |
|-------|--------------|
| `seryus.ddns.net/unir/easyocr-cpu:latest` | amd64, arm64 |
| `seryus.ddns.net/unir/easyocr-gpu:latest` | amd64, arm64 |

## Sources

- [EasyOCR Documentation](https://www.jaided.ai/easyocr/documentation/)
- [EasyOCR GitHub](https://github.com/JaidedAI/EasyOCR)
- [PyTorch ARM64 CUDA Wheels](https://github.com/pytorch/pytorch/issues/160162)
