jeyanthangj2004 commited on
Commit
78c7412
Β·
verified Β·
1 Parent(s): e50ff33

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +377 -381
app.py CHANGED
@@ -1,381 +1,377 @@
1
- """
2
- eDOCr2 - Engineering Drawing OCR
3
- Gradio Interface for Hugging Face Spaces
4
-
5
- Extract dimensions, tables, and GD&T symbols from engineering drawings
6
- """
7
-
8
- import gradio as gr
9
- import cv2
10
- import numpy as np
11
- import json
12
- import os
13
- import time
14
- from pathlib import Path
15
- import zipfile
16
- import tempfile
17
- from PIL import Image
18
-
19
- # Import eDOCr2 modules
20
- from edocr2 import tools
21
- from edocr2.keras_ocr.recognition import Recognizer
22
- from edocr2.keras_ocr.detection import Detector
23
- from pdf2image import convert_from_path
24
-
25
- # Global variables for models
26
- recognizer_gdt = None
27
- recognizer_dim = None
28
- detector = None
29
- alphabet_dim = None
30
- models_loaded = False
31
-
32
- def load_models():
33
- """Load OCR models at startup"""
34
- global recognizer_gdt, recognizer_dim, detector, alphabet_dim, models_loaded
35
-
36
- if models_loaded:
37
- return True
38
-
39
- try:
40
- print("πŸ”§ Loading OCR models...")
41
- start_time = time.time()
42
-
43
- # Model paths
44
- gdt_model = 'edocr2/models/recognizer_gdts.keras'
45
- dim_model = 'edocr2/models/recognizer_dimensions_2.keras'
46
-
47
- if not os.path.exists(gdt_model) or not os.path.exists(dim_model):
48
- print("❌ Model files not found!")
49
- return False
50
-
51
- # Load GD&T recognizer
52
- recognizer_gdt = Recognizer(alphabet=tools.ocr_pipelines.read_alphabet(gdt_model))
53
- recognizer_gdt.model.load_weights(gdt_model)
54
-
55
- # Load dimension recognizer
56
- alphabet_dim = tools.ocr_pipelines.read_alphabet(dim_model)
57
- recognizer_dim = Recognizer(alphabet=alphabet_dim)
58
- recognizer_dim.model.load_weights(dim_model)
59
-
60
- # Load detector
61
- detector = Detector()
62
-
63
- # Warm up models
64
- dummy_image = np.zeros((1, 1, 3), dtype=np.float32)
65
- _ = recognizer_gdt.recognize(dummy_image)
66
- _ = recognizer_dim.recognize(dummy_image)
67
- dummy_image = np.zeros((32, 32, 3), dtype=np.float32)
68
- _ = detector.detect([dummy_image])
69
-
70
- end_time = time.time()
71
- print(f"βœ… Models loaded in {end_time - start_time:.2f} seconds")
72
-
73
- models_loaded = True
74
- return True
75
-
76
- except Exception as e:
77
- print(f"❌ Error loading models: {e}")
78
- import traceback
79
- traceback.print_exc()
80
- return False
81
-
82
- def process_drawing(image_file):
83
- """
84
- Process an engineering drawing and extract information
85
-
86
- Args:
87
- image_file: Uploaded image file (PIL Image or file path)
88
-
89
- Returns:
90
- tuple: (annotated_image, json_data, csv_file, zip_file, stats_html)
91
- """
92
-
93
- if not models_loaded:
94
- return None, "❌ Models not loaded. Please check the logs.", None, None, "Error: Models not loaded"
95
-
96
- try:
97
- start_time = time.time()
98
-
99
- # Read image
100
- if isinstance(image_file, str):
101
- # File path
102
- if image_file.lower().endswith('.pdf'):
103
- img = convert_from_path(image_file)
104
- img = np.array(img[0])
105
- gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
106
- _, img = cv2.threshold(gray, 200, 255, cv2.THRESH_BINARY)
107
- img = cv2.merge([img, img, img])
108
- else:
109
- img = cv2.imread(image_file)
110
- else:
111
- # PIL Image
112
- img = np.array(image_file)
113
- if len(img.shape) == 2: # Grayscale
114
- img = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
115
- elif img.shape[2] == 4: # RGBA
116
- img = cv2.cvtColor(img, cv2.COLOR_RGBA2BGR)
117
- else: # RGB
118
- img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
119
-
120
- if img is None:
121
- return None, "❌ Could not read image file", None, None, "Error: Invalid image"
122
-
123
- # Create temporary output directory
124
- with tempfile.TemporaryDirectory() as temp_dir:
125
- output_dir = temp_dir
126
-
127
- # Segmentation
128
- print("πŸ” Segmenting layers...")
129
- img_boxes, frame, gdt_boxes, tables, dim_boxes = tools.layer_segm.segment_img(
130
- img, autoframe=True, frame_thres=0.7, GDT_thres=0.02, binary_thres=127
131
- )
132
-
133
- # OCR Tables
134
- print("πŸ“‹ Processing tables...")
135
- process_img = img.copy()
136
- table_results, updated_tables, process_img = tools.ocr_pipelines.ocr_tables(
137
- tables, process_img, language='eng'
138
- )
139
-
140
- # OCR GD&T
141
- print("🎯 Processing GD&T symbols...")
142
- gdt_results, updated_gdt_boxes, process_img = tools.ocr_pipelines.ocr_gdt(
143
- process_img, gdt_boxes, recognizer_gdt
144
- )
145
-
146
- # OCR Dimensions
147
- print("πŸ“ Processing dimensions...")
148
- if frame:
149
- process_img = process_img[frame.y : frame.y + frame.h, frame.x : frame.x + frame.w]
150
-
151
- dimensions, other_info, process_img, dim_tess = tools.ocr_pipelines.ocr_dimensions(
152
- process_img, detector, recognizer_dim, alphabet_dim, frame, dim_boxes,
153
- cluster_thres=20, max_img_size=1048, language='eng', backg_save=False
154
- )
155
-
156
- # Generate mask image
157
- print("🎨 Generating visualization...")
158
- mask_img = tools.output_tools.mask_img(
159
- img, updated_gdt_boxes, updated_tables, dimensions, frame, other_info
160
- )
161
-
162
- # Convert to RGB for display
163
- mask_img_rgb = cv2.cvtColor(mask_img, cv2.COLOR_BGR2RGB)
164
-
165
- # Process and save results
166
- print("πŸ’Ύ Saving results...")
167
- table_results, gdt_results, dimensions, other_info = tools.output_tools.process_raw_output(
168
- output_dir, table_results, gdt_results, dimensions, other_info, save=True
169
- )
170
-
171
- # Prepare JSON data
172
- json_data = {
173
- 'tables': table_results,
174
- 'gdts': gdt_results,
175
- 'dimensions': dimensions,
176
- 'other_info': other_info
177
- }
178
- json_str = json.dumps(json_data, indent=2)
179
-
180
- # Save JSON file
181
- json_path = os.path.join(output_dir, 'results.json')
182
- with open(json_path, 'w') as f:
183
- f.write(json_str)
184
-
185
- # Create CSV file (if exists)
186
- csv_files = list(Path(output_dir).glob('*.csv'))
187
- csv_path = csv_files[0] if csv_files else None
188
-
189
- # Create ZIP file with all results
190
- zip_path = os.path.join(output_dir, 'edocr2_results.zip')
191
- with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
192
- # Add mask image
193
- mask_img_path = os.path.join(output_dir, 'annotated_drawing.png')
194
- cv2.imwrite(mask_img_path, mask_img)
195
- zipf.write(mask_img_path, 'annotated_drawing.png')
196
-
197
- # Add JSON
198
- zipf.write(json_path, 'results.json')
199
-
200
- # Add CSV if exists
201
- if csv_path:
202
- zipf.write(csv_path, os.path.basename(csv_path))
203
-
204
- # Copy ZIP to a permanent location
205
- permanent_zip = os.path.join(tempfile.gettempdir(), f'edocr2_results_{int(time.time())}.zip')
206
- import shutil
207
- shutil.copy(zip_path, permanent_zip)
208
-
209
- end_time = time.time()
210
- processing_time = round(end_time - start_time, 2)
211
-
212
- # Create statistics HTML
213
- stats_html = f"""
214
- <div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
215
- padding: 20px; border-radius: 10px; color: white; margin: 10px 0;">
216
- <h3 style="margin-top: 0;">πŸ“Š Processing Results</h3>
217
- <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 15px; margin-top: 15px;">
218
- <div style="background: rgba(255,255,255,0.2); padding: 15px; border-radius: 8px; text-align: center;">
219
- <div style="font-size: 2em; font-weight: bold;">{len(table_results)}</div>
220
- <div style="font-size: 0.9em; opacity: 0.9;">Tables Found</div>
221
- </div>
222
- <div style="background: rgba(255,255,255,0.2); padding: 15px; border-radius: 8px; text-align: center;">
223
- <div style="font-size: 2em; font-weight: bold;">{len(gdt_results)}</div>
224
- <div style="font-size: 0.9em; opacity: 0.9;">GD&T Symbols</div>
225
- </div>
226
- <div style="background: rgba(255,255,255,0.2); padding: 15px; border-radius: 8px; text-align: center;">
227
- <div style="font-size: 2em; font-weight: bold;">{len(dimensions)}</div>
228
- <div style="font-size: 0.9em; opacity: 0.9;">Dimensions</div>
229
- </div>
230
- <div style="background: rgba(255,255,255,0.2); padding: 15px; border-radius: 8px; text-align: center;">
231
- <div style="font-size: 2em; font-weight: bold;">{processing_time}s</div>
232
- <div style="font-size: 0.9em; opacity: 0.9;">Processing Time</div>
233
- </div>
234
- </div>
235
- </div>
236
- """
237
-
238
- print(f"βœ… Processing complete in {processing_time}s")
239
-
240
- return (
241
- Image.fromarray(mask_img_rgb),
242
- json_str,
243
- csv_path if csv_path else None,
244
- permanent_zip,
245
- stats_html
246
- )
247
-
248
- except Exception as e:
249
- error_msg = f"❌ Error processing drawing: {str(e)}"
250
- print(error_msg)
251
- import traceback
252
- traceback.print_exc()
253
- return None, error_msg, None, None, f"<div style='color: red;'>{error_msg}</div>"
254
-
255
- # Load models at startup
256
- print("="*60)
257
- print("πŸ”§ eDOCr2 - Engineering Drawing OCR")
258
- print("="*60)
259
- load_models()
260
-
261
- # Create Gradio interface
262
- with gr.Blocks(theme=gr.themes.Soft(), title="eDOCr2 - Engineering Drawing OCR") as demo:
263
-
264
- gr.Markdown("""
265
- # πŸ”§ eDOCr2 - Engineering Drawing OCR
266
-
267
- Extract **dimensions**, **tables**, and **GD&T symbols** from engineering drawings automatically.
268
-
269
- Upload your engineering drawing (JPG, PNG, or PDF) and get structured data extracted instantly!
270
- """)
271
-
272
- with gr.Row():
273
- with gr.Column(scale=1):
274
- gr.Markdown("### πŸ“€ Upload Drawing")
275
- image_input = gr.Image(
276
- type="pil",
277
- label="Engineering Drawing",
278
- sources=["upload", "clipboard"],
279
- height=400
280
- )
281
-
282
- process_btn = gr.Button(
283
- "πŸš€ Process Drawing",
284
- variant="primary",
285
- size="lg"
286
- )
287
-
288
- gr.Markdown("""
289
- **Supported formats:** JPG, PNG, PDF
290
- **Best results:** High-resolution scans with clear text
291
- """)
292
-
293
- with gr.Column(scale=1):
294
- gr.Markdown("### πŸ“Š Statistics")
295
- stats_output = gr.HTML()
296
-
297
- gr.Markdown("### 🎨 Annotated Drawing")
298
- image_output = gr.Image(
299
- label="Processed Drawing",
300
- type="pil",
301
- height=400
302
- )
303
-
304
- with gr.Row():
305
- with gr.Column():
306
- gr.Markdown("### πŸ“‹ Extracted Data (JSON)")
307
- json_output = gr.Textbox(
308
- label="Structured Data",
309
- lines=15,
310
- max_lines=20,
311
- show_copy_button=True
312
- )
313
-
314
- with gr.Column():
315
- gr.Markdown("### πŸ’Ύ Download Results")
316
-
317
- csv_output = gr.File(
318
- label="πŸ“Š CSV File (if available)",
319
- type="filepath"
320
- )
321
-
322
- zip_output = gr.File(
323
- label="πŸ“¦ Complete Results (ZIP)",
324
- type="filepath"
325
- )
326
-
327
- gr.Markdown("""
328
- **ZIP contains:**
329
- - Annotated drawing image
330
- - Structured data (JSON)
331
- - Tabular data (CSV, if applicable)
332
- """)
333
-
334
- # Examples section
335
- gr.Markdown("### πŸ§ͺ Try Example Drawings")
336
-
337
- example_files = []
338
- examples_dir = "tests/test_samples"
339
- if os.path.exists(examples_dir):
340
- for file in os.listdir(examples_dir):
341
- if file.endswith(('.jpg', '.png')):
342
- example_files.append([os.path.join(examples_dir, file)])
343
-
344
- if example_files:
345
- gr.Examples(
346
- examples=example_files[:3], # Show first 3 examples
347
- inputs=image_input,
348
- label="Click to load example"
349
- )
350
-
351
- # Footer
352
- gr.Markdown("""
353
- ---
354
-
355
- ### πŸ“š About eDOCr2
356
-
357
- eDOCr2 is a specialized OCR tool for engineering drawings that can extract:
358
- - **Tables**: Title blocks, revision tables, bill of materials
359
- - **GD&T Symbols**: Geometric dimensioning and tolerancing
360
- - **Dimensions**: Measurements with tolerances
361
- - **Other Information**: Additional text and annotations
362
-
363
- **Research Paper:** [http://dx.doi.org/10.2139/ssrn.5045921](http://dx.doi.org/10.2139/ssrn.5045921)
364
- **GitHub:** [github.com/javvi51/edocr2](https://github.com/javvi51/edocr2)
365
- **Created by:** Javier Villena Toro | **Deployed by:** Jeyanthan GJ
366
- """)
367
-
368
- # Connect the process button
369
- process_btn.click(
370
- fn=process_drawing,
371
- inputs=image_input,
372
- outputs=[image_output, json_output, csv_output, zip_output, stats_output]
373
- )
374
-
375
- # Launch the app
376
- if __name__ == "__main__":
377
- demo.launch(
378
- server_name="0.0.0.0",
379
- server_port=7860,
380
- share=False
381
- )
 
