Spaces:
Paused
Paused
update the app
Browse files- .gitignore +2 -1
- README.md +1 -1
- app.py +62 -10
.gitignore
CHANGED
|
@@ -3,4 +3,5 @@ __pycache__/
|
|
| 3 |
route_venv/
|
| 4 |
.env
|
| 5 |
/updates
|
| 6 |
-
/github code/
|
|
|
|
|
|
| 3 |
route_venv/
|
| 4 |
.env
|
| 5 |
/updates
|
| 6 |
+
/github code/
|
| 7 |
+
Project_Inventory_and_Technical_Overview.md
|
README.md
CHANGED
|
@@ -16,4 +16,4 @@ short_description: Route image to structured JSON with constraint extraction.
|
|
| 16 |
|
| 17 |
Upload a route document image — get clean structured JSON with constraint classification.
|
| 18 |
|
| 19 |
-
**Model:** `
|
|
|
|
| 16 |
|
| 17 |
Upload a route document image — get clean structured JSON with constraint classification.
|
| 18 |
|
| 19 |
+
**Model:** `gemini-flash-latest` (via Google GenAI) and `PaddleOCR` (Lightweight)
|
app.py
CHANGED
|
@@ -1,15 +1,6 @@
|
|
| 1 |
"""
|
| 2 |
app.py – OCR Route Data Extraction | Hugging Face Space
|
| 3 |
=======================================================
|
| 4 |
-
|
| 5 |
-
Improved Pipeline:
|
| 6 |
-
1. Engine-specific preprocessing
|
| 7 |
-
2. OCR with EasyOCR OR Lightweight PaddleOCR
|
| 8 |
-
3. OCR normalization
|
| 9 |
-
4. Content-based semantic column classification
|
| 10 |
-
5. Row reconstruction
|
| 11 |
-
6. Rule-based constraint extraction
|
| 12 |
-
7. Structured JSON output
|
| 13 |
"""
|
| 14 |
|
| 15 |
from __future__ import annotations
|
|
@@ -23,12 +14,57 @@ import logging
|
|
| 23 |
import re
|
| 24 |
import time
|
| 25 |
from statistics import median
|
| 26 |
-
from typing import Optional
|
| 27 |
|
| 28 |
import cv2
|
| 29 |
import gradio as gr
|
| 30 |
import numpy as np
|
| 31 |
from PIL import Image
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
|
| 33 |
|
| 34 |
# ============================================================
|
|
@@ -753,6 +789,7 @@ def run_gemini_correction(image_pil: Image.Image, initial_json: dict, api_key: s
|
|
| 753 |
|
| 754 |
client = genai.Client(api_key=api_key)
|
| 755 |
|
|
|
|
| 756 |
prompt = f"""
|
| 757 |
You are an expert OCR data extraction and validation system for route documents.
|
| 758 |
You are given an image of a route document and an initial JSON extraction. The basic OCR system is flawed and often misses data or misinterprets columns.
|
|
@@ -770,6 +807,9 @@ CRITICAL EXTRACTION RULES:
|
|
| 770 |
|
| 771 |
Output ONLY valid JSON matching the exact schema.
|
| 772 |
|
|
|
|
|
|
|
|
|
|
| 773 |
Initial JSON (FLAWED - USE FOR REFERENCE ONLY):
|
| 774 |
{json.dumps(initial_json, indent=2)}
|
| 775 |
"""
|
|
@@ -779,11 +819,17 @@ Initial JSON (FLAWED - USE FOR REFERENCE ONLY):
|
|
| 779 |
contents=[image_pil, prompt],
|
| 780 |
config=types.GenerateContentConfig(
|
| 781 |
response_mime_type="application/json",
|
|
|
|
| 782 |
temperature=0.0
|
| 783 |
)
|
| 784 |
)
|
| 785 |
|
| 786 |
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 787 |
return json.loads(response.text)
|
| 788 |
except json.JSONDecodeError:
|
| 789 |
raise ValueError(f"Failed to parse JSON from Gemini response:\n{response.text}")
|
|
@@ -986,6 +1032,11 @@ def run_pipeline(
|
|
| 986 |
"steps": steps
|
| 987 |
}
|
| 988 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 989 |
if "Gemini" in ocr_engine or api_key.strip():
|
| 990 |
progress(0.85, desc="Validating the JSON output ...")
|
| 991 |
try:
|
|
@@ -999,6 +1050,7 @@ def run_pipeline(
|
|
| 999 |
result["accuracy_metrics"] = {}
|
| 1000 |
result["accuracy_metrics"]["total_accuracy"] = 98.5
|
| 1001 |
result["accuracy_metrics"]["gemini_confidence"] = "High"
|
|
|
|
| 1002 |
except Exception as e:
|
| 1003 |
import traceback; traceback.print_exc()
|
| 1004 |
error_text = str(e)
|
|
|
|
| 1 |
"""
|
| 2 |
app.py – OCR Route Data Extraction | Hugging Face Space
|
| 3 |
=======================================================
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
"""
|
| 5 |
|
| 6 |
from __future__ import annotations
|
|
|
|
| 14 |
import re
|
| 15 |
import time
|
| 16 |
from statistics import median
|
| 17 |
+
from typing import List, Optional
|
| 18 |
|
| 19 |
import cv2
|
| 20 |
import gradio as gr
|
| 21 |
import numpy as np
|
| 22 |
from PIL import Image
|
| 23 |
+
from pydantic import BaseModel, ConfigDict, Field, ValidationError
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
class ConstraintModel(BaseModel):
|
| 27 |
+
model_config = ConfigDict(populate_by_name=True)
|
| 28 |
+
|
| 29 |
+
type: str
|
| 30 |
+
action: str
|
| 31 |
+
from_: str = Field(alias="from")
|
| 32 |
+
to: str
|
| 33 |
+
priority: Optional[str] = None
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
class StepModel(BaseModel):
|
| 37 |
+
step: int = Field(ge=1)
|
| 38 |
+
given_miles: float = Field(ge=0)
|
| 39 |
+
road: str
|
| 40 |
+
instruction: str
|
| 41 |
+
distance: float = Field(ge=0)
|
| 42 |
+
est_time: str = Field(pattern=r"^\d{2}:\d{2}$")
|
| 43 |
+
constraints: List[ConstraintModel] = Field(default_factory=list)
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
class AccuracyMetricsModel(BaseModel):
|
| 47 |
+
ocr_confidence: Optional[float] = Field(default=None, ge=0, le=100)
|
| 48 |
+
extraction_score: Optional[float] = Field(default=None, ge=0, le=100)
|
| 49 |
+
total_accuracy: Optional[float] = Field(default=None, ge=0, le=100)
|
| 50 |
+
gemini_confidence: Optional[str] = None
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
class RouteExtractionModel(BaseModel):
|
| 54 |
+
source: str
|
| 55 |
+
extracted_at: str
|
| 56 |
+
ocr_engine: str
|
| 57 |
+
extraction: str
|
| 58 |
+
accuracy_metrics: Optional[AccuracyMetricsModel] = None
|
| 59 |
+
total_steps: int = Field(ge=0)
|
| 60 |
+
total_miles: float = Field(ge=0)
|
| 61 |
+
total_time: str = Field(pattern=r"^\d{2}:\d{2}$")
|
| 62 |
+
steps: List[StepModel]
|
| 63 |
+
warning: Optional[str] = None
|
| 64 |
+
gemini_error: Optional[str] = None
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
ROUTE_EXTRACTION_JSON_SCHEMA = RouteExtractionModel.model_json_schema()
|
| 68 |
|
| 69 |
|
| 70 |
# ============================================================
|
|
|
|
| 789 |
|
| 790 |
client = genai.Client(api_key=api_key)
|
| 791 |
|
| 792 |
+
schema_text = json.dumps(ROUTE_EXTRACTION_JSON_SCHEMA, indent=2)
|
| 793 |
prompt = f"""
|
| 794 |
You are an expert OCR data extraction and validation system for route documents.
|
| 795 |
You are given an image of a route document and an initial JSON extraction. The basic OCR system is flawed and often misses data or misinterprets columns.
|
|
|
|
| 807 |
|
| 808 |
Output ONLY valid JSON matching the exact schema.
|
| 809 |
|
| 810 |
+
JSON SCHEMA (MUST MATCH):
|
| 811 |
+
{schema_text}
|
| 812 |
+
|
| 813 |
Initial JSON (FLAWED - USE FOR REFERENCE ONLY):
|
| 814 |
{json.dumps(initial_json, indent=2)}
|
| 815 |
"""
|
|
|
|
| 819 |
contents=[image_pil, prompt],
|
| 820 |
config=types.GenerateContentConfig(
|
| 821 |
response_mime_type="application/json",
|
| 822 |
+
response_schema=RouteExtractionModel,
|
| 823 |
temperature=0.0
|
| 824 |
)
|
| 825 |
)
|
| 826 |
|
| 827 |
try:
|
| 828 |
+
parsed = getattr(response, "parsed", None)
|
| 829 |
+
if parsed is not None:
|
| 830 |
+
if hasattr(parsed, "model_dump"):
|
| 831 |
+
return parsed.model_dump(by_alias=True)
|
| 832 |
+
return parsed
|
| 833 |
return json.loads(response.text)
|
| 834 |
except json.JSONDecodeError:
|
| 835 |
raise ValueError(f"Failed to parse JSON from Gemini response:\n{response.text}")
|
|
|
|
| 1032 |
"steps": steps
|
| 1033 |
}
|
| 1034 |
|
| 1035 |
+
try:
|
| 1036 |
+
result = RouteExtractionModel.model_validate(result).model_dump(by_alias=True)
|
| 1037 |
+
except ValidationError as e:
|
| 1038 |
+
log.warning("Schema validation failed for rule-based output: %s", e)
|
| 1039 |
+
|
| 1040 |
if "Gemini" in ocr_engine or api_key.strip():
|
| 1041 |
progress(0.85, desc="Validating the JSON output ...")
|
| 1042 |
try:
|
|
|
|
| 1050 |
result["accuracy_metrics"] = {}
|
| 1051 |
result["accuracy_metrics"]["total_accuracy"] = 98.5
|
| 1052 |
result["accuracy_metrics"]["gemini_confidence"] = "High"
|
| 1053 |
+
result = RouteExtractionModel.model_validate(result).model_dump(by_alias=True)
|
| 1054 |
except Exception as e:
|
| 1055 |
import traceback; traceback.print_exc()
|
| 1056 |
error_text = str(e)
|