Varun95 commited on
Commit
fdc34a4
·
verified ·
1 Parent(s): a133e12

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +28 -248
app.py CHANGED
@@ -1,253 +1,33 @@
1
- """
2
- ScriptFlow — Handwriting PDF → Word Document Converter
3
- Hugging Face Spaces (CPU Free Tier)
4
- """
5
-
6
- import os
7
- import glob
8
- import shutil
9
- import tempfile
10
- import subprocess
11
- from pathlib import Path
12
-
13
  import gradio as gr
14
- import torch
15
  from PIL import Image
16
- from transformers import TrOCRProcessor, VisionEncoderDecoderModel, pipeline
17
- from docx import Document
18
- from docx.shared import Pt, Inches
19
- from docx.enum.text import WD_ALIGN_PARAGRAPH
20
-
21
-
22
- # ──────────────────────────────────────────────────────────────────────────────
23
- # Model Caching Setup
24
- # ──────────────────────────────────────────────────────────────────────────────
25
-
26
- _trocr_cache = {}
27
- _grammar_pipe = None
28
-
29
-
30
- def get_trocr_model(model_name: str):
31
- if model_name not in _trocr_cache:
32
- processor = TrOCRProcessor.from_pretrained(model_name)
33
- model = VisionEncoderDecoderModel.from_pretrained(model_name)
34
- model.eval()
35
- _trocr_cache[model_name] = (processor, model)
36
- return _trocr_cache[model_name]
37
-
38
-
39
- def get_grammar_pipeline():
40
- global _grammar_pipe
41
- if _grammar_pipe is None:
42
- _grammar_pipe = pipeline(
43
- "text2text-generation",
44
- model="vennify/t5-base-grammar-correction",
45
- tokenizer="vennify/t5-base-grammar-correction",
46
- )
47
- return _grammar_pipe
48
-
49
-
50
- # ──────────────────────────────────────────────────────────────────────────────
51
- # Core Processing Functions
52
- # ──────────────────────────────────────────────────────────────────────────────
53
-
54
- def pdf_to_images(pdf_path: str, out_dir: str) -> list[str]:
55
- pages_dir = os.path.join(out_dir, "pages")
56
- os.makedirs(pages_dir, exist_ok=True)
57
-
58
- result = subprocess.run(
59
- ["pdftoppm", "-png", "-r", "200", pdf_path, os.path.join(pages_dir, "page")],
60
- capture_output=True,
61
- text=True,
62
- timeout=180,
63
- )
64
- if result.returncode != 0:
65
- raise RuntimeError(f"pdftoppm failed: {result.stderr.strip()}")
66
-
67
- images = sorted(glob.glob(os.path.join(pages_dir, "*.png")))
68
- if not images:
69
- raise RuntimeError("No page images could be extracted from the PDF.")
70
- return images
71
-
72
-
73
- def ocr_images(image_paths: list[str], model_name: str, progress: gr.Progress) -> list[str]:
74
- processor, model = get_trocr_model(model_name)
75
- page_texts = []
76
-
77
- total_pages = len(image_paths)
78
- for idx, img_path in enumerate(image_paths):
79
- progress((0.1 + (idx / total_pages) * 0.5), desc=f"OCR processing: page {idx + 1}/{total_pages}")
80
-
81
- image = Image.open(img_path).convert("RGB")
82
- pixel_values = processor(images=image, return_tensors="pt").pixel_values
83
-
84
- with torch.no_grad():
85
- generated_ids = model.generate(pixel_values)
86
-
87
- text = processor.batch_decode(generated_ids, skip_special_tokens=True)[0]
88
- page_texts.append(text.strip())
89
-
90
- return page_texts
91
-
92
-
93
- def refine_grammar(raw_pages: list[str], progress: gr.Progress) -> list[str]:
94
- pipe = get_grammar_pipeline()
95
- refined_pages = []
96
-
97
- total_pages = len(raw_pages)
98
- for idx, page in enumerate(raw_pages):
99
- progress((0.6 + (idx / total_pages) * 0.3), desc=f"Refining grammar: page {idx + 1}/{total_pages}")
100
-
101
- if not page.strip():
102
- refined_pages.append("")
103
- continue
104
-
105
- # Chunk text into 400-char segments for T5 context window
106
- chunks = [page[i:i + 400] for i in range(0, len(page), 400)]
107
- corrected_chunks = []
108
-
109
- for chunk in chunks:
110
- if not chunk.strip():
111
- continue
112
- out = pipe(
113
- f"grammar: {chunk}",
114
- max_length=512,
115
- num_beams=4,
116
- early_stopping=True,
117
- )
118
- corrected_chunks.append(out[0]["generated_text"])
119
-
120
- refined_pages.append(" ".join(corrected_chunks) if corrected_chunks else page)
121
-
122
- return refined_pages
123
-
124
-
125
- def export_to_docx(pages: list[str], output_path: str):
126
- doc = Document()
127
-
128
- for section in doc.sections:
129
- section.top_margin = Inches(1)
130
- section.bottom_margin = Inches(1)
131
- section.left_margin = Inches(1)
132
- section.right_margin = Inches(1)
133
-
134
- heading = doc.add_heading("Converted Handwritten Document", level=1)
135
- heading.alignment = WD_ALIGN_PARAGRAPH.CENTER
136
- doc.add_paragraph()
137
-
138
- for idx, page_text in enumerate(pages):
139
- if idx > 0:
140
- doc.add_page_break()
141
-
142
- lines = page_text.split("\n")
143
- for line in lines:
144
- line_str = line.strip()
145
- p = doc.add_paragraph()
146
- if line_str:
147
- run = p.add_run(line_str)
148
- run.font.name = "Calibri"
149
- run.font.size = Pt(12)
150
-
151
- doc.save(output_path)
152
-
153
-
154
- # ──────────────────────────────────────────────────────────────────────────────
155
- # Main Pipeline Executable
156
- # ──────────────────────────────────────────────────────────────────────────────
157
-
158
- def convert_pdf(pdf_file, trocr_model: str, use_grammar: bool, progress=gr.Progress()):
159
- if pdf_file is None:
160
- raise gr.Error("Please upload a handwritten PDF file.")
161
-
162
- # Cross-version filepath handling
163
- pdf_path = pdf_file if isinstance(pdf_file, str) else getattr(pdf_file, "name", str(pdf_file))
164
-
165
- with tempfile.TemporaryDirectory() as tmp_dir:
166
- # Step 1: Convert PDF to PNGs
167
- progress(0.05, desc="Extracting pages from PDF...")
168
- images = pdf_to_images(pdf_path, tmp_dir)
169
-
170
- # Step 2: OCR with TrOCR
171
- progress(0.10, desc="Loading OCR Model...")
172
- raw_pages = ocr_images(images, trocr_model, progress)
173
- raw_text_combined = "\n\n--- [PAGE BREAK] ---\n\n".join(raw_pages)
174
-
175
- # Step 3: Optional T5 Grammar Correction
176
- if use_grammar:
177
- progress(0.60, desc="Loading Grammar Model...")
178
- final_pages = refine_grammar(raw_pages, progress)
179
- else:
180
- final_pages = raw_pages
181
-
182
- final_text_combined = "\n\n--- [PAGE BREAK] ---\n\n".join(final_pages)
183
-
184
- # Step 4: Write .docx File
185
- progress(0.92, desc="Creating Word Document...")
186
- docx_temp_path = os.path.join(tmp_dir, "ScriptFlow_Output.docx")
187
- export_to_docx(final_pages, docx_temp_path)
188
-
189
- # Export file out of temporary scope for downloading
190
- output_file = tempfile.NamedTemporaryFile(suffix=".docx", delete=False, prefix="scriptflow_")
191
- output_file.close()
192
- shutil.copy(docx_temp_path, output_file.name)
193
-
194
- progress(1.0, desc="Conversion Complete!")
195
- return raw_text_combined, final_text_combined, output_file.name
196
-
197
-
198
- # ──────────────────────────────────────────────────────────────────────────────
199
- # Gradio UI Design
200
- # ──────────────────────────────────────────────────────────────────────────────
201
-
202
- with gr.Blocks(title="ScriptFlow — PDF to Word Converter", theme=gr.themes.Soft()) as demo:
203
- gr.Markdown(
204
- """
205
- # ✍️ ScriptFlow
206
- ### Convert Handwritten PDFs to Word (.docx) Documents
207
- *Extract handwritten text with TrOCR, clean up syntax with T5, and download a ready-to-edit `.docx` file.*
208
- """
209
- )
210
-
211
- with gr.Row():
212
- with gr.Column(scale=1):
213
- pdf_input = gr.File(
214
- label="Upload Handwritten PDF",
215
- file_types=[".pdf"],
216
- type="filepath",
217
- )
218
- model_selector = gr.Radio(
219
- choices=[
220
- ("Small (~330 MB) — Faster", "microsoft/trocr-small-handwritten"),
221
- ("Base (~440 MB) — More Accurate", "microsoft/trocr-base-handwritten"),
222
- ],
223
- value="microsoft/trocr-small-handwritten",
224
- label="TrOCR Model",
225
- )
226
- grammar_toggle = gr.Checkbox(
227
- label="Enable T5 Grammar & Refinement",
228
- value=False,
229
- )
230
- convert_btn = gr.Button("Convert Document", variant="primary")
231
-
232
- with gr.Column(scale=2):
233
- raw_output = gr.Textbox(label="Raw OCR Output", lines=10, interactive=False)
234
- refined_output = gr.Textbox(label="Refined Output", lines=10, interactive=False)
235
- docx_download = gr.File(label="Download Word (.docx) File", interactive=False)
236
-
237
- gr.Markdown(
238
- """
239
- ---
240
- **Tips for best results:**
241
- * High-resolution scans (200+ DPI) work significantly better.
242
- * Grammar correction adds extra processing time per page on CPU.
243
- """
244
- )
245
-
246
- convert_btn.click(
247
- fn=convert_pdf,
248
- inputs=[pdf_input, model_selector, grammar_toggle],
249
- outputs=[raw_output, refined_output, docx_download],
250
- )
251
 
