KaushikSid commited on
Commit
3c0c19c
Β·
1 Parent(s): dd97b7a

Fresh start: Absolute minimal Gradio app

Browse files
Files changed (8) hide show
  1. README.md +2 -24
  2. app.py +4 -10
  3. dataset_discovery.py +0 -207
  4. labeler_app.py +0 -604
  5. labels.csv +0 -117
  6. requirements.txt +2 -2
  7. test_discovery.py +0 -87
  8. video_downloader.py +0 -163
README.md CHANGED
@@ -4,34 +4,12 @@ emoji: 🎯
4
  colorFrom: blue
5
  colorTo: purple
6
  sdk: gradio
7
- sdk_version: 4.44.1
8
  app_file: app.py
9
  pinned: false
10
- license: mit
11
  ---
12
 
13
  # Trajectory End Point Labeler
14
 
15
- A tool for labeling trajectory end points in robot/human demonstration datasets from HuggingFace.
16
 
17
- ## Features
18
-
19
- - 🎯 **Frame-Accurate Video Player**: Shows current frame number (0-N) with percentage
20
- - πŸ“Š **Pattern Analysis**: Analyzes manually labeled end points to detect patterns
21
- - πŸ€– **Auto-Labeling**: Automatically labels similar trajectories based on detected patterns
22
- - πŸ’Ύ **CSV Export**: Saves all labels with metadata
23
-
24
- ## Usage
25
-
26
- 1. Enter a dataset repository (e.g., `jesbu1/epic_rfm`)
27
- 2. Optionally specify config name
28
- 3. Set number of human and robot samples to load
29
- 4. Navigate videos and label end points
30
- 5. Analyze patterns after labeling all trajectories
31
-
32
- ## Output
33
-
34
- Labels are saved to CSV with:
35
- - Dataset info, trajectory IDs, end frames/percentages
36
- - Task descriptions
37
- - Pattern analysis results
 
4
  colorFrom: blue
5
  colorTo: purple
6
  sdk: gradio
7
+ sdk_version: 4.0.0
8
  app_file: app.py
9
  pinned: false
 
10
  ---
11
 
12
  # Trajectory End Point Labeler
13
 
14
+ Testing basic Gradio deployment on HuggingFace Spaces.
15
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app.py CHANGED
@@ -1,14 +1,8 @@
1
- """
2
- Trajectory End Point Labeler - Step 1: Minimal test
3
- """
4
-
5
  import gradio as gr
6
 
7
- # Minimal interface to test Spaces deployment
8
  demo = gr.Interface(
9
- fn=lambda name: f"Hello {name}! App is working.",
10
- inputs=gr.Textbox(label="Enter your name"),
11
- outputs=gr.Textbox(label="Result"),
12
- title="Trajectory End Point Labeler",
13
- description="Testing deployment - will add features incrementally"
14
  )
 
 
 
 
 
 
1
  import gradio as gr
2
 
 
3
  demo = gr.Interface(
4
+ fn=lambda x: f"You entered: {x}",
5
+ inputs="text",
6
+ outputs="text"
 
 
7
  )
