ginipick commited on
Commit
a3e6550
·
verified ·
1 Parent(s): 2187315

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +191 -139
app.py CHANGED
@@ -9,118 +9,203 @@ import base64, os
9
  from huggingface_hub import snapshot_download
10
  import traceback
11
  import warnings
 
12
 
13
  # Suppress specific warnings
14
  warnings.filterwarnings("ignore", category=FutureWarning)
15
  warnings.filterwarnings("ignore", message=".*_supports_sdpa.*")
16
 
17
- # Import 유틸리티 함수들
18
- from util.utils import check_ocr_box, get_yolo_model, get_caption_model_processor, get_som_labeled_img
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
 
20
  # Download repository (if not already downloaded)
21
- repo_id = "microsoft/OmniParser-v2.0" # HF repository ID
22
- local_dir = "weights" # Local directory for weights
23
 
24
- # Check if weights already exist to avoid re-downloading
25
  if not os.path.exists(local_dir):
26
  snapshot_download(repo_id=repo_id, local_dir=local_dir)
27
  print(f"Repository downloaded to: {local_dir}")
28
  else:
29
  print(f"Weights already exist at: {local_dir}")
30
 
31
- # Monkey patch for Florence2 model compatibility
32
- def patch_florence2_model():
33
- """Patch Florence2 model to fix compatibility issues with newer transformers"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
  try:
35
- import transformers
36
- from transformers import AutoModelForCausalLM
37
 
38
- # Try to import the Florence2 model class
39
- try:
40
- from transformers_modules.microsoft.Florence_2_base_ft.modeling_florence2 import Florence2ForConditionalGeneration
41
- except ImportError:
42
- # If not available, we'll patch it when loaded
43
- pass
 
 
44
 
45
- # Patch the model loading process
46
- original_from_pretrained = AutoModelForCausalLM.from_pretrained
 
 
 
 
47
 
48
- def patched_from_pretrained(model_name_or_path, *args, **kwargs):
49
- # Force trust_remote_code and add config overrides for Florence2
50
- if "florence" in model_name_or_path.lower() or "Florence" in model_name_or_path:
51
- kwargs['trust_remote_code'] = True
52
- # Add config to avoid SDPA issues
53
- kwargs['attn_implementation'] = "eager"
54
- kwargs['use_cache'] = False
 
 
55
 
56
- model = original_from_pretrained(model_name_or_path, *args, **kwargs)
57
-
58
- # Add missing attributes if needed
59
- if not hasattr(model, '_supports_sdpa'):
60
- model._supports_sdpa = False
61
-
62
- return model
 
 
 
 
 
 
63
 
64
- AutoModelForCausalLM.from_pretrained = patched_from_pretrained
65
- print("Applied Florence2 compatibility patch")
 
 
 
66
 
67
  except Exception as e:
68
- print(f"Warning: Could not apply Florence2 patch: {e}")
69
-
70
- # Apply the patch before loading models
71
- patch_florence2_model()
72
 
73
- # Load models with error handling
74
  try:
75
  print("Loading YOLO model...")
76
  yolo_model = get_yolo_model(model_path='weights/icon_detect/model.pt')
77
  print("YOLO model loaded successfully")
78
 
79
  print("Loading caption model...")
80
- # Try loading with fallback options
81
- try:
82
- caption_model_processor = get_caption_model_processor(
83
- model_name="florence2",
84
- model_name_or_path="weights/icon_caption"
85
- )
86
- print("Florence2 caption model loaded successfully")
87
- except Exception as e:
88
- print(f"Error loading Florence2, trying alternative approach: {e}")
89
- # Alternative loading method
90
- import sys
91
- sys.path.insert(0, "weights/icon_caption")
92
-
93
- from transformers import AutoProcessor, AutoModelForCausalLM
94
-
95
- # Load with specific configurations to avoid SDPA issues
96
- processor = AutoProcessor.from_pretrained(
97
- "weights/icon_caption",
98
- trust_remote_code=True,
99
- revision="main"
100
- )
101
-
102
- model = AutoModelForCausalLM.from_pretrained(
103
- "weights/icon_caption",
104
- torch_dtype=torch.float16 if torch.cuda.is_available() else torch.float32,
105
- trust_remote_code=True,
106
- revision="main",
107
- attn_implementation="eager", # Avoid SDPA issues
108
- device_map="auto" if torch.cuda.is_available() else None
109
- )
110
-
111
- # Add missing attribute
112
- if not hasattr(model, '_supports_sdpa'):
113
- model._supports_sdpa = False
114
-
115
- caption_model_processor = {'model': model, 'processor': processor}
116
- print("Caption model loaded with alternative method")
117
-
118
  except Exception as e:
119
  print(f"Critical error loading models: {e}")
120
  print(traceback.format_exc())
121
- # Try to continue with a dummy model for testing
122
  caption_model_processor = None
123
- raise RuntimeError(f"Failed to load models: {e}")
124
 
125
  # Markdown header text
126
  MARKDOWN = """
