Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,35 @@ jobs:
- name: Build package
run: python -m build

- name: Verify wheel contents (training/ excluded, collection/ included)
run: |
python - <<'PY'
import glob, sys, zipfile

wheel = sorted(glob.glob("dist/*.whl"))[-1]
names = zipfile.ZipFile(wheel).namelist()

leaked = [
n for n in names
if n.startswith("training")
or "/class_mapping" in n
or "/prepare_dataset" in n
or "/review_cli" in n
or n.endswith("train.py")
or n.endswith("export_onnx.py")
or n.endswith("compute_sha256.py")
]
if leaked:
print(f"ERROR: training/ artifacts leaked into wheel: {leaked}")
sys.exit(1)

if not any("vision_ai_recaptcha_solver/collection/collector.py" in n for n in names):
print("ERROR: collection/ module missing from wheel")
sys.exit(1)

print(f"OK: {wheel} excludes training/, includes collection/")
PY

- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
Expand Down
11 changes: 10 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -86,4 +86,13 @@ src/recaptcha_solver/models/*.onnx

yolo12x.pt
recaptcha_classification_57k.onnx
modelOld.onnx
modelOld.onnx

# Active-learning runtime output (never commit collected samples)
collected/

# Training dataset (raw images live on Hugging Face / git-lfs, not in git)
training/dataset/
training/detection_dataset/
training/runs/
runs/
316 changes: 316 additions & 0 deletions docs/codebase-summary.md

Large diffs are not rendered by default.

82 changes: 82 additions & 0 deletions docs/journals/journal-260613-recaptcha-data-flywheel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# Data Flywheel: 4-Phase Cook Execution Complete

**Date**: 2026-06-13 23:10
**Severity**: Medium
**Component**: solver + detector + collection module
**Status**: Resolved

## What Happened

Four-phase implementation of active-learning data collection pipeline for reCAPTCHA solver (commit `824f8ff`). Added opt-in `DataCollector` to capture uncertain/failed tiles for human review, feeding a training loop that re-exports ONNX models. All 107 tests pass; public API unchanged; wheel excludes training code.

## The Brutal Truth

This was a clean execution — no surprises, no fires. The plan was thorough (pre-verification caught design changes before code), and the team wrote tests before features. That meant code review found a subtle but critical bug that testing missed entirely: exception handling in a telemetry path that **must never abort the solve**.

## Technical Details

**Collector architecture:**
- `collection/DataCollector` writes PNG tiles + `metadata.jsonl` (reasons: `uncertain` ≤ confidence < threshold, `failed` no tile match, `unknown_keyword` unmapped class)
- Hook placed in `YOLODetector.classify_tiles_with_confidence` (line ~518) to reuse already-cropped tiles (DRY principle)
- Wired symmetrically into both `RecaptchaSolver` and `AsyncRecaptchaSolver` (parallel impls, not wrappers)
- Async disk writes offloaded via `_run_in_executor` to avoid blocking event loop
- Config flag `collect_data=False` by default → zero I/O overhead for PyPI users

**Training tooling (outside wheel):**
- `training/class_mapping.py` — single source of truth: folder ↔ class_id ↔ label (14 classes, validated vs `types.CLASS_NAMES`)
- `prepare_dataset.py`, `review_cli.py`, `train.py`, `export_onnx.py`, `compute_sha256.py`
- Excluded from wheel via `tool.setuptools.packages.find where=src` (training/ lives at root)

## What We Tried

Wrote tests first per TDD mode, blocking all new code:
- `test_config.py` (+7) — config sentinel & thresholds
- `test_collector_scaffold.py` — no-op disabled collector
- `test_data_collector.py` — tile I/O, metadata format
- `test_class_mapping.py` — class id/label round-trip
- `test_prepare_dataset.py` — dataset preparation
- `test_training_scripts_args.py` — script CLI args (dry run, no GPU)

CI green: 107 passed (+38 new), ruff clean on `src/` + `training/`, mypy `src/` showing only 4 pre-existing errors on HEAD.

## Root Cause Analysis (the Hard Lesson)

Code review flagged a narrow exception handler that nearly shipped:

```python
try:
cv2.imwrite(tile_path, tile)
except OSError: # WRONG
logger.warning("failed to write tile")
```

`cv2.error` (from `cv2.imwrite`) is **not** an `OSError` subclass. A corrupt OpenCV environment would raise `cv2.error`, bypass the handler, and propagate into `classify_tiles_with_confidence`, breaking the solve pipeline for the user. Telemetry must **never** abort the primary flow.

**Fixed to:**
```python
except Exception: # catch ALL, never abort
logger.warning("failed to write tile")
```

Tests passed because test environment had healthy OpenCV. The bug only surfaces in edge cases (missing codec, corrupted install, file system full on unknown error). Code review caught it; tests didn't.

## Lessons Learned

1. **Telemetry/observability code must be defensive.** If the feature is "nice to have" (data collection), wrap it in a broad exception handler. Narrow catches (`OSError`) assume the stdlib exception hierarchy is stable; it's not (NumPy, OpenCV, Pillow each have their own exception trees).

2. **TDD locks behavior, but doesn't catch all bugs.** Tests verify the happy path and specified error cases. They don't enumerate all possible exception types the third-party libs might throw. Code review with domain knowledge (knowing `cv2.error` exists) caught what tests missed.

3. **Symmetry matters.** Because `RecaptchaSolver` and `AsyncRecaptchaSolver` are **parallel implementations, not wrappers**, every logic change must land in both. Phase 1 wiring + Phase 2 hooks went into both without friction — the pattern worked.

4. **Hook placement at the detector level (DRY).** The detector already crops tiles; asking handlers to re-crop them is waste. Putting the collection hook in `classify_tiles_with_confidence` reused existing context, reduced code paths, and simplified testing.

## Next Steps

- Monitor production for telemetry failures (won't abort, but log + metrics will signal issues)
- Phase 4 training loop (cloud GPU) is out-of-scope for local testing — real training will validate end-to-end
- Wheel-exclusion config is guaranteed; actual build verification deferred to CI/release pipeline

**Status: DONE**

Commit: `824f8ff` (feat: implement data collector scaffold and solver integration)
Branch: `feat/data-flywheel`
58 changes: 58 additions & 0 deletions docs/journals/journal-260614-tier-b-detection-and-mps.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Tier B: 4x4 Detection Model Pipeline + Apple Silicon MPS Support

**Date**: 2026-06-14 14:00
**Severity**: Medium
**Component**: Data flywheel (Tier B), training infrastructure, runtime solver
**Status**: Completed

## What Happened

Completed Tier B of the data flywheel in a single autonomous session: a full custom detection model pipeline for reCAPTCHA's 4x4 grid challenge type. Simultaneously discovered and enabled Apple Silicon (M2 Max) MPS training support, eliminating the assumption that Mac users must use cloud GPUs for small datasets.

## The Critical Realization

The hardest lesson came at the intersection of automation and reality: **a production detection model cannot be trained without human annotation**. The pipeline is complete and smoke-proven, but the trained artifact is blocked indefinitely on humans manually annotating collected cell bboxes. This is honest framing: "pipeline done" ≠ "model trained". Every automation attempt (pseudo-labeling from reCAPTCHA's pass/fail signal) was a dead end — there's no ground truth signal in the challenge itself.

## Technical Details

**Commits (4):**
- `526eb81` — YOLODetector fail-fast + per-cell 4x4 fallback
- `72f9db7` — Tier B scaffold phases 1, 2, 4 (full-image collection, bbox annotation CLI, detection dataset builder, solver integration)
- `3bcca17` — Device auto-detection (CUDA > MPS > CPU) + `--amp/--no-amp` flag
- `54d7200` — Detection trainer (`train_detection.py`), model card writer, collect loop driver

**Pipeline architecture:**
- Phase 1: `DataCollector.record_challenge_image()` → `collected/full/` (full 4x4 images + metadata, separate from existing per-cell tiles)
- Phase 2: `annotate_detection_cli.py` (human marks cell bboxes) → `prepare_detection_dataset.py` builds YOLO detection data.yaml
- Phase 3: `train_detection.py` (device auto-resolve, `--amp`, resumable) + `export_onnx.py` + SHA256 verification + model card
- Phase 4: Runtime 3-tier dispatch for 4x4: COCO detections → custom detection (if present) → per-cell classification fallback, all behind optional `custom_detection_model_path`

**MPS discovery:** Tested smoke train on M2 Max MPS (synthetic YOLO-detect data, 1 epoch, base yolo11n.pt) → `best.pt` with `args.yaml task=detect device=mps`. Confirmed the training loop handles MPS correctly. Dev extras now include `onnx` and `onnxslim` for export; runtime keeps `onnxruntime` only.

## What We Tried

1. **Pseudo-label automation:** Attempted to infer cell class from reCAPTCHA pass/fail. Rejected — no per-tile signal available.
2. **Training on CPU-only Mac:** Before discovering MPS, assumed Colab was mandatory. MPS proves small Tier B datasets train fast locally (5–10 min on M2 Max).
3. **Reusing classification data:** Initially considered repurposing per-cell tiles as weak supervision. Decided against — different domain (single objects vs. multi-object grid). New data pipeline built.

## Root Cause Analysis

The annotation bottleneck is not a bug but a **design boundary**. reCAPTCHA challenges are verification-only (human solves, system verifies); they emit no fine-grained ground truth. We built a collection and annotation workflow, but execution depends on human time. This is the "brutal honesty" stated in the plan: "no shortcuts" for data quality.

The MPS assumption was overly pessimistic. Mac's machine learning ecosystem includes GPU support via MPS; we didn't check initially because the focus was rCAPTCHA solving, not training. The device auto-detection logic (`resolve_device`) now handles all three tiers.

## Lessons Learned

1. **Pipeline completeness ≠ model readiness.** Code done, artifact blocked, and that's OK to say aloud.
2. **Check your hardware assumptions.** Apple Silicon has MPS; M2 Max runs small training jobs competitively with cloud for iteration. Saves cost and latency for fast prototyping.
3. **Weak supervision is a choice, not a shortcut.** Cell-level grid bboxes are honest weak labels (one bbox per clicked cell); they're acceptable for a first model but require human annotation still.
4. **Test contracts across training and runtime.** Added assert: `DETECTION_CLASSES (train) == CUSTOM_DETECTION_CLASSES (runtime)`. Prevents silent mismatches.

## Next Steps

1. **Human annotation phase:** Collect and annotate real challenge 4x4 images (use `annotate_detection_cli.py`). No timeline given — data quality is the gate.
2. **Train on collected data:** Once 50+ annotated images exist, run `train_detection.py --device mps --epochs 10` locally or scale to Colab for larger datasets.
3. **Validate and export:** Verify mAP on held-out set, export ONNX, compute SHA256, push to Hugging Face.
4. **Upstream PR:** #8 awaits maintainer review (145 unit tests pass, ruff/mypy clean except 4 pre-existing mypy only).

**Status**: DONE
Loading