8
+
dataset_discovery.py DELETED
@@ -1,207 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- Discover and analyze datasets from HuggingFace Hub.
4
- Checks for datasets with both human and robot data.
5
- """
6
-
7
- import random
8
- from collections import defaultdict
9
- from typing import Dict, List, Optional, Tuple
10
-
11
- from datasets import load_dataset
12
- from huggingface_hub import HfApi, list_datasets
13
- from tqdm import tqdm
14
-
15
-
16
- def list_rewardfm_datasets() -> List[str]:
17
- """List all datasets from rewardfm organization/users."""
18
- api = HfApi()
19
-
20
- # Known datasets - you can add more here
21
- known_datasets = [
22
- "jesbu1/epic_rfm",
23
- "abraranwar/libero_rfm",
24
- "ykorkmaz/libero_failure_rfm",
25
- "jesbu1/oxe_rfm",
26
- "jesbu1/oxe_rfm_eval",
27
- "aliangdw/metaworld_rfm",
28
- "HenryZhang/metaworld_rewind_rfm_train",
29
- "HenryZhang/metaworld_rewind_rfm_eval",
30
- "ykorkmaz/h2r_rfm",
31
- "abraranwar/agibotworld_rfm",
32
- "abraranwar/egodex_rfm",
33
- ]
34
-
35
- datasets = known_datasets.copy()
36
-
37
- # Also search for datasets with known patterns
38
- search_terms = ["rfm"]
39
-
40
- for term in search_terms:
41
- try:
42
- results = list_datasets(search=term, sort="downloads", direction=-1, limit=50)
43
- for ds in results:
44
- repo_id = ds.id
45
- if repo_id not in datasets:
46
- datasets.append(repo_id)
47
- except Exception as e:
48
- print(f"Warning: Could not search for {term}: {e}")
49
-
50
- return datasets
51
-
52
-
53
- def check_dataset_has_both_types(dataset_repo: str, config_name: Optional[str] = None) -> Tuple[bool, Dict]:
54
- """
55
- Check if dataset has both human and robot trajectories.
56
-
57
- Returns:
58
- (has_both, stats) where stats contains counts and info
59
- """
60
- try:
61
- if config_name:
62
- dataset = load_dataset(dataset_repo, config_name, split="train", streaming=True)
63
- else:
64
- dataset = load_dataset(dataset_repo, split="train", streaming=True)
65
-
66
- human_count = 0
67
- robot_count = 0
68
- total_checked = 0
69
- max_samples_to_check = 1000
70
-
71
- for sample in dataset:
72
- total_checked += 1
73
- is_robot = sample.get("is_robot", False)
74
-
75
- if is_robot:
76
- robot_count += 1
77
- else:
78
- human_count += 1
79
-
80
- if human_count > 0 and robot_count > 0:
81
- return True, {
82
- "human_count": human_count,
83
- "robot_count": robot_count,
84
- "checked": total_checked,
85
- "has_both": True
86
- }
87
-
88
- if total_checked >= max_samples_to_check:
89
- break
90
-
91
- return False, {
92
- "human_count": human_count,
93
- "robot_count": robot_count,
94
- "checked": total_checked,
95
- "has_both": False
96
- }
97
-
98
- except Exception as e:
99
- return False, {"error": str(e)}
100
-
101
-
102
- def sample_trajectories(
103
- dataset_repo: str,
104
- config_name: Optional[str] = None,
105
- is_robot: bool = True,
106
- num_samples: int = 10,
107
- max_to_check: int = 10000
108
- ) -> List[Dict]:
109
- """
110
- Sample random trajectories of a specific type (human or robot).
111
-
112
- Args:
113
- dataset_repo: HuggingFace dataset repository ID
114
- config_name: Optional config name for the dataset
115
- is_robot: If True, sample robot trajectories; else human
116
- num_samples: Number of trajectories to sample
117
- max_to_check: Maximum number of trajectories to check
118
-
119
- Returns:
120
- List of trajectory dictionaries
121
- """
122
- try:
123
- if config_name:
124
- dataset = load_dataset(dataset_repo, config_name, split="train", streaming=True)
125
- else:
126
- dataset = load_dataset(dataset_repo, split="train", streaming=True)
127
-
128
- matching_trajectories = []
129
- checked = 0
130
-
131
- # Collect all matching trajectories up to max_to_check
132
- for sample in dataset:
133
- checked += 1
134
- if sample.get("is_robot", False) == is_robot:
135
- matching_trajectories.append(sample)
136
-
137
- if checked >= max_to_check:
138
- break
139
-
140
- # Randomly sample from collected trajectories
141
- if len(matching_trajectories) == 0:
142
- return []
143
-
144
- if len(matching_trajectories) <= num_samples:
145
- # If we have fewer or equal, return all (shuffled for randomness)
146
- random.shuffle(matching_trajectories)
147
- return matching_trajectories
148
- else:
149
- # Randomly sample exactly num_samples
150
- return random.sample(matching_trajectories, num_samples)
151
-
152
- except Exception as e:
153
- print(f"Error sampling trajectories: {e}")
154
- return []
155
-
156
-
157
- def discover_datasets_with_both_types() -> Dict[str, Dict]:
158
- """
159
- Discover all datasets that have both human and robot data.
160
-
161
- Returns:
162
- Dictionary mapping dataset_repo -> stats
163
- """
164
- print("πŸ” Discovering datasets...")
165
- all_datasets = list_rewardfm_datasets()
166
-
167
- print(f"Found {len(all_datasets)} potential datasets")
168
- print("Checking for datasets with both human and robot data...\n")
169
-
170
- results = {}
171
-
172
- for dataset_repo in tqdm(all_datasets, desc="Checking datasets"):
173
- # Try to load with and without config name
174
- has_both, stats = check_dataset_has_both_types(dataset_repo)
175
-
176
- if has_both:
177
- results[dataset_repo] = stats
178
- print(f"βœ… {dataset_repo}: {stats['human_count']} human, {stats['robot_count']} robot")
179
-
180
- # Also check with config names if available
181
- try:
182
- from huggingface_hub import HfApi
183
- api = HfApi()
184
- dataset_info = api.dataset_info(dataset_repo)
185
- if hasattr(dataset_info, 'configs') and dataset_info.configs:
186
- for config in dataset_info.configs:
187
- config_name = config.config_name
188
- has_both, stats = check_dataset_has_both_types(dataset_repo, config_name)
189
- if has_both:
190
- repo_with_config = f"{dataset_repo}/{config_name}"
191
- results[repo_with_config] = stats
192
- print(f"βœ… {repo_with_config}: {stats['human_count']} human, {stats['robot_count']} robot")
193
- except Exception as e:
194
- # Skip if can't get config info
195
- pass
196
-
197
- return results
198
-
199
-
200
- if __name__ == "__main__":
201
- # Example usage
202
- results = discover_datasets_with_both_types()
203
- print("\n" + "="*60)
204
- print(f"Found {len(results)} datasets with both human and robot data:")
205
- for repo, stats in results.items():
206
- print(f" {repo}: {stats}")
207
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
labeler_app.py DELETED
@@ -1,604 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- Main Gradio app for labeling trajectory end points.
4
- Features:
5
- - Video player with frame counter
6
- - Manual labeling interface
7
- - Pattern analysis and auto-labeling
8
- """
9
-
10
- import os
11
- from pathlib import Path
12
- from typing import Dict, List, Optional, Tuple
13
-
14
- import cv2
15
- import gradio as gr
16
- import numpy as np
17
- import pandas as pd
18
-
19
- from dataset_discovery import sample_trajectories
20
- from video_downloader import VideoDownloader
21
-
22
-
23
- class TrajectoryLabeler:
24
- """Main labeling application."""
25
-
26
- def __init__(self, cache_dir: str = "video_cache", labels_file: str = "labels.csv"):
27
- self.downloader = VideoDownloader(cache_dir)
28
- self.labels_file = Path(labels_file)
29
- self.labels = self._load_labels()
30
- self.current_trajectories = []
31
- self.current_idx = 0
32
-
33
- def _load_labels(self) -> pd.DataFrame:
34
- """Load existing labels from CSV."""
35
- if self.labels_file.exists():
36
- df = pd.read_csv(self.labels_file)
37
- # Add task column if it doesn't exist (for backward compatibility)
38
- if 'task' not in df.columns:
39
- df['task'] = ''
40
- return df
41
- else:
42
- # Create empty DataFrame with columns
43
- return pd.DataFrame(columns=[
44
- "dataset_repo", "config_name", "trajectory_id", "is_robot",
45
- "task", "manual_end_frame", "manual_end_percent", "auto_labeled",
46
- "pattern_matched", "notes"
47
- ])
48
-
49
- def _save_labels(self):
50
- """Save labels to CSV."""
51
- # Ensure columns are in the correct order
52
- column_order = [
53
- "dataset_repo", "config_name", "trajectory_id", "is_robot",
54
- "task", "manual_end_frame", "manual_end_percent", "auto_labeled",
55
- "pattern_matched", "notes"
56
- ]
57
- # Only include columns that exist
58
- existing_columns = [col for col in column_order if col in self.labels.columns]
59
- # Add any other columns that might exist
60
- other_columns = [col for col in self.labels.columns if col not in column_order]
61
- final_order = existing_columns + other_columns
62
-
63
- self.labels[final_order].to_csv(self.labels_file, index=False)
64
-
65
- def load_dataset_trajectories(
66
- self,
67
- dataset_repo: str,
68
- config_name: Optional[str] = None,
69
- num_human: int = 10,
70
- num_robot: int = 10
71
- ) -> Tuple[str, List[Dict]]:
72
- """Load and download trajectories for a dataset."""
73
- try:
74
- # Sample human trajectories
75
- human_trajs = sample_trajectories(dataset_repo, config_name, is_robot=False, num_samples=num_human)
76
- # Sample robot trajectories
77
- robot_trajs = sample_trajectories(dataset_repo, config_name, is_robot=True, num_samples=num_robot)
78
-
79
- all_trajectories = human_trajs + robot_trajs
80
-
81
- # Download videos
82
- downloaded = self.downloader.download_trajectories(all_trajectories, dataset_repo, config_name)
83
-
84
- self.current_trajectories = downloaded
85
- self.current_idx = 0
86
-
87
- return f"βœ… Loaded {len(downloaded)} trajectories ({len([t for t in downloaded if not t.get('is_robot')])} human, {len([t for t in downloaded if t.get('is_robot')])} robot)", downloaded
88
-
89
- except Exception as e:
90
- return f"❌ Error: {str(e)}", []
91
-
92
- def extract_frame(self, video_path: str, frame_num: int) -> Optional[np.ndarray]:
93
- """Extract a specific frame from video."""
94
- cap = cv2.VideoCapture(video_path)
95
- total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
96
-
97
- if frame_num >= total_frames:
98
- frame_num = total_frames - 1
99
-
100
- cap.set(cv2.CAP_PROP_POS_FRAMES, frame_num)
101
- ret, frame = cap.read()
102
- cap.release()
103
-
104
- if ret:
105
- # Convert BGR to RGB for display
106
- frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
107
- return frame_rgb
108
- return None
109
-
110
- def get_current_trajectory(self) -> Optional[Dict]:
111
- """Get current trajectory."""
112
- if 0 <= self.current_idx < len(self.current_trajectories):
113
- return self.current_trajectories[self.current_idx]
114
- return None
115
-
116
- def next_trajectory(self):
117
- """Move to next trajectory."""
118
- if self.current_idx < len(self.current_trajectories) - 1:
119
- self.current_idx += 1
120
- return self.current_idx
121
-
122
- def prev_trajectory(self):
123
- """Move to previous trajectory."""
124
- if self.current_idx > 0:
125
- self.current_idx -= 1
126
- return self.current_idx
127
-
128
- def save_label(
129
- self,
130
- trajectory_id: str,
131
- dataset_repo: str,
132
- config_name: str,
133
- is_robot: bool,
134
- end_frame: int,
135
- task: str = "",
136
- notes: str = ""
137
- ):
138
- """Save a manual label. Only one label per trajectory_id."""
139
- # Calculate percentage
140
- traj = next((t for t in self.current_trajectories if t.get('id') == trajectory_id), None)
141
- if not traj:
142
- return "Trajectory not found"
143
-
144
- video_path = traj.get('local_video_path')
145
- if not video_path:
146
- return "Video path not found"
147
-
148
- cap = cv2.VideoCapture(video_path)
149
- total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
150
- cap.release()
151
-
152
- end_percent = (end_frame / total_frames * 100) if total_frames > 0 else 0
153
-
154
- # Check if label already exists for this trajectory
155
- existing_mask = (
156
- (self.labels['dataset_repo'] == dataset_repo) &
157
- (self.labels['config_name'] == (config_name or "")) &
158
- (self.labels['trajectory_id'] == trajectory_id) &
159
- (self.labels['auto_labeled'] == False) # Only check manual labels
160
- )
161
-
162
- if existing_mask.any():
163
- # Update existing label
164
- idx = self.labels[existing_mask].index[0]
165
- self.labels.at[idx, 'manual_end_frame'] = end_frame
166
- self.labels.at[idx, 'manual_end_percent'] = end_percent
167
- self.labels.at[idx, 'task'] = task
168
- self.labels.at[idx, 'notes'] = notes
169
- self._save_labels()
170
- return f"βœ… Updated label: Frame {end_frame} ({end_percent:.1f}%)"
171
-
172
- # Add new label
173
- new_row = {
174
- "dataset_repo": dataset_repo,
175
- "config_name": config_name or "",
176
- "trajectory_id": trajectory_id,
177
- "is_robot": is_robot,
178
- "task": task,
179
- "manual_end_frame": end_frame,
180
- "manual_end_percent": end_percent,
181
- "auto_labeled": False,
182
- "pattern_matched": False,
183
- "notes": notes
184
- }
185
-
186
- self.labels = pd.concat([self.labels, pd.DataFrame([new_row])], ignore_index=True)
187
- self._save_labels()
188
-
189
- return f"βœ… Saved label: Frame {end_frame} ({end_percent:.1f}%)"
190
-
191
- def analyze_patterns(
192
- self,
193
- dataset_repo: str,
194
- config_name: Optional[str],
195
- is_robot: bool,
196
- expected_count: Optional[int] = None
197
- ) -> Dict:
198
- """
199
- Analyze patterns in manually labeled end points.
200
- Only works when all expected trajectories are labeled.
201
- """
202
- # Filter labels for this dataset and type
203
- filtered = self.labels[
204
- (self.labels['dataset_repo'] == dataset_repo) &
205
- (self.labels['config_name'] == (config_name or "")) &
206
- (self.labels['is_robot'] == is_robot) &
207
- (self.labels['auto_labeled'] == False)
208
- ]
209
-
210
- labeled_count = len(filtered)
211
-
212
- # Check if all trajectories are labeled
213
- if expected_count is not None:
214
- if labeled_count < expected_count:
215
- return {
216
- "pattern_found": False,
217
- "error": True,
218
- "message": f"Only {labeled_count}/{expected_count} trajectories labeled. Please label all trajectories before analyzing patterns.",
219
- "labeled_count": labeled_count,
220
- "expected_count": expected_count
221
- }
222
-
223
- if labeled_count < 3:
224
- return {
225
- "pattern_found": False,
226
- "error": True,
227
- "message": "Need at least 3 manual labels to detect pattern",
228
- "labeled_count": labeled_count
229
- }
230
-
231
- # Calculate statistics
232
- end_percents = filtered['manual_end_percent'].values
233
- mean_percent = np.mean(end_percents)
234
- std_percent = np.std(end_percents)
235
- min_percent = np.min(end_percents)
236
- max_percent = np.max(end_percents)
237
- quantile_90 = np.percentile(end_percents, 90)
238
- median_percent = np.median(end_percents)
239
-
240
- # Consider it a pattern if std < 10%
241
- if std_percent < 10:
242
- return {
243
- "pattern_found": True,
244
- "error": False,
245
- "mean_percent": round(mean_percent, 2),
246
- "median_percent": round(median_percent, 2),
247
- "std_percent": round(std_percent, 2),
248
- "min_percent": round(min_percent, 2),
249
- "max_percent": round(max_percent, 2),
250
- "quantile_90": round(quantile_90, 2),
251
- "count": labeled_count,
252
- "suggested_label": round(mean_percent)
253
- }
254
- else:
255
- return {
256
- "pattern_found": False,
257
- "error": False,
258
- "mean_percent": round(mean_percent, 2),
259
- "median_percent": round(median_percent, 2),
260
- "std_percent": round(std_percent, 2),
261
- "min_percent": round(min_percent, 2),
262
- "max_percent": round(max_percent, 2),
263
- "quantile_90": round(quantile_90, 2),
264
- "count": labeled_count,
265
- "message": f"High variance ({std_percent:.1f}%) - no clear pattern"
266
- }
267
-
268
- def auto_label_similar(
269
- self,
270
- dataset_repo: str,
271
- config_name: Optional[str],
272
- is_robot: bool,
273
- target_percent: float,
274
- threshold: float = 5.0
275
- ) -> str:
276
- """Auto-label trajectories similar to the pattern."""
277
- # Find unlabeled trajectories
278
- labeled_ids = set(self.labels[
279
- (self.labels['dataset_repo'] == dataset_repo) &
280
- (self.labels['config_name'] == (config_name or "")) &
281
- (self.labels['is_robot'] == is_robot)
282
- ]['trajectory_id'].values)
283
-
284
- # Load all trajectories for this dataset
285
- all_human = sample_trajectories(dataset_repo, config_name, is_robot=False, num_samples=1000)
286
- all_robot = sample_trajectories(dataset_repo, config_name, is_robot=True, num_samples=1000)
287
- all_trajs = (all_human if not is_robot else all_robot)
288
-
289
- # Filter unlabeled
290
- unlabeled = [t for t in all_trajs if t.get('id') not in labeled_ids]
291
-
292
- # For each unlabeled trajectory, label with target_percent
293
- labeled_count = 0
294
- for traj in unlabeled[:100]: # Limit to 100 for performance
295
- video_path = self.downloader.download_video(traj, dataset_repo, config_name)
296
- if not video_path:
297
- continue
298
-
299
- cap = cv2.VideoCapture(video_path)
300
- total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
301
- cap.release()
302
-
303
- end_frame = int(total_frames * target_percent / 100)
304
-
305
- new_row = {
306
- "dataset_repo": dataset_repo,
307
- "config_name": config_name or "",
308
- "trajectory_id": traj.get('id'),
309
- "is_robot": is_robot,
310
- "task": traj.get('task', ''),
311
- "manual_end_frame": end_frame,
312
- "manual_end_percent": target_percent,
313
- "auto_labeled": True,
314
- "pattern_matched": True,
315
- "notes": f"Auto-labeled based on pattern: {target_percent:.1f}%"
316
- }
317
-
318
- self.labels = pd.concat([self.labels, pd.DataFrame([new_row])], ignore_index=True)
319
- labeled_count += 1
320
-
321
- self._save_labels()
322
- return f"βœ… Auto-labeled {labeled_count} trajectories with {target_percent:.1f}%"
323
-
324
-
325
- def create_gradio_interface():
326
- """Create the Gradio interface."""
327
- labeler = TrajectoryLabeler()
328
-
329
- with gr.Blocks(title="Trajectory End Point Labeler") as demo:
330
- gr.Markdown("# Trajectory End Point Labeler")
331
- gr.Markdown("Label the success completion point for robot/human trajectories")
332
-
333
- with gr.Row():
334
- with gr.Column(scale=1):
335
- dataset_repo_input = gr.Textbox(
336
- label="Dataset Repository",
337
- placeholder="jesbu1/epic_rfm",
338
- value="jesbu1/epic_rfm"
339
- )
340
- config_name_input = gr.Textbox(
341
- label="Config Name (optional)",
342
- placeholder="Leave empty if no config"
343
- )
344
- num_human_input = gr.Number(label="Human Samples", value=10, precision=0)
345
- num_robot_input = gr.Number(label="Robot Samples", value=10, precision=0)
346
- load_btn = gr.Button("Load Dataset", variant="primary")
347
- load_status = gr.Textbox(label="Status", interactive=False)
348
-
349
- with gr.Column(scale=2):
350
- # Trajectory info and navigation
351
- traj_info = gr.Textbox(label="Current Trajectory", interactive=False)
352
- task_display = gr.Textbox(label="Task Description", interactive=False)
353
- with gr.Row():
354
- prev_btn = gr.Button("← Previous", variant="secondary")
355
- next_btn = gr.Button("Next β†’", variant="secondary")
356
-
357
- # Video player
358
- video_output = gr.Video(label="Video Player (64 frames)")
359
- frame_slider = gr.Slider(
360
- minimum=0,
361
- maximum=63,
362
- step=1,
363
- value=0,
364
- label="Frame Number (0-63)"
365
- )
366
- current_frame_img = gr.Image(label="Current Frame Preview")
367
-
368
- # Labeling controls
369
- with gr.Row():
370
- end_frame_input = gr.Number(label="End Frame", value=0, precision=0)
371
- end_percent_display = gr.Textbox(label="End Percent", interactive=False)
372
- notes_input = gr.Textbox(label="Notes", placeholder="Optional notes...")
373
- save_label_btn = gr.Button("Save Label", variant="primary")
374
- label_status = gr.Textbox(label="Label Status", interactive=False)
375
-
376
- # Pattern analysis section
377
- with gr.Row():
378
- with gr.Column():
379
- analyze_btn = gr.Button("Analyze Pattern")
380
- pattern_output = gr.JSON(label="Pattern Analysis")
381
-
382
- with gr.Column():
383
- auto_label_percent = gr.Number(label="Target Percent", value=95.0)
384
- auto_label_btn = gr.Button("Auto-Label Similar", variant="secondary")
385
- auto_label_status = gr.Textbox(label="Auto-Label Status")
386
-
387
- # Load dataset handler
388
- def load_dataset(dataset_repo, config_name, num_human, num_robot):
389
- config = config_name.strip() if config_name else None
390
- status, trajectories = labeler.load_dataset_trajectories(
391
- dataset_repo, config, int(num_human), int(num_robot)
392
- )
393
-
394
- if trajectories:
395
- # Update frame slider max and show first trajectory
396
- first_traj = trajectories[0]
397
- first_video = first_traj.get('local_video_path')
398
- if first_video and os.path.exists(first_video):
399
- cap = cv2.VideoCapture(first_video)
400
- max_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
401
- cap.release()
402
-
403
- traj_info = f"Trajectory 1/{len(trajectories)} | ID: {first_traj.get('id', 'unknown')[:20]}... | Type: {'Robot' if first_traj.get('is_robot') else 'Human'}"
404
- task_desc = first_traj.get('task', 'No task description')
405
-
406
- return (
407
- status,
408
- traj_info,
409
- task_desc,
410
- gr.update(maximum=max_frames-1, value=0),
411
- first_video,
412
- None,
413
- "0.0%"
414
- )
415
-
416
- return status, "No trajectories loaded", "", gr.update(), None, None, "0.0%"
417
-
418
- # Trajectory navigation handlers
419
- def navigate_next():
420
- idx = labeler.next_trajectory()
421
- traj = labeler.get_current_trajectory()
422
- if traj:
423
- video_path = traj.get('local_video_path')
424
- if video_path and os.path.exists(video_path):
425
- cap = cv2.VideoCapture(video_path)
426
- max_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
427
- cap.release()
428
-
429
- traj_info = f"Trajectory {idx+1}/{len(labeler.current_trajectories)} | ID: {traj.get('id', 'unknown')[:20]}... | Type: {'Robot' if traj.get('is_robot') else 'Human'}"
430
- task_desc = traj.get('task', 'No task description')
431
-
432
- return (
433
- traj_info,
434
- task_desc,
435
- gr.update(maximum=max_frames-1, value=0),
436
- video_path,
437
- None,
438
- "0.0%"
439
- )
440
- return "No more trajectories", "", gr.update(), None, None, "0.0%"
441
-
442
- def navigate_prev():
443
- idx = labeler.prev_trajectory()
444
- traj = labeler.get_current_trajectory()
445
- if traj:
446
- video_path = traj.get('local_video_path')
447
- if video_path and os.path.exists(video_path):
448
- cap = cv2.VideoCapture(video_path)
449
- max_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
450
- cap.release()
451
-
452
- traj_info = f"Trajectory {idx+1}/{len(labeler.current_trajectories)} | ID: {traj.get('id', 'unknown')[:20]}... | Type: {'Robot' if traj.get('is_robot') else 'Human'}"
453
- task_desc = traj.get('task', 'No task description')
454
-
455
- return (
456
- traj_info,
457
- task_desc,
458
- gr.update(maximum=max_frames-1, value=0),
459
- video_path,
460
- None,
461
- "0.0%"
462
- )
463
- return "No more trajectories", "", gr.update(), None, None, "0.0%"
464
-
465
- # Frame navigation handler
466
- def show_frame(video_path, frame_num):
467
- if not video_path or not os.path.exists(video_path):
468
- return None, "0.0%"
469
-
470
- frame_num = int(frame_num)
471
- frame = labeler.extract_frame(video_path, frame_num)
472
-
473
- # Calculate percent
474
- cap = cv2.VideoCapture(video_path)
475
- total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
476
- cap.release()
477
-
478
- percent = (frame_num / total_frames * 100) if total_frames > 0 else 0
479
-
480
- return frame, f"{percent:.1f}%"
481
-
482
- def update_end_percent(video_path, end_frame):
483
- if not video_path or not os.path.exists(video_path):
484
- return "0.0%"
485
-
486
- cap = cv2.VideoCapture(video_path)
487
- total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
488
- cap.release()
489
-
490
- percent = (int(end_frame) / total_frames * 100) if total_frames > 0 else 0
491
- return f"{percent:.1f}%"
492
-
493
- # Save label handler
494
- def save_label_handler(dataset_repo, config_name, end_frame, notes):
495
- if not labeler.current_trajectories:
496
- return "No trajectories loaded"
497
-
498
- traj = labeler.current_trajectories[labeler.current_idx]
499
- task_desc = traj.get('task', '')
500
- result = labeler.save_label(
501
- traj.get('id'),
502
- dataset_repo,
503
- config_name,
504
- traj.get('is_robot', False),
505
- int(end_frame),
506
- task_desc,
507
- notes
508
- )
509
- return result
510
-
511
- # Pattern analysis handler
512
- def analyze_pattern_handler(dataset_repo, config_name):
513
- if not labeler.current_trajectories:
514
- return {"error": True, "message": "No trajectories loaded"}
515
-
516
- traj = labeler.current_trajectories[labeler.current_idx]
517
- is_robot = traj.get('is_robot', False)
518
-
519
- # Count expected trajectories for this type
520
- expected_count = len([
521
- t for t in labeler.current_trajectories
522
- if t.get('is_robot', False) == is_robot
523
- ])
524
-
525
- return labeler.analyze_patterns(
526
- dataset_repo,
527
- config_name,
528
- is_robot,
529
- expected_count=expected_count
530
- )
531
-
532
- # Auto-label handler
533
- def auto_label_handler(dataset_repo, config_name, target_percent):
534
- if not labeler.current_trajectories:
535
- return "No trajectories loaded"
536
-
537
- traj = labeler.current_trajectories[labeler.current_idx]
538
- return labeler.auto_label_similar(
539
- dataset_repo,
540
- config_name,
541
- traj.get('is_robot', False),
542
- float(target_percent)
543
- )
544
-
545
- # Connect handlers
546
- load_btn.click(
547
- load_dataset,
548
- inputs=[dataset_repo_input, config_name_input, num_human_input, num_robot_input],
549
- outputs=[load_status, traj_info, task_display, frame_slider, video_output, current_frame_img, end_percent_display]
550
- )
551
-
552
- next_btn.click(
553
- navigate_next,
554
- outputs=[traj_info, task_display, frame_slider, video_output, current_frame_img, end_percent_display]
555
- )
556
-
557
- prev_btn.click(
558
- navigate_prev,
559
- outputs=[traj_info, task_display, frame_slider, video_output, current_frame_img, end_percent_display]
560
- )
561
-
562
- frame_slider.change(
563
- show_frame,
564
- inputs=[video_output, frame_slider],
565
- outputs=[current_frame_img, end_percent_display]
566
- )
567
-
568
- end_frame_input.change(
569
- update_end_percent,
570
- inputs=[video_output, end_frame_input],
571
- outputs=[end_percent_display]
572
- )
573
-
574
- video_output.change(
575
- lambda v: show_frame(v, 0) if v else (None, "0.0%"),
576
- inputs=[video_output],
577
- outputs=[current_frame_img, end_percent_display]
578
- )
579
-
580
- save_label_btn.click(
581
- save_label_handler,
582
- inputs=[dataset_repo_input, config_name_input, end_frame_input, notes_input],
583
- outputs=[label_status]
584
- )
585
-
586
- analyze_btn.click(
587
- analyze_pattern_handler,
588
- inputs=[dataset_repo_input, config_name_input],
589
- outputs=[pattern_output]
590
- )
591
-
592
- auto_label_btn.click(
593
- auto_label_handler,
594
- inputs=[dataset_repo_input, config_name_input, auto_label_percent],
595
- outputs=[auto_label_status]
596
- )
597
-
598
- return demo
599
-
600
-
601
- if __name__ == "__main__":
602
- demo = create_gradio_interface()
603
- demo.launch(server_name="0.0.0.0", server_port=7860, share=False)
604
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
labels.csv DELETED
@@ -1,117 +0,0 @@
1
- dataset_repo,config_name,trajectory_id,is_robot,task,manual_end_frame,manual_end_percent,auto_labeled,pattern_matched,notes
2
- abraranwar/agibotworld_alpha_rfm,,5c7b9812-0374-40af-a7d7-ef94febc2cf1,True,,0,0.0,False,False,
3
- abraranwar/agibotworld_alpha_rfm,,e672c58f-ee21-4471-835e-eeecbd915eaa,True,,55,85.9375,False,False,
4
- abraranwar/agibotworld_alpha_rfm,,e672c58f-ee21-4471-835e-eeecbd915eaa,True,,58,90.625,False,False,
5
- abraranwar/agibotworld_alpha_rfm,,5c7b9812-0374-40af-a7d7-ef94febc2cf1,True,,50,78.125,False,False,
6
- abraranwar/agibotworld_alpha_rfm,,d2015f14-b370-4448-9ec2-35827541bd1a,True,,48,75.0,False,False,
7
- abraranwar/agibotworld_alpha_rfm,,d2015f14-b370-4448-9ec2-35827541bd1a,True,,51,79.6875,False,False,
8
- anqil/rh20t_subset_rfm,rh20t_human,edc7c228-16f3-44e6-9add-03183e87b553,False,Press the button from top to bottom,16,66.66666666666666,False,False,
9
- anqil/rh20t_subset_rfm,rh20t_human,253bb21b-f6ee-449d-8e26-aef100a675e9,False,Press the button from top to bottom,14,63.63636363636363,False,False,
10
- anqil/rh20t_subset_rfm,rh20t_human,7c624645-f428-44be-b3da-b8b4df08b06f,False,Press the button from top to bottom,21,75.0,False,False,
11
- anqil/rh20t_subset_rfm,rh20t_human,3346f2d7-460a-4e1f-beda-b73870a69ccb,False,Press the button from top to bottom,19,76.0,False,False,
12
- anqil/rh20t_subset_rfm,rh20t_human,8b848169-8e75-4c97-85e7-ef067e9f299b,False,Press the button from top to bottom,33,86.8421052631579,False,False,
13
- anqil/rh20t_subset_rfm,rh20t_human,38ed0e20-33a7-4077-bd05-3070483c5898,False,Press the button from top to bottom,25,80.64516129032258,False,False,
14
- anqil/rh20t_subset_rfm,rh20t_human,2ea39435-4ac1-4b82-8e51-1488c6b6c9ed,False,Press the button from top to bottom,26,81.25,False,False,
15
- anqil/rh20t_subset_rfm,rh20t_human,92275b63-339d-49b2-8a8f-8f23673993b8,False,Press the button from top to bottom,36,87.8048780487805,False,False,
16
- anqil/rh20t_subset_rfm,rh20t_human,5d92a0d2-d963-40a0-a077-6fd7a53c4aa8,False,Press the button from top to bottom,21,80.76923076923077,False,False,
17
- anqil/rh20t_subset_rfm,rh20t_human,80e94c43-2167-4b75-be66-b66a7c7e6b9e,False,Press the button from top to bottom,14,77.77777777777779,False,False,
18
- anqil/rh20t_subset_rfm,rh20t_robot,4830d4e6-d612-4a15-817b-71bba67f5f0a,True,Press the button from top to bottom,50,78.125,False,False,
19
- anqil/rh20t_subset_rfm,rh20t_robot,2e4399a7-ede2-42d7-b518-504b359ea896,True,Press the button from top to bottom,41,64.0625,False,False,
20
- anqil/rh20t_subset_rfm,rh20t_robot,a00bcbf3-bf68-4090-990f-0e66580f0e29,True,Press the button from top to bottom,44,68.75,False,False,
21
- anqil/rh20t_subset_rfm,rh20t_robot,25cb76a2-3964-4430-8f4d-44d988d2e75a,True,Press the button from top to bottom,45,70.3125,False,False,
22
- anqil/rh20t_subset_rfm,rh20t_robot,5ee5eee8-f9ea-43c5-90c4-a92436632481,True,Press the button from top to bottom,40,62.5,False,False,
23
- anqil/rh20t_subset_rfm,rh20t_robot,aa30fbd5-f1c1-45f5-a08b-ff3f71227e0d,True,Press the button from top to bottom,47,73.4375,False,False,
24
- anqil/rh20t_subset_rfm,rh20t_robot,0d8d506e-8bac-4dbd-a858-a743bd011ec1,True,Press the button from top to bottom,51,79.6875,False,False,
25
- anqil/rh20t_subset_rfm,rh20t_robot,7301509f-8a40-41ba-a3f5-13fd3b053cd3,True,Press the button from top to bottom,47,73.4375,False,False,
26
- anqil/rh20t_subset_rfm,rh20t_robot,74437d99-97e0-4dcc-860b-c0d316c6fbfe,True,Press the button from top to bottom,36,56.25,False,False,
27
- anqil/rh20t_subset_rfm,rh20t_robot,c1056af0-16a6-48d6-a979-4fecb488a0eb,True,Press the button from top to bottom,42,65.625,False,False,
28
- anqil/rh20t_subset_rfm,rh20t_robot,ede4327f-95e4-40fd-9b7d-612edbca7963,True,Remove the object from the scale,56,87.5,False,False,
29
- anqil/rh20t_subset_rfm,rh20t_robot,7dc22f64-1038-477a-83f9-5a906beaafcf,True,Dial a number on an old rotary phone,59,92.1875,False,False,
30
- anqil/rh20t_subset_rfm,rh20t_robot,d81591d8-aa9b-4da2-9b60-71faccfb8d9d,True,Drop coins into a piggy bank,56,87.5,False,False,
31
- anqil/rh20t_subset_rfm,rh20t_robot,d557ab79-1041-4583-8456-48a56da5eda8,True,Put the pen into the pen holder,53,82.8125,False,False,
32
- anqil/rh20t_subset_rfm,rh20t_robot,4133da48-539a-4dfe-97f6-8c46c678513e,True,Drag the plate to the goal post after holding it down,57,89.0625,False,False,
33
- anqil/rh20t_subset_rfm,rh20t_robot,197fc077-ad66-4a29-861c-0a36db6246d4,True,Scrub the table with a brush,49,76.5625,False,False,
34
- anqil/rh20t_subset_rfm,rh20t_robot,79e7d74b-bfd1-45be-9b14-24a544ff8ce6,True,Stack the squares into a pyramid shape,64,100.0,False,False,
35
- anqil/rh20t_subset_rfm,rh20t_robot,28e35846-4bbf-47be-89f5-447e438ed0a7,True,Use a shovel to scoop up an object,62,96.875,False,False,
36
- anqil/rh20t_subset_rfm,rh20t_robot,bf17468f-8161-40d5-a2f6-292e93bb20cb,True,Stack blocks (small Lego) one on top of the other every time,57,89.0625,False,False,
37
- anqil/rh20t_subset_rfm,rh20t_robot,b403a108-a6e2-4910-8b8c-efb59cdf093e,True,Open a box,60,93.75,False,False,
38
- anqil/rh20t_subset_rfm,rh20t_human,7f7df6ad-32a1-44ad-a0e6-295d1bb65c76,False,Turn the hands of a clock,36,87.8048780487805,False,False,
39
- anqil/rh20t_subset_rfm,rh20t_human,a01d4769-4c96-4e00-b67e-8f4749396c90,False,Slice the lotus root,55,85.9375,False,False,
40
- anqil/rh20t_subset_rfm,rh20t_human,6de0f308-bf23-4f8f-949e-fbc9f70612d3,False,Sharpen the pencil with a pencil sharpener,56,87.5,False,False,
41
- anqil/rh20t_subset_rfm,rh20t_human,01ad9018-59fb-4215-affe-6bd364b0fdec,False,Put the toilet paper on its holder,12,100.0,False,False,
42
- anqil/rh20t_subset_rfm,rh20t_human,b216b59f-efb8-4e15-a194-e58c55994086,False,Assemble one piece of a puzzle,59,92.1875,False,False,
43
- anqil/rh20t_subset_rfm,rh20t_human,d529c6ff-1383-43c1-80d6-7bafd2f7a0a5,False,Pick up the cup,24,75.0,False,False,
44
- anqil/rh20t_subset_rfm,rh20t_human,db988113-8aab-4b8f-8ee0-ed141fd910c7,False,Cover the box,43,91.48936170212765,False,False,
45
- anqil/rh20t_subset_rfm,rh20t_human,e0463214-4e47-40aa-83b4-bc1606a6393d,False,Finish setting up the starting position of a chessboard that is almost arranged,32,78.04878048780488,False,False,
46
- anqil/rh20t_subset_rfm,rh20t_human,3cb04ccf-93c2-4d8a-a2f0-9b39dc02c10f,False,Turn on the water tap,17,77.27272727272727,False,False,
47
- anqil/rh20t_subset_rfm,rh20t_human,473b7d30-80e0-4510-8ac4-e87eff084afd,False,Transfer liquid using a dropper,54,84.375,False,False,
48
- jesbu1/molmoact_rfm,molmoact_dataset_household,bd7a4fde-ef2a-49d3-b0c9-453b26febcb8,True,put my clothes in the laundry bag,58,90.625,False,False,
49
- jesbu1/molmoact_rfm,molmoact_dataset_household,1825aac8-6dbb-488c-ac36-de1d120e082c,True,put the toy car in the basket,59,92.1875,False,False,
50
- jesbu1/molmoact_rfm,molmoact_dataset_household,8424dd49-b1f5-49ed-90a7-1f25fd4bd72c,True,put the fork in the dishwasher,60,93.75,False,False,
51
- jesbu1/molmoact_rfm,molmoact_dataset_household,4a5493f9-c04e-46eb-807a-4c904be6cf7e,True,put the apple in the tray,58,90.625,False,False,
52
- jesbu1/molmoact_rfm,molmoact_dataset_household,c122c462-6207-4a61-966e-85389eebbd97,True,put the protein bar into the container,59,92.1875,False,False,
53
- jesbu1/molmoact_rfm,molmoact_dataset_household,c47da149-c1b2-4c33-92c9-79c199640893,True,pour me some water,53,82.8125,False,False,
54
- jesbu1/molmoact_rfm,molmoact_dataset_household,a010f8c4-d74c-4306-a844-0159ecebbe6f,True,put the markers back in the holder,54,84.375,False,False,
55
- jesbu1/molmoact_rfm,molmoact_dataset_household,013b967c-9f7d-422d-9b07-47c18c4ac9dc,True,turn off the cold water,36,94.73684210526315,False,False,
56
- jesbu1/molmoact_rfm,molmoact_dataset_household,46cb42ae-6c91-4223-a0e4-87979382e1d3,True,put the tongs back in the holder,59,92.1875,False,False,
57
- jesbu1/molmoact_rfm,molmoact_dataset_household,4ffe10df-0e91-4b61-81cc-741827cf0dc3,True,stand the pillow up,55,85.9375,False,False,
58
- jesbu1/molmoact_rfm,molmoact_dataset_tabletop,5949019c-7d3b-4e3f-908f-1ab6dad1c71f,True,knock down the dish soap,56,91.80327868852459,False,False,
59
- jesbu1/molmoact_rfm,molmoact_dataset_tabletop,3280cd5f-8610-49bb-8616-8bc91afab9a1,True,knock down the dish soap,52,89.65517241379311,False,False,
60
- jesbu1/molmoact_rfm,molmoact_dataset_tabletop,c46051d3-e31e-4517-aa69-51a038be6c81,True,close the top drawer,57,89.0625,False,False,
61
- jesbu1/molmoact_rfm,molmoact_dataset_tabletop,79a3ad30-55fb-4703-8935-b3b404522587,True,knock down the dish soap,54,84.375,False,False,
62
- jesbu1/molmoact_rfm,molmoact_dataset_tabletop,bb5c7271-f755-41ec-90e2-8e381649f7ed,True,fold the towel,56,87.5,False,False,
63
- jesbu1/molmoact_rfm,molmoact_dataset_tabletop,30d0dc38-c11f-4677-873d-0aa8614cc3d3,True,hang the mug,59,92.1875,False,False,
64
- jesbu1/molmoact_rfm,molmoact_dataset_tabletop,6b0985e3-32f6-474c-acec-7a6dfddf8373,True,load the plate,61,95.3125,False,False,
65
- jesbu1/molmoact_rfm,molmoact_dataset_tabletop,983438c9-208c-4a56-9c07-35536e5fb64c,True,stand the sanitizer,58,90.625,False,False,
66
- jesbu1/molmoact_rfm,molmoact_dataset_tabletop,497997c8-7134-42cd-ae09-0143c7ad9ad3,True,load the plate,61,95.3125,False,False,
67
- jesbu1/molmoact_rfm,molmoact_dataset_tabletop,0e246684-b029-436d-af14-55cb7b01cba4,True,flip the mug upright,58,90.625,False,False,
68
- jesbu1/galaxea_rfm,galaxea_part1_r1_lite,84bc312a-b111-4adb-a5e9-f723979c00b0,True,Remove the flower from the vase with your right hand and place it on the right side of the table.,58,90.625,False,False,
69
- jesbu1/galaxea_rfm,galaxea_part1_r1_lite,d9d2272e-a195-4f4c-9e23-ab00f5e0f0f6,True,Pick up the kettle with the right hand and move it under the faucet.,64,100.0,False,False,
70
- jesbu1/galaxea_rfm,galaxea_part1_r1_lite,ee7754ce-0174-4760-87bf-782230ada240,True,Turn off the faucet with your right hand.,62,96.875,False,False,
71
- jesbu1/galaxea_rfm,galaxea_part1_r1_lite,36a1cdd5-2126-41f9-bea5-06a23f0c93d8,True,Pick up the vegetable leaf on the black table with your right hand.,62,144.1860465116279,False,False,
72
- jesbu1/galaxea_rfm,galaxea_part1_r1_lite,24006f47-27e4-4d00-8cdc-429030942502,True,Pick up the cigarette butt on the black table with your right hand.,62,96.875,False,False,
73
- jesbu1/galaxea_rfm,galaxea_part1_r1_lite,d623ad49-3f90-46da-a647-3bc7b616c37f,True,"Pick up the sponge with the right hand, scrub the cutting board, then place it on the table.",62,96.875,False,False,
74
- jesbu1/fino_net_rfm,,5ef99446-50eb-4174-974b-00551ecef49f,True,put the single block on the table onto the stack,49,76.5625,False,False,
75
- jesbu1/fino_net_rfm,,d6baa6cf-10db-4d62-915e-1de3ad8f08bf,True,put the single block on the table onto the stack,49,76.5625,False,False,
76
- jesbu1/fino_net_rfm,,0499983c-c52e-4b17-bb50-5bc63374b4c3,True,put the object on the table into the container,32,76.19047619047619,False,False,
77
- jesbu1/h2r_rfm,,0ed22a8d-6d1b-494a-ba1f-23d98e95cdbd,False,pick up the marker and place it on the plate,47,73.4375,False,False,
78
- jesbu1/h2r_rfm,,53c4fc6e-32ca-4f9b-b869-305f8b3e2b95,False,pull the plate from bottom to top,49,76.5625,False,False,
79
- jesbu1/h2r_rfm,,fc8a6de7-bbaa-4cdd-9008-a0000943207f,False,pick up the marker and place it on the plate,49,76.5625,False,False,
80
- jesbu1/h2r_rfm,,45b4dce3-4aeb-42c6-9b85-b19918474b97,False,move the cup from left to right,57,89.0625,False,False,
81
- jesbu1/h2r_rfm,,40d487af-eccc-4699-bd8b-3a6a3c3e2181,False,pull the plate from bottom to top,49,76.5625,False,False,
82
- jesbu1/h2r_rfm,,f9b5c6e7-da35-441a-a093-2fbada53a385,False,put the red cube on the darker plate,51,79.6875,False,False,
83
- jesbu1/h2r_rfm,,308aaadb-e2b4-4325-8699-2e5b17d3da11,False,"put the cube on the plate, then pull the plate from bottom to top",51,79.6875,False,False,
84
- jesbu1/h2r_rfm,,0408b830-5ba3-4f2b-92ea-fa907578cd06,False,"put the cube on the plate, then pull the plate from bottom to top",52,81.25,False,False,
85
- jesbu1/h2r_rfm,,575b629a-16f8-4bdd-972b-a9315f6bd460,False,move the cup from left to right,57,89.0625,False,False,
86
- jesbu1/h2r_rfm,,2c460bcf-cb19-4c02-8a3b-e63a239cd18b,False,pick up the marker and place it on the plate,49,76.5625,False,False,
87
- jesbu1/h2r_rfm,,fc3a70c9-9f77-4eb4-a155-4a9a8588fddf,True,pick up the marker and place it on the plate,52,81.25,False,False,
88
- jesbu1/h2r_rfm,,a4afdcc0-4c4b-4e5f-a3bb-224a4a963c71,True,pick up the green cube and place it onto the plate,50,78.125,False,False,
89
- jesbu1/h2r_rfm,,2627f693-778e-47e1-b6ec-cc1c92e0d74d,True,pick up the marker and place it on the plate,54,84.375,False,False,
90
- jesbu1/h2r_rfm,,ef91ed87-e672-46ea-8698-4ba556f90c85,True,pull the plate from bottom to top,53,82.8125,False,False,
91
- jesbu1/h2r_rfm,,d95dc409-d04f-431f-84f2-9fff51a585f7,True,pick up the green cube and place it onto the plate,51,79.6875,False,False,
92
- jesbu1/h2r_rfm,,5c4d9545-1ab8-4d91-9a9e-763eeb71043a,True,pull the plate from bottom to top,52,81.25,False,False,
93
- jesbu1/h2r_rfm,,8a562db7-79b6-49de-a3a2-a41eb083d43b,True,pick up the marker and place it on the plate,54,84.375,False,False,
94
- jesbu1/h2r_rfm,,103bb2bf-0bc2-478f-86ed-95c65d8895f4,True,move the cup from left to right,57,89.0625,False,False,
95
- jesbu1/h2r_rfm,,3c00c0e6-bfa2-4f60-b321-eb26b8f2048a,True,pick up the red cube and place it onto the darker plate,59,92.1875,False,False,
96
- jesbu1/h2r_rfm,,49444f63-a51f-491f-8013-d8ef2cb6630b,True,pull the plate from bottom to top,55,85.9375,False,False,
97
- jesbu1/humanoid_everyday_rfm,,0f804831-1cd8-48a1-8d31-60fff45b84a7,True,The robot uses its right hand to grab and squeeze a pink dumpling toy kept on the black mask on the desk,49,76.5625,False,False,
98
- jesbu1/humanoid_everyday_rfm,,c0d74c20-62ab-4bb6-b918-b3225cb70954,True,the robot uses its right hand to grab the drawer kept on the middle of the desk and pull it out towards the robot,44,68.75,False,False,
99
- jesbu1/humanoid_everyday_rfm,,2e7d1cc5-f563-44dd-86ea-ec99d9b9ddab,True,A hammer is kept on the right hand side and a pillow is kept on the left hand side on the desk infront of the robot and the robot uses its right hand to pick up the hammer and makes a downward swinging motion to hit the pillow,44,68.75,False,False,
100
- jesbu1/humanoid_everyday_rfm,,d4066d1c-a284-4fff-8fa8-18f038e28d52,True,"The robot uses left hand to click on the left button of the black mouse, which is placed on the left area of the table",47,73.4375,False,False,
101
- jesbu1/humanoid_everyday_rfm,,1afa6a70-b5c1-4552-87a7-aa4ea437212b,True,the robot uses its left hand to push the cover of the diary kept in the middle of the desk to close it,47,73.4375,False,False,
102
- jesbu1/humanoid_everyday_rfm,,22f0419b-d5a3-47b6-80bf-68cfd6c13eb3,True,the robot picks up a small bag of sunflower seeds on the right hand side and take it to the empty plate on the left hand side then pours some sunflower seeds on to the plate. ,47,73.4375,False,False,
103
- jesbu1/humanoid_everyday_rfm,,24de05de-208d-479e-976c-7cfb716844b6,True,the robot uses its right hand to make a fist and press the red button kept in the middle of the desk,45,70.3125,False,False,
104
- jesbu1/humanoid_everyday_rfm,,e28e9adb-c80d-43db-b4e4-5cbcc7020e25,True,The robot picks the blue plier placed on top of the container and places it on the grey area of the table,48,75.0,False,False,
105
- jesbu1/humanoid_everyday_rfm,,685621ee-de8f-4fd8-a93e-89559c721f1d,True,A controller is being placed at the center of the desk near the left hand and the robot uses its left hand's index finger to press a button at the left area of the controller,50,78.125,False,False,
106
- jesbu1/humanoid_everyday_rfm,,8116b7d4-5076-4c2c-9aa7-69b82cc129ca,True,use the right hand to grab the handle of the kettle from the base and place it on the right side,48,75.0,False,False,
107
- jesbu1/motif_rfm,,62b48133-e804-4b46-93e7-1cd120c33e0b,False,pick up the cup and place it to the lower left of the laptop: move downward and to the left,53,82.8125,False,False,
108
- jesbu1/motif_rfm,,e5661543-2bd0-47b8-a84d-457907e50505,False,"pick up the cup and place it to the lower left of the laptop: move downward, then move to the left",57,89.0625,False,False,
109
- jesbu1/motif_rfm,,bc3efef9-5f1c-45c0-b092-271bdc1f7a9d,False,brush hair: move downward and to the right,54,84.375,False,False,
110
- jesbu1/motif_rfm,,67cc01d1-323d-4666-8e67-e6b5f6ab5390,False,shake the boba: make a circular motion clockwise,54,84.375,False,False,
111
- jesbu1/motif_rfm,,7d6cd285-248f-4422-9128-a0d9e49e3524,False,stir: make a circular motion counter-clockwise,54,84.375,False,False,
112
- jesbu1/motif_rfm,,7a79788d-5021-4f20-a53e-87753271a6bb,False,brush hair: move downward,56,87.5,False,False,
113
- jesbu1/motif_rfm,,a8a39a08-aa89-4acd-86a5-3fdf38082de5,False,brush hair: move downward,57,89.0625,False,False,
114
- jesbu1/motif_rfm,,abc6f7f8-985b-4174-8d8f-dcd8135e989b,False,brush hair: move downward,57,89.0625,False,False,
115
- jesbu1/motif_rfm,,457ce97a-eecc-4299-a7f3-37b6bc7762e6,False,brush hair: move downward,51,79.6875,False,False,
116
- jesbu1/motif_rfm,,4d29c82d-e73e-4061-9c14-6ca2b8179570,False,pick up the cup and place it to the lower left of the laptop: move downward and to the left,51,79.6875,False,False,
117
- jesbu1/motif_rfm,,0740db4e-bf3d-453e-8560-8560181b0d85,True,stir: make a circular motion clockwise,51,79.6875,False,False,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
requirements.txt CHANGED
@@ -1,2 +1,2 @@
1
- huggingface-hub==0.19.4
2
- gradio>=4.0.0
 
