manishw7 commited on
Commit
9487c54
·
1 Parent(s): a00b760

Deep Alignment: Merged LoRA weights and synced token configs

Browse files
Files changed (1) hide show
  1. app.py +81 -45
app.py CHANGED
@@ -1,32 +1,57 @@
1
  import os
 
2
  import gradio as gr
3
  import torch
4
  import numpy as np
5
  import cv2
6
  from PIL import Image
7
  from peft import PeftModel
8
- from transformers import TrOCRProcessor, VisionEncoderDecoderModel
9
  from cnn_model import CharacterClassifier
10
 
11
- # --- CONFIGURATION ---
12
  BASE_MODEL_ID = "paudelanil/trocr-devanagari-2"
13
  ADAPTER_ID = "manishw10/devgen-trocr-devanagari-lora"
14
  CNN_MODEL_PATH = "devanagari-cnn-classifier.pt"
 
15
 
16
  IS_SPACE = "SPACE_ID" in os.environ
17
  device = "cuda" if torch.cuda.is_available() else "cpu"
18
 
19
- # --- INITIALIZATION ---
20
- print(f"System: Restoring Original DevGen Logic...")
21
- processor = TrOCRProcessor.from_pretrained(BASE_MODEL_ID)
 
 
 
 
 
 
 
 
 
22
  base_model = VisionEncoderDecoderModel.from_pretrained(BASE_MODEL_ID)
23
- model = PeftModel.from_pretrained(base_model, ADAPTER_ID)
 
 
 
 
 
 
 
 
 
 
 
 
 
24
  model.to(device)
25
  model.eval()
26
 
 
27
  cnn_engine = CharacterClassifier(model_path=CNN_MODEL_PATH, device=device)
28
 
29
- # --- ORIGINAL ROUTING LOGIC (1:1 from image_router.py) ---
30
  def _flood_fill(binary, visited, start_y, start_x, h, w):
31
  stack = [(start_y, start_x)]
32
  size = 0
@@ -36,10 +61,10 @@ def _flood_fill(binary, visited, start_y, start_x, h, w):
36
  continue
37
  visited[y, x] = True
38
  size += 1
39
- stack.extend([(y + 1, x), (y - 1, x), (y, x + 1), (y, x - 1)])
40
  return size
41
 
42
- def _count_blobs(binary, min_size=10):
43
  h, w = binary.shape
44
  visited = np.zeros_like(binary, dtype=bool)
45
  count = 0
@@ -51,7 +76,7 @@ def _count_blobs(binary, min_size=10):
51
  count += 1
52
  return count
53
 
54
- def original_classify_input(image):
55
  gray = image.convert("L")
56
  arr = np.array(gray)
57
  threshold = min(arr.mean() * 0.75, 200)
@@ -60,15 +85,15 @@ def original_classify_input(image):
60
  rows = np.any(binary, axis=1)
61
  cols = np.any(binary, axis=0)
62
  if not rows.any() or not cols.any():
63
- return "character", "no_ink"
64
 
65
  rmin, rmax = np.where(rows)[0][[0, -1]]
66
  cmin, cmax = np.where(cols)[0][[0, -1]]
67
  w, h = cmax - cmin + 1, rmax - rmin + 1
68
  aspect_ratio = w / max(h, 1)
69
- blob_count = _count_blobs(binary, min_size=max(binary.size * 0.001, 10))
70
 
71
- # decision logic (exact copy)
72
  is_character = True
73
  if aspect_ratio > 2.5: is_character = False
74
  elif aspect_ratio > 1.8 and blob_count >= 3: is_character = False
@@ -77,48 +102,59 @@ def original_classify_input(image):
77
  elif blob_count == 1 and aspect_ratio < 1.5: is_character = True
78
  elif aspect_ratio > 1.6: is_character = False
79
 
80
- return "character" if is_character else "word", f"AR: {aspect_ratio:.2f}, Blobs: {blob_count}"
81
 
82
- # --- MAIN INFERENCE ---
83
  def predict(image, manual_mode):
84
- if image is None: return None, "Upload image.", ""
85
 
 
86
  if manual_mode == "Automatic":
87
- mode, reason = original_classify_input(image)
 
88
  else:
