Spaces:
Running
Running
KaushikSid
commited on
Commit
Β·
3c0c19c
1
Parent(s):
dd97b7a
Fresh start: Absolute minimal Gradio app
Browse files- README.md +2 -24
- app.py +4 -10
- dataset_discovery.py +0 -207
- labeler_app.py +0 -604
- labels.csv +0 -117
- requirements.txt +2 -2
- test_discovery.py +0 -87
- 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.
|
| 8 |
app_file: app.py
|
| 9 |
pinned: false
|
| 10 |
-
license: mit
|
| 11 |
---
|
| 12 |
|
| 13 |
# Trajectory End Point Labeler
|
| 14 |
|
| 15 |
-
|
| 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
|
| 10 |
-
inputs=
|
| 11 |
-
outputs=
|
| 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 |
-
|
| 2 |
-
|
|
|
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|