Spaces:
Running on Zero
Running on Zero
Download src/data/object_scale.py from hugging-apps/rigidformer: direct link, hf CLI and curl.
- Browser
- Download file 6.06 kB
-
https://huggingface.co/spaces/hugging-apps/rigidformer/resolve/main/src/data/object_scale.py
- Command line
-
hf download hf://spaces/hugging-apps/rigidformer/src/data/object_scale.py
-
curl -L -o object_scale.py https://huggingface.co/spaces/hugging-apps/rigidformer/resolve/main/src/data/object_scale.py
6.06 kB
| """Per-object metadata scale for converted MOVi vertex data (data.object_scale_file). | |
| data_generation/movi_to_sim_data.py wrote every object's vertices as R v_asset + p and never applied the metadata | |
| `size`, so stored meshes do not have the simulated dimensions (MOVi-Sphere: all spheres ~1.0 m across, simulated radii | |
| 0.35 / 0.7 m; resting mesh bottoms at z ~ -0.15 / +0.2 m instead of the floor). With a scale file from | |
| data_generation/build_object_scales.py, every array read from <scene>/frame_<t>/obj_<i>_pos.npy is scaled about the | |
| object origin p_t (vis_traj.npy[0, t, i, :3], equal to the metadata positions): | |
| v' = p_t + size_i (v - p_t) | |
| after which resting objects of every size have their lowest vertex at z ~ 0.002 m. Anchor and FPS-order caches are | |
| unchanged (farthest-point sampling is invariant to a uniform scaling about any point). Without a scale file nothing | |
| changes. | |
| """ | |
| import json | |
| from pathlib import Path | |
| from typing import Dict, Optional | |
| import numpy as np | |
| try: | |
| from data.scene_frames import load_frame | |
| except ImportError: # imported as src.data.object_scale | |
| from src.data.scene_frames import load_frame | |
| class ObjectScale: | |
| def __init__(self, scale_file: str): | |
| data = json.load(open(scale_file)) | |
| self.scale_file = str(scale_file) | |
| # converted dataset the sizes belong to (scene names movi_<n> repeat across MOVi-A/B/Sphere) | |
| self.converted = data.get('converted') | |
| self.scales: Dict[str, np.ndarray] = {k: np.asarray(v, dtype=np.float64) for k, v in data['scales'].items()} | |
| self.fingerprint = {k: np.asarray(v, dtype=np.float64) for k, v in (data.get('fingerprint') or {}).items()} | |
| if 'radius0' not in data: | |
| raise ValueError(f'{scale_file} has no radius0 entries (the guard against vertex data that already carries the ' | |
| f'size); rebuild it with data_generation/build_object_scales.py') | |
| self.radius0 = {k: np.asarray(v, dtype=np.float64) for k, v in data['radius0'].items()} | |
| for name, table in (('scales', self.scales), ('fingerprint', self.fingerprint), ('radius0', self.radius0)): | |
| bad = [k for k, v in table.items() if not np.isfinite(v).all() or (name != 'fingerprint' and not (v > 0).all())] | |
| if bad: | |
| raise ValueError(f'{scale_file}: non-finite or non-positive {name} for {len(bad)} scene(s), e.g. {bad[:3]}') | |
| self._origins: Dict[str, np.ndarray] = {} | |
| def origins(self, scene_dir: Path) -> np.ndarray: | |
| key = scene_dir.name | |
| if key not in self._origins: | |
| o = np.load(scene_dir / 'vis_traj.npy')[0, :, :, :3].astype(np.float64) # (T, Nobj, 3) | |
| if not np.isfinite(o).all(): | |
| raise ValueError(f'ObjectScale: non-finite object origins in {scene_dir / "vis_traj.npy"}') | |
| self._origins[key] = o | |
| return self._origins[key] | |
| def apply(self, vertex_path: Path, V: np.ndarray) -> np.ndarray: | |
| """Scaled copy of V, the array stored at <scene>/frame_<t>/obj_<i>_pos.npy (V itself is never modified).""" | |
| vertex_path = Path(vertex_path) | |
| frame_dir = vertex_path.parent | |
| scene_dir = frame_dir.parent | |
| if not frame_dir.name.startswith('frame_') or not vertex_path.name.startswith('obj_'): | |
| raise ValueError(f'ObjectScale: not a per-frame vertex file: {vertex_path}') | |
| if scene_dir.name not in self.scales: | |
| raise KeyError(f'ObjectScale: scene {scene_dir.name} missing from {self.scale_file}') | |
| t = int(frame_dir.name.split('_')[1]) | |
| i = int(vertex_path.name.split('_')[1]) | |
| s = self.scales[scene_dir.name][i] | |
| p = self.origins(scene_dir)[t, i] | |
| return (p + s * (V.astype(np.float64) - p)).astype(V.dtype) | |
| def from_config(config: dict) -> Optional[ObjectScale]: | |
| f = (config.get('data', {}) or {}).get('object_scale_file') | |
| return ObjectScale(f) if f else None | |
| def check_object_counts(object_scale: 'ObjectScale', data_path, scenes) -> None: | |
| """For every scene: as many sizes as objects in frame_00000; object 0's frame-0 origin equal to the sidecar's | |
| fingerprint (MOVi-A and MOVi-Sphere share seeds and hence object counts, so the counts alone cannot tell them | |
| apart); and every object's largest frame-0 vertex distance from its origin equal to the sidecar's radius0, i.e. the | |
| stored vertices are still the unscaled ones the sizes apply to (data re-converted with the size would otherwise be | |
| scaled twice).""" | |
| import os | |
| bad = [] | |
| for sc in scenes: | |
| if sc not in object_scale.scales: | |
| bad.append(f'{sc}: missing') | |
| continue | |
| frame0 = load_frame(Path(data_path) / sc, 0) # either layout (src/data/scene_frames.py) | |
| n = len(frame0) | |
| if n != len(object_scale.scales[sc]): | |
| bad.append(f'{sc}: {n} objects vs {len(object_scale.scales[sc])} sizes') | |
| continue | |
| if sc not in object_scale.fingerprint or sc not in object_scale.radius0: | |
| bad.append(f'{sc}: no fingerprint / radius0 in the sidecar') | |
| continue | |
| p0 = np.load(os.path.join(str(data_path), sc, 'vis_traj.npy'))[0, 0, :, :3].astype(np.float64) # (Nobj, 3) | |
| if not np.abs(p0[0] - object_scale.fingerprint[sc]).max() <= 1e-4: # (a NaN fails this test too) | |
| bad.append(f'{sc}: frame-0 origin {p0[0].round(4).tolist()} vs fingerprint {object_scale.fingerprint[sc].tolist()}') | |
| continue | |
| r = np.array([np.linalg.norm(frame0[o].astype(np.float64) - p0[o], axis=1).max() for o in range(n)]) | |
| if not np.abs(r - object_scale.radius0[sc]).max() <= 1e-4: # (a NaN fails this test too) | |
| bad.append(f'{sc}: frame-0 vertex radii {r.round(4).tolist()} vs radius0 {object_scale.radius0[sc].tolist()} ' | |
| f'(sizes {object_scale.scales[sc].round(4).tolist()})') | |
| if bad: | |
| raise ValueError(f'{object_scale.scale_file}: {len(bad)} scene(s) do not match the data, e.g. {bad[:3]}') | |