@@ -149,22 +234,6 @@ button:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0,0,0,0.
149
  .gr-padded { padding: 16px; }
150
  """
151
 
152
- def safe_process_wrapper(*args, **kwargs):
153
- """Wrapper to handle SDPA attribute errors"""
154
- try:
155
- return process(*args, **kwargs)
156
- except AttributeError as e:
157
- if '_supports_sdpa' in str(e):
158
- # Try to fix the model on the fly
159
- global caption_model_processor
160
- if caption_model_processor and 'model' in caption_model_processor:
161
- model = caption_model_processor['model']
162
- if not hasattr(model, '_supports_sdpa'):
163
- model._supports_sdpa = False
164
- return process(*args, **kwargs)
165
- else:
166
- raise
167
-
168
  @spaces.GPU
169
  @torch.inference_mode()
170
  def process(
@@ -182,7 +251,7 @@ def process(
182
 
183
  # Check if caption model is loaded
184
  if caption_model_processor is None:
185
- return None, "⚠️ Caption model not loaded. Please restart the application."
186
 
187
  try:
188
  # Log processing parameters
@@ -191,7 +260,7 @@ def process(
191
 
192
  # Calculate overlay ratio based on input image width
193
  image_width = image_input.size[0]
194
- box_overlay_ratio = max(0.5, min(2.0, image_width / 3200)) # Clamp ratio between 0.5 and 2.0
195
 
196
  draw_bbox_config = {
197
  'text_scale': 0.8 * box_overlay_ratio,
@@ -200,7 +269,7 @@ def process(
200
  'thickness': max(int(3 * box_overlay_ratio), 1),
201
  }
202
 
203
- # Run OCR bounding box detection with error handling
204
  try:
205
  ocr_bbox_rslt, is_goal_filtered = check_ocr_box(
206
  image_input,
@@ -230,9 +299,9 @@ def process(
230
  print(f"OCR error: {e}, continuing with empty OCR results")
231
  text, ocr_bbox = [], []
232
 
233
- # Get labeled image and parsed content via SOM (YOLO + caption model)
234
  try:
235
- # Fix model attributes before calling
236
  if isinstance(caption_model_processor, dict) and 'model' in caption_model_processor:
237
  model = caption_model_processor['model']
238
  if not hasattr(model, '_supports_sdpa'):
@@ -243,10 +312,10 @@ def process(
243
  yolo_model,
244
  BOX_TRESHOLD=box_threshold,
245
  output_coord_in_ratio=True,
246
- ocr_bbox=ocr_bbox if ocr_bbox else [], # Ensure it's never None
247
  draw_bbox_config=draw_bbox_config,
248
  caption_model_processor=caption_model_processor,
249
- ocr_text=text if text else [], # Ensure it's never None
250
  iou_threshold=iou_threshold,
251
  imgsz=imgsz
252
  )
@@ -254,24 +323,9 @@ def process(
254
  if dino_labled_img is None:
255
  raise ValueError("Failed to generate labeled image")
256
 
257
- except AttributeError as e:
258
- if '_supports_sdpa' in str(e):
259
- print(f"SDPA attribute error, attempting to fix: {e}")
260
- # Try to fix and retry
261
- if isinstance(caption_model_processor, dict) and 'model' in caption_model_processor:
262
- caption_model_processor['model']._supports_sdpa = False
263
- # Retry the operation
264
- dino_labled_img, label_coordinates, parsed_content_list = get_som_labeled_img(
265
- image_input, yolo_model, BOX_TRESHOLD=box_threshold,
266
- output_coord_in_ratio=True, ocr_bbox=ocr_bbox if ocr_bbox else [],
267
- draw_bbox_config=draw_bbox_config, caption_model_processor=caption_model_processor,
268
- ocr_text=text if text else [], iou_threshold=iou_threshold, imgsz=imgsz
269
- )
270
- else:
271
- raise
272
  except Exception as e:
273
  print(f"Error in SOM processing: {e}")
274
- # Return original image with error message if SOM fails
275
  return image_input, f"⚠️ Error during element detection: {str(e)}"
276
 
277
  # Decode processed image from base64
@@ -282,7 +336,7 @@ def process(
282
  print(f"Error decoding image: {e}")
283
  return image_input, f"⚠️ Error decoding processed image: {str(e)}"
284
 
285
- # Format parsed content list into a multi-line string
286
  if parsed_content_list and len(parsed_content_list) > 0:
287
  parsed_text = "🎯 **Detected Elements:**\n\n"
288
  for i, v in enumerate(parsed_content_list):
@@ -300,10 +354,14 @@ def process(
300
  print(traceback.format_exc())
301
  return None, error_msg
302
 
303
- # Build Gradio UI with enhanced layout and functionality
304
  with gr.Blocks(css=custom_css, theme=gr.themes.Soft(), title="OmniParser V2 Pro") as demo:
305
  gr.Markdown(MARKDOWN)
306
 
 
 
 
 
307
  with gr.Row():
308
  # Left sidebar: Upload and settings
309
  with gr.Column(scale=1):
@@ -323,7 +381,7 @@ with gr.Blocks(css=custom_css, theme=gr.themes.Soft(), title="OmniParser V2 Pro"
323
  maximum=1.0,
324
  step=0.01,
325
  value=0.05,
326
- info="Lower values detect more elements (may include false positives)"
327
  )
328
 
329
  iou_threshold_component = gr.Slider(
@@ -332,13 +390,13 @@ with gr.Blocks(css=custom_css, theme=gr.themes.Soft(), title="OmniParser V2 Pro"
332
  maximum=1.0,
333
  step=0.01,
334
  value=0.1,
335
- info="Controls overlap filtering (lower = less filtering)"
336
  )
337
 
338
  use_paddleocr_component = gr.Checkbox(
339
  label='🔤 Use PaddleOCR',
340
  value=True,
341
- info="✓ PaddleOCR (faster) | ✗ EasyOCR (more languages)"
342
  )
343
 
344
  imgsz_component = gr.Slider(
@@ -347,7 +405,7 @@ with gr.Blocks(css=custom_css, theme=gr.themes.Soft(), title="OmniParser V2 Pro"
347
  maximum=1920,
348
  step=32,
349
  value=640,
350
- info="Higher = better accuracy but slower (640 recommended)"
351
  )
352
 
353
  submit_button_component = gr.Button(
@@ -356,13 +414,12 @@ with gr.Blocks(css=custom_css, theme=gr.themes.Soft(), title="OmniParser V2 Pro"
356
  size='lg'
357
  )
358
 
359
- # Add examples section
360
  gr.Markdown("### 💡 Quick Tips")
361
  gr.Markdown("""