89
- mode, reason = manual_mode.lower(), "Manual"
90
-
91
- if mode == "character" and cnn_engine.available:
92
- # Use exact CNN preprocessing from CharacterClassifier
93
- result = cnn_engine.predict(image)
94
- return result["text"], f"Original Logic: {mode.upper()} ({reason})", "CNN Classifier"
95
- else:
96
- # Standard TrOCR inference
97
- pixel_values = processor(image.convert("RGB"), return_tensors="pt").pixel_values.to(device)
98
- with torch.no_grad():
99
- gen_ids = model.base_model.generate(
100
- pixel_values=pixel_values,
101
- num_beams=4,
102
- max_new_tokens=64,
103
- decoder_start_token_id=model.config.decoder_start_token_id
104
- )
105
- text = processor.batch_decode(gen_ids, skip_special_tokens=True)[0]
106
- return text, f"Original Logic: {mode.upper()} ({reason})", "TrOCR + LoRA"
 
 
 
107
 
108
  # --- UI ---
109
- with gr.Blocks(theme=gr.themes.Default(), css=".gradio-container {background: #0f172a; color: white;}") as demo:
110
- gr.Markdown("# 🕉️ DevGen OCR (Original Logic)")
111
- with gr.Row():
 
 
 
 
 
 
112
  with gr.Column():
113
- inp = gr.Image(type="pil", label="Input")
114
- mode = gr.Radio(["Automatic", "Word", "Character"], value="Automatic", label="Mode")
115
- btn = gr.Button("Recognize", variant="primary")
116
  with gr.Column():
117
- out = gr.Textbox(label="Result")
118
- status = gr.Markdown("Ready.")
119
- eng = gr.Textbox(label="Engine", interactive=False)
120
 
121
- btn.click(predict, [inp, mode], [out, status, eng])
122
 
123
  if __name__ == "__main__":
124
  demo.launch()
 
1
  import os
2
+ import time
3
  import gradio as gr
4
  import torch
5
  import numpy as np
6
  import cv2
7
  from PIL import Image
8
  from peft import PeftModel
9
+ from transformers import AutoTokenizer, TrOCRProcessor, ViTImageProcessor, VisionEncoderDecoderModel
10
  from cnn_model import CharacterClassifier
11
 
12
+ # --- CONSTANTS (From local trocr_engine.py) ---
13
  BASE_MODEL_ID = "paudelanil/trocr-devanagari-2"
14
  ADAPTER_ID = "manishw10/devgen-trocr-devanagari-lora"
15
  CNN_MODEL_PATH = "devanagari-cnn-classifier.pt"
16
+ SPECIAL_TOKEN_NAMES = ("bos_token_id", "cls_token_id", "eos_token_id", "pad_token_id", "sep_token_id")
17
 
18
  IS_SPACE = "SPACE_ID" in os.environ
19
  device = "cuda" if torch.cuda.is_available() else "cpu"
20
 
21
+ # --- MODEL LOADING (Mirrored from TrOCREngine.__init__) ---
22
+ print(f"System: Aligning with local TrOCREngine...")
23
+
24
+ # 1. Load Processor
25
+ try:
26
+ processor = TrOCRProcessor.from_pretrained(BASE_MODEL_ID)
27
+ except Exception:
28
+ image_processor = ViTImageProcessor.from_pretrained("google/vit-base-patch16-224-in21k")
29
+ tokenizer = AutoTokenizer.from_pretrained(BASE_MODEL_ID)
30
+ processor = TrOCRProcessor(image_processor=image_processor, tokenizer=tokenizer)
31
+
32
+ # 2. Load and Config Model
33
  base_model = VisionEncoderDecoderModel.from_pretrained(BASE_MODEL_ID)
34
+ base_model.config.decoder_start_token_id = processor.tokenizer.cls_token_id
35
+ base_model.config.pad_token_id = processor.tokenizer.pad_token_id
36
+ base_model.config.eos_token_id = processor.tokenizer.sep_token_id
37
+ base_model.config.vocab_size = base_model.config.decoder.vocab_size
38
+
39
+ # 3. Apply and MERGE LoRA (Critical for consistency)
40
+ peft_model = PeftModel.from_pretrained(base_model, ADAPTER_ID)
41
+ try:
42
+ model = peft_model.merge_and_unload()
43
+ print("System: LoRA weights merged successfully (1:1 with local).")
44
+ except Exception as e:
45
+ print(f"System: Merge failed, using wrapper: {e}")
46
+ model = peft_model
47
+
48
  model.to(device)
49
  model.eval()
50
 
51
+ # 4. Load CNN
52
  cnn_engine = CharacterClassifier(model_path=CNN_MODEL_PATH, device=device)
53
 
54
+ # --- ORIGINAL ROUTING (Mirrored from image_router.py) ---
55
  def _flood_fill(binary, visited, start_y, start_x, h, w):
