aishaikds commited on
Commit
ba4392a
Β·
verified Β·
1 Parent(s): f3f3544

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +237 -71
app.py CHANGED
@@ -6,10 +6,10 @@ import pandas as pd
6
  import tempfile
7
  import sys
8
  import os
9
- from huggingface_hub import hf_hub_download, list_repo_files
10
 
11
  print("="*60)
12
- print("Downloading and patching model files...")
13
  print("="*60)
14
 
15
  repo_id = "julianzu9612/RFDETR-Soccernet"
@@ -19,47 +19,34 @@ try:
19
  print("\nDownloading inference.py...")
20
  inference_path = hf_hub_download(repo_id=repo_id, filename="inference.py")
21
 
22
- # Read the inference.py file
23
  with open(inference_path, 'r') as f:
24
  inference_code = f.read()
25
 
26
- print("\nPatching inference.py to use correct model size...")
 
27
 
28
- # Patch: Change rf-detr-base to rf-detr-large (or x if needed)
29
- # Look for the line that loads the base model and change it
30
  inference_code = inference_code.replace(
31
- "'rf-detr-base'",
32
- "'rf-detr-l'" # Try large model first
33
  )
34
  inference_code = inference_code.replace(
35
- '"rf-detr-base"',
36
- '"rf-detr-l"'
37
- )
38
- inference_code = inference_code.replace(
39
- "model_name = 'base'",
40
- "model_name = 'l'"
41
- )
42
- inference_code = inference_code.replace(
43
- 'model_name = "base"',
44
- 'model_name = "l"'
45
  )
46
 
47
- # Save patched version
48
- patched_path = inference_path.replace('.py', '_patched.py')
49
- with open(patched_path, 'w') as f:
50
- f.write(inference_code)
51
- print(f"βœ“ Saved patched version to: {patched_path}")
52
-
53
- # Also save as inference.py in the same directory
54
  with open(inference_path, 'w') as f:
55
  f.write(inference_code)
56
- print("βœ“ Updated original inference.py")
57
 
58
  # Download weights
59
  print("\nDownloading model weights...")
60
  weights_path = hf_hub_download(repo_id=repo_id, filename="weights/checkpoint_best_regular.pth")
 
61
 
62
- # Setup directories
63
  cache_dir = os.path.dirname(inference_path)
64
 
65
  if cache_dir not in sys.path:
@@ -68,23 +55,25 @@ try:
68
  original_dir = os.getcwd()
69
  os.chdir(cache_dir)
70
 
 
71
  weights_dir = os.path.join(cache_dir, "weights")
72
- if not os.path.exists(weights_dir):
73
- os.makedirs(weights_dir)
74
 
75
  expected_weights = os.path.join(weights_dir, "checkpoint_best_regular.pth")
76
  if not os.path.exists(expected_weights):
77
  import shutil
78
  shutil.copy(weights_path, expected_weights)
 
79
 
80
  print("\n" + "="*60)
81
- print("Initializing model...")
82
  print("="*60)
83
 
 
84
  from inference import RFDETRSoccerNet
85
 
86
  detector = RFDETRSoccerNet()
87
- print("\nβœ“ Model loaded successfully!")
88
 
89
  os.chdir(original_dir)
90
 
@@ -92,18 +81,11 @@ except Exception as e:
92
  print(f"\n❌ Error: {e}")
93
  import traceback
94
  traceback.print_exc()
95
-
96
- print("\n" + "="*60)
97
- print("SOLUTION:")
98
- print("="*60)
99
- print("The model repository has a bug in inference.py.")
100
- print("It's loading 'rf-detr-base' but the checkpoint was trained")
101
- print("with 'rf-detr-l' (large) or 'rf-detr-x' (xlarge).")
102
- print("\nPlease contact the model owner to fix the inference.py file.")
103
- print("="*60)
104
  raise
105
 
 
106
  def draw_detections_on_image(image, df):
 
107
  draw = ImageDraw.Draw(image)
108
 
109
  try:
@@ -111,7 +93,12 @@ def draw_detections_on_image(image, df):
111
  except:
112
  font = ImageFont.load_default()