252
  if __name__ == "__main__":
253
  demo.launch()
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import gradio as gr
 
2
  from PIL import Image
3
+ from transformers import TrOCRProcessor, VisionEncoderDecoderModel
4
+
5
+ # Load lightweight CPU-friendly model
6
+ processor = TrOCRProcessor.from_pretrained("microsoft/trocr-small-handwritten")
7
+ model = VisionEncoderDecoderModel.from_pretrained("microsoft/trocr-small-handwritten")
8
+
9
+ def parse_handwriting(image):
10
+ if image is None:
11
+ return "Please upload an image."
12
+
13
+ # Convert image to RGB
14
+ image = image.convert("RGB")
15
+
16
+ # Process image and generate text
17
+ pixel_values = processor(images=image, return_tensors="pt").pixel_values
18
+ generated_ids = model.generate(pixel_values)
19
+ generated_text = processor.batch_decode(generated_ids, skip_special_tokens=True)[0]
20
+
21
+ return generated_text
22
+
23
+ # Simple Gradio interface
24
+ demo = gr.Interface(
25
+ fn=parse_handwriting,
26
+ inputs=gr.Image(type="pil", label="Upload Handwritten Image"),
27
+ outputs=gr.Textbox(label="Extracted Text"),
28
+ title="Handwriting Parser (CPU Friendly)",
29
+ description="Upload a cropped image of handwritten text to transcribe it."
30
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
 
32
  if __name__ == "__main__":
33
  demo.launch()