lsnu commited on
Commit
cbf1741
·
verified ·
1 Parent(s): 1bb6705

Add missing batch GIF renderer

Browse files
Files changed (1) hide show
  1. code/scripts/render_oven_gif_batch.py +228 -0
code/scripts/render_oven_gif_batch.py ADDED
@@ -0,0 +1,228 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pathlib import Path
2
+ import argparse
3
+ import json
4
+ import os
5
+ import shutil
6
+ import subprocess
7
+ import sys
8
+ import time
9
+ from typing import Dict, List, Optional, Sequence
10
+
11
+
12
+ PROJECT_ROOT = Path(__file__).resolve().parents[1]
13
+ if str(PROJECT_ROOT) not in sys.path:
14
+ sys.path.insert(0, str(PROJECT_ROOT))
15
+
16
+
17
+ def _configure_thread_env() -> None:
18
+ defaults = {
19
+ "OMP_NUM_THREADS": "1",
20
+ "OPENBLAS_NUM_THREADS": "1",
21
+ "MKL_NUM_THREADS": "1",
22
+ "NUMEXPR_NUM_THREADS": "1",
23
+ "VECLIB_MAXIMUM_THREADS": "1",
24
+ "BLIS_NUM_THREADS": "1",
25
+ "MALLOC_ARENA_MAX": "2",
26
+ }
27
+ for key, value in defaults.items():
28
+ os.environ.setdefault(key, value)
29
+
30
+
31
+ def _configure_coppeliasim_env() -> None:
32
+ coppeliasim_root = os.environ.setdefault("COPPELIASIM_ROOT", "/workspace/coppelia_sim")
33
+ ld_library_path_parts = [
34
+ part for part in os.environ.get("LD_LIBRARY_PATH", "").split(":") if part
35
+ ]
36
+ if coppeliasim_root not in ld_library_path_parts:
37
+ ld_library_path_parts.insert(0, coppeliasim_root)
38
+ os.environ["LD_LIBRARY_PATH"] = ":".join(ld_library_path_parts)
39
+
40
+
41
+ _configure_thread_env()
42
+ _configure_coppeliasim_env()
43
+
44
+ from rr_label_study.oven_study import _episode_dirs
45
+
46
+
47
+ def _select_episode_indices(
48
+ total_episodes: int,
49
+ episode_offset: int,
50
+ max_episodes: Optional[int],
51
+ episode_indices: Optional[Sequence[int]],
52
+ ) -> List[int]:
53
+ if episode_indices is not None:
54
+ selected: List[int] = []
55
+ seen = set()
56
+ for raw_index in episode_indices:
57
+ episode_index = int(raw_index)
58
+ if not (0 <= episode_index < total_episodes):
59
+ raise ValueError(
60
+ f"episode index {episode_index} outside available range 0..{total_episodes - 1}"
61
+ )
62
+ if episode_index in seen:
63
+ continue
64
+ selected.append(episode_index)
65
+ seen.add(episode_index)
66
+ return selected
67
+
68
+ remaining = max(0, total_episodes - episode_offset)
69
+ if max_episodes is not None:
70
+ remaining = min(remaining, max_episodes)
71
+ if remaining <= 0:
72
+ return []
73
+ return list(range(episode_offset, episode_offset + remaining))
74
+
75
+
76
+ def _has_gif_suite(episode_output_dir: Path, episode_name: str) -> bool:
77
+ visualizations_dir = episode_output_dir.joinpath("visualizations")
78
+ required = [
79
+ visualizations_dir.joinpath(f"{episode_name}_all_metrics.gif"),
80
+ visualizations_dir.joinpath(f"{episode_name}_visibility_focus.gif"),
81
+ visualizations_dir.joinpath(f"{episode_name}_path_quality_focus.gif"),
82
+ visualizations_dir.joinpath("README.md"),
83
+ ]
84
+ return all(path.exists() for path in required)
85
+
86
+
87
+ def _write_json(path: Path, payload: Dict[str, object]) -> None:
88
+ path.parent.mkdir(parents=True, exist_ok=True)
89
+ with path.open("w", encoding="utf-8") as handle:
90
+ json.dump(payload, handle, indent=2)
91
+
92
+
93
+ def main() -> int:
94
+ parser = argparse.ArgumentParser()
95
+ parser.add_argument(
96
+ "--dataset-root",
97
+ default="/workspace/data/bimanual_take_tray_out_of_oven_train_128",
98
+ )
99
+ parser.add_argument("--result-dir", required=True)
100
+ parser.add_argument("--episode-offset", type=int, default=0)
101
+ parser.add_argument("--max-episodes", type=int, default=100)
102
+ parser.add_argument("--episode-indices")
103
+ parser.add_argument("--checkpoint-stride", type=int, default=16)
104
+ parser.add_argument("--num-workers", type=int, default=6)
105
+ parser.add_argument("--base-display", type=int, default=1200)
106
+ args = parser.parse_args()
107
+
108
+ dataset_root = Path(args.dataset_root)
109
+ result_dir = Path(args.result_dir)
110
+ logs_dir = result_dir.joinpath("render_logs")
111
+ logs_dir.mkdir(parents=True, exist_ok=True)
112
+ progress_path = result_dir.joinpath("render_progress.json")
113
+
114
+ all_episode_dirs = _episode_dirs(dataset_root)
115
+ explicit_episode_indices = None
116
+ if args.episode_indices:
117
+ explicit_episode_indices = [
118
+ int(chunk.strip()) for chunk in args.episode_indices.split(",") if chunk.strip()
119
+ ]
120
+ selected_episode_indices = _select_episode_indices(
121
+ total_episodes=len(all_episode_dirs),
122
+ episode_offset=args.episode_offset,
123
+ max_episodes=args.max_episodes,
124
+ episode_indices=explicit_episode_indices,
125
+ )
126
+
127
+ completed: List[int] = []
128
+ for episode_index in selected_episode_indices:
129
+ episode_name = f"episode{episode_index}"
130
+ episode_output_dir = result_dir.joinpath(episode_name)
131
+ if not episode_output_dir.exists():
132
+ raise FileNotFoundError(f"missing episode output dir: {episode_output_dir}")
133
+ if _has_gif_suite(episode_output_dir, episode_name):
134
+ completed.append(episode_index)
135
+ _write_json(
136
+ progress_path,
137
+ {
138
+ "current_episode": None,
139
+ "completed_episode_indices": completed,
140
+ "total_selected": len(selected_episode_indices),
141
+ "updated_at_epoch": time.time(),
142
+ },
143
+ )
144
+ continue
145
+
146
+ temp_output_dir = episode_output_dir.joinpath("visualizations_tmp")
147
+ if temp_output_dir.exists():
148
+ shutil.rmtree(temp_output_dir)
149
+
150
+ log_path = logs_dir.joinpath(f"{episode_name}.log")
151
+ env = os.environ.copy()
152
+ env["OMP_NUM_THREADS"] = "1"
153
+ env["OPENBLAS_NUM_THREADS"] = "1"
154
+ env["MKL_NUM_THREADS"] = "1"
155
+ env["NUMEXPR_NUM_THREADS"] = "1"
156
+ env["VECLIB_MAXIMUM_THREADS"] = "1"
157
+ env["BLIS_NUM_THREADS"] = "1"
158
+ env["MALLOC_ARENA_MAX"] = "2"
159
+ env["PYTHONUNBUFFERED"] = "1"
160
+ _write_json(
161
+ progress_path,
162
+ {
163
+ "current_episode": episode_name,
164
+ "completed_episode_indices": completed,
165
+ "total_selected": len(selected_episode_indices),
166
+ "updated_at_epoch": time.time(),
167
+ },
168
+ )
169
+ with log_path.open("w", encoding="utf-8") as log_handle:
170
+ process = subprocess.Popen(
171
+ [
172
+ sys.executable,
173
+ str(PROJECT_ROOT.joinpath("scripts", "render_oven_metric_gifs.py")),
174
+ "--episode-dir",
175
+ str(all_episode_dirs[episode_index]),
176
+ "--dense-csv",
177
+ str(episode_output_dir.joinpath(f"{episode_name}.dense.csv")),
178
+ "--templates-pkl",
179
+ str(episode_output_dir.joinpath("templates.pkl")),
180
+ "--output-dir",
181
+ str(temp_output_dir),
182
+ "--debug-jsonl",
183
+ str(episode_output_dir.joinpath(f"{episode_name}.debug.jsonl")),
184
+ "--checkpoint-stride",
185
+ str(args.checkpoint_stride),
186
+ "--num-workers",
187
+ str(args.num_workers),
188
+ "--base-display",
189
+ str(args.base_display),
190
+ ],
191
+ stdout=log_handle,
192
+ stderr=subprocess.STDOUT,
193
+ cwd=str(PROJECT_ROOT),
194
+ env=env,
195
+ )
196
+ return_code = process.wait()
197
+ if return_code != 0:
198
+ raise RuntimeError(f"gif render failed for {episode_name}; see {log_path}")
199
+
200
+ final_visualizations_dir = episode_output_dir.joinpath("visualizations")
201
+ if final_visualizations_dir.exists():
202
+ shutil.rmtree(final_visualizations_dir)
203
+ temp_output_dir.rename(final_visualizations_dir)
204
+ completed.append(episode_index)
205
+ _write_json(
206
+ progress_path,
207
+ {
208
+ "current_episode": None,
209
+ "completed_episode_indices": completed,
210
+ "total_selected": len(selected_episode_indices),
211
+ "updated_at_epoch": time.time(),
212
+ },
213
+ )
214
+
215
+ _write_json(
216
+ progress_path,
217
+ {
218
+ "current_episode": None,
219
+ "completed_episode_indices": completed,
220
+ "total_selected": len(selected_episode_indices),
221
+ "finished_at_epoch": time.time(),
222
+ },
223
+ )
224
+ return 0
225
+
226
+
227
+ if __name__ == "__main__":
228
+ raise SystemExit(main())