mdnaseif commited on
Commit
9f4c20d
Β·
verified Β·
1 Parent(s): f909164

Upload inference.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. inference.py +167 -0
inference.py ADDED
@@ -0,0 +1,167 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ HAFITH β€” Arabic Manuscript OCR Inference
3
+ =========================================
4
+ Standalone script. Downloads models from HF Hub on first run.
5
+
6
+ Usage:
7
+ python inference.py manuscript.jpg
8
+ python inference.py manuscript.jpg --output result.txt
9
+ python inference.py manuscript.jpg --gemini-key YOUR_KEY
10
+ """
11
+
12
+ import argparse
13
+ import sys
14
+ import os
15
+ from pathlib import Path
16
+
17
+
18
+ # ── CLI ───────────────────────────────────────────────────────────────────────
19
+
20
+ def parse_args():
21
+ p = argparse.ArgumentParser(description="HAFITH Arabic Manuscript OCR")
22
+ p.add_argument("image", help="Path to manuscript image (JPG/PNG/TIFF)")
23
+ p.add_argument("--output", "-o", help="Save transcription to this file")
24
+ p.add_argument("--gemini-key", help="Gemini API key for AI post-correction")
25
+ p.add_argument("--device", default="auto", help="cuda / cpu / auto (default: auto)")
26
+ p.add_argument("--models-dir", default=None, help="Local models directory (skips HF download)")
27
+ return p.parse_args()
28
+
29
+
30
+ # ── Model download ─────────────────────────────────────────────────────────────
31
+
32
+ def get_models_dir(local_override=None):
33
+ if local_override:
34
+ return local_override
35
+ try:
36
+ from huggingface_hub import snapshot_download
37
+ except ImportError:
38
+ print("Run: pip install huggingface_hub")
39
+ sys.exit(1)
40
+
41
+ print("Downloading models from HF Hub (one-time, ~4.3 GB)...")
42
+ return snapshot_download("mdnaseif/hafith-models")
43
+
44
+
45
+ # ── Pipeline ──────────────────────────────────────────────────────────────────
46
+
47
+ def run(image_path, models_dir, device, gemini_key=None):
48
+ # Add hafith app/ to path β€” works whether running from repo or standalone
49
+ repo_app = Path(__file__).parent / "app"
50
+ if repo_app.exists():
51
+ sys.path.insert(0, str(repo_app))
52
+
53
+ try:
54
+ from pipeline import (
55
+ load_lines_model, load_regions_model, load_ocr,
56
+ segment, detect_regions, classify_lines_by_region,
57
+ get_line_images, recognise_lines_batch,
58
+ )
59
+ except ImportError as e:
60
+ print(f"Import error: {e}")
61
+ print("Make sure you cloned https://github.com/mdnaseif/hafith_mvp and are running from there.")
62
+ sys.exit(1)
63
+
64
+ models_dir = Path(models_dir)
65
+
66
+ # ── 1. Load models ─────────────────────────────────────────────────────────
67
+ print(f"Loading models on {device}...")
68
+
69
+ print(" β†’ RTMDet line segmentation")
70
+ lines_model = load_lines_model(
71
+ config_path=str(models_dir / "rtmdet_lines.py"),
72
+ checkpoint_path=str(models_dir / "lines.pth"),
73
+ device=device,
74
+ )
75
+
76
+ print(" β†’ YOLO region detection")
77
+ regions_model = load_regions_model(str(models_dir / "regions.pt"))
78
+
79
+ print(" β†’ OCR model (SigLIP2 + Qwen3-0.6B)")
80
+ ocr_model, processor, tokenizer = load_ocr(str(models_dir / "ocr"), device=device)
81
+
82
+ print("Models loaded.\n")
83
+
84
+ # ── 2. Segment ─────────────────────────────────────────────────────────────
85
+ print("Segmenting lines...")
86
+ image_bgr, polygons = segment(lines_model, image_path, conf=0.2)
87
+ num_lines = len(polygons)
88
+
89
+ if num_lines == 0:
90
+ print("No text lines detected. Try a higher-resolution scan.")
91
+ sys.exit(1)
92
+
93
+ print(f"Found {num_lines} lines.")
94
+
95
+ # ── 3. Region classification ───────────────────────────────────────────────
96
+ try:
97
+ region_polys, region_conf = detect_regions(regions_model, image_path, conf=0.5)
98
+ if region_conf >= 0.75:
99
+ main_idx, margin_idx, _ = classify_lines_by_region(polygons, region_polys)
100
+ else:
101
+ import numpy as np
102
+ main_idx = sorted(range(num_lines),
103
+ key=lambda i: np.array(polygons[i])[:, 1].mean())
104
+ margin_idx = []
105
+ except Exception:
106
+ import numpy as np
107
+ main_idx = sorted(range(num_lines),
108
+ key=lambda i: np.array(polygons[i])[:, 1].mean())
109
+ margin_idx = []
110
+
111
+ # ── 4. OCR ─────────────────────────────────────────────────────────────────
112
+ print("Recognising text...")
113
+ line_images = get_line_images(image_bgr, polygons)
114
+ reading_order = list(main_idx) + list(margin_idx)
115
+ ordered_images = [line_images[i] for i in reading_order]
116
+
117
+ texts = recognise_lines_batch(
118
+ ocr_model, processor, tokenizer,
119
+ ordered_images,
120
+ device=device,
121
+ max_patches=512,
122
+ max_len=64,
123
+ batch_size=8,
124
+ )
125
+
126
+ raw_texts = [""] * num_lines
127
+ for idx, text in zip(reading_order, texts):
128
+ raw_texts[idx] = text
129
+
130
+ # ── 5. Gemini correction (optional) ───────────────────────────────────────
131
+ final_texts = list(raw_texts)
132
+ if gemini_key:
133
+ print("Applying AI post-correction...")
134
+ os.environ["GEMINI_API_KEY"] = gemini_key
135
+ from pipeline.correction import init_local_llm, correct_full_text_local
136
+ corrector = init_local_llm("gemini-2.0-flash")
137
+ final_texts = correct_full_text_local(corrector, raw_texts, sorted_indices=reading_order)
138
+
139
+ # ── 6. Output ──────────────────────────────────────────────────────────────
140
+ full_text = "\n".join(final_texts[i] for i in reading_order)
141
+
142
+ print("\n" + "─" * 60)
143
+ print(full_text)
144
+ print("─" * 60)
145
+ print(f"\n{num_lines} lines recognised.")
146
+
147
+ return full_text
148
+
149
+
150
+ # ── Entry point ───────────────────────────────────────────────────────────────
151
+
152
+ if __name__ == "__main__":
153
+ args = parse_args()
154
+
155
+ import torch
156
+ if args.device == "auto":
157
+ device = "cuda" if torch.cuda.is_available() else "cpu"
158
+ print(f"Device: {device}")
159
+ else:
160
+ device = args.device
161
+
162
+ models_dir = get_models_dir(args.models_dir)
163
+ result = run(args.image, models_dir, device, gemini_key=args.gemini_key)
164
+
165
+ if args.output:
166
+ Path(args.output).write_text(result, encoding="utf-8")
167
+ print(f"Saved to {args.output}")