| import os |
| import json |
| from collections import defaultdict |
| from tqdm import tqdm |
|
|
| import numpy as np |
| from dataset_upload.helpers import generate_unique_id |
|
|
| trajectory_info_template = { |
| "id": [], |
| "task": [], |
| |
| "data_source": None, |
| "frames": None, |
| "is_robot": None, |
| "quality_label": None, |
| "partial_success": None, |
| } |
|
|
|
|
| class EgoCOTFrameloader: |
| """Pickle-able loader that reads EgoCoT frames from disk on demand. |
| |
| Stores only simple fields so it can be safely passed across processes. |
| """ |
|
|
| def __init__(self, frames_path: str) -> None: |
| self.frames_path = frames_path |
|
|
| def __call__(self) -> np.ndarray: |
| """Load frames from disk when called. |
| |
| Returns: |
| np.ndarray of shape (T, H, W, 3), dtype uint8 |
| """ |
| |
| frames = np.load(self.frames_path) |
|
|
| |
| |
| assert frames.ndim == 4, f"Expected 4D array, got {frames.ndim}D array" |
|
|
| |
| if not isinstance(frames, np.ndarray) or frames.ndim != 4 or frames.shape[1] != 3: |
| raise ValueError(f"Unexpected frames shape for {self.frames_path}: {getattr(frames, 'shape', None)}") |
|
|
| |
| if frames.dtype != np.uint8: |
| |
| if frames.dtype in [np.float32, np.float64]: |
| if frames.max() <= 1.0: |
| |
| frames = (frames * 255).astype(np.uint8) |
| else: |
| |
| |
| |
| imagenet_mean = np.array([0.485, 0.456, 0.406]).reshape(1, 3, 1, 1) |
| imagenet_std = np.array([0.229, 0.224, 0.225]).reshape(1, 3, 1, 1) |
|
|
| |
| frames = frames * imagenet_std + imagenet_mean |
|
|
| |
| frames = np.clip(frames, 0, 1) |
| frames = (frames * 255).astype(np.uint8) |
| else: |
| frames = frames.astype(np.uint8, copy=False) |
|
|
| |
| frames = frames.transpose(0, 2, 3, 1) |
|
|
| return frames |
|
|
|
|
| def create_new_trajectory(frames_path: str, caption: str) -> dict: |
| """Create a new trajectory from EgoCoT data.""" |
| trajectory_info = {} |
| trajectory_info["id"] = generate_unique_id() |
| trajectory_info["task"] = caption |
| trajectory_info["frames"] = EgoCOTFrameloader(frames_path) |
| trajectory_info["is_robot"] = False |
| trajectory_info["quality_label"] = "successful" |
| trajectory_info["partial_success"] = 1 |
| trajectory_info["data_source"] = "egocot" |
| return trajectory_info |
|
|
|
|
| def load_egocot_dataset(dataset_path: str) -> dict[str, list[dict]]: |
| """Load EgoCoT dataset from results.json and .npy frame files (EgoCOT_clear). |
| |
| Expected layout (example): |
| <dataset_path>/ |
| results.json |
| EgoCOT_clear/ |
| EGO_0000.npy |
| EGO_0001.npy |
| |
| Args: |
| dataset_path: Path to a directory containing one or more EgoCoT result folders |
| |
| Returns: |
| Dictionary mapping task descriptions to lists of trajectory dictionaries |
| """ |
| |
| json_files = [] |
| for root, dirs, files in os.walk(dataset_path): |
| for file in files: |
| if file.lower() == "results.json": |
| json_files.append(os.path.join(root, file)) |
|
|
| if not json_files: |
| raise FileNotFoundError(f"No results.json files found in {dataset_path}") |
|
|
| task_data = defaultdict(list) |
| total_trajectories = 0 |
|
|
| for json_file in json_files: |
| print(f"Loading annotations from {json_file}") |
|
|
| with open(json_file, "r") as f: |
| annotations = json.load(f) |
|
|
| |
| if isinstance(annotations, list): |
| data_items = annotations |
| elif isinstance(annotations, dict): |
| if "data" in annotations: |
| data_items = annotations["data"] |
| elif "results" in annotations: |
| data_items = annotations["results"] |
| elif "annotations" in annotations: |
| data_items = annotations["annotations"] |
| elif "samples" in annotations: |
| data_items = annotations["samples"] |
| elif "image" in annotations: |
| data_items = [annotations] |
| else: |
| raise ValueError(f"Unexpected JSON structure in {json_file}: cannot find data list") |
| else: |
| raise ValueError(f"Unexpected JSON structure in {json_file}") |
|
|
| for item in tqdm(data_items, desc="Processing trajectories"): |
| |
| image_filename = item.get("image") |
| |
| caption = item.get("planing").split("\n")[0][1:] |
| if not caption: |
| caption = item.get("caption") |
| score = item.get("score") |
|
|
| if not image_filename or not caption: |
| print(f"Skipping item with missing image or caption: {item}") |
| continue |
|
|
| |
| base_dir = os.path.dirname(json_file) |
| candidate_paths = [ |
| os.path.join(base_dir, "EgoCOT_clear", image_filename) if image_filename else None, |
| |
| |
| |
| ] |
|
|
| frames_path = None |
| for cand in candidate_paths: |
| if cand and os.path.exists(cand): |
| frames_path = cand |
| break |
|
|
| if frames_path is None: |
| print(f"Warning: .npy frame file not found for: {image_filename}") |
| continue |
|
|
| if not frames_path.lower().endswith(".npy"): |
| print(f"Warning: expected .npy file, got: {frames_path}. Skipping.") |
| continue |
|
|
| |
| trajectory = create_new_trajectory(frames_path, caption) |
|
|
| |
| task_key = caption[:50] + "..." if len(caption) > 50 else caption |
| task_data[task_key].append(trajectory) |
| total_trajectories += 1 |
|
|
| print(f"Loaded {total_trajectories} trajectories from {len(task_data)} unique tasks") |
| return task_data |
|
|