Download data/dataset.py from MissawB/creature-flow-point-dit: direct link, hf CLI and curl.
- Browser
- Download file 4.7 kB
-
https://huggingface.co/MissawB/creature-flow-point-dit/resolve/main/data/dataset.py
- Command line
-
hf download hf://MissawB/creature-flow-point-dit/data/dataset.py
-
curl -L -o dataset.py https://huggingface.co/MissawB/creature-flow-point-dit/resolve/main/data/dataset.py
4.7 kB
| import os | |
| import torch | |
| import numpy as np | |
| from torch.utils.data import Dataset | |
| from typing import Optional | |
| from data.smal_quadrupeds import SMALQuadrupedGenerator, FAMILIES, PARENT_INDICES, GENERATOR_VERSION | |
| from data.ot_coupling import precompute_ot_noise | |
| class CreatureDataset(Dataset): | |
| """PyTorch Dataset for 3D creatures conditioned on kinematic skeletons.""" | |
| def __init__( | |
| self, | |
| num_samples_per_family: int = 50, | |
| num_points: int = 2048, | |
| augment: bool = True, | |
| cache_dir: Optional[str] = "data/cache", | |
| seed: int = 42, | |
| ot_noise_per_sample: int = 0, | |
| independent_noise_prob: float = 0.0, | |
| ot_workers: Optional[int] = None | |
| ): | |
| super().__init__() | |
| self.num_points = num_points | |
| self.augment = augment | |
| self.cache_dir = cache_dir | |
| self.independent_noise_prob = independent_noise_prob | |
| self.samples = [] | |
| self.ot_noise = None | |
| generator = SMALQuadrupedGenerator(seed=seed) | |
| # Cache file is keyed on generation parameters so a config change never reuses stale samples | |
| cache_file = os.path.join(cache_dir, f"dataset_v{GENERATOR_VERSION}_n{num_samples_per_family}_p{num_points}_s{seed}.pt") if cache_dir else None | |
| if cache_file and os.path.exists(cache_file): | |
| cached = torch.load(cache_file, weights_only=False) | |
| self.samples = cached["samples"] | |
| else: | |
| for family in FAMILIES: | |
| for _ in range(num_samples_per_family): | |
| instance = generator.generate_instance(family=family, num_points=num_points) | |
| self.samples.append(instance) | |
| if cache_file: | |
| os.makedirs(cache_dir, exist_ok=True) | |
| torch.save({"samples": self.samples}, cache_file) | |
| if ot_noise_per_sample > 0: | |
| self.ot_noise = self._load_or_compute_ot_noise(cache_file, ot_noise_per_sample, seed, ot_workers) | |
| def _load_or_compute_ot_noise(self, cache_file: Optional[str], num_noise: int, seed: int, workers: Optional[int]): | |
| noise_file = cache_file.replace(".pt", f"_ot{num_noise}.pt") if cache_file else None | |
| if noise_file and os.path.exists(noise_file): | |
| return torch.load(noise_file, weights_only=True)["noise"] | |
| noise = precompute_ot_noise([s["points"] for s in self.samples], num_noise, seed=seed, num_workers=workers) | |
| noise = [torch.from_numpy(n) for n in noise] | |
| if noise_file: | |
| torch.save({"noise": noise}, noise_file) | |
| return noise | |
| def __len__(self) -> int: | |
| return len(self.samples) | |
| def _apply_augmentation(self, points: np.ndarray, joints: np.ndarray, noise: Optional[np.ndarray] = None): | |
| # Coherent rotation around vertical Y axis | |
| theta = np.random.uniform(-np.pi, np.pi) | |
| cos_t, sin_t = np.cos(theta), np.sin(theta) | |
| R = np.array([ | |
| [cos_t, 0.0, sin_t], | |
| [0.0, 1.0, 0.0 ], | |
| [-sin_t, 0.0, cos_t] | |
| ], dtype=np.float32) | |
| points = points @ R | |
| joints = joints @ R | |
| # N(0, I) is rotation invariant: rotating the matched noise keeps it Gaussian and keeps the OT pairing | |
| if noise is not None: | |
| noise = noise @ R | |
| # Scaling jitter | |
| scale = np.random.uniform(0.92, 1.08) | |
| points *= scale | |
| joints *= scale | |
| return points, joints, noise | |
| def __getitem__(self, idx: int): | |
| raw = self.samples[idx] | |
| pts = raw["points"].copy() | |
| jts = raw["joints"].copy() | |
| noise = None | |
| if self.ot_noise is not None: | |
| if np.random.rand() < self.independent_noise_prob: | |
| # Hybrid coupling: some independent noise keeps the field easy to learn near t = 0 | |
| noise = np.random.randn(*pts.shape).astype(np.float32) | |
| else: | |
| stack = self.ot_noise[idx] | |
| noise = stack[np.random.randint(len(stack))].numpy().copy() | |
| if self.augment: | |
| pts, jts, noise = self._apply_augmentation(pts, jts, noise) | |
| item = { | |
| "points": torch.from_numpy(pts).float(), | |
| "joints": torch.from_numpy(jts).float(), | |
| "joint_parents": torch.from_numpy(raw["joint_parents"]).long(), | |
| "family_id": torch.tensor(raw["family_id"]).long() | |
| } | |
| if noise is not None: | |
| item["noise"] = torch.from_numpy(noise).float() | |
| return item | |