scan-core 0.1.1

Computer vision engine for exam detection and ArUco marker rectification
Documentation
# scan-core — Plan de Refactorización


Limpieza de código paja y duplicado. Sin cambios funcionales, solo organización interna.

---

## 1. Eliminar `cv/warp.rs` (DUPLICADO)


El módulo `cv/warp.rs` (158 líneas) reimplementa la transformación de perspectiva que ya existe en `transform/perspective.rs` + `image_utils::warp_perspective`.

| Archivo      | Funciones a eliminar                                       |
| :----------- | :--------------------------------------------------------- |
| `cv/warp.rs` | `warp()`, `get_perspective_transform()`, `square_2_quad()` |
| `cv/mod.rs`  | `pub mod warp`                                             |

**Verificar** que nadie importa `cv::warp` — actualmente `aruco/detector.rs` usa `cv::warp::warp()` y debe migrar a `image_utils::warp_perspective()` + `PerspectiveTransform`.

---

## 2. Eliminar `cv/grayscale.rs` (TRIVIAL)


```rust
// grayscale.rs:5-6 — esto envuelve una función de 1 línea del crate image
pub fn grayscale(img: &DynamicImage) -> GrayImage {
    img.to_luma8()
}
```

- `grayscale()` → reemplazar por `img.to_luma8()` directo
- `grayscale_rgba()` → mover a `cv/mod.rs` como helper si alguien la usa (verificar)
- Eliminar el archivo y el `pub mod grayscale` de `cv/mod.rs`

---

## 3. Mover `otsu_threshold` y `global_threshold` de `aruco/detector.rs` a `cv/threshold.rs`


Actualmente `aruco/detector.rs` tiene implementaciones privadas de:

- `otsu_threshold()` (35 líneas) — debería ser público en `cv/threshold.rs`
- `global_threshold()` (8 líneas) — debería ser público en `cv/threshold.rs`

**Acción**: Mover ambas funciones a `cv/threshold.rs` como `pub fn` y hacer que `aruco/detector.rs` las importe con `use crate::cv::threshold::{otsu_threshold, global_threshold}`.

---

## 4. Mover `encode_debug_image` y `to_png_bytes` a un solo lugar


- `aruco/detector.rs:343` tiene `encode_debug_image()` que llama a `image_utils::to_png_bytes()`
- `invisible/detector.rs` tiene su propia versión inline

**Acción**: Hacer una sola función pública `image_utils::encode_debug_image()` y eliminar las copias locales.

---

## 5. Unificar `warp` entre ArUco e Invisible


| Motor         | Cómo hace warp actualmente                                                     |
| :------------ | :----------------------------------------------------------------------------- |
| **ArUco**     | `cv::warp::warp(&gray, &pts, size)` — usa `square_2_quad`                      |
| **Invisible** | `image_utils::warp_perspective(&gray, &pt, w, h)` — usa `PerspectiveTransform` |

**Acción**: Ambos deben usar `PerspectiveTransform` + `image_utils::warp_perspective()`:

```rust
// En aruco/detector.rs, reemplazar:
let cropped = cv::warp::warp(&gray, &pts, size);
// Por:
let pt = PerspectiveTransform::new(&pts, &dst_pts)?;
let cropped = image_utils::warp_perspective(&gray, &pt, w as u32, h as u32);
```

---

## 6. Limpiar `cv/mod.rs`


Eliminar:

- `pub mod warp`
- `pub mod grayscale`
- `to_gray_image()` — usa `GrayImage::from_raw()` directo

Resultado esperado de `cv/mod.rs`:

```rust
pub mod threshold;
pub mod contours;
pub mod geometry;
pub mod ncc;
pub mod clahe;
pub mod canny;
```

---

## 7. Compatibilidad con `skeleton-scanner-exams`


Verificar que el `ScanResult` serializado incluya los campos que `Scanner.jsx` espera:

| Campo                            | Usado en skeleton | Estado actual                             |
| :------------------------------- | :---------------- | :---------------------------------------- |
| `success`                        || ✅ Existe                                 |
| `croppedImage` / `cropped_image` || ✅ Existe (base64 PNG)                    |
| `debugImage` / `debug_image`     || ✅ Existe                                 |
| `markedImage` / `marked_image`   || ✅ Existe                                 |
| `boundingBox`                    || ⚠️ No existe — agregar alias de `corners` |
| `detectedMarkers`                || ✅ Existe en ArUco                        |
| `match_score`                    || ✅ Existe en Invisible                    |

**Acción**: Agregar campo `bounding_box` como alias de `corners` en `ScanResult` para que el skeleton no tenga que adaptar nombres.

---

## Resumen de archivos afectados


| Acción        | Archivo                                                          |
| :------------ | :--------------------------------------------------------------- |
| **ELIMINAR**  | `cv/warp.rs`                                                     |
| **ELIMINAR**  | `cv/grayscale.rs`                                                |
| **MODIFICAR** | `cv/mod.rs` — quitar mods eliminados                             |
| **MODIFICAR** | `cv/threshold.rs` — agregar `otsu_threshold`, `global_threshold` |
| **MODIFICAR** | `aruco/detector.rs` — usar imports de cv + PerspectiveTransform  |
| **MODIFICAR** | `image_utils.rs` — agregar `encode_debug_image` público          |
| **MODIFICAR** | `invisible/detector.rs` — usar `image_utils::encode_debug_image` |
| **MODIFICAR** | `lib.rs` — agregar `bounding_box` a ScanResult                   |

## Verificación


```bash
cargo build --release
cargo run --release -- scan --input test.jpg --mode aruco4x4 --json
cargo run --release -- scan --input test.jpg --mode invisible --json
```