| from __future__ import annotations |
|
|
| import json |
| import tempfile |
| import unittest |
| from pathlib import Path |
|
|
| from datasets.field_notes import FieldNote, FieldNoteStore |
| from datasets.ocr import ( |
| export_corrected_ocr_notes, |
| import_uncertain_predictions, |
| load_ocr_predictions, |
| ocr_import_summary, |
| uncertain_predictions, |
| ) |
| from ui.notes_tab import import_ocr_predictions, preview_ocr_predictions |
|
|
|
|
| class OCRPipelineTest(unittest.TestCase): |
| def test_loads_ocr_predictions_from_jsonl(self) -> None: |
| with tempfile.TemporaryDirectory() as tmp: |
| path = Path(tmp) / "ocr.jsonl" |
| path.write_text( |
| json.dumps( |
| { |
| "source_path": "receipt.png", |
| "text": "Total 12.30", |
| "confidence": 0.91, |
| "page": 1, |
| } |
| ) |
| + "\n", |
| encoding="utf-8", |
| ) |
|
|
| predictions = load_ocr_predictions(path) |
|
|
| self.assertEqual(len(predictions), 1) |
| self.assertEqual(predictions[0].source_path, "receipt.png") |
| self.assertEqual(predictions[0].confidence, 0.91) |
| self.assertEqual(predictions[0].page, "1") |
|
|
| def test_loads_ocr_predictions_from_csv_alias_columns(self) -> None: |
| with tempfile.TemporaryDirectory() as tmp: |
| path = Path(tmp) / "ocr.csv" |
| path.write_text( |
| "image_path,prediction,score\nlabel.png,Best before 2026,0.72\n", |
| encoding="utf-8", |
| ) |
|
|
| predictions = load_ocr_predictions(path) |
|
|
| self.assertEqual(predictions[0].source_path, "label.png") |
| self.assertEqual(predictions[0].text, "Best before 2026") |
| self.assertEqual(predictions[0].confidence, 0.72) |
|
|
| def test_filters_uncertain_predictions_by_threshold_and_empty_text(self) -> None: |
| with tempfile.TemporaryDirectory() as tmp: |
| path = Path(tmp) / "ocr.jsonl" |
| rows = [ |
| {"source_path": "a.png", "text": "clear", "confidence": 0.95}, |
| {"source_path": "b.png", "text": "maybe", "confidence": 0.7}, |
| {"source_path": "c.png", "text": "", "confidence": 0.99}, |
| ] |
| path.write_text( |
| "\n".join(json.dumps(row) for row in rows), |
| encoding="utf-8", |
| ) |
|
|
| uncertain = uncertain_predictions(load_ocr_predictions(path), 0.8) |
|
|
| self.assertEqual([item.source_path for item in uncertain], ["b.png", "c.png"]) |
|
|
| def test_imports_uncertain_predictions_to_field_notes(self) -> None: |
| with tempfile.TemporaryDirectory() as tmp: |
| predictions_path = Path(tmp) / "ocr.jsonl" |
| predictions_path.write_text( |
| "\n".join( |
| [ |
| json.dumps( |
| { |
| "source_path": "uncertain.png", |
| "text": "hel1o", |
| "confidence": 0.5, |
| } |
| ), |
| json.dumps( |
| { |
| "source_path": "confident.png", |
| "text": "hello", |
| "confidence": 0.99, |
| } |
| ), |
| ] |
| ), |
| encoding="utf-8", |
| ) |
| store = FieldNoteStore(Path(tmp) / "field_notes.csv") |
|
|
| imported = import_uncertain_predictions( |
| store, |
| load_ocr_predictions(predictions_path), |
| "minicpm5_1b", |
| confidence_threshold=0.8, |
| ) |
|
|
| notes = store.list_notes(tag="ocr") |
| self.assertEqual(imported, 1) |
| self.assertEqual(len(notes), 1) |
| self.assertEqual(notes[0].response, "hel1o") |
| self.assertEqual(notes[0].image_path, "uncertain.png") |
| self.assertFalse(notes[0].use_for_training) |
|
|
| def test_exports_corrected_ocr_notes(self) -> None: |
| with tempfile.TemporaryDirectory() as tmp: |
| store = FieldNoteStore(Path(tmp) / "field_notes.csv") |
| store.save( |
| FieldNote.create( |
| model_id="minicpm5_1b", |
| prompt="Review OCR text for receipt.png.", |
| response="Tota1", |
| correction="Total", |
| tags="ocr,uncertain", |
| image_path="receipt.png", |
| ) |
| ) |
| store.save( |
| FieldNote.create( |
| model_id="minicpm5_1b", |
| prompt="Other correction", |
| response="abc", |
| correction="abc", |
| tags="demo", |
| ) |
| ) |
|
|
| output = export_corrected_ocr_notes(store, Path(tmp) / "ocr_corrections.jsonl") |
|
|
| rows = [json.loads(line) for line in output.read_text(encoding="utf-8").splitlines()] |
| self.assertEqual(len(rows), 1) |
| self.assertEqual(rows[0]["source_path"], "receipt.png") |
| self.assertEqual(rows[0]["predicted_text"], "Tota1") |
| self.assertEqual(rows[0]["corrected_text"], "Total") |
|
|
| def test_ocr_import_summary_reports_uncertain_sample(self) -> None: |
| with tempfile.TemporaryDirectory() as tmp: |
| path = Path(tmp) / "ocr.jsonl" |
| path.write_text( |
| json.dumps({"source_path": "a.png", "text": "?", "confidence": 0.1}), |
| encoding="utf-8", |
| ) |
|
|
| summary = ocr_import_summary(path, 0.8) |
|
|
| self.assertEqual(summary["rows"], 1) |
| self.assertEqual(summary["uncertain_rows"], 1) |
| self.assertEqual(summary["confidence_threshold"], 0.8) |
|
|
| def test_notes_tab_ocr_preview_callback_reports_missing_path(self) -> None: |
| self.assertEqual( |
| preview_ocr_predictions("", 0.8), |
| {"error": "Enter a local OCR prediction file path."}, |
| ) |
|
|
| def test_notes_tab_ocr_import_callback_binds_store(self) -> None: |
| with tempfile.TemporaryDirectory() as tmp: |
| path = Path(tmp) / "ocr.jsonl" |
| path.write_text( |
| json.dumps({"source_path": "a.png", "text": "?", "confidence": 0.1}), |
| encoding="utf-8", |
| ) |
| store = FieldNoteStore(Path(tmp) / "field_notes.csv") |
|
|
| status = import_ocr_predictions(store, "minicpm5_1b", str(path), 0.8) |
|
|
| self.assertIn("Imported 1 uncertain OCR predictions", status) |
| self.assertEqual(len(store.list_notes(tag="ocr")), 1) |
|
|
|
|
| if __name__ == "__main__": |
| unittest.main() |
|
|