113
 
114
- colors = {'ball': (255, 0, 0), 'player': (0, 255, 0), 'referee': (255, 255, 0), 'goalkeeper': (0, 0, 255)}
 
 
 
 
 
115
 
116
  for _, row in df.iterrows():
117
  x1, y1, x2, y2 = row['x1'], row['y1'], row['x2'], row['y2']
@@ -120,6 +107,7 @@ def draw_detections_on_image(image, df):
120
  color = colors.get(class_name, (255, 255, 255))
121
 
122
  draw.rectangle([x1, y1, x2, y2], outline=color, width=3)
 
123
  text = f"{class_name}: {conf:.2f}"
124
  bbox = draw.textbbox((x1, y1-20), text, font=font)
125
  draw.rectangle([bbox[0]-2, bbox[1]-2, bbox[2]+2, bbox[3]+2], fill=color)
@@ -128,37 +116,59 @@ def draw_detections_on_image(image, df):
128
  return image
129
 
130
  def process_image_interface(image, confidence_threshold):
 
131
  if image is None:
132
  return None, pd.DataFrame()
133
 
134
  try:
 
135
  temp_path = tempfile.mktemp(suffix='.jpg')
136
  Image.fromarray(image if isinstance(image, np.ndarray) else np.array(image)).save(temp_path)
137
 
 
138
  df = detector.process_image(temp_path, confidence_threshold=confidence_threshold)
 
 
139
  img = Image.open(temp_path)
140
  annotated_img = draw_detections_on_image(img, df)
 
 
141
  os.remove(temp_path)
142
 
143
  return annotated_img, df
 
144
  except Exception as e:
145
- print(f"Error: {e}")
 
 
146
  return None, pd.DataFrame()
147
 
148
  def process_video_interface(video, confidence_threshold, frame_skip, max_frames):
 
149
  if video is None:
150
  return None, pd.DataFrame()
151
 
152
  try:
153
  max_frames_val = int(max_frames) if max_frames > 0 else None
154
- df = detector.process_video(video, confidence_threshold=confidence_threshold,
155
- frame_skip=int(frame_skip), max_frames=max_frames_val)
156
 
 
 
 
 
 
 
 
 
 
 
157
  cap = cv2.VideoCapture(video)
158
- fps, width, height = int(cap.get(cv2.CAP_PROP_FPS)), int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)), int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
 
 
159
 
160
  output_path = tempfile.mktemp(suffix='.mp4')
161
- out = cv2.VideoWriter(output_path, cv2.VideoWriter_fourcc(*'mp4v'), fps, (width, height))
 
162
 
163
  frame_num = 0
164
  while cap.isOpened():
@@ -166,58 +176,214 @@ def process_video_interface(video, confidence_threshold, frame_skip, max_frames)
166
  if not ret:
167
  break
168
 
 
169
  frame_detections = df[df['frame'] == frame_num]
 
170
  if not frame_detections.empty:
