shriarul5273 commited on
Commit
6dbd5d6
Β·
1 Parent(s): 8ca4dce

Implemented a Gradio application for comparing stereo matching algorithms.

Browse files
Files changed (3) hide show
  1. app.py +742 -0
  2. app_local.py +731 -0
  3. requirements.txt +52 -0
app.py ADDED
@@ -0,0 +1,742 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Stereo Matching Methods Comparison Demo (Hugging Face Spaces with ZeroGPU)
3
+
4
+ This demo compares different stereo matching algorithms using Gradio's ImageSlider.
5
+ Optimized for Hugging Face Spaces with ZeroGPU support.
6
+
7
+ Currently supports:
8
+ - FoundationStereo (Low-cost and High-quality variants)
9
+ - CREStereo (ETH3D pre-trained model)
10
+ """
11
+
12
+ import os
13
+ import sys
14
+ import logging
15
+ import gc
16
+ import tempfile
17
+ from pathlib import Path
18
+ from typing import Optional, Tuple, Union, Dict, List
19
+ import numpy as np
20
+ import cv2
21
+ import gradio as gr
22
+ import imageio
23
+ import argparse
24
+ import random
25
+
26
+ # Import spaces BEFORE torch to ensure proper ZeroGPU initialization
27
+ import spaces
28
+
29
+ import torch
30
+ import torch.nn.functional as F
31
+
32
+ # Configure logging
33
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
34
+
35
+ # Get current directory
36
+ current_dir = os.path.dirname(os.path.abspath(__file__))
37
+
38
+ # Add subdemo directories to path
39
+ foundation_stereo_dir = os.path.join(current_dir, "FoundationStereo_demo")
40
+ crestereo_dir = os.path.join(current_dir, "CREStereo_demo")
41
+ sys.path.insert(0, foundation_stereo_dir)
42
+ sys.path.insert(0, crestereo_dir)
43
+
44
+ # Global variables for model caching
45
+ _cached_models = {}
46
+ _available_methods = {}
47
+
48
+ class StereoMethodBase:
49
+ """Base class for stereo matching methods"""
50
+
51
+ def __init__(self, name: str, display_name: str):
52
+ self.name = name
53
+ self.display_name = display_name
54
+ self._model = None
55
+ self._device = None
56
+
57
+ def load_model(self):
58
+ """Load the model for this method"""
59
+ raise NotImplementedError
60
+
61
+ def process_stereo_pair(self, left_img: np.ndarray, right_img: np.ndarray, progress_callback=None) -> Tuple[np.ndarray, str]:
62
+ """Process stereo pair and return disparity visualization and status"""
63
+ raise NotImplementedError
64
+
65
+ def cleanup(self):
66
+ """Clean up model and free memory"""
67
+ if self._model is not None:
68
+ del self._model
69
+ self._model = None
70
+ self._device = None
71
+ torch.cuda.empty_cache()
72
+ gc.collect()
73
+
74
+
75
+ class FoundationStereoMethod(StereoMethodBase):
76
+ """FoundationStereo implementation"""
77
+
78
+ def __init__(self, variant: str = "11-33-40"):
79
+ display_name = f"FoundationStereo ({variant})"
80
+ super().__init__(f"foundation_stereo_{variant}", display_name)
81
+ self.variant = variant
82
+
83
+ def load_model(self):
84
+ """Load FoundationStereo model"""
85
+ try:
86
+ # Import FoundationStereo modules
87
+ from FoundationStereo_demo.app_local import get_cached_model, get_available_models
88
+
89
+ # Get available models
90
+ available_models = get_available_models()
91
+
92
+ # Find the appropriate model selection
93
+ model_selection = None
94
+ for model_name in available_models.keys():
95
+ if self.variant in model_name:
96
+ model_selection = model_name
97
+ break
98
+
99
+ if model_selection is None:
100
+ # Fallback to first available model
101
+ model_selection = list(available_models.keys())[0] if available_models else None
102
+
103
+ if model_selection is None:
104
+ raise ValueError("No FoundationStereo models available")
105
+
106
+ self._model, self._device = get_cached_model(model_selection)
107
+ logging.info(f"βœ… FoundationStereo {self.variant} loaded successfully")
108
+ return True
109
+
110
+ except Exception as e:
111
+ logging.error(f"Failed to load FoundationStereo {self.variant}: {e}")
112
+ return False
113
+
114
+ def process_stereo_pair(self, left_img: np.ndarray, right_img: np.ndarray, progress_callback=None) -> Tuple[np.ndarray, str]:
115
+ """Process stereo pair using FoundationStereo"""
116
+ try:
117
+ from FoundationStereo_demo.app_local import process_stereo_pair
118
+
119
+ # Save images temporarily
120
+ with tempfile.NamedTemporaryFile(suffix='.png', delete=False) as left_tmp:
121
+ cv2.imwrite(left_tmp.name, cv2.cvtColor(left_img, cv2.COLOR_RGB2BGR))
122
+ left_path = left_tmp.name
123
+
124
+ with tempfile.NamedTemporaryFile(suffix='.png', delete=False) as right_tmp:
125
+ cv2.imwrite(right_tmp.name, cv2.cvtColor(right_img, cv2.COLOR_RGB2BGR))
126
+ right_path = right_tmp.name
127
+
128
+ try:
129
+ # Find the model selection
130
+ from FoundationStereo_demo.app_local import get_available_models
131
+ available_models = get_available_models()
132
+ model_selection = None
133
+ for model_name in available_models.keys():
134
+ if self.variant in model_name:
135
+ model_selection = model_name
136
+ break
137
+
138
+ if model_selection is None:
139
+ model_selection = list(available_models.keys())[0]
140
+
141
+ # Process the stereo pair
142
+ result_img, status = process_stereo_pair(model_selection, left_path, right_path)
143
+
144
+ if result_img is not None:
145
+ return result_img, f"βœ… {self.display_name}: {status}"
146
+ else:
147
+ return None, f"❌ {self.display_name}: Processing failed"
148
+
149
+ finally:
150
+ # Clean up temporary files
151
+ if os.path.exists(left_path):
152
+ os.unlink(left_path)
153
+ if os.path.exists(right_path):
154
+ os.unlink(right_path)
155
+
156
+ except Exception as e:
157
+ logging.error(f"FoundationStereo processing failed: {e}")
158
+ return None, f"❌ {self.display_name}: {str(e)}"
159
+
160
+
161
+ class CREStereoMethod(StereoMethodBase):
162
+ """CREStereo implementation"""
163
+
164
+ def __init__(self):
165
+ super().__init__("crestereo", "CREStereo (ETH3D)")
166
+
167
+ def load_model(self):
168
+ """Load CREStereo model"""
169
+ try:
170
+ from CREStereo_demo.app_local import get_cached_model, get_available_models
171
+
172
+ # Get available models
173
+ available_models = get_available_models()
174
+
175
+ if not available_models:
176
+ raise ValueError("No CREStereo models available")
177
+
178
+ # Use the first available model
179
+ model_selection = list(available_models.keys())[0]
180
+ self._model, self._device = get_cached_model(model_selection)
181
+ logging.info("βœ… CREStereo loaded successfully")
182
+ return True
183
+
184
+ except Exception as e:
185
+ logging.error(f"Failed to load CREStereo: {e}")
186
+ return False
187
+
188
+ def process_stereo_pair(self, left_img: np.ndarray, right_img: np.ndarray, progress_callback=None) -> Tuple[np.ndarray, str]:
189
+ """Process stereo pair using CREStereo"""
190
+ try:
191
+ from CREStereo_demo.app_local import process_stereo_pair
192
+
193
+ # Save images temporarily
194
+ with tempfile.NamedTemporaryFile(suffix='.png', delete=False) as left_tmp:
195
+ cv2.imwrite(left_tmp.name, cv2.cvtColor(left_img, cv2.COLOR_RGB2BGR))
196
+ left_path = left_tmp.name
197
+
198
+ with tempfile.NamedTemporaryFile(suffix='.png', delete=False) as right_tmp:
199
+ cv2.imwrite(right_tmp.name, cv2.cvtColor(right_img, cv2.COLOR_RGB2BGR))
200
+ right_path = right_tmp.name
201
+
202
+ try:
203
+ # Find the model selection
204
+ from CREStereo_demo.app_local import get_available_models
205
+ available_models = get_available_models()
206
+ model_selection = list(available_models.keys())[0]
207
+
208
+ # Process the stereo pair
209
+ result_img, status = process_stereo_pair(model_selection, left_path, right_path)
210
+
211
+ if result_img is not None:
212
+ return result_img, f"βœ… {self.display_name}: {status}"
213
+ else:
214
+ return None, f"❌ {self.display_name}: Processing failed"
215
+
216
+ finally:
217
+ # Clean up temporary files
218
+ if os.path.exists(left_path):
219
+ os.unlink(left_path)
220
+ if os.path.exists(right_path):
221
+ os.unlink(right_path)
222
+
223
+ except Exception as e:
224
+ logging.error(f"CREStereo processing failed: {e}")
225
+ return None, f"❌ {self.display_name}: {str(e)}"
226
+
227
+
228
+ def initialize_methods() -> Dict[str, StereoMethodBase]:
229
+ """Initialize available stereo matching methods"""
230
+ methods = {}
231
+
232
+ # Initialize FoundationStereo variants
233
+ for variant in ["11-33-40", "23-51-11"]:
234
+ method = FoundationStereoMethod(variant)
235
+ methods[method.name] = method
236
+
237
+ # Initialize CREStereo
238
+ crestereo_method = CREStereoMethod()
239
+ methods[crestereo_method.name] = crestereo_method
240
+
241
+ return methods
242
+
243
+
244
+ def load_example_images() -> List[Tuple[str, str, str]]:
245
+ """Load example stereo pairs"""
246
+ examples = []
247
+ assets_dir = os.path.join(current_dir, "assets")
248
+
249
+ if os.path.exists(assets_dir):
250
+ for example_dir in os.listdir(assets_dir):
251
+ example_path = os.path.join(assets_dir, example_dir)
252
+ if os.path.isdir(example_path):
253
+ left_path = os.path.join(example_path, "left.png")
254
+ right_path = os.path.join(example_path, "right.png")
255
+
256
+ if os.path.exists(left_path) and os.path.exists(right_path):
257
+ examples.append((left_path, right_path, example_dir))
258
+
259
+ return examples
260
+
261
+
262
+ @spaces.GPU(duration=120) # 2 minutes for comparison processing
263
+ def compare_methods(left_image: np.ndarray, right_image: np.ndarray,
264
+ method1_name: str, method2_name: str,
265
+ progress: gr.Progress = gr.Progress()) -> Tuple[Optional[np.ndarray], str]:
266
+ """Compare two stereo matching methods"""
267
+
268
+ if left_image is None or right_image is None:
269
+ return None, "❌ Please upload both left and right images."
270
+
271
+ if method1_name == method2_name:
272
+ return None, "❌ Please select two different methods for comparison."
273
+
274
+ # Get methods
275
+ methods = initialize_methods()
276
+ method1 = methods.get(method1_name)
277
+ method2 = methods.get(method2_name)
278
+
279
+ if method1 is None or method2 is None:
280
+ return None, "❌ Selected methods not available."
281
+
282
+ progress(0.1, desc=f"Loading {method1.display_name}...")
283
+
284
+ # Load method 1
285
+ if not method1.load_model():
286
+ return None, f"❌ Failed to load {method1.display_name}"
287
+
288
+ progress(0.2, desc=f"Processing with {method1.display_name}...")
289
+
290
+ # Process with method 1
291
+ result1, status1 = method1.process_stereo_pair(left_image, right_image)
292
+
293
+ progress(0.5, desc=f"Loading {method2.display_name}...")
294
+
295
+ # Load method 2
296
+ if not method2.load_model():
297
+ method1.cleanup()
298
+ return None, f"❌ Failed to load {method2.display_name}"
299
+
300
+ progress(0.7, desc=f"Processing with {method2.display_name}...")
301
+
302
+ # Process with method 2
303
+ result2, status2 = method2.process_stereo_pair(left_image, right_image)
304
+
305
+ progress(0.9, desc="Creating comparison...")
306
+
307
+ if result1 is None or result2 is None:
308
+ method1.cleanup()
309
+ method2.cleanup()
310
+ return None, "❌ One or both methods failed to process the images."
311
+
312
+ # Create side-by-side comparison
313
+ comparison_img = create_comparison_image(result1, result2, method1.display_name, method2.display_name)
314
+
315
+ # Clean up
316
+ method1.cleanup()
317
+ method2.cleanup()
318
+
319
+ progress(1.0, desc="Complete!")
320
+
321
+ status = f"""πŸ” **Comparison Results**
322
+
323
+ **{method1.display_name}:**
324
+ {status1}
325
+
326
+ **{method2.display_name}:**
327
+ {status2}
328
+
329
+ πŸ’‘ **Tip:** Use the slider in the comparison image to switch between results."""
330
+
331
+ return comparison_img, status
332
+
333
+
334
+ def create_comparison_image(img1: np.ndarray, img2: np.ndarray, label1: str, label2: str) -> np.ndarray:
335
+ """Create a side-by-side comparison image with labels"""
336
+ h, w = img1.shape[:2]
337
+
338
+ # Create comparison canvas
339
+ comparison = np.zeros((h + 60, w * 2 + 20, 3), dtype=np.uint8)
340
+ comparison.fill(255) # White background
341
+
342
+ # Place images
343
+ comparison[50:50+h, 10:10+w] = img1
344
+ comparison[50:50+h, w+20:w*2+20] = img2
345
+
346
+ # Add labels
347
+ font = cv2.FONT_HERSHEY_SIMPLEX
348
+ font_scale = 0.8
349
+ font_thickness = 2
350
+
351
+ # Method 1 label
352
+ text_size1 = cv2.getTextSize(label1, font, font_scale, font_thickness)[0]
353
+ text_x1 = 10 + (w - text_size1[0]) // 2
354
+ cv2.putText(comparison, label1, (text_x1, 30), font, font_scale, (0, 0, 0), font_thickness)
355
+
356
+ # Method 2 label
357
+ text_size2 = cv2.getTextSize(label2, font, font_scale, font_thickness)[0]
358
+ text_x2 = w + 20 + (w - text_size2[0]) // 2
359
+ cv2.putText(comparison, label2, (text_x2, 30), font, font_scale, (0, 0, 0), font_thickness)
360
+
361
+ return comparison
362
+
363
+
364
+ @spaces.GPU(duration=90) # 1.5 minutes for single method processing
365
+ def single_method_inference(left_image: np.ndarray, right_image: np.ndarray,
366
+ method_name: str,
367
+ progress: gr.Progress = gr.Progress()) -> Tuple[Optional[np.ndarray], str]:
368
+ """Run inference with a single method"""
369
+
370
+ if left_image is None or right_image is None:
371
+ return None, "❌ Please upload both left and right images."
372
+
373
+ methods = initialize_methods()
374
+ method = methods.get(method_name)
375
+
376
+ if method is None:
377
+ return None, "❌ Selected method not available."
378
+
379
+ progress(0.2, desc=f"Loading {method.display_name}...")
380
+
381
+ if not method.load_model():
382
+ return None, f"❌ Failed to load {method.display_name}"
383
+
384
+ progress(0.5, desc=f"Processing with {method.display_name}...")
385
+
386
+ result, status = method.process_stereo_pair(left_image, right_image)
387
+
388
+ method.cleanup()
389
+
390
+ progress(1.0, desc="Complete!")
391
+
392
+ return result, status
393
+
394
+
395
+ @spaces.GPU(duration=120) # 2 minutes for slider comparison
396
+ def create_slider_comparison(left_img, right_img, method1, method2, progress=gr.Progress()):
397
+ """Create comparison for image slider"""
398
+ if left_img is None or right_img is None:
399
+ return None, "❌ Please upload both images."
400
+
401
+ if method1 == method2:
402
+ return None, "❌ Please select different methods."
403
+
404
+ methods = initialize_methods()
405
+ m1 = methods.get(method1)
406
+ m2 = methods.get(method2)
407
+
408
+ if m1 is None or m2 is None:
409
+ return None, "❌ Methods not available."
410
+
411
+ progress(0.1, desc=f"Processing with {m1.display_name}...")
412
+
413
+ # Process with method 1
414
+ if not m1.load_model():
415
+ return None, f"❌ Failed to load {m1.display_name}"
416
+
417
+ result1, status1 = m1.process_stereo_pair(left_img, right_img)
418
+
419
+ progress(0.5, desc=f"Processing with {m2.display_name}...")
420
+
421
+ # Process with method 2
422
+ if not m2.load_model():
423
+ m1.cleanup()
424
+ return None, f"❌ Failed to load {m2.display_name}"
425
+
426
+ result2, status2 = m2.process_stereo_pair(left_img, right_img)
427
+
428
+ # Clean up
429
+ m1.cleanup()
430
+ m2.cleanup()
431
+
432
+ progress(1.0, desc="Complete!")
433
+
434
+ if result1 is None or result2 is None:
435
+ return None, "❌ Processing failed."
436
+
437
+ status = f"""🎚️ **Interactive Comparison Ready**
438
+
439
+ **{m1.display_name}:** {status1.split(':')[-1].strip() if ':' in status1 else status1}
440
+ **{m2.display_name}:** {status2.split(':')[-1].strip() if ':' in status2 else status2}
441
+
442
+ πŸ’‘ **Tip:** Drag the slider to compare the two methods interactively!"""
443
+
444
+ return (result1, result2), status
445
+
446
+
447
+ def create_app() -> gr.Blocks:
448
+ """Create the Gradio application"""
449
+
450
+ # Load examples
451
+ examples = load_example_images()
452
+
453
+ # Get available methods
454
+ methods = initialize_methods()
455
+ method_choices = [(method.display_name, method.name) for method in methods.values()]
456
+
457
+ with gr.Blocks(
458
+ title="Stereo Matching Methods Comparison",
459
+ theme=gr.themes.Soft(),
460
+ css="footer {visibility: hidden}"
461
+ ) as app:
462
+
463
+ gr.Markdown("""
464
+ # πŸ† Stereo Matching Methods Comparison
465
+
466
+ Compare different stereo matching algorithms side-by-side using advanced deep learning models.
467
+
468
+ **Available Methods:**
469
+ - 🎯 **FoundationStereo** (Low-cost & High-quality variants) - Zero-shot stereo matching
470
+ - ⚑ **CREStereo** - Practical stereo matching with high efficiency
471
+
472
+ ⚠️ **Important**: Upload **rectified** stereo image pairs for best results.
473
+ πŸš€ **Powered by ZeroGPU**: Automatic GPU allocation for fast processing!
474
+ """)
475
+
476
+ # Instructions section
477
+ with gr.Accordion("πŸ“‹ How to Use", open=False):
478
+ gr.Markdown("""
479
+ ### πŸ–ΌοΈ Input Requirements
480
+ 1. **Rectified stereo pairs**: Images should be epipolar-aligned (horizontal epipolar lines)
481
+ 2. **Same resolution**: Left and right images must have identical dimensions
482
+ 3. **Good quality**: Clear, well-lit images work best
483
+
484
+ ### πŸ” Comparison Modes
485
+ 1. **Method Comparison**: Compare two different methods side-by-side
486
+ 2. **Single Method**: Test individual methods
487
+ 3. **Interactive Slider**: Use ImageSlider for easy comparison
488
+
489
+ ### πŸ“Š Example Images
490
+ Try the provided example stereo pairs to see the differences between methods.
491
+
492
+ ### πŸš€ ZeroGPU Integration
493
+ - Automatic GPU allocation when processing starts
494
+ - Optimized memory management
495
+ - Fast model loading and cleanup
496
+ """)
497
+
498
+ with gr.Tabs():
499
+ # Tab 1: Method Comparison
500
+ with gr.Tab("πŸ” Method Comparison"):
501
+ gr.Markdown("### Compare Two Stereo Matching Methods")
502
+
503
+ with gr.Row():
504
+ with gr.Column():
505
+ left_img_comp = gr.Image(label="Left Image", type="numpy")
506
+ right_img_comp = gr.Image(label="Right Image", type="numpy")
507
+
508
+ with gr.Column():
509
+ method1_dropdown = gr.Dropdown(
510
+ choices=method_choices,
511
+ label="Method 1",
512
+ value=method_choices[0][1] if method_choices else None
513
+ )
514
+ method2_dropdown = gr.Dropdown(
515
+ choices=method_choices,
516
+ label="Method 2",
517
+ value=method_choices[1][1] if len(method_choices) > 1 else None
518
+ )
519
+ compare_btn = gr.Button("πŸš€ Compare Methods", variant="primary", size="lg")
520
+
521
+ comparison_result = gr.Image(label="Comparison Result")
522
+ comparison_status = gr.Markdown()
523
+
524
+ compare_btn.click(
525
+ fn=compare_methods,
526
+ inputs=[left_img_comp, right_img_comp, method1_dropdown, method2_dropdown],
527
+ outputs=[comparison_result, comparison_status],
528
+ show_progress=True
529
+ )
530
+
531
+ # Examples for method comparison
532
+ if examples:
533
+ example_inputs = []
534
+ for left_path, right_path, name in examples[:3]:
535
+ # Load images as numpy arrays
536
+ left_img = cv2.imread(left_path)
537
+ right_img = cv2.imread(right_path)
538
+ if left_img is not None:
539
+ left_img = cv2.cvtColor(left_img, cv2.COLOR_BGR2RGB)
540
+ if right_img is not None:
541
+ right_img = cv2.cvtColor(right_img, cv2.COLOR_BGR2RGB)
542
+ example_inputs.append([left_img, right_img])
543
+
544
+ gr.Examples(
545
+ examples=example_inputs,
546
+ inputs=[left_img_comp, right_img_comp],
547
+ label="πŸ“Έ Example Stereo Pairs",
548
+ examples_per_page=3
549
+ )
550
+
551
+ # Tab 2: Interactive Slider Comparison
552
+ with gr.Tab("🎚️ Interactive Comparison"):
553
+ gr.Markdown("### Interactive Method Comparison with Slider")
554
+
555
+ with gr.Row():
556
+ with gr.Column():
557
+ left_img_slider = gr.Image(label="Left Image", type="numpy")
558
+ right_img_slider = gr.Image(label="Right Image", type="numpy")
559
+
560
+ with gr.Column():
561
+ method1_slider = gr.Dropdown(
562
+ choices=method_choices,
563
+ label="Method A",
564
+ value=method_choices[0][1] if method_choices else None
565
+ )
566
+ method2_slider = gr.Dropdown(
567
+ choices=method_choices,
568
+ label="Method B",
569
+ value=method_choices[1][1] if len(method_choices) > 1 else None
570
+ )
571
+ slider_compare_btn = gr.Button("🎚️ Generate Slider Comparison", variant="primary", size="lg")
572
+
573
+ # Image slider for comparison
574
+ comparison_slider = gr.ImageSlider(
575
+ label="Method Comparison (Drag slider to compare)",
576
+ show_label=True
577
+ )
578
+ slider_status = gr.Markdown()
579
+
580
+ slider_compare_btn.click(
581
+ fn=create_slider_comparison,
582
+ inputs=[left_img_slider, right_img_slider, method1_slider, method2_slider],
583
+ outputs=[comparison_slider, slider_status],
584
+ show_progress=True
585
+ )
586
+
587
+ # Examples for interactive slider
588
+ if examples:
589
+ example_inputs_slider = []
590
+ for left_path, right_path, name in examples[:3]:
591
+ # Load images as numpy arrays
592
+ left_img = cv2.imread(left_path)
593
+ right_img = cv2.imread(right_path)
594
+ if left_img is not None:
595
+ left_img = cv2.cvtColor(left_img, cv2.COLOR_BGR2RGB)
596
+ if right_img is not None:
597
+ right_img = cv2.cvtColor(right_img, cv2.COLOR_BGR2RGB)
598
+ example_inputs_slider.append([left_img, right_img])
599
+
600
+ gr.Examples(
601
+ examples=example_inputs_slider,
602
+ inputs=[left_img_slider, right_img_slider],
603
+ label="πŸ“Έ Example Stereo Pairs",
604
+ examples_per_page=3
605
+ )
606
+
607
+ # Tab 3: Single Method Testing
608
+ with gr.Tab("🎯 Single Method"):
609
+ gr.Markdown("### Test Individual Methods")
610
+
611
+ with gr.Row():
612
+ with gr.Column():
613
+ left_img_single = gr.Image(label="Left Image", type="numpy")
614
+ right_img_single = gr.Image(label="Right Image", type="numpy")
615
+
616
+ with gr.Column():
617
+ method_single = gr.Dropdown(
618
+ choices=method_choices,
619
+ label="Select Method",
620
+ value=method_choices[0][1] if method_choices else None
621
+ )
622
+ single_btn = gr.Button("πŸš€ Process", variant="primary", size="lg")
623
+
624
+ single_result = gr.Image(label="Disparity Result")
625
+ single_status = gr.Markdown()
626
+
627
+ single_btn.click(
628
+ fn=single_method_inference,
629
+ inputs=[left_img_single, right_img_single, method_single],
630
+ outputs=[single_result, single_status],
631
+ show_progress=True
632
+ )
633
+
634
+ # Examples for single method
635
+ if examples:
636
+ example_inputs_single = []
637
+ for left_path, right_path, name in examples[:3]:
638
+ # Load images as numpy arrays
639
+ left_img = cv2.imread(left_path)
640
+ right_img = cv2.imread(right_path)
641
+ if left_img is not None:
642
+ left_img = cv2.cvtColor(left_img, cv2.COLOR_BGR2RGB)
643
+ if right_img is not None:
644
+ right_img = cv2.cvtColor(right_img, cv2.COLOR_BGR2RGB)
645
+ example_inputs_single.append([left_img, right_img])
646
+
647
+ gr.Examples(
648
+ examples=example_inputs_single,
649
+ inputs=[left_img_single, right_img_single],
650
+ label="πŸ“Έ Example Stereo Pairs",
651
+ examples_per_page=3
652
+ )
653
+
654
+ # Information section
655
+ with gr.Accordion("ℹ️ Method Information", open=False):
656
+ gr.Markdown("""
657
+ ### 🎯 FoundationStereo
658
+ - **Type**: Zero-shot stereo matching using foundation models
659
+ - **Variants**: Low-cost (11-33-40) and High-quality (23-51-11)
660
+ - **Strengths**: Generalizes well to different domains without training
661
+ - **Paper**: [FoundationStereo: Zero-Shot Stereo Matching via Foundation Model](https://arxiv.org/abs/2501.09898)
662
+
663
+ ### ⚑ CREStereo
664
+ - **Type**: Practical stereo matching with iterative refinement
665
+ - **Model**: ETH3D pre-trained weights
666
+ - **Strengths**: Fast inference with good accuracy
667
+ - **Paper**: [Practical Stereo Matching via Cascaded Recurrent Network with Adaptive Correlation](https://arxiv.org/abs/2203.11483)
668
+
669
+ ### 🎚️ Interactive Comparison Tips
670
+ - Use the **ImageSlider** to quickly compare methods
671
+ - Drag the slider to see differences in detail preservation
672
+ - Look for differences in depth boundaries and texture regions
673
+ - Different methods may perform better on different scene types
674
+
675
+ ### πŸš€ ZeroGPU Features
676
+ - **Automatic GPU allocation**: GPU resources allocated on-demand
677
+ - **Optimized timeouts**: Different durations for different operations
678
+ - **Memory management**: Automatic cleanup after processing
679
+ - **Queue management**: Fair resource sharing among users
680
+ """)
681
+
682
+ # Footer
683
+ gr.Markdown("""
684
+ ---
685
+ ### πŸ“ Notes
686
+ - **πŸš€ ZeroGPU Powered**: Automatic GPU allocation for optimal performance
687
+ - **⏱️ Processing Times**: Method comparison ~2min, Single method ~1.5min
688
+ - **🧠 Memory Management**: Automatic cleanup between comparisons
689
+ - **πŸ“Š Best Results**: Use high-quality, well-rectified stereo pairs
690
+
691
+ ### πŸ”— References
692
+ - [FoundationStereo Repository](https://github.com/NVlabs/FoundationStereo)
693
+ - [CREStereo Repository](https://github.com/megvii-research/CREStereo)
694
+ - [Gradio ImageSlider Documentation](https://gradio.app/docs/#imageslider)
695
+ - [Hugging Face ZeroGPU](https://huggingface.co/zero-gpu-explorers)
696
+ """)
697
+
698
+ return app
699
+
700
+
701
+ def main():
702
+ """Main function to launch the comparison app"""
703
+
704
+ logging.info("πŸš€ Starting Stereo Matching Comparison App (ZeroGPU)...")
705
+
706
+ # Check if we're in Hugging Face Spaces
707
+ if 'SPACE_ID' in os.environ:
708
+ logging.info("Running in Hugging Face Spaces environment")
709
+
710
+ try:
711
+ # Check if subdemo directories exist
712
+ foundation_exists = os.path.exists(foundation_stereo_dir)
713
+ crestereo_exists = os.path.exists(crestereo_dir)
714
+
715
+ if not foundation_exists and not crestereo_exists:
716
+ logging.error("No stereo matching demo directories found!")
717
+ return
718
+
719
+ logging.info(f"FoundationStereo demo: {'βœ…' if foundation_exists else '❌'}")
720
+ logging.info(f"CREStereo demo: {'βœ…' if crestereo_exists else '❌'}")
721
+
722
+ # Create and launch app
723
+ logging.info("Creating comparison app...")
724
+ app = create_app()
725
+ logging.info("βœ… Comparison app created successfully")
726
+
727
+ # Launch with Spaces-optimized settings
728
+ app.launch(
729
+ share=False, # Spaces handles sharing
730
+ show_error=True,
731
+ favicon_path=None,
732
+ ssr_mode=False,
733
+ allowed_paths=["./"]
734
+ )
735
+
736
+ except Exception as e:
737
+ logging.error(f"Failed to launch app: {e}")
738
+ raise
739
+
740
+
741
+ if __name__ == "__main__":
742
+ main()
app_local.py ADDED
@@ -0,0 +1,731 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Stereo Matching Methods Comparison Demo
3
+
4
+ This demo compares different stereo matching algorithms using Gradio's ImageSlider.
5
+ Currently supports:
6
+ - FoundationStereo (Low-cost and High-quality variants)
7
+ - CREStereo (ETH3D pre-trained model)
8
+ """
9
+
10
+ import os
11
+ import sys
12
+ import logging
13
+ import gc
14
+ import tempfile
15
+ from pathlib import Path
16
+ from typing import Optional, Tuple, Union, Dict, List
17
+ import numpy as np
18
+ import cv2
19
+ import gradio as gr
20
+ import imageio
21
+ import argparse
22
+ import random
23
+
24
+ import torch
25
+ import torch.nn.functional as F
26
+
27
+ # Configure logging
28
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
29
+
30
+ # Get current directory
31
+ current_dir = os.path.dirname(os.path.abspath(__file__))
32
+
33
+ # Add subdemo directories to path
34
+ foundation_stereo_dir = os.path.join(current_dir, "FoundationStereo_demo")
35
+ crestereo_dir = os.path.join(current_dir, "CREStereo_demo")
36
+ sys.path.insert(0, foundation_stereo_dir)
37
+ sys.path.insert(0, crestereo_dir)
38
+
39
+ # Global variables for model caching
40
+ _cached_models = {}
41
+ _available_methods = {}
42
+
43
+ class StereoMethodBase:
44
+ """Base class for stereo matching methods"""
45
+
46
+ def __init__(self, name: str, display_name: str):
47
+ self.name = name
48
+ self.display_name = display_name
49
+ self._model = None
50
+ self._device = None
51
+
52
+ def load_model(self):
53
+ """Load the model for this method"""
54
+ raise NotImplementedError
55
+
56
+ def process_stereo_pair(self, left_img: np.ndarray, right_img: np.ndarray, progress_callback=None) -> Tuple[np.ndarray, str]:
57
+ """Process stereo pair and return disparity visualization and status"""
58
+ raise NotImplementedError
59
+
60
+ def cleanup(self):
61
+ """Clean up model and free memory"""
62
+ if self._model is not None:
63
+ del self._model
64
+ self._model = None
65
+ self._device = None
66
+ torch.cuda.empty_cache()
67
+ gc.collect()
68
+
69
+
70
+ class FoundationStereoMethod(StereoMethodBase):
71
+ """FoundationStereo implementation"""
72
+
73
+ def __init__(self, variant: str = "11-33-40"):
74
+ display_name = f"FoundationStereo ({variant})"
75
+ super().__init__(f"foundation_stereo_{variant}", display_name)
76
+ self.variant = variant
77
+
78
+ def load_model(self):
79
+ """Load FoundationStereo model"""
80
+ try:
81
+ # Import FoundationStereo modules
82
+ from FoundationStereo_demo.app_local import get_cached_model, get_available_models
83
+
84
+ # Get available models
85
+ available_models = get_available_models()
86
+
87
+ # Find the appropriate model selection
88
+ model_selection = None
89
+ for model_name in available_models.keys():
90
+ if self.variant in model_name:
91
+ model_selection = model_name
92
+ break
93
+
94
+ if model_selection is None:
95
+ # Fallback to first available model
96
+ model_selection = list(available_models.keys())[0] if available_models else None
97
+
98
+ if model_selection is None:
99
+ raise ValueError("No FoundationStereo models available")
100
+
101
+ self._model, self._device = get_cached_model(model_selection)
102
+ logging.info(f"βœ… FoundationStereo {self.variant} loaded successfully")
103
+ return True
104
+
105
+ except Exception as e:
106
+ logging.error(f"Failed to load FoundationStereo {self.variant}: {e}")
107
+ return False
108
+
109
+ def process_stereo_pair(self, left_img: np.ndarray, right_img: np.ndarray, progress_callback=None) -> Tuple[np.ndarray, str]:
110
+ """Process stereo pair using FoundationStereo"""
111
+ try:
112
+ from FoundationStereo_demo.app_local import process_stereo_pair
113
+
114
+ # Save images temporarily
115
+ with tempfile.NamedTemporaryFile(suffix='.png', delete=False) as left_tmp:
116
+ cv2.imwrite(left_tmp.name, cv2.cvtColor(left_img, cv2.COLOR_RGB2BGR))
117
+ left_path = left_tmp.name
118
+
119
+ with tempfile.NamedTemporaryFile(suffix='.png', delete=False) as right_tmp:
120
+ cv2.imwrite(right_tmp.name, cv2.cvtColor(right_img, cv2.COLOR_RGB2BGR))
121
+ right_path = right_tmp.name
122
+
123
+ try:
124
+ # Find the model selection
125
+ from FoundationStereo_demo.app_local import get_available_models
126
+ available_models = get_available_models()
127
+ model_selection = None
128
+ for model_name in available_models.keys():
129
+ if self.variant in model_name:
130
+ model_selection = model_name
131
+ break
132
+
133
+ if model_selection is None:
134
+ model_selection = list(available_models.keys())[0]
135
+
136
+ # Process the stereo pair
137
+ result_img, status = process_stereo_pair(model_selection, left_path, right_path)
138
+
139
+ if result_img is not None:
140
+ return result_img, f"βœ… {self.display_name}: {status}"
141
+ else:
142
+ return None, f"❌ {self.display_name}: Processing failed"
143
+
144
+ finally:
145
+ # Clean up temporary files
146
+ if os.path.exists(left_path):
147
+ os.unlink(left_path)
148
+ if os.path.exists(right_path):
149
+ os.unlink(right_path)
150
+
151
+ except Exception as e:
152
+ logging.error(f"FoundationStereo processing failed: {e}")
153
+ return None, f"❌ {self.display_name}: {str(e)}"
154
+
155
+
156
+ class CREStereoMethod(StereoMethodBase):
157
+ """CREStereo implementation"""
158
+
159
+ def __init__(self):
160
+ super().__init__("crestereo", "CREStereo (ETH3D)")
161
+
162
+ def load_model(self):
163
+ """Load CREStereo model"""
164
+ try:
165
+ from CREStereo_demo.app_local import get_cached_model, get_available_models
166
+
167
+ # Get available models
168
+ available_models = get_available_models()
169
+
170
+ if not available_models:
171
+ raise ValueError("No CREStereo models available")
172
+
173
+ # Use the first available model
174
+ model_selection = list(available_models.keys())[0]
175
+ self._model, self._device = get_cached_model(model_selection)
176
+ logging.info("βœ… CREStereo loaded successfully")
177
+ return True
178
+
179
+ except Exception as e:
180
+ logging.error(f"Failed to load CREStereo: {e}")
181
+ return False
182
+
183
+ def process_stereo_pair(self, left_img: np.ndarray, right_img: np.ndarray, progress_callback=None) -> Tuple[np.ndarray, str]:
184
+ """Process stereo pair using CREStereo"""
185
+ try:
186
+ from CREStereo_demo.app_local import process_stereo_pair
187
+
188
+ # Save images temporarily
189
+ with tempfile.NamedTemporaryFile(suffix='.png', delete=False) as left_tmp:
190
+ cv2.imwrite(left_tmp.name, cv2.cvtColor(left_img, cv2.COLOR_RGB2BGR))
191
+ left_path = left_tmp.name
192
+
193
+ with tempfile.NamedTemporaryFile(suffix='.png', delete=False) as right_tmp:
194
+ cv2.imwrite(right_tmp.name, cv2.cvtColor(right_img, cv2.COLOR_RGB2BGR))
195
+ right_path = right_tmp.name
196
+
197
+ try:
198
+ # Find the model selection
199
+ from CREStereo_demo.app_local import get_available_models
200
+ available_models = get_available_models()
201
+ model_selection = list(available_models.keys())[0]
202
+
203
+ # Process the stereo pair
204
+ result_img, status = process_stereo_pair(model_selection, left_path, right_path)
205
+
206
+ if result_img is not None:
207
+ return result_img, f"βœ… {self.display_name}: {status}"
208
+ else:
209
+ return None, f"❌ {self.display_name}: Processing failed"
210
+
211
+ finally:
212
+ # Clean up temporary files
213
+ if os.path.exists(left_path):
214
+ os.unlink(left_path)
215
+ if os.path.exists(right_path):
216
+ os.unlink(right_path)
217
+
218
+ except Exception as e:
219
+ logging.error(f"CREStereo processing failed: {e}")
220
+ return None, f"❌ {self.display_name}: {str(e)}"
221
+
222
+
223
+ def initialize_methods() -> Dict[str, StereoMethodBase]:
224
+ """Initialize available stereo matching methods"""
225
+ methods = {}
226
+
227
+ # Initialize FoundationStereo variants
228
+ for variant in ["11-33-40", "23-51-11"]:
229
+ method = FoundationStereoMethod(variant)
230
+ methods[method.name] = method
231
+
232
+ # Initialize CREStereo
233
+ crestereo_method = CREStereoMethod()
234
+ methods[crestereo_method.name] = crestereo_method
235
+
236
+ return methods
237
+
238
+
239
+ def load_example_images() -> List[Tuple[str, str, str]]:
240
+ """Load example stereo pairs"""
241
+ examples = []
242
+ assets_dir = os.path.join(current_dir, "assets")
243
+
244
+ if os.path.exists(assets_dir):
245
+ for example_dir in os.listdir(assets_dir):
246
+ example_path = os.path.join(assets_dir, example_dir)
247
+ if os.path.isdir(example_path):
248
+ left_path = os.path.join(example_path, "left.png")
249
+ right_path = os.path.join(example_path, "right.png")
250
+
251
+ if os.path.exists(left_path) and os.path.exists(right_path):
252
+ examples.append((left_path, right_path, example_dir))
253
+
254
+ return examples
255
+
256
+
257
+ def compare_methods(left_image: np.ndarray, right_image: np.ndarray,
258
+ method1_name: str, method2_name: str,
259
+ progress: gr.Progress = gr.Progress()) -> Tuple[Optional[np.ndarray], str]:
260
+ """Compare two stereo matching methods"""
261
+
262
+ if left_image is None or right_image is None:
263
+ return None, "❌ Please upload both left and right images."
264
+
265
+ if method1_name == method2_name:
266
+ return None, "❌ Please select two different methods for comparison."
267
+
268
+ # Get methods
269
+ methods = initialize_methods()
270
+ method1 = methods.get(method1_name)
271
+ method2 = methods.get(method2_name)
272
+
273
+ if method1 is None or method2 is None:
274
+ return None, "❌ Selected methods not available."
275
+
276
+ progress(0.1, desc=f"Loading {method1.display_name}...")
277
+
278
+ # Load method 1
279
+ if not method1.load_model():
280
+ return None, f"❌ Failed to load {method1.display_name}"
281
+
282
+ progress(0.2, desc=f"Processing with {method1.display_name}...")
283
+
284
+ # Process with method 1
285
+ result1, status1 = method1.process_stereo_pair(left_image, right_image)
286
+
287
+ progress(0.5, desc=f"Loading {method2.display_name}...")
288
+
289
+ # Load method 2
290
+ if not method2.load_model():
291
+ method1.cleanup()
292
+ return None, f"❌ Failed to load {method2.display_name}"
293
+
294
+ progress(0.7, desc=f"Processing with {method2.display_name}...")
295
+
296
+ # Process with method 2
297
+ result2, status2 = method2.process_stereo_pair(left_image, right_image)
298
+
299
+ progress(0.9, desc="Creating comparison...")
300
+
301
+ if result1 is None or result2 is None:
302
+ method1.cleanup()
303
+ method2.cleanup()
304
+ return None, "❌ One or both methods failed to process the images."
305
+
306
+ # Create side-by-side comparison
307
+ comparison_img = create_comparison_image(result1, result2, method1.display_name, method2.display_name)
308
+
309
+ # Clean up
310
+ method1.cleanup()
311
+ method2.cleanup()
312
+
313
+ progress(1.0, desc="Complete!")
314
+
315
+ status = f"""πŸ” **Comparison Results**
316
+
317
+ **{method1.display_name}:**
318
+ {status1}
319
+
320
+ **{method2.display_name}:**
321
+ {status2}
322
+
323
+ πŸ’‘ **Tip:** Use the slider in the comparison image to switch between results."""
324
+
325
+ return comparison_img, status
326
+
327
+
328
+ def create_comparison_image(img1: np.ndarray, img2: np.ndarray, label1: str, label2: str) -> np.ndarray:
329
+ """Create a side-by-side comparison image with labels"""
330
+ h, w = img1.shape[:2]
331
+
332
+ # Create comparison canvas
333
+ comparison = np.zeros((h + 60, w * 2 + 20, 3), dtype=np.uint8)
334
+ comparison.fill(255) # White background
335
+
336
+ # Place images
337
+ comparison[50:50+h, 10:10+w] = img1
338
+ comparison[50:50+h, w+20:w*2+20] = img2
339
+
340
+ # Add labels
341
+ font = cv2.FONT_HERSHEY_SIMPLEX
342
+ font_scale = 0.8
343
+ font_thickness = 2
344
+
345
+ # Method 1 label
346
+ text_size1 = cv2.getTextSize(label1, font, font_scale, font_thickness)[0]
347
+ text_x1 = 10 + (w - text_size1[0]) // 2
348
+ cv2.putText(comparison, label1, (text_x1, 30), font, font_scale, (0, 0, 0), font_thickness)
349
+
350
+ # Method 2 label
351
+ text_size2 = cv2.getTextSize(label2, font, font_scale, font_thickness)[0]
352
+ text_x2 = w + 20 + (w - text_size2[0]) // 2
353
+ cv2.putText(comparison, label2, (text_x2, 30), font, font_scale, (0, 0, 0), font_thickness)
354
+
355
+ return comparison
356
+
357
+
358
+ def single_method_inference(left_image: np.ndarray, right_image: np.ndarray,
359
+ method_name: str,
360
+ progress: gr.Progress = gr.Progress()) -> Tuple[Optional[np.ndarray], str]:
361
+ """Run inference with a single method"""
362
+
363
+ if left_image is None or right_image is None:
364
+ return None, "❌ Please upload both left and right images."
365
+
366
+ methods = initialize_methods()
367
+ method = methods.get(method_name)
368
+
369
+ if method is None:
370
+ return None, "❌ Selected method not available."
371
+
372
+ progress(0.2, desc=f"Loading {method.display_name}...")
373
+
374
+ if not method.load_model():
375
+ return None, f"❌ Failed to load {method.display_name}"
376
+
377
+ progress(0.5, desc=f"Processing with {method.display_name}...")
378
+
379
+ result, status = method.process_stereo_pair(left_image, right_image)
380
+
381
+ method.cleanup()
382
+
383
+ progress(1.0, desc="Complete!")
384
+
385
+ return result, status
386
+
387
+
388
+ def create_app() -> gr.Blocks:
389
+ """Create the Gradio application"""
390
+
391
+ # Load examples
392
+ examples = load_example_images()
393
+
394
+ # Get available methods
395
+ methods = initialize_methods()
396
+ method_choices = [(method.display_name, method.name) for method in methods.values()]
397
+
398
+ with gr.Blocks(
399
+ title="Stereo Matching Methods Comparison",
400
+ theme=gr.themes.Soft(),
401
+ css="footer {visibility: hidden}"
402
+ ) as app:
403
+
404
+ gr.Markdown("""
405
+ # πŸ† Stereo Matching Methods Comparison
406
+
407
+ Compare different stereo matching algorithms side-by-side using advanced deep learning models.
408
+
409
+ **Available Methods:**
410
+ - 🎯 **FoundationStereo** (Low-cost & High-quality variants) - Zero-shot stereo matching
411
+ - ⚑ **CREStereo** - Practical stereo matching with high efficiency
412
+
413
+ ⚠️ **Important**: Upload **rectified** stereo image pairs for best results.
414
+ """)
415
+
416
+ # Instructions section
417
+ with gr.Accordion("πŸ“‹ How to Use", open=False):
418
+ gr.Markdown("""
419
+ ### πŸ–ΌοΈ Input Requirements
420
+ 1. **Rectified stereo pairs**: Images should be epipolar-aligned (horizontal epipolar lines)
421
+ 2. **Same resolution**: Left and right images must have identical dimensions
422
+ 3. **Good quality**: Clear, well-lit images work best
423
+
424
+ ### πŸ” Comparison Modes
425
+ 1. **Method Comparison**: Compare two different methods side-by-side
426
+ 2. **Single Method**: Test individual methods
427
+ 3. **Interactive Slider**: Use ImageSlider for easy comparison
428
+
429
+ ### πŸ“Š Example Images
430
+ Try the provided example stereo pairs to see the differences between methods.
431
+ """)
432
+
433
+ with gr.Tabs():
434
+ # Tab 1: Method Comparison
435
+ with gr.Tab("πŸ” Method Comparison"):
436
+ gr.Markdown("### Compare Two Stereo Matching Methods")
437
+
438
+ with gr.Row():
439
+ with gr.Column():
440
+ left_img_comp = gr.Image(label="Left Image", type="numpy")
441
+ right_img_comp = gr.Image(label="Right Image", type="numpy")
442
+
443
+ with gr.Column():
444
+ method1_dropdown = gr.Dropdown(
445
+ choices=method_choices,
446
+ label="Method 1",
447
+ value=method_choices[0][1] if method_choices else None
448
+ )
449
+ method2_dropdown = gr.Dropdown(
450
+ choices=method_choices,
451
+ label="Method 2",
452
+ value=method_choices[1][1] if len(method_choices) > 1 else None
453
+ )
454
+ compare_btn = gr.Button("πŸš€ Compare Methods", variant="primary", size="lg")
455
+
456
+ comparison_result = gr.Image(label="Comparison Result")
457
+ comparison_status = gr.Markdown()
458
+
459
+ compare_btn.click(
460
+ fn=compare_methods,
461
+ inputs=[left_img_comp, right_img_comp, method1_dropdown, method2_dropdown],
462
+ outputs=[comparison_result, comparison_status],
463
+ show_progress=True
464
+ )
465
+
466
+ # Examples for method comparison
467
+ if examples:
468
+ example_inputs = []
469
+ for left_path, right_path, name in examples[:3]:
470
+ # Load images as numpy arrays
471
+ left_img = cv2.imread(left_path)
472
+ right_img = cv2.imread(right_path)
473
+ if left_img is not None:
474
+ left_img = cv2.cvtColor(left_img, cv2.COLOR_BGR2RGB)
475
+ if right_img is not None:
476
+ right_img = cv2.cvtColor(right_img, cv2.COLOR_BGR2RGB)
477
+ example_inputs.append([left_img, right_img])
478
+
479
+ gr.Examples(
480
+ examples=example_inputs,
481
+ inputs=[left_img_comp, right_img_comp],
482
+ label="πŸ“Έ Example Stereo Pairs",
483
+ examples_per_page=3
484
+ )
485
+
486
+ # Tab 2: Interactive Slider Comparison
487
+ with gr.Tab("🎚️ Interactive Comparison"):
488
+ gr.Markdown("### Interactive Method Comparison with Slider")
489
+
490
+ with gr.Row():
491
+ with gr.Column():
492
+ left_img_slider = gr.Image(label="Left Image", type="numpy")
493
+ right_img_slider = gr.Image(label="Right Image", type="numpy")
494
+
495
+ with gr.Column():
496
+ method1_slider = gr.Dropdown(
497
+ choices=method_choices,
498
+ label="Method A",
499
+ value=method_choices[0][1] if method_choices else None
500
+ )
501
+ method2_slider = gr.Dropdown(
502
+ choices=method_choices,
503
+ label="Method B",
504
+ value=method_choices[1][1] if len(method_choices) > 1 else None
505
+ )
506
+ slider_compare_btn = gr.Button("🎚️ Generate Slider Comparison", variant="primary", size="lg")
507
+
508
+ # Image slider for comparison
509
+ comparison_slider = gr.ImageSlider(
510
+ label="Method Comparison (Drag slider to compare)",
511
+ show_label=True
512
+ )
513
+ slider_status = gr.Markdown()
514
+
515
+ def create_slider_comparison(left_img, right_img, method1, method2, progress=gr.Progress()):
516
+ """Create comparison for image slider"""
517
+ if left_img is None or right_img is None:
518
+ return None, "❌ Please upload both images."
519
+
520
+ if method1 == method2:
521
+ return None, "❌ Please select different methods."
522
+
523
+ methods = initialize_methods()
524
+ m1 = methods.get(method1)
525
+ m2 = methods.get(method2)
526
+
527
+ if m1 is None or m2 is None:
528
+ return None, "❌ Methods not available."
529
+
530
+ progress(0.1, desc=f"Processing with {m1.display_name}...")
531
+
532
+ # Process with method 1
533
+ if not m1.load_model():
534
+ return None, f"❌ Failed to load {m1.display_name}"
535
+
536
+ result1, status1 = m1.process_stereo_pair(left_img, right_img)
537
+
538
+ progress(0.5, desc=f"Processing with {m2.display_name}...")
539
+
540
+ # Process with method 2
541
+ if not m2.load_model():
542
+ m1.cleanup()
543
+ return None, f"❌ Failed to load {m2.display_name}"
544
+
545
+ result2, status2 = m2.process_stereo_pair(left_img, right_img)
546
+
547
+ # Clean up
548
+ m1.cleanup()
549
+ m2.cleanup()
550
+
551
+ progress(1.0, desc="Complete!")
552
+
553
+ if result1 is None or result2 is None:
554
+ return None, "❌ Processing failed."
555
+
556
+ status = f"""🎚️ **Interactive Comparison Ready**
557
+
558
+ **{m1.display_name}:** {status1.split(':')[-1].strip() if ':' in status1 else status1}
559
+ **{m2.display_name}:** {status2.split(':')[-1].strip() if ':' in status2 else status2}
560
+
561
+ πŸ’‘ **Tip:** Drag the slider to compare the two methods interactively!"""
562
+
563
+ return (result1, result2), status
564
+
565
+ slider_compare_btn.click(
566
+ fn=create_slider_comparison,
567
+ inputs=[left_img_slider, right_img_slider, method1_slider, method2_slider],
568
+ outputs=[comparison_slider, slider_status],
569
+ show_progress=True
570
+ )
571
+
572
+ # Examples for interactive slider
573
+ if examples:
574
+ example_inputs_slider = []
575
+ for left_path, right_path, name in examples[:3]:
576
+ # Load images as numpy arrays
577
+ left_img = cv2.imread(left_path)
578
+ right_img = cv2.imread(right_path)
579
+ if left_img is not None:
580
+ left_img = cv2.cvtColor(left_img, cv2.COLOR_BGR2RGB)
581
+ if right_img is not None:
582
+ right_img = cv2.cvtColor(right_img, cv2.COLOR_BGR2RGB)
583
+ example_inputs_slider.append([left_img, right_img])
584
+
585
+ gr.Examples(
586
+ examples=example_inputs_slider,
587
+ inputs=[left_img_slider, right_img_slider],
588
+ label="πŸ“Έ Example Stereo Pairs",
589
+ examples_per_page=3
590
+ )
591
+
592
+ # Tab 3: Single Method Testing
593
+ with gr.Tab("🎯 Single Method"):
594
+ gr.Markdown("### Test Individual Methods")
595
+
596
+ with gr.Row():
597
+ with gr.Column():
598
+ left_img_single = gr.Image(label="Left Image", type="numpy")
599
+ right_img_single = gr.Image(label="Right Image", type="numpy")
600
+
601
+ with gr.Column():
602
+ method_single = gr.Dropdown(
603
+ choices=method_choices,
604
+ label="Select Method",
605
+ value=method_choices[0][1] if method_choices else None
606
+ )
607
+ single_btn = gr.Button("πŸš€ Process", variant="primary", size="lg")
608
+
609
+ single_result = gr.Image(label="Disparity Result")
610
+ single_status = gr.Markdown()
611
+
612
+ single_btn.click(
613
+ fn=single_method_inference,
614
+ inputs=[left_img_single, right_img_single, method_single],
615
+ outputs=[single_result, single_status],
616
+ show_progress=True
617
+ )
618
+
619
+ # Examples for single method
620
+ if examples:
621
+ example_inputs_single = []
622
+ for left_path, right_path, name in examples[:3]:
623
+ # Load images as numpy arrays
624
+ left_img = cv2.imread(left_path)
625
+ right_img = cv2.imread(right_path)
626
+ if left_img is not None:
627
+ left_img = cv2.cvtColor(left_img, cv2.COLOR_BGR2RGB)
628
+ if right_img is not None:
629
+ right_img = cv2.cvtColor(right_img, cv2.COLOR_BGR2RGB)
630
+ example_inputs_single.append([left_img, right_img])
631
+
632
+ gr.Examples(
633
+ examples=example_inputs_single,
634
+ inputs=[left_img_single, right_img_single],
635
+ label="πŸ“Έ Example Stereo Pairs",
636
+ examples_per_page=3
637
+ )
638
+
639
+ # Information section
640
+ with gr.Accordion("ℹ️ Method Information", open=False):
641
+ gr.Markdown("""
642
+ ### 🎯 FoundationStereo
643
+ - **Type**: Zero-shot stereo matching using foundation models
644
+ - **Variants**: Low-cost (11-33-40) and High-quality (23-51-11)
645
+ - **Strengths**: Generalizes well to different domains without training
646
+ - **Paper**: [FoundationStereo: Zero-Shot Stereo Matching via Foundation Model](https://arxiv.org/abs/2501.09898)
647
+
648
+ ### ⚑ CREStereo
649
+ - **Type**: Practical stereo matching with iterative refinement
650
+ - **Model**: ETH3D pre-trained weights
651
+ - **Strengths**: Fast inference with good accuracy
652
+ - **Paper**: [Practical Stereo Matching via Cascaded Recurrent Network with Adaptive Correlation](https://arxiv.org/abs/2203.11483)
653
+
654
+ ### 🎚️ Interactive Comparison Tips
655
+ - Use the **ImageSlider** to quickly compare methods
656
+ - Drag the slider to see differences in detail preservation
657
+ - Look for differences in depth boundaries and texture regions
658
+ - Different methods may perform better on different scene types
659
+ """)
660
+
661
+ # Footer
662
+ gr.Markdown("""
663
+ ---
664
+ ### πŸ“ Notes
665
+ - **GPU Acceleration**: Requires CUDA-compatible GPU for best performance
666
+ - **Model Caching**: Models are cached after first use for efficiency
667
+ - **Memory Management**: Automatic cleanup between comparisons
668
+ - **Best Results**: Use high-quality, well-rectified stereo pairs
669
+
670
+ ### πŸ”— References
671
+ - [FoundationStereo Repository](https://github.com/NVlabs/FoundationStereo)
672
+ - [CREStereo Repository](https://github.com/megvii-research/CREStereo)
673
+ - [Gradio ImageSlider Documentation](https://gradio.app/docs/#imageslider)
674
+ """)
675
+
676
+ return app
677
+
678
+
679
+ def main():
680
+ """Main function to launch the comparison app"""
681
+
682
+ logging.info("πŸš€ Starting Stereo Matching Comparison App...")
683
+
684
+ # Parse command line arguments
685
+ parser = argparse.ArgumentParser(description="Stereo Matching Comparison App")
686
+ parser.add_argument("--host", type=str, default="0.0.0.0", help="Host to bind to")
687
+ parser.add_argument("--port", type=int, default=7860, help="Port to bind to")
688
+ parser.add_argument("--debug", action="store_true", help="Enable debug mode")
689
+ parser.add_argument("--share", action="store_true", help="Enable public sharing")
690
+
691
+ args = parser.parse_args()
692
+
693
+ if args.debug:
694
+ logging.getLogger().setLevel(logging.DEBUG)
695
+
696
+ try:
697
+ # Check if subdemo directories exist
698
+ foundation_exists = os.path.exists(foundation_stereo_dir)
699
+ crestereo_exists = os.path.exists(crestereo_dir)
700
+
701
+ if not foundation_exists and not crestereo_exists:
702
+ logging.error("No stereo matching demo directories found!")
703
+ return
704
+
705
+ logging.info(f"FoundationStereo demo: {'βœ…' if foundation_exists else '❌'}")
706
+ logging.info(f"CREStereo demo: {'βœ…' if crestereo_exists else '❌'}")
707
+
708
+ # Create and launch app
709
+ logging.info("Creating comparison app...")
710
+ app = create_app()
711
+ logging.info("βœ… Comparison app created successfully")
712
+
713
+ logging.info(f"Launching app on {args.host}:{args.port}")
714
+
715
+ app.launch(
716
+ server_name=args.host,
717
+ server_port=args.port,
718
+ share=args.share,
719
+ show_error=True,
720
+ favicon_path=None,
721
+ ssr_mode=False,
722
+ allowed_paths=["./"]
723
+ )
724
+
725
+ except Exception as e:
726
+ logging.error(f"Failed to launch app: {e}")
727
+ raise
728
+
729
+
730
+ if __name__ == "__main__":
731
+ main()
requirements.txt ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # FoundationStereo Gradio Application Requirements
2
+ # Based on foundation_stereo conda environment
3
+
4
+ # Core PyTorch and deep learning
5
+ torch
6
+ torchvision==0.19.1
7
+ torchaudio==2.4.1
8
+ xformers==0.0.28.post1
9
+
10
+ # Computer vision and image processing
11
+ scikit-image
12
+ opencv-contrib-python
13
+ imageio
14
+ imgaug
15
+ albumentations
16
+
17
+ # Configuration and utilities
18
+ omegaconf
19
+ pyyaml
20
+ ruamel.yaml
21
+
22
+ # Scientific computing
23
+ scipy
24
+ numpy
25
+ scikit-learn
26
+ joblib
27
+
28
+ # Deep learning utilities
29
+ timm
30
+ einops
31
+ transformations
32
+
33
+ # 3D processing and visualization
34
+ open3d
35
+ trimesh
36
+
37
+ # Model and data utilities
38
+ huggingface-hub
39
+ gdown
40
+
41
+ # Development and notebooks
42
+ jupyterlab
43
+ ninja
44
+
45
+ # Web interface (Gradio)
46
+ gradio>=4.0.0
47
+ spaces # For Hugging Face Spaces ZeroGPU support
48
+
49
+ # Additional dependencies for the Gradio app
50
+ matplotlib
51
+ pillow
52
+ tqdm