Add all-metrics-only GIF render mode
Browse files
code/scripts/render_oven_metric_gifs.py
CHANGED
|
@@ -17,6 +17,33 @@ if str(PROJECT_ROOT) not in sys.path:
|
|
| 17 |
sys.path.insert(0, str(PROJECT_ROOT))
|
| 18 |
|
| 19 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
def _launch_xvfb(display_num: int, log_path: Path) -> subprocess.Popen:
|
| 21 |
log_handle = log_path.open("w", encoding="utf-8")
|
| 22 |
return subprocess.Popen(
|
|
@@ -146,6 +173,7 @@ def main() -> int:
|
|
| 146 |
parser.add_argument("--checkpoint-stride", type=int, default=16)
|
| 147 |
parser.add_argument("--num-workers", type=int, default=6)
|
| 148 |
parser.add_argument("--base-display", type=int, default=190)
|
|
|
|
| 149 |
args = parser.parse_args()
|
| 150 |
|
| 151 |
episode_dir = Path(args.episode_dir)
|
|
@@ -182,18 +210,29 @@ def main() -> int:
|
|
| 182 |
visibility_dir = frames_dir.joinpath("visibility_focus")
|
| 183 |
path_dir = frames_dir.joinpath("path_quality_focus")
|
| 184 |
all_dir = frames_dir.joinpath("all_metrics")
|
| 185 |
-
|
| 186 |
-
|
|
|
|
|
|
|
|
|
|
| 187 |
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 195 |
)
|
| 196 |
-
)
|
| 197 |
all_frames = _range_inclusive(0, max_frame, 2)
|
| 198 |
unique_frames = sorted(set(visibility_frames) | set(path_frames) | set(all_frames))
|
| 199 |
|
|
@@ -250,47 +289,58 @@ def main() -> int:
|
|
| 250 |
visibility_pngs = [visibility_dir.joinpath(f"frame_{frame:04d}.png") for frame in visibility_frames]
|
| 251 |
path_pngs = [path_dir.joinpath(f"frame_{frame:04d}.png") for frame in path_frames]
|
| 252 |
all_pngs = [all_dir.joinpath(f"frame_{frame:04d}.png") for frame in all_frames]
|
| 253 |
-
for path in
|
| 254 |
if not path.exists():
|
| 255 |
raise RuntimeError(f"missing rendered frame: {path}")
|
| 256 |
|
| 257 |
-
|
| 258 |
-
|
| 259 |
-
|
| 260 |
-
|
| 261 |
-
|
| 262 |
-
|
| 263 |
-
|
| 264 |
-
|
| 265 |
-
|
| 266 |
-
|
|
|
|
| 267 |
_assemble_gif(
|
| 268 |
all_pngs,
|
| 269 |
output_dir.joinpath(f"{episode_name}_all_metrics.gif"),
|
| 270 |
duration_ms=100,
|
| 271 |
)
|
| 272 |
|
| 273 |
-
|
| 274 |
-
|
| 275 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 276 |
[
|
| 277 |
-
"# Visualizations",
|
| 278 |
-
"",
|
| 279 |
f"- `{episode_name}_visibility_focus.gif`: three-view visibility montage over dense frames {visibility_frames[0]}-{visibility_frames[-1]}.",
|
| 280 |
f"- `{episode_name}_path_quality_focus.gif`: debug-aware p_ext planner montage over dense frames {path_frames[0]}-{path_frames[-1]}.",
|
| 281 |
-
f"- `{episode_name}_all_metrics.gif`: full episode overlay GIF over dense frames 0-{max_frame}, sampled every 2 frames.",
|
| 282 |
"- `frames/visibility_focus/`: per-frame PNGs with scene overlays, x-ray projections, and tray-mask views.",
|
| 283 |
"- `frames/path_quality_focus/`: per-frame PNGs with demo wrist trails, milestone pose overlays, and p_ext planner-search tables from the debug JSONL sidecar.",
|
| 284 |
-
|
| 285 |
-
|
| 286 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 287 |
"- Visibility: blue = sampled tray surface, magenta = sampled grasp region, green = depth-consistent visible grasp samples.",
|
| 288 |
"- Path quality: cyan/purple = recent left/right demo wrist trails, yellow = pregrasp, orange = grasp, green = retreat / extraction path.",
|
| 289 |
-
"- All metrics: red banner = reveal phase, green banner = retrieve phase.",
|
| 290 |
]
|
| 291 |
-
)
|
| 292 |
-
|
| 293 |
-
)
|
| 294 |
print(output_dir)
|
| 295 |
return 0
|
| 296 |
|
|
|
|
| 17 |
sys.path.insert(0, str(PROJECT_ROOT))
|
| 18 |
|
| 19 |
|
| 20 |
+
def _configure_thread_env() -> None:
|
| 21 |
+
defaults = {
|
| 22 |
+
"OMP_NUM_THREADS": "1",
|
| 23 |
+
"OPENBLAS_NUM_THREADS": "1",
|
| 24 |
+
"MKL_NUM_THREADS": "1",
|
| 25 |
+
"NUMEXPR_NUM_THREADS": "1",
|
| 26 |
+
"VECLIB_MAXIMUM_THREADS": "1",
|
| 27 |
+
"BLIS_NUM_THREADS": "1",
|
| 28 |
+
}
|
| 29 |
+
for key, value in defaults.items():
|
| 30 |
+
os.environ.setdefault(key, value)
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
def _configure_coppeliasim_env() -> None:
|
| 34 |
+
coppeliasim_root = os.environ.setdefault("COPPELIASIM_ROOT", "/workspace/coppelia_sim")
|
| 35 |
+
ld_library_path_parts = [
|
| 36 |
+
part for part in os.environ.get("LD_LIBRARY_PATH", "").split(":") if part
|
| 37 |
+
]
|
| 38 |
+
if coppeliasim_root not in ld_library_path_parts:
|
| 39 |
+
ld_library_path_parts.insert(0, coppeliasim_root)
|
| 40 |
+
os.environ["LD_LIBRARY_PATH"] = ":".join(ld_library_path_parts)
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
_configure_thread_env()
|
| 44 |
+
_configure_coppeliasim_env()
|
| 45 |
+
|
| 46 |
+
|
| 47 |
def _launch_xvfb(display_num: int, log_path: Path) -> subprocess.Popen:
|
| 48 |
log_handle = log_path.open("w", encoding="utf-8")
|
| 49 |
return subprocess.Popen(
|
|
|
|
| 173 |
parser.add_argument("--checkpoint-stride", type=int, default=16)
|
| 174 |
parser.add_argument("--num-workers", type=int, default=6)
|
| 175 |
parser.add_argument("--base-display", type=int, default=190)
|
| 176 |
+
parser.add_argument("--all-metrics-only", action="store_true")
|
| 177 |
args = parser.parse_args()
|
| 178 |
|
| 179 |
episode_dir = Path(args.episode_dir)
|
|
|
|
| 210 |
visibility_dir = frames_dir.joinpath("visibility_focus")
|
| 211 |
path_dir = frames_dir.joinpath("path_quality_focus")
|
| 212 |
all_dir = frames_dir.joinpath("all_metrics")
|
| 213 |
+
if args.all_metrics_only:
|
| 214 |
+
all_dir.mkdir(parents=True, exist_ok=True)
|
| 215 |
+
else:
|
| 216 |
+
for directory in [visibility_dir, path_dir, all_dir]:
|
| 217 |
+
directory.mkdir(parents=True, exist_ok=True)
|
| 218 |
|
| 219 |
+
if args.all_metrics_only:
|
| 220 |
+
visibility_frames = []
|
| 221 |
+
path_frames = []
|
| 222 |
+
else:
|
| 223 |
+
visibility_frames = _range_inclusive(
|
| 224 |
+
max(0, phase_cross - 12), min(max_frame, phase_cross + 28), 1
|
| 225 |
+
)
|
| 226 |
+
path_start = max(0, min(phase_cross, pext_cross) - 16)
|
| 227 |
+
path_end = min(max_frame, max(pext_cross, ready_cross, retrieve_cross) + 24)
|
| 228 |
+
path_frames = sorted(
|
| 229 |
+
set(
|
| 230 |
+
_range_inclusive(
|
| 231 |
+
path_start, min(path_end, max(path_start, pext_cross - 18)), 2
|
| 232 |
+
)
|
| 233 |
+
+ _range_inclusive(max(path_start, pext_cross - 18), path_end, 1)
|
| 234 |
+
)
|
| 235 |
)
|
|
|
|
| 236 |
all_frames = _range_inclusive(0, max_frame, 2)
|
| 237 |
unique_frames = sorted(set(visibility_frames) | set(path_frames) | set(all_frames))
|
| 238 |
|
|
|
|
| 289 |
visibility_pngs = [visibility_dir.joinpath(f"frame_{frame:04d}.png") for frame in visibility_frames]
|
| 290 |
path_pngs = [path_dir.joinpath(f"frame_{frame:04d}.png") for frame in path_frames]
|
| 291 |
all_pngs = [all_dir.joinpath(f"frame_{frame:04d}.png") for frame in all_frames]
|
| 292 |
+
for path in all_pngs + visibility_pngs + path_pngs:
|
| 293 |
if not path.exists():
|
| 294 |
raise RuntimeError(f"missing rendered frame: {path}")
|
| 295 |
|
| 296 |
+
if not args.all_metrics_only:
|
| 297 |
+
_assemble_gif(
|
| 298 |
+
visibility_pngs,
|
| 299 |
+
output_dir.joinpath(f"{episode_name}_visibility_focus.gif"),
|
| 300 |
+
duration_ms=120,
|
| 301 |
+
)
|
| 302 |
+
_assemble_gif(
|
| 303 |
+
path_pngs,
|
| 304 |
+
output_dir.joinpath(f"{episode_name}_path_quality_focus.gif"),
|
| 305 |
+
duration_ms=160,
|
| 306 |
+
)
|
| 307 |
_assemble_gif(
|
| 308 |
all_pngs,
|
| 309 |
output_dir.joinpath(f"{episode_name}_all_metrics.gif"),
|
| 310 |
duration_ms=100,
|
| 311 |
)
|
| 312 |
|
| 313 |
+
readme_lines = [
|
| 314 |
+
"# Visualizations",
|
| 315 |
+
"",
|
| 316 |
+
f"- `{episode_name}_all_metrics.gif`: full episode overlay GIF over dense frames 0-{max_frame}, sampled every 2 frames.",
|
| 317 |
+
"- `frames/all_metrics/`: per-frame PNGs with the episode-wide metric bars and phase banner.",
|
| 318 |
+
]
|
| 319 |
+
if not args.all_metrics_only:
|
| 320 |
+
readme_lines.extend(
|
| 321 |
[
|
|
|
|
|
|
|
| 322 |
f"- `{episode_name}_visibility_focus.gif`: three-view visibility montage over dense frames {visibility_frames[0]}-{visibility_frames[-1]}.",
|
| 323 |
f"- `{episode_name}_path_quality_focus.gif`: debug-aware p_ext planner montage over dense frames {path_frames[0]}-{path_frames[-1]}.",
|
|
|
|
| 324 |
"- `frames/visibility_focus/`: per-frame PNGs with scene overlays, x-ray projections, and tray-mask views.",
|
| 325 |
"- `frames/path_quality_focus/`: per-frame PNGs with demo wrist trails, milestone pose overlays, and p_ext planner-search tables from the debug JSONL sidecar.",
|
| 326 |
+
]
|
| 327 |
+
)
|
| 328 |
+
readme_lines.extend(
|
| 329 |
+
[
|
| 330 |
+
"",
|
| 331 |
+
"Legend highlights:",
|
| 332 |
+
"- All metrics: red banner = reveal phase, green banner = retrieve phase.",
|
| 333 |
+
]
|
| 334 |
+
)
|
| 335 |
+
if not args.all_metrics_only:
|
| 336 |
+
readme_lines.extend(
|
| 337 |
+
[
|
| 338 |
"- Visibility: blue = sampled tray surface, magenta = sampled grasp region, green = depth-consistent visible grasp samples.",
|
| 339 |
"- Path quality: cyan/purple = recent left/right demo wrist trails, yellow = pregrasp, orange = grasp, green = retreat / extraction path.",
|
|
|
|
| 340 |
]
|
| 341 |
+
)
|
| 342 |
+
readme = output_dir.joinpath("README.md")
|
| 343 |
+
readme.write_text("\n".join(readme_lines), encoding="utf-8")
|
| 344 |
print(output_dir)
|
| 345 |
return 0
|
| 346 |
|