171
- pil_img = Image.fromarray(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
172
- frame = cv2.cvtColor(np.array(draw_detections_on_image(pil_img, frame_detections)), cv2.COLOR_RGB2BGR)
 
 
173
 
174
  out.write(frame)
175
  frame_num += 1
176
 
177
  cap.release()
178
  out.release()
 
179
  return output_path, df
 
180
  except Exception as e:
181
- print(f"Error: {e}")
 
 
182
  return None, pd.DataFrame()
183
 
184
- with gr.Blocks(title="⚽ Soccer Object Detection") as demo:
 
185
  gr.Markdown("""
186
  # ⚽ Soccer Object Detection with RF-DETR
187
 
188
- Detect **balls**, **players**, **referees**, and **goalkeepers**!
189
 
190
  ### Model: [julianzu9612/RFDETR-Soccernet](https://huggingface.co/julianzu9612/RFDETR-Soccernet)
191
- - mAP@50: 85.7% | Classes: Ball, Player, Referee, Goalkeeper
 
 
 
192
  """)
193
 
194
- with gr.Tab("πŸ“Έ Image"):
 
 
195
  with gr.Row():
196
  with gr.Column():
197
  image_input = gr.Image(label="Upload Soccer Image", type="numpy")
198
- image_confidence = gr.Slider(0.1, 1.0, value=0.5, step=0.05, label="Confidence")
199
- image_button = gr.Button("πŸ” Detect", variant="primary")
 
 
 
 
 
 
 
 
200
  with gr.Column():
201
- image_output = gr.Image(label="Results")
202
- image_detections = gr.Dataframe(label="Detections")
203
- image_button.click(process_image_interface, [image_input, image_confidence], [image_output, image_detections])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
204
 
205
- with gr.Tab("πŸŽ₯ Video"):
 
 
206
  with gr.Row():
207
  with gr.Column():
208
- video_input = gr.Video(label="Upload Video")
209
- video_confidence = gr.Slider(0.1, 1.0, value=0.5, step=0.05, label="Confidence")
210
- video_frame_skip = gr.Slider(1, 10, value=5, step=1, label="Frame Skip")
211
- video_max_frames = gr.Number(value=300, label="Max Frames")
212
- video_button = gr.Button("🎬 Process", variant="primary")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
213
  with gr.Column():
214
- video_output = gr.Video(label="Results")
215
- video_detections = gr.Dataframe(label="Detections")
216
- video_button.click(process_video_interface, [video_input, video_confidence, video_frame_skip, video_max_frames], [video_output, video_detections])
 
 
 
 
 
 
 
 
 
 
217
 
218
- gr.Markdown("""
219
- ---
220
- πŸ”΄ Ball | 🟒 Player | 🟑 Referee | πŸ”΅ Goalkeeper
221
- """)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
222
 
223
  demo.launch()
 
6
  import tempfile
7
  import sys
8
  import os
9
+ from huggingface_hub import hf_hub_download
10
 
11
  print("="*60)
12
+ print("Setting up RF-DETR SoccerNet Model...")
13
  print("="*60)
14
 
15
  repo_id = "julianzu9612/RFDETR-Soccernet"
 
19
  print("\nDownloading inference.py...")
20
  inference_path = hf_hub_download(repo_id=repo_id, filename="inference.py")
21
 
22
+ # Read the file
23
  with open(inference_path, 'r') as f:
24
  inference_code = f.read()
25
 
26
+ print("\nπŸ”§ Patching inference.py...")
27
+ print(" Changing: RFDETRBase() β†’ RFDETRLarge()")
28
 
29
+ # THE FIX: Replace RFDETRBase with RFDETRLarge
 
30
  inference_code = inference_code.replace(
31
+ 'from rfdetr import RFDETRBase',
32
+ 'from rfdetr import RFDETRLarge'
33
  )
34
  inference_code = inference_code.replace(
35
+ 'self.model = RFDETRBase()',
36
+ 'self.model = RFDETRLarge()'
 
 
 
 
 
 
 
 
37
  )
38
 
39
+ # Save the patched version
 
 
 
 
 
 
40
  with open(inference_path, 'w') as f:
41
  f.write(inference_code)
42
+ print("βœ“ Patched inference.py successfully!")
43
 
44
  # Download weights
45
  print("\nDownloading model weights...")
46
  weights_path = hf_hub_download(repo_id=repo_id, filename="weights/checkpoint_best_regular.pth")
47
+ print(f"βœ“ Downloaded weights")
48
 
49
+ # Setup environment
50
  cache_dir = os.path.dirname(inference_path)
51
 
52
  if cache_dir not in sys.path:
 
55
  original_dir = os.getcwd()
56
  os.chdir(cache_dir)
57
 
58
+ # Create weights directory structure
59
  weights_dir = os.path.join(cache_dir, "weights")
60
+ os.makedirs(weights_dir, exist_ok=True)
 
61
 
62
  expected_weights = os.path.join(weights_dir, "checkpoint_best_regular.pth")
63
  if not os.path.exists(expected_weights):
64
  import shutil
65
  shutil.copy(weights_path, expected_weights)
66
+ print(f"βœ“ Weights copied to: {expected_weights}")
67
 
68
  print("\n" + "="*60)
69
+ print("Initializing RF-DETR SoccerNet Model...")
70
  print("="*60)
71
 
72
+ # Import and initialize the patched model
73
  from inference import RFDETRSoccerNet
74
 
75
  detector = RFDETRSoccerNet()
76
+ print("\nβœ… Model loaded successfully!")
77
 
78
  os.chdir(original_dir)
79
 
 
81
  print(f"\n❌ Error: {e}")
82
  import traceback
83
  traceback.print_exc()
 
 
 
 
 
 
 
 
 
84
  raise
85
 
86
+ # Helper functions for Gradio
87
  def draw_detections_on_image(image, df):
88
+ """Draw bounding boxes on PIL image"""
89
  draw = ImageDraw.Draw(image)
90
 
91
  try:
 
93
  except:
94
  font = ImageFont.load_default()
95
 
96
+ colors = {
97
+ 'ball': (255, 0, 0),
98
+ 'player': (0, 255, 0),
99
+ 'referee': (255, 255, 0),
100
+ 'goalkeeper': (0, 0, 255)
101
+ }
102
 
103
  for _, row in df.iterrows():
104
  x1, y1, x2, y2 = row['x1'], row['y1'], row['x2'], row['y2']
 
107
  color = colors.get(class_name, (255, 255, 255))
108
 
109
  draw.rectangle([x1, y1, x2, y2], outline=color, width=3)
110
+
111
  text = f"{class_name}: {conf:.2f}"
112
  bbox = draw.textbbox((x1, y1-20), text, font=font)
113
  draw.rectangle([bbox[0]-2, bbox[1]-2, bbox[2]+2, bbox[3]+2], fill=color)
 
116
  return image
117
 
118
  def process_image_interface(image, confidence_threshold):
119
+ """Process image with the model"""
120
  if image is None:
121
  return None, pd.DataFrame()
122
 
123
  try:
124
+ # Save temporary image
125
  temp_path = tempfile.mktemp(suffix='.jpg')
126
  Image.fromarray(image if isinstance(image, np.ndarray) else np.array(image)).save(temp_path)
127
 
128
+ # Process with model
129
  df = detector.process_image(temp_path, confidence_threshold=confidence_threshold)
130
+
131
+ # Draw detections
132
  img = Image.open(temp_path)
133
  annotated_img = draw_detections_on_image(img, df)
134
+
135
+ # Cleanup
136
  os.remove(temp_path)
137
 
138
  return annotated_img, df
139
+
140
  except Exception as e:
141
+ print(f"Error processing image: {e}")
142
+ import traceback
143
+ traceback.print_exc()
144
  return None, pd.DataFrame()
145
 
146
  def process_video_interface(video, confidence_threshold, frame_skip, max_frames):
147
+ """Process video with the model"""
148
  if video is None:
149
  return None, pd.DataFrame()
150
 
151
  try:
152
  max_frames_val = int(max_frames) if max_frames > 0 else None
 
 
153
 
154
+ # Process video
155
+ print(f"Processing video with confidence={confidence_threshold}, frame_skip={frame_skip}, max_frames={max_frames_val}")
156
+ df = detector.process_video(
157
+ video,
158
+ confidence_threshold=confidence_threshold,
159
+ frame_skip=int(frame_skip),
160
+ max_frames=max_frames_val
161
+ )
162
+
163
+ # Create annotated video
164
  cap = cv2.VideoCapture(video)
165
+ fps = int(cap.get(cv2.CAP_PROP_FPS))
166
+ width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
167
+ height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
168
 
169
  output_path = tempfile.mktemp(suffix='.mp4')
170
+ fourcc = cv2.VideoWriter_fourcc(*'mp4v')
171
+ out = cv2.VideoWriter(output_path, fourcc, fps, (width, height))
172
 
173
  frame_num = 0
174
  while cap.isOpened():
 
176
  if not ret:
177
  break
178
 
179
+ # Get detections for this frame
180
  frame_detections = df[df['frame'] == frame_num]
181
+
182
  if not frame_detections.empty:
183
+ rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
184
+ pil_img = Image.fromarray(rgb_frame)
185
+ annotated_pil = draw_detections_on_image(pil_img, frame_detections)
186
+ frame = cv2.cvtColor(np.array(annotated_pil), cv2.COLOR_RGB2BGR)
187
 
188
  out.write(frame)
189
  frame_num += 1
190
 
191
  cap.release()
192
  out.release()
193
+
194
  return output_path, df
195
+
196
  except Exception as e:
197
+ print(f"Error processing video: {e}")
198
+ import traceback
199
+ traceback.print_exc()
200
  return None, pd.DataFrame()
201
 
202
+ # Create Gradio interface
203
+ with gr.Blocks(title="⚽ Soccer Object Detection", theme=gr.themes.Soft()) as demo:
204
  gr.Markdown("""
205
  # ⚽ Soccer Object Detection with RF-DETR
206
 
207
+ Professional-grade object detection for soccer videos using RF-DETR-Large model.
208
 
209
  ### Model: [julianzu9612/RFDETR-Soccernet](https://huggingface.co/julianzu9612/RFDETR-Soccernet)
210
+ - **Architecture**: RF-DETR-Large (128M parameters)
211
+ - **Performance**: 85.7% mAP@50, 49.8% mAP
212
+ - **Dataset**: SoccerNet-Tracking 2023 (42,750 images)
213
+ - **Classes**: Ball, Player, Referee, Goalkeeper
214
  """)
215
 
216
+ with gr.Tab("πŸ“Έ Image Detection"):
217
+ gr.Markdown("### Upload a soccer image to detect objects")
218
+
219
  with gr.Row():
220
  with gr.Column():
221
  image_input = gr.Image(label="Upload Soccer Image", type="numpy")
222
+ image_confidence = gr.Slider(
223
+ minimum=0.1,
224
+ maximum=1.0,
225
+ value=0.5,
226
+ step=0.05,
227
+ label="Confidence Threshold",
228
+ info="Lower values detect more objects but may include false positives"
229
+ )
230
+ image_button = gr.Button("πŸ” Detect Objects", variant="primary", size="lg")
231
+
232
  with gr.Column():
233
+ image_output = gr.Image(label="Detected Objects")
234
+
235
+ image_detections = gr.Dataframe(
236
+ label="Detection Results",
237
+ wrap=True,
238
+ interactive=False
239
+ )
240
+
241
+ image_button.click(
242
+ fn=process_image_interface,
243
+ inputs=[image_input, image_confidence],
244
+ outputs=[image_output, image_detections]
245
+ )
246
+
247
+ gr.Examples(
248
+ examples=[],
249
+ inputs=image_input,
250
+ label="Example Images (Upload your own!)"
251
+ )
252
 
253
+ with gr.Tab("πŸŽ₯ Video Detection"):
254
+ gr.Markdown("### Upload a soccer video to track objects frame by frame")
255
+
256
  with gr.Row():
257
  with gr.Column():
258
+ video_input = gr.Video(label="Upload Soccer Video")
259
+ video_confidence = gr.Slider(
260
+ minimum=0.1,
261
+ maximum=1.0,
262
+ value=0.5,
263
+ step=0.05,
264
+ label="Confidence Threshold"
265
+ )
266
+ video_frame_skip = gr.Slider(
267
+ minimum=1,
268
+ maximum=10,
269
+ value=5,
270
+ step=1,
271
+ label="Frame Skip",
272
+ info="Process every Nth frame (higher = faster but less detections)"
273
+ )
274
+ video_max_frames = gr.Number(
275
+ value=300,
276
+ label="Max Frames to Process",
277
+ info="Set to 0 to process entire video (300 frames β‰ˆ 10 seconds at 30 FPS)"
278
+ )
279
+
280
+ gr.Markdown("""
281
+ #### ⚑ Performance Tips:
282
+ - **CPU**: 2-3 FPS (slow) - Use frame_skip=5 and limit frames
283
+ - **GPU**: 12-30 FPS (fast) - Can process full videos
284
+ - **Quick test**: Use 300 frames with frame_skip=5
285
+ """)
286
+
287
+ video_button = gr.Button("🎬 Process Video", variant="primary", size="lg")
288
+
289
  with gr.Column():
290
+ video_output = gr.Video(label="Annotated Video")
291
+
292
+ video_detections = gr.Dataframe(
293
+ label="Detection Results",
294
+ wrap=True,
295
+ interactive=False
296
+ )
297
+
298
+ video_button.click(
299
+ fn=process_video_interface,
300
+ inputs=[video_input, video_confidence, video_frame_skip, video_max_frames],
301
+ outputs=[video_output, video_detections]
302
+ )
303
 
304
+ with gr.Tab("ℹ️ About"):
305
+ gr.Markdown("""
306
+ ## About This Model
307
+
308
+ ### 🎯 Detected Classes
309
+
310
+ | Class | Color | Precision | Description |
311
+ |-------|-------|-----------|-------------|
312
+ | πŸ”΄ Ball | Red | 78.5% | Soccer ball detection |
313
+ | 🟒 Player | Green | 91.3% | Field players from both teams |
314
+ | 🟑 Referee | Yellow | 85.2% | Match officials |
315
+ | πŸ”΅ Goalkeeper | Blue | 88.9% | Specialized goalkeeper detection |
316
+
317
+ ### πŸ“Š Model Performance
318
+
319
+ - **mAP@50**: 85.7%
320
+ - **mAP**: 49.8%
321
+ - **mAP@75**: 52.0%
322
+ - **Parameters**: 128M
323
+ - **Training Time**: ~14 hours on NVIDIA A100 40GB
324
+
325
+ ### πŸŽ“ Training Details
326
+
327
+ - **Dataset**: SoccerNet-Tracking 2023
328
+ - **Images**: 42,750 annotated images
329
+ - **Source**: Professional soccer broadcasts
330
+ - **Input Resolution**: 1280x1280 pixels
331
+ - **Optimizer**: AdamW (lr=1e-4)
332
+
333
+ ### πŸ’‘ Best Practices
334
+
335
+ 1. **Confidence Threshold**:
336
+ - Use 0.5 for general detection
337
+ - Use 0.7+ for high-precision applications
338
+
339
+ 2. **Video Quality**:
340
+ - Works best on 720p+ broadcast footage
341
+ - Standard broadcast camera angles preferred
342
+
343
+ 3. **Frame Processing**:
344
+ - frame_skip=1: Every frame (best accuracy, slow)
345
+ - frame_skip=5: Every 5th frame (good balance)
346
+ - frame_skip=10: Every 10th frame (fast, lower accuracy)
347
+
348
+ ### 🚨 Limitations
349
+
350
+ - Optimized for professional broadcast footage
351
+ - May have reduced accuracy in poor lighting
352
+ - Small balls may be missed when heavily occluded
353
+ - Camera angle dependency
354
+
355
+ ### πŸ“š Use Cases
356
+
357
+ - **Sports Analytics**: Player tracking, formation analysis
358
+ - **Broadcast Enhancement**: Automatic highlighting, statistics overlay
359
+ - **Research**: Tactical analysis, computer vision benchmarking
360
+ - **Video Analytics**: Automated video processing pipelines
361
+
362
+ ### πŸ”— Links
363
+
364
+ - [Model on Hugging Face](https://huggingface.co/julianzu9612/RFDETR-Soccernet)
365
+ - [SoccerNet Dataset](https://www.soccer-net.org/)
366
+ - [RF-DETR Paper](https://arxiv.org/abs/2304.08069)
367
+
368
+ ### πŸ“„ Citation
369
+
370
+ ```bibtex
371
+ @misc{rfdetr-soccernet-2025,
372
+ title={RF-DETR SoccerNet: High-Performance Soccer Object Detection},
373
+ author={Computer Vision Research Team},
374
+ year={2025},
375
+ publisher={Hugging Face},
376
+ url={https://huggingface.co/julianzu9612/rf-detr-soccernet}
377
+ }
378
+ ```
379
+
380
+ ---
381
+
382
+ **License**: Apache 2.0
383
+ """)
384
+
385
+ print("\n" + "="*60)
386
+ print("πŸš€ Launching Gradio Interface...")
387
+ print("="*60)
388
 
389
  demo.launch()