362
- - **For mobile apps:** Use default settings
363
- - **For desktop apps:** Try image size 1280
364
- - **For complex UIs:** Lower box threshold to 0.03
365
- - **Too many boxes?** Increase IOU threshold
366
  """)
367
 
368
  # Right main area: Results tabs
@@ -380,13 +437,10 @@ with gr.Blocks(css=custom_css, theme=gr.themes.Soft(), title="OmniParser V2 Pro"
380
  value="*Parsed elements will appear here after processing...*",
381
  elem_classes=["parsed-text"]
382
  )
383
-
384
- # Add status indicator
385
- status_text = gr.Markdown("", visible=True)
386
 
387
- # Button click event with loading spinner
388
  submit_button_component.click(
389
- fn=safe_process_wrapper, # Use wrapper function
390
  inputs=[
391
  image_input_component,
392
  box_threshold_component,
@@ -398,13 +452,12 @@ with gr.Blocks(css=custom_css, theme=gr.themes.Soft(), title="OmniParser V2 Pro"
398
  show_progress=True
399
  )
400
 
401
- # Launch with queue support and error handling
402
  if __name__ == "__main__":
403
  try:
404
- # Set environment variables for better compatibility
405
  os.environ['TRANSFORMERS_OFFLINE'] = '0'
406
  os.environ['HF_HUB_OFFLINE'] = '0'
407
- os.environ['CUDA_LAUNCH_BLOCKING'] = '1' # For better error messages
408
 
409
  demo.queue(max_size=10)
410
  demo.launch(
@@ -415,5 +468,4 @@ if __name__ == "__main__":
415
  )
416
  except Exception as e:
417
  print(f"Failed to launch app: {e}")
418
- print(traceback.format_exc())
419
- raise
 
9
  from huggingface_hub import snapshot_download
10
  import traceback
11
  import warnings
12
+ import sys
13
 
14
  # Suppress specific warnings
15
  warnings.filterwarnings("ignore", category=FutureWarning)
16
  warnings.filterwarnings("ignore", message=".*_supports_sdpa.*")
17
 
18
+ # CRITICAL: Fix Florence2 model before any imports
19
+ def fix_florence2_import():
20
+ """Pre-patch the Florence2 model class before it's imported"""
21
+ import importlib.util
22
+ import types
23
+
24
+ # Create a custom import hook
25
+ class Florence2ImportHook:
26
+ def find_spec(self, fullname, path, target=None):
27
+ if "florence2" in fullname.lower() or "modeling_florence2" in fullname:
28
+ return importlib.util.spec_from_loader(fullname, Florence2Loader())
29
+ return None
30
+
31
+ class Florence2Loader:
32
+ def create_module(self, spec):
33
+ return None
34
+
35
+ def exec_module(self, module):
36
+ # Load the original module
37
+ import importlib.machinery
38
+ import importlib.util
39
+
40
+ # Find the actual florence2 module
41
+ for path in sys.path:
42
+ florence_path = os.path.join(path, "modeling_florence2.py")
43
+ if os.path.exists(florence_path):
44
+ spec = importlib.util.spec_from_file_location("modeling_florence2", florence_path)
45
+ if spec and spec.loader:
46
+ spec.loader.exec_module(module)
47
+
48
+ # Patch the module after loading
49
+ if hasattr(module, 'Florence2ForConditionalGeneration'):
50
+ original_init = module.Florence2ForConditionalGeneration.__init__
51
+
52
+ def patched_init(self, config):
53
+ # Add the missing attribute before calling super().__init__
54
+ self._supports_sdpa = False
55
+ original_init(self, config)
56
+
57
+ module.Florence2ForConditionalGeneration.__init__ = patched_init
58
+ module.Florence2ForConditionalGeneration._supports_sdpa = False
59
+ break
60
+
61
+ # Install the import hook
62
+ hook = Florence2ImportHook()
63
+ sys.meta_path.insert(0, hook)
64
+
65
+ # Apply the fix before any model imports
66
+ try:
67
+ fix_florence2_import()
68
+ except Exception as e:
69
+ print(f"Warning: Could not apply import hook: {e}")
70
+
71
+ # Alternative fix: Monkey-patch transformers before importing utils
72
+ def monkey_patch_transformers():
73
+ """Monkey patch transformers to handle _supports_sdpa"""
74
+ try:
75
+ import transformers.modeling_utils as modeling_utils
76
+
77
+ original_check = modeling_utils.PreTrainedModel._check_and_adjust_attn_implementation
78
+
79
+ def patched_check(self, *args, **kwargs):
80
+ # Add the attribute if missing
81
+ if not hasattr(self, '_supports_sdpa'):
82
+ self._supports_sdpa = False
83
+ try:
84
+ return original_check(self, *args, **kwargs)
85
+ except AttributeError as e:
86
+ if '_supports_sdpa' in str(e):
87
+ # Return a safe default
88
+ return "eager"
89
+ raise
90
+
91
+ modeling_utils.PreTrainedModel._check_and_adjust_attn_implementation = patched_check
92
+
93
+ # Also patch the getter
94
+ original_getattr = modeling_utils.PreTrainedModel.__getattribute__
95
+
96
+ def patched_getattr(self, name):
97
+ if name == '_supports_sdpa' and not hasattr(self, '_supports_sdpa'):
98
+ return False
99
+ return original_getattr(self, name)
100
+
101
+ modeling_utils.PreTrainedModel.__getattribute__ = patched_getattr
102
+
103
+ print("Successfully patched transformers for Florence2 compatibility")
104
+
105
+ except Exception as e:
106
+ print(f"Warning: Could not patch transformers: {e}")
107
+
108
+ # Apply the monkey patch
109
+ monkey_patch_transformers()
110
+
111
+ # Now import the utils after patching
112
+ from util.utils import check_ocr_box, get_yolo_model, get_som_labeled_img
113
 