1
+ """
2
+ eDOCr2 - Engineering Drawing OCR
3
+ Gradio Interface for Hugging Face Spaces
4
+
5
+ Extract dimensions, tables, and GD&T symbols from engineering drawings
6
+ """
7
+
8
+ import gradio as gr
9
+ import cv2
10
+ import numpy as np
11
+ import json
12
+ import os
13
+ import time
14
+ from pathlib import Path
15
+ import zipfile
16
+ import tempfile
17
+ from PIL import Image
18
+
19
+ # Import eDOCr2 modules
20
+ from edocr2 import tools
21
+ from edocr2.keras_ocr.recognition import Recognizer
22
+ from edocr2.keras_ocr.detection import Detector
23
+ from pdf2image import convert_from_path
24
+
25
+ # Global variables for models
26
+ recognizer_gdt = None
27
+ recognizer_dim = None
28
+ detector = None
29
+ alphabet_dim = None
30
+ models_loaded = False
31
+
32
+ def load_models():
33
+ """Load OCR models at startup"""
34
+ global recognizer_gdt, recognizer_dim, detector, alphabet_dim, models_loaded
35
+
36
+ if models_loaded:
37
+ return True
38
+
39
+ try:
40
+ print("πŸ”§ Loading OCR models...")
41
+ start_time = time.time()
42
+
43
+ # Model paths
44
+ gdt_model = 'edocr2/models/recognizer_gdts.keras'
45
+ dim_model = 'edocr2/models/recognizer_dimensions_2.keras'
46
+
47
+ if not os.path.exists(gdt_model) or not os.path.exists(dim_model):
48
+ print("❌ Model files not found!")
49
+ return False
50
+
51
+ # Load GD&T recognizer
52
+ recognizer_gdt = Recognizer(alphabet=tools.ocr_pipelines.read_alphabet(gdt_model))
53
+ recognizer_gdt.model.load_weights(gdt_model)
54
+
55
+ # Load dimension recognizer
56
+ alphabet_dim = tools.ocr_pipelines.read_alphabet(dim_model)
57
+ recognizer_dim = Recognizer(alphabet=alphabet_dim)
58
+ recognizer_dim.model.load_weights(dim_model)
59
+
60
+ # Load detector
61
+ detector = Detector()
62
+
63
+ # Warm up models
64
+ dummy_image = np.zeros((1, 1, 3), dtype=np.float32)
65
+ _ = recognizer_gdt.recognize(dummy_image)
66
+ _ = recognizer_dim.recognize(dummy_image)
67
+ dummy_image = np.zeros((32, 32, 3), dtype=np.float32)
68
+ _ = detector.detect([dummy_image])
69
+
70
+ end_time = time.time()
71
+ print(f"βœ… Models loaded in {end_time - start_time:.2f} seconds")
72
+
73
+ models_loaded = True
74
+ return True
75
+
76
+ except Exception as e:
77
+ print(f"❌ Error loading models: {e}")
78
+ import traceback
79
+ traceback.print_exc()
80
+ return False
81
+
82
+ def process_drawing(image_file):
83
+ """
84
+ Process an engineering drawing and extract information
85
+
86
+ Args:
87
+ image_file: Uploaded image file (PIL Image or file path)
88
+
89
+ Returns:
90
+ tuple: (annotated_image, json_data, csv_file, zip_file, stats_html)
91
+ """
92
+
93
+ if not models_loaded:
94
+ return None, "❌ Models not loaded. Please check the logs.", None, None, "Error: Models not loaded"
95
+
96
+ try:
97
+ start_time = time.time()
98
+
99
+ # Read image
100
+ if isinstance(image_file, str):
101
+ # File path
102
+ if image_file.lower().endswith('.pdf'):
103
+ img = convert_from_path(image_file)
104
+ img = np.array(img[0])
105
+ gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
106
+ _, img = cv2.threshold(gray, 200, 255, cv2.THRESH_BINARY)
107
+ img = cv2.merge([img, img, img])
108
+ else:
109
+ img = cv2.imread(image_file)
110
+ else:
111
+ # PIL Image
112
+ img = np.array(image_file)
113
+ if len(img.shape) == 2: # Grayscale
114
+ img = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
115
+ elif img.shape[2] == 4: # RGBA
116
+ img = cv2.cvtColor(img, cv2.COLOR_RGBA2BGR)
117
+ else: # RGB
118
+ img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
119
+
120
+ if img is None:
121
+ return None, "❌ Could not read image file", None, None, "Error: Invalid image"
122
+
123
+ # Create temporary output directory
124
+ with tempfile.TemporaryDirectory() as temp_dir:
125
+ output_dir = temp_dir
126
+
127
+ # Segmentation
128
+ print("πŸ” Segmenting layers...")
129
+ img_boxes, frame, gdt_boxes, tables, dim_boxes = tools.layer_segm.segment_img(
130
+ img, autoframe=True, frame_thres=0.7, GDT_thres=0.02, binary_thres=127
131
+ )
132
+
133
+ # OCR Tables
134
+ print("πŸ“‹ Processing tables...")
135
+ process_img = img.copy()
136
+ table_results, updated_tables, process_img = tools.ocr_pipelines.ocr_tables(
137
+ tables, process_img, language='eng'
138
+ )
139
+
140
+ # OCR GD&T
141
+ print("🎯 Processing GD&T symbols...")
142
+ gdt_results, updated_gdt_boxes, process_img = tools.ocr_pipelines.ocr_gdt(
143
+ process_img, gdt_boxes, recognizer_gdt
144
+ )
145
+
146
+ # OCR Dimensions
147
+ print("πŸ“ Processing dimensions...")
148
+ if frame:
149
+ process_img = process_img[frame.y : frame.y + frame.h, frame.x : frame.x + frame.w]
150
+
151
+ dimensions, other_info, process_img, dim_tess = tools.ocr_pipelines.ocr_dimensions(
152
+ process_img, detector, recognizer_dim, alphabet_dim, frame, dim_boxes,
153
+ cluster_thres=20, max_img_size=1048, language='eng', backg_save=False
154
+ )
155
+
156
+ # Generate mask image
157
+ print("🎨 Generating visualization...")
158
+ mask_img = tools.output_tools.mask_img(
159
+ img, updated_gdt_boxes, updated_tables, dimensions, frame, other_info
160
+ )
161
+
162
+ # Convert to RGB for display
163
+ mask_img_rgb = cv2.cvtColor(mask_img, cv2.COLOR_BGR2RGB)
164
+
165
+ # Process and save results
166
+ print("πŸ’Ύ Saving results...")
167
+ table_results, gdt_results, dimensions, other_info = tools.output_tools.process_raw_output(
168
+ output_dir, table_results, gdt_results, dimensions, other_info, save=True
169
+ )
170
+
171
+ # Prepare JSON data
172
+ json_data = {
173
+ 'tables': table_results,
174
+ 'gdts': gdt_results,
175
+ 'dimensions': dimensions,
176
+ 'other_info': other_info
177
+ }
178
+ json_str = json.dumps(json_data, indent=2)
179
+
180
+ # Save JSON file
181
+ json_path = os.path.join(output_dir, 'results.json')
182
+ with open(json_path, 'w') as f:
183
+ f.write(json_str)
184
+
185
+ # Create CSV file (if exists)
186
+ csv_files = list(Path(output_dir).glob('*.csv'))
187
+ csv_path = csv_files[0] if csv_files else None
188
+
189
+ # Create ZIP file with all results
190
+ zip_path = os.path.join(output_dir, 'edocr2_results.zip')
191
+ with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
192
+ # Add mask image
193
+ mask_img_path = os.path.join(output_dir, 'annotated_drawing.png')
194
+ cv2.imwrite(mask_img_path, mask_img)
195
+ zipf.write(mask_img_path, 'annotated_drawing.png')
196
+
197
+ # Add JSON
198
+ zipf.write(json_path, 'results.json')
199
+
200
+ # Add CSV if exists
201
+ if csv_path:
202
+ zipf.write(csv_path, os.path.basename(csv_path))
203
+
204
+ # Copy ZIP to a permanent location
205
+ permanent_zip = os.path.join(tempfile.gettempdir(), f'edocr2_results_{int(time.time())}.zip')
206
+ import shutil
207
+ shutil.copy(zip_path, permanent_zip)
208
+
209
+ end_time = time.time()
210
+ processing_time = round(end_time - start_time, 2)
211
+
212
+ # Create statistics HTML
213
+ stats_html = f"""
214
+ <div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
215
+ padding: 20px; border-radius: 10px; color: white; margin: 10px 0;">
216
+ <h3 style="margin-top: 0;">πŸ“Š Processing Results</h3>
217
+ <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 15px; margin-top: 15px;">
218
+ <div style="background: rgba(255,255,255,0.2); padding: 15px; border-radius: 8px; text-align: center;">
219
+ <div style="font-size: 2em; font-weight: bold;">{len(table_results)}</div>
220
+ <div style="font-size: 0.9em; opacity: 0.9;">Tables Found</div>
221
+ </div>
222
+ <div style="background: rgba(255,255,255,0.2); padding: 15px; border-radius: 8px; text-align: center;">
223
+ <div style="font-size: 2em; font-weight: bold;">{len(gdt_results)}</div>
224
+ <div style="font-size: 0.9em; opacity: 0.9;">GD&T Symbols</div>
225
+ </div>
226
+ <div style="background: rgba(255,255,255,0.2); padding: 15px; border-radius: 8px; text-align: center;">
227
+ <div style="font-size: 2em; font-weight: bold;">{len(dimensions)}</div>
228
+ <div style="font-size: 0.9em; opacity: 0.9;">Dimensions</div>
229
+ </div>
230
+ <div style="background: rgba(255,255,255,0.2); padding: 15px; border-radius: 8px; text-align: center;">
231
+ <div style="font-size: 2em; font-weight: bold;">{processing_time}s</div>
232
+ <div style="font-size: 0.9em; opacity: 0.9;">Processing Time</div>
233
+ </div>
234
+ </div>
235
+ </div>
236
+ """
237
+
238
+ print(f"βœ… Processing complete in {processing_time}s")
239
+
240
+ return (
241
+ Image.fromarray(mask_img_rgb),
242
+ json_str,
243
+ csv_path if csv_path else None,
244
+ permanent_zip,
245
+ stats_html
246
+ )
247
+
248
+ except Exception as e:
249
+ error_msg = f"❌ Error processing drawing: {str(e)}"
250
+ print(error_msg)
251
+ import traceback
252
+ traceback.print_exc()
253
+ return None, error_msg, None, None, f"<div style='color: red;'>{error_msg}</div>"
254
+
255
+ # Load models at startup
256
+ print("="*60)
257
+ print("πŸ”§ eDOCr2 - Engineering Drawing OCR")
258
+ print("="*60)
259
+ load_models()
260
+
261
+ # Create Gradio interface
262
+ with gr.Blocks(theme=gr.themes.Soft(), title="eDOCr2 - Engineering Drawing OCR") as demo:
263
+
264
+ gr.Markdown("""
265
+ # πŸ”§ eDOCr2 - Engineering Drawing OCR
266
+
267
+ Extract **dimensions**, **tables**, and **GD&T symbols** from engineering drawings automatically.
268
+
269
+ Upload your engineering drawing (JPG, PNG, or PDF) and get structured data extracted instantly!
270
+ """)
271
+
272
+ with gr.Row():
273
+ with gr.Column(scale=1):
274
+ gr.Markdown("### πŸ“€ Upload Drawing")
275
+ image_input = gr.Image(
276
+ type="pil",
277
+ label="Engineering Drawing",
278
+ sources=["upload", "clipboard"],
279
+ height=400
280
+ )
281
+
282
+ process_btn = gr.Button(
283
+ "πŸš€ Process Drawing",
284
+ variant="primary",
285
+ size="lg"
286
+ )
287
+
288
+ gr.Markdown("""
289
+ **Supported formats:** JPG, PNG, PDF
290
+ **Best results:** High-resolution scans with clear text
291
+ """)
292
+
293
+ with gr.Column(scale=1):
294
+ gr.Markdown("### πŸ“Š Statistics")
295
+ stats_output = gr.HTML()
296
+
297
+ gr.Markdown("### 🎨 Annotated Drawing")
298
+ image_output = gr.Image(
299
+ label="Processed Drawing",
300
+ type="pil",
301
+ height=400
302
+ )
303
+
304
+ with gr.Row():
305
+ with gr.Column():
306
+ gr.Markdown("### πŸ“‹ Extracted Data (JSON)")
307
+ json_output = gr.Textbox(
308
+ label="Structured Data",
309
+ lines=15,
310
+ max_lines=20,
311
+ show_copy_button=True
312
+ )
313
+
314
+ with gr.Column():
315
+ gr.Markdown("### πŸ’Ύ Download Results")
316
+
317
+ csv_output = gr.File(
318
+ label="πŸ“Š CSV File (if available)",
319
+ type="filepath"
320
+ )
321
+
322
+ zip_output = gr.File(
323
+ label="πŸ“¦ Complete Results (ZIP)",
324
+ type="filepath"
325
+ )
326
+
327
+ gr.Markdown("""
328
+ **ZIP contains:**
329
+ - Annotated drawing image
330
+ - Structured data (JSON)
331
+ - Tabular data (CSV, if applicable)
332
+ """)
333
+
334
+ # Examples section
335
+ gr.Markdown("### πŸ§ͺ Try Example Drawings")
336
+
337
+ example_files = []
338
+ examples_dir = "tests/test_samples"
339
+ if os.path.exists(examples_dir):
340
+ for file in os.listdir(examples_dir):
341
+ if file.endswith(('.jpg', '.png')):
342
+ example_files.append([os.path.join(examples_dir, file)])
343
+
344
+ if example_files:
345
+ gr.Examples(
346
+ examples=example_files[:3], # Show first 3 examples
347
+ inputs=image_input,
348
+ label="Click to load example"
349
+ )
350
+
351
+ # Footer
352
+ gr.Markdown("""
353
+ ---
354
+
355
+ ### πŸ“š About eDOCr2
356
+
357
+ eDOCr2 is a specialized OCR tool for engineering drawings that can extract:
358
+ - **Tables**: Title blocks, revision tables, bill of materials
359
+ - **GD&T Symbols**: Geometric dimensioning and tolerancing
360
+ - **Dimensions**: Measurements with tolerances
361
+ - **Other Information**: Additional text and annotations
362
+
363
+ **Research Paper:** [http://dx.doi.org/10.2139/ssrn.5045921](http://dx.doi.org/10.2139/ssrn.5045921)
364
+ **GitHub:** [github.com/javvi51/edocr2](https://github.com/javvi51/edocr2)
365
+ **Created by:** Javier Villena Toro | **Deployed by:** Jeyanthan GJ
366
+ """)
367
+
368
+ # Connect the process button
369
+ process_btn.click(
370
+ fn=process_drawing,
371
+ inputs=image_input,
372
+ outputs=[image_output, json_output, csv_output, zip_output, stats_output]
373
+ )
374
+
375
+ # Launch the app
376
+ if __name__ == "__main__":
377
+ demo.launch()