56
  stack = [(start_y, start_x)]
57
  size = 0
 
61
  continue
62
  visited[y, x] = True
63
  size += 1
64
+ stack.extend([(y+1, x), (y-1, x), (y, x+1), (y, x-1)])
65
  return size
66
 
67
+ def count_blobs(binary, min_size=10):
68
  h, w = binary.shape
69
  visited = np.zeros_like(binary, dtype=bool)
70
  count = 0
 
76
  count += 1
77
  return count
78
 
79
+ def classify_input_type(image):
80
  gray = image.convert("L")
81
  arr = np.array(gray)
82
  threshold = min(arr.mean() * 0.75, 200)
 
85
  rows = np.any(binary, axis=1)
86
  cols = np.any(binary, axis=0)
87
  if not rows.any() or not cols.any():
88
+ return "character", 0.5, 0.0, 0
89
 
90
  rmin, rmax = np.where(rows)[0][[0, -1]]
91
  cmin, cmax = np.where(cols)[0][[0, -1]]
92
  w, h = cmax - cmin + 1, rmax - rmin + 1
93
  aspect_ratio = w / max(h, 1)
94
+ blob_count = count_blobs(binary, min_size=max(binary.size * 0.001, 10))
95
 
96
+ # DECISION LOGIC (1:1 with local image_router.py)
97
  is_character = True
98
  if aspect_ratio > 2.5: is_character = False
99
  elif aspect_ratio > 1.8 and blob_count >= 3: is_character = False
 
102
  elif blob_count == 1 and aspect_ratio < 1.5: is_character = True
103
  elif aspect_ratio > 1.6: is_character = False
104
 
105
+ return ("character" if is_character else "word"), aspect_ratio, blob_count
106
 
107
+ # --- PREDICT ---
108
  def predict(image, manual_mode):
109
+ if image is None: return None, "Upload an image.", "", ""
110
 
111
+ # 1. Routing
112
  if manual_mode == "Automatic":
113
+ mode, ar, bc = classify_input_type(image)
114
+ routing_status = f"Auto: {mode.upper()} (AR: {ar:.2f}, Blobs: {bc})"
115
  else:
116
+ mode = manual_mode.lower()
117
+ routing_status = f"Manual: {mode.upper()}"
118
+
119
+ try:
120
+ if mode == "character" and cnn_engine.available:
121
+ result = cnn_engine.predict(image)
122
+ return result["text"], routing_status, "CNN Classifier", ""
123
+ else:
124
+ # Word Recognition
125
+ pixel_values = processor(image.convert("RGB"), return_tensors="pt").pixel_values.to(device)
126
+ with torch.no_grad():
127
+ outputs = model.generate(
128
+ pixel_values,
129
+ num_beams=4,
130
+ max_length=128,
131
+ early_stopping=True
132
+ )
133
+ text = processor.batch_decode(outputs, skip_special_tokens=True)[0]
134
+ return text, routing_status, "TrOCR + LoRA", ""
135
+ except Exception as e:
136
+ return f"Error: {str(e)}", "Inference Failed", "None", ""
137
 
138
  # --- UI ---
139
+ CSS = """
140
+ .gradio-container { background: #0f172a; color: white; font-family: 'Inter', sans-serif; }
141
+ .panel { background: rgba(30, 41, 59, 0.8); border-radius: 20px; padding: 20px; border: 1px solid #334155; }
142
+ .result-box { font-size: 2.5rem !important; font-weight: bold; color: #818cf8; text-align: center; }
143
+ """
144
+
145
+ with gr.Blocks(css=CSS, theme=gr.themes.Default()) as demo:
146
+ gr.Markdown("# 🕉️ DevGen OCR — Professional Engine")
147
+ with gr.Row(elem_classes="panel"):
148
  with gr.Column():
149
+ input_img = gr.Image(type="pil", label="Input Handwriting")
150
+ mode_sel = gr.Radio(["Automatic", "Word", "Character"], value="Automatic", label="Mode")
151
+ run_btn = gr.Button("Recognize", variant="primary")
152
  with gr.Column():
153
+ output_text = gr.Textbox(label="Recognition Result", elem_classes="result-box")
154
+ status_lbl = gr.Markdown("Engine ready.")
155
+ engine_lbl = gr.Textbox(label="Model Used", interactive=False)
156
 
157
+ run_btn.click(predict, [input_img, mode_sel], [output_text, status_lbl, engine_lbl])
158
 
159
  if __name__ == "__main__":
160
  demo.launch()