1
+ # Minimal requirements - Gradio is pre-installed by Spaces
2
+
test_discovery.py DELETED
@@ -1,87 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- Quick script to test dataset discovery and video downloading.
4
- """
5
-
6
- from dataset_discovery import discover_datasets_with_both_types, sample_trajectories
7
- from video_downloader import VideoDownloader
8
-
9
-
10
- def main():
11
- print("=" * 60)
12
- print("Trajectory Labeler - Dataset Discovery Test")
13
- print("=" * 60)
14
-
15
- # Discover datasets
16
- print("\n1. Discovering datasets with both human and robot data...")
17
- datasets = discover_datasets_with_both_types()
18
-
19
- if not datasets:
20
- print("No datasets found. You can manually test with a known dataset.")
21
- print("\nExample: jesbu1/epic_rfm")
22
- return
23
-
24
- print(f"\nFound {len(datasets)} datasets:")
25
- for repo, stats in datasets.items():
26
- print(f" - {repo}: {stats}")
27
-
28
- # Test sampling from first dataset
29
- if datasets:
30
- print("\n2. Testing trajectory sampling...")
31
- first_repo = list(datasets.keys())[0]
32
-
33
- # Handle repo with config
34
- if "/" in first_repo and first_repo.count("/") >= 2:
35
- parts = first_repo.split("/")
36
- repo_id = "/".join(parts[:2])
37
- config_name = parts[2] if len(parts) > 2 else None
38
- else:
39
- repo_id = first_repo
40
- config_name = None
41
-
42
- print(f"Sampling from: {repo_id} (config: {config_name})")
43
-
44
- try:
45
- human_samples = sample_trajectories(repo_id, config_name, is_robot=False, num_samples=2)
46
- robot_samples = sample_trajectories(repo_id, config_name, is_robot=True, num_samples=2)
47
-
48
- print(f" βœ… Human samples: {len(human_samples)}")
49
- print(f" βœ… Robot samples: {len(robot_samples)}")
50
-
51
- if human_samples:
52
- print(f" Example human trajectory ID: {human_samples[0].get('id', 'unknown')[:30]}...")
53
- if robot_samples:
54
- print(f" Example robot trajectory ID: {robot_samples[0].get('id', 'unknown')[:30]}...")
55
-
56
- # Test video download
57
- print("\n3. Testing video download...")
58
- downloader = VideoDownloader()
59
-
60
- if human_samples:
61
- print(f" Downloading human video...")
62
- video_path = downloader.download_video(human_samples[0], repo_id, config_name)
63
- if video_path:
64
- print(f" βœ… Downloaded to: {video_path}")
65
- info = downloader.get_video_info(video_path)
66
- print(f" Video info: {info.get('frame_count')} frames, {info.get('fps'):.1f} fps")
67
- else:
68
- print(f" ❌ Failed to download video")
69
-
70
- except Exception as e:
71
- print(f" ❌ Error: {e}")
72
- import traceback
73
- traceback.print_exc()
74
-
75
- print("\n" + "=" * 60)
76
- print("Test complete! Run 'python labeler_app.py' to start the labeling app.")
77
- print("=" * 60)
78
-
79
-
80
- if __name__ == "__main__":
81
- main()
82
-
83
-
84
-
85
-
86
-
87
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
video_downloader.py DELETED
@@ -1,163 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- Download videos for sampled trajectories.
4
- Manages local cache and handles video file paths.
5
- """
6
-
7
- import os
8
- import shutil
9
- from pathlib import Path
10
- from typing import Dict, List, Optional
11
-
12
- from datasets import load_dataset
13
- from huggingface_hub import hf_hub_download
14
- from tqdm import tqdm
15
-
16
-
17
- class VideoDownloader:
18
- """Handles downloading and caching trajectory videos."""
19
-
20
- def __init__(self, cache_dir: str = "video_cache"):
21
- self.cache_dir = Path(cache_dir)
22
- self.cache_dir.mkdir(exist_ok=True)
23
- self.downloaded_videos = {} # repo -> video_path mapping
24
-
25
- def download_video(self, trajectory: Dict, dataset_repo: str, config_name: Optional[str] = None) -> Optional[str]:
26
- """
27
- Download a single video for a trajectory.
28
-
29
- Args:
30
- trajectory: Trajectory dictionary with 'frames' field (video path)
31
- dataset_repo: HuggingFace dataset repository ID
32
- config_name: Optional config name
33
-
34
- Returns:
35
- Local path to downloaded video, or None if failed
36
- """
37
- video_path = trajectory.get("frames")
38
- if not video_path:
39
- return None
40
-
41
- # Create cache subdirectory for this dataset
42
- repo_key = f"{dataset_repo}_{config_name}" if config_name else dataset_repo
43
- repo_key = repo_key.replace("/", "_").replace("\\", "_")
44
- dataset_cache_dir = self.cache_dir / repo_key
45
- dataset_cache_dir.mkdir(parents=True, exist_ok=True)
46
-
47
- # Handle relative paths - preserve directory structure
48
- video_path_obj = Path(video_path)
49
- if video_path_obj.is_absolute():
50
- # If absolute, just use filename
51
- local_video_path = dataset_cache_dir / video_path_obj.name
52
- else:
53
- # Preserve relative directory structure
54
- local_video_path = dataset_cache_dir / video_path
55
- local_video_path.parent.mkdir(parents=True, exist_ok=True)
56
-
57
- # Check if already downloaded
58
- if local_video_path.exists():
59
- return str(local_video_path)
60
-
61
- # Download from HuggingFace Hub
62
- try:
63
- # Use local_dir to preserve directory structure
64
- downloaded_path = hf_hub_download(
65
- repo_id=dataset_repo,
66
- repo_type="dataset",
67
- filename=video_path,
68
- local_dir=str(dataset_cache_dir),
69
- local_dir_use_symlinks=False,
70
- )
71
-
72
- # hf_hub_download returns the full path, check if it matches our expected path
73
- if Path(downloaded_path).exists():
74
- return downloaded_path
75
- elif local_video_path.exists():
76
- return str(local_video_path)
77
- else:
78
- # Try to find the downloaded file
79
- downloaded_file = Path(downloaded_path)
80
- if downloaded_file.exists():
81
- # Copy to expected location
82
- local_video_path.parent.mkdir(parents=True, exist_ok=True)
83
- shutil.copy2(downloaded_file, local_video_path)
84
- return str(local_video_path)
85
- else:
86
- print(f"Downloaded file not found: {downloaded_path}")
87
- return None
88
-
89
- except Exception as e:
90
- print(f"Error downloading video {video_path}: {e}")
91
- # Try alternative: load via datasets library which handles paths better
92
- try:
93
- from datasets import load_dataset
94
- if config_name:
95
- dataset = load_dataset(dataset_repo, config_name, split="train", streaming=False)
96
- else:
97
- dataset = load_dataset(dataset_repo, split="train", streaming=False)
98
-
99
- # Find trajectory by ID
100
- traj_id = trajectory.get("id")
101
- for sample in dataset:
102
- if sample.get("id") == traj_id:
103
- # Try to get the video file
104
- frames = sample.get("frames")
105
- if isinstance(frames, str) and os.path.exists(frames):
106
- return frames
107
- break
108
- except Exception as e2:
109
- print(f"Alternative download method also failed: {e2}")
110
-
111
- return None
112
-
113
- def download_trajectories(
114
- self,
115
- trajectories: List[Dict],
116
- dataset_repo: str,
117
- config_name: Optional[str] = None
118
- ) -> List[Dict]:
119
- """
120
- Download videos for multiple trajectories.
121
-
122
- Returns:
123
- List of trajectory dicts with 'local_video_path' added
124
- """
125
- results = []
126
-
127
- for traj in tqdm(trajectories, desc="Downloading videos"):
128
- local_path = self.download_video(traj, dataset_repo, config_name)
129
- if local_path:
130
- traj_copy = traj.copy()
131
- traj_copy["local_video_path"] = local_path
132
- traj_copy["dataset_repo"] = dataset_repo
133
- traj_copy["config_name"] = config_name
134
- results.append(traj_copy)
135
- else:
136
- print(f"Warning: Failed to download video for trajectory {traj.get('id', 'unknown')}")
137
-
138
- return results
139
-
140
- def get_video_info(self, video_path: str) -> Dict:
141
- """Get video metadata (frame count, duration, etc.)."""
142
- import cv2
143
-
144
- if not os.path.exists(video_path):
145
- return {"error": "Video not found"}
146
-
147
- cap = cv2.VideoCapture(video_path)
148
- if not cap.isOpened():
149
- return {"error": "Could not open video"}
150
-
151
- frame_count = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
152
- fps = cap.get(cv2.CAP_PROP_FPS)
153
- duration = frame_count / fps if fps > 0 else 0
154
-
155
- cap.release()
156
-
157
- return {
158
- "frame_count": frame_count,
159
- "fps": fps,
160
- "duration": duration,
161
- "path": video_path
162
- }
163
-