114
  # Download repository (if not already downloaded)
115
+ repo_id = "microsoft/OmniParser-v2.0"
116
+ local_dir = "weights"
117
 
 
118
  if not os.path.exists(local_dir):
119
  snapshot_download(repo_id=repo_id, local_dir=local_dir)
120
  print(f"Repository downloaded to: {local_dir}")
121
  else:
122
  print(f"Weights already exist at: {local_dir}")
123
 
124
+ # Custom function to load caption model with proper error handling
125
+ def load_caption_model_safe(model_name="florence2", model_name_or_path="weights/icon_caption"):
126
+ """Safely load caption model with multiple fallback methods"""
127
+
128
+ device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
129
+
130
+ try:
131
+ # Method 1: Try the original function with patching
132
+ from util.utils import get_caption_model_processor
133
+ return get_caption_model_processor(model_name, model_name_or_path)
134
+ except AttributeError as e:
135
+ if '_supports_sdpa' in str(e):
136
+ print(f"SDPA error detected, trying alternative loading method...")
137
+ else:
138
+ raise
139
+
140
+ # Method 2: Load directly with specific configuration
141
  try:
142
+ from transformers import AutoProcessor, AutoModelForCausalLM
 
143
 
144
+ print(f"Loading caption model from {model_name_or_path} with alternative method...")
145
+
146
+ # Load processor
147
+ processor = AutoProcessor.from_pretrained(
148
+ model_name_or_path,
149
+ trust_remote_code=True,
150
+ revision="main"
151
+ )
152
 
153
+ # Try to load model with different configurations
154
+ configs_to_try = [
155
+ {"attn_implementation": "eager", "use_cache": False},
156
+ {"use_flash_attention_2": False, "use_cache": False},
157
+ {"torch_dtype": torch.float32}, # Try float32 instead of float16
158
+ ]
159
 
160
+ model = None
161
+ for config in configs_to_try:
162
+ try:
163
+ model = AutoModelForCausalLM.from_pretrained(
164
+ model_name_or_path,
165
+ trust_remote_code=True,
166
+ device_map="auto" if torch.cuda.is_available() else None,
167
+ **config
168
+ )
169
 
170
+ # Ensure the attribute exists
171
+ if not hasattr(model, '_supports_sdpa'):
172
+ model._supports_sdpa = False
173
+
174
+ print(f"Model loaded successfully with config: {config}")
175
+ break
176
+
177
+ except Exception as e:
178
+ print(f"Failed with config {config}: {e}")
179
+ continue
180
+
181
+ if model is None:
182
+ raise RuntimeError("Could not load model with any configuration")
183
 
184
+ # Move to device if needed
185
+ if device.type == 'cuda' and not next(model.parameters()).is_cuda:
186
+ model = model.to(device)
187
+
188
+ return {'model': model, 'processor': processor}
189
 
190
  except Exception as e:
191
+ print(f"Error in alternative loading: {e}")
192
+ raise
 
 
193
 
194
+ # Load models
195
  try:
196
  print("Loading YOLO model...")
197
  yolo_model = get_yolo_model(model_path='weights/icon_detect/model.pt')
198
  print("YOLO model loaded successfully")
199
 
200
  print("Loading caption model...")
201
+ caption_model_processor = load_caption_model_safe()
202
+ print("Caption model loaded successfully")
203
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
204
  except Exception as e:
205
  print(f"Critical error loading models: {e}")
206
  print(traceback.format_exc())
 
207
  caption_model_processor = None
208
+ # Don't raise here, let the UI handle it
209
 
210
  # Markdown header text
211
  MARKDOWN = """
 
234
  .gr-padded { padding: 16px; }
235
  """
236
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
237
  @spaces.GPU
238
  @torch.inference_mode()
239
  def process(
 
251
 
252
  # Check if caption model is loaded
253
  if caption_model_processor is None:
254
+ return None, "⚠️ Caption model not loaded. There was an error during initialization. Please check the logs."
255
 
256
  try:
257
  # Log processing parameters
 
260
 
261
  # Calculate overlay ratio based on input image width
262
  image_width = image_input.size[0]
263
+ box_overlay_ratio = max(0.5, min(2.0, image_width / 3200))
264
 
265
  draw_bbox_config = {
266
  'text_scale': 0.8 * box_overlay_ratio,
 
269
  'thickness': max(int(3 * box_overlay_ratio), 1),
270
  }
271
 
272
+ # Run OCR bounding box detection
273
  try:
274
  ocr_bbox_rslt, is_goal_filtered = check_ocr_box(
275
  image_input,
 
299
  print(f"OCR error: {e}, continuing with empty OCR results")
300
  text, ocr_bbox = [], []
301
 
302
+ # Get labeled image and parsed content
303
  try:
304
+ # Ensure the model has the required attribute
305
  if isinstance(caption_model_processor, dict) and 'model' in caption_model_processor:
306
  model = caption_model_processor['model']
307
  if not hasattr(model, '_supports_sdpa'):
 
312
  yolo_model,
313
  BOX_TRESHOLD=box_threshold,
314
  output_coord_in_ratio=True,
315
+ ocr_bbox=ocr_bbox if ocr_bbox else [],
316
  draw_bbox_config=draw_bbox_config,
317
  caption_model_processor=caption_model_processor,
318
+ ocr_text=text if text else [],
319
  iou_threshold=iou_threshold,
320
  imgsz=imgsz
321
  )
 
323
  if dino_labled_img is None:
324
  raise ValueError("Failed to generate labeled image")
325
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
326
  except Exception as e:
327
  print(f"Error in SOM processing: {e}")
328
+ print(traceback.format_exc())
329
  return image_input, f"⚠️ Error during element detection: {str(e)}"
330
 
331
  # Decode processed image from base64
 
336
  print(f"Error decoding image: {e}")
337
  return image_input, f"⚠️ Error decoding processed image: {str(e)}"
338
 
339
+ # Format parsed content list
340
  if parsed_content_list and len(parsed_content_list) > 0:
341
  parsed_text = "🎯 **Detected Elements:**\n\n"
342
  for i, v in enumerate(parsed_content_list):
 
354
  print(traceback.format_exc())
355
  return None, error_msg
356
 
357
+ # Build Gradio UI
358
  with gr.Blocks(css=custom_css, theme=gr.themes.Soft(), title="OmniParser V2 Pro") as demo:
359
  gr.Markdown(MARKDOWN)
360
 
361
+ # Check if models loaded successfully
362
+ if caption_model_processor is None:
363
+ gr.Markdown("### ⚠️ Warning: Caption model failed to load. Some features may not work.")
364
+
365
  with gr.Row():
366
  # Left sidebar: Upload and settings
367
  with gr.Column(scale=1):
 
381
  maximum=1.0,
382
  step=0.01,
383
  value=0.05,
384
+ info="Lower values detect more elements"
385
  )
386
 
387
  iou_threshold_component = gr.Slider(
 
390
  maximum=1.0,
391
  step=0.01,
392
  value=0.1,
393
+ info="Controls overlap filtering"
394
  )
395
 
396
  use_paddleocr_component = gr.Checkbox(
397
  label='🔤 Use PaddleOCR',
398
  value=True,
399
+ info="✓ PaddleOCR | ✗ EasyOCR"
400
  )
401
 
402
  imgsz_component = gr.Slider(
 
405
  maximum=1920,
406
  step=32,
407
  value=640,
408
+ info="Higher = better accuracy but slower"
409
  )
410
 
411
  submit_button_component = gr.Button(
 
414
  size='lg'
415
  )
416
 
 
417
  gr.Markdown("### 💡 Quick Tips")
418
  gr.Markdown("""
419
+ - **Mobile apps:** Use default settings
420
+ - **Desktop apps:** Try image size 1280
421
+ - **Complex UIs:** Lower box threshold to 0.03
422
+ - **Too many boxes:** Increase IOU threshold
423
  """)
424
 
425
  # Right main area: Results tabs
 
437
  value="*Parsed elements will appear here after processing...*",
438
  elem_classes=["parsed-text"]
439
  )
 
 
 
440
 
441
+ # Button click event
442
  submit_button_component.click(
443
+ fn=process,
444
  inputs=[
445
  image_input_component,
446
  box_threshold_component,
 
452
  show_progress=True
453
  )
454
 
455
+ # Launch with queue support
456
  if __name__ == "__main__":
457
  try:
458
+ # Set environment variables
459
  os.environ['TRANSFORMERS_OFFLINE'] = '0'
460
  os.environ['HF_HUB_OFFLINE'] = '0'
 
461
 
462
  demo.queue(max_size=10)
463
  demo.launch(
 
468
  )
469
  except Exception as e:
470
  print(f"Failed to launch app: {e}")
471
+ print(traceback.format_exc())