@@ -212,6 +212,133 @@ def _run_in_chunks(args_cli: argparse.Namespace, master_cfg: dict) -> None:
212212 sys .exit (returncode )
213213
214214
215+ def evaluate_jobs (
216+ args_cli : argparse .Namespace ,
217+ jobs : list [Job | dict ],
218+ * ,
219+ environment_loader = None ,
220+ ) -> None :
221+ """Evaluate jobs inside an active ``SimulationAppContext``.
222+
223+ Args:
224+ args_cli: Evaluation-runner options shared by every job.
225+ jobs: Resolved jobs or legacy job dictionaries.
226+ environment_loader: Optional callable that builds one job's environment.
227+ """
228+ job_manager = JobManager (jobs )
229+ metrics_logger = MetricsLogger ()
230+
231+ job_manager .print_jobs_info ()
232+
233+ # One reverse-dated run directory shared by all jobs; each job gets a subdirectory within it.
234+ # Always dated so every run produces its own report dir, recording or not.
235+ # TODO(alexmillane): Currently each chunk produces its own output directory.
236+ # We should use the same output directory for all chunks in the future.
237+ run_output_dir = timestamped_run_dir (args_cli .output_base_dir )
238+
239+ if args_cli .record_viewport_video :
240+ os .makedirs (run_output_dir , exist_ok = True )
241+ print (f"[INFO] Video recording enabled. Videos will be saved to: { run_output_dir } " )
242+
243+ for job in job_manager :
244+ if job is None :
245+ continue
246+ env = None
247+ policy = None
248+
249+ metrics_per_run : list [MetricsDataCollection ] = []
250+
251+ # num_episodes is the total across rebuilds, so split it over the rebuilds.
252+ num_episodes_per_rebuild = _split_episodes_across_rebuilds (job .num_episodes , job .num_rebuilds , job .name )
253+
254+ # Rebuild the environment and re-run the rollout job.num_rebuilds times, then
255+ # aggregate the metrics across rebuilds into a single result.
256+ for rebuild_idx in range (job .num_rebuilds ):
257+ try :
258+ job_output_dir = os .path .join (run_output_dir , job .name )
259+
260+ # Per-job video output directory; cameras are tagged with the rebuild index.
261+ video_cfg = VideoRecordingCfg (
262+ record_viewport_video = args_cli .record_viewport_video ,
263+ record_camera_video = args_cli .record_camera_video ,
264+ video_base_dir = job_output_dir ,
265+ camera_name_prefix = f"robot-cam-rebuild{ rebuild_idx } " ,
266+ )
267+ if environment_loader is None :
268+ env = load_env (
269+ job .arena_env_args ,
270+ job .name ,
271+ variations = job .variations ,
272+ render_mode = video_cfg .render_mode ,
273+ language_instruction = job .language_instruction ,
274+ )
275+ else :
276+ env = environment_loader (job , video_cfg .render_mode )
277+
278+ # Write per-episode results to disk.
279+ # TODO: Aggregate the per-episode records across rebuilds into a single file,
280+ # as is done for the metrics below.
281+ results_path = os .path .join (job_output_dir , f"episode_results_rebuild{ rebuild_idx } .jsonl" )
282+ env .unwrapped .episode_recorder .set_job_name (job .name )
283+ env .unwrapped .episode_recorder .set_output_path (results_path )
284+
285+ policy = get_policy_from_job (job )
286+
287+ # Episodes allotted to this rebuild (None when the job is length-driven by steps).
288+ num_episodes_this_rebuild = num_episodes_per_rebuild [rebuild_idx ]
289+
290+ # Resolve simulation length: num_steps and num_episodes are mutually exclusive.
291+ # Priority: job config -> policy length -> CLI default
292+ if job .num_steps is None and num_episodes_this_rebuild is None :
293+ if policy .has_length ():
294+ job .num_steps = policy .length ()
295+ else :
296+ job .num_steps = args_cli .num_steps
297+
298+ env = wrap_env_for_video (env , video_cfg , job .num_steps , num_episodes_this_rebuild )
299+
300+ metrics = rollout_policy (
301+ env ,
302+ policy ,
303+ num_steps = job .num_steps ,
304+ num_episodes = num_episodes_this_rebuild ,
305+ )
306+
307+ job_manager .complete_job (job , metrics = metrics , status = Status .COMPLETED )
308+
309+ # users may not specify metrics for a task, although it's not recommended
310+ if metrics is not None :
311+ metrics_per_run .append (metrics )
312+
313+ except Exception as e :
314+ job_manager .complete_job (job , metrics = {}, status = Status .FAILED )
315+ print (f"Job { job .name } failed with error: { e } " )
316+ print (f"Traceback: { traceback .format_exc ()} " )
317+ if not args_cli .continue_on_error :
318+ raise
319+
320+ finally :
321+ try :
322+ _close_job_resources (policy , env )
323+ finally :
324+ policy = None
325+ env = None
326+ collect_garbage_and_clear_cuda_cache ()
327+
328+ # Aggregate the metrics from the different experiments into a single view.
329+ if metrics_per_run :
330+ aggregated_metrics = aggregate_metrics (metrics_per_run )
331+ metrics_logger .append_job_metrics (job .name , aggregated_metrics )
332+
333+ job_manager .print_jobs_info ()
334+ metrics_logger .print_metrics ()
335+
336+ # Write HTML report.
337+ report_path = build_report (run_output_dir )
338+ if args_cli .serve_evaluation_report :
339+ serve_until_ctrl_c (report_path .parent , args_cli .evaluation_report_port , report_path .name )
340+
341+
215342def main ():
216343 args_parser = get_isaaclab_arena_cli_parser ()
217344 args_cli , unknown = args_parser .parse_known_args ()
@@ -252,115 +379,7 @@ def main():
252379 enable_cameras_if_required (eval_jobs_config , args_cli )
253380
254381 with SimulationAppContext (args_cli ):
255- job_manager = JobManager (eval_jobs_config ["jobs" ])
256- metrics_logger = MetricsLogger ()
257-
258- job_manager .print_jobs_info ()
259-
260- # One reverse-dated run directory shared by all jobs; each job gets a subdirectory within it.
261- # Always dated so every run produces its own report dir, recording or not.
262- # TODO(alexmillane): Currently each chunk produces its own output directory.
263- # We should use the same output directory for all chunks in the future.
264- run_output_dir = timestamped_run_dir (args_cli .output_base_dir )
265-
266- if args_cli .record_viewport_video :
267- os .makedirs (run_output_dir , exist_ok = True )
268- print (f"[INFO] Video recording enabled. Videos will be saved to: { run_output_dir } " )
269-
270- for job in job_manager :
271- if job is None :
272- continue
273- env = None
274- policy = None
275-
276- metrics_per_run : list [MetricsDataCollection ] = []
277-
278- # num_episodes is the total across rebuilds, so split it over the rebuilds.
279- num_episodes_per_rebuild = _split_episodes_across_rebuilds (job .num_episodes , job .num_rebuilds , job .name )
280-
281- # Rebuild the environment and re-run the rollout job.num_rebuilds times, then
282- # aggregate the metrics across rebuilds into a single result.
283- for rebuild_idx in range (job .num_rebuilds ):
284- try :
285- job_output_dir = os .path .join (run_output_dir , job .name )
286-
287- # Per-job video output directory; cameras are tagged with the rebuild index.
288- video_cfg = VideoRecordingCfg (
289- record_viewport_video = args_cli .record_viewport_video ,
290- record_camera_video = args_cli .record_camera_video ,
291- video_base_dir = job_output_dir ,
292- camera_name_prefix = f"robot-cam-rebuild{ rebuild_idx } " ,
293- )
294- env = load_env (
295- job .arena_env_args ,
296- job .name ,
297- variations = job .variations ,
298- render_mode = video_cfg .render_mode ,
299- language_instruction = job .language_instruction ,
300- )
301-
302- # Write per-episode results to disk.
303- # TODO: Aggregate the per-episode records across rebuilds into a single file,
304- # as is done for the metrics below.
305- results_path = os .path .join (job_output_dir , f"episode_results_rebuild{ rebuild_idx } .jsonl" )
306- env .unwrapped .episode_recorder .set_job_name (job .name )
307- env .unwrapped .episode_recorder .set_output_path (results_path )
308-
309- policy = get_policy_from_job (job )
310-
311- # Episodes allotted to this rebuild (None when the job is length-driven by steps).
312- num_episodes_this_rebuild = num_episodes_per_rebuild [rebuild_idx ]
313-
314- # Resolve simulation length: num_steps and num_episodes are mutually exclusive.
315- # Priority: job config -> policy length -> CLI default
316- if job .num_steps is None and num_episodes_this_rebuild is None :
317- if policy .has_length ():
318- job .num_steps = policy .length ()
319- else :
320- job .num_steps = args_cli .num_steps
321-
322- env = wrap_env_for_video (env , video_cfg , job .num_steps , num_episodes_this_rebuild )
323-
324- metrics = rollout_policy (
325- env ,
326- policy ,
327- num_steps = job .num_steps ,
328- num_episodes = num_episodes_this_rebuild ,
329- )
330-
331- job_manager .complete_job (job , metrics = metrics , status = Status .COMPLETED )
332-
333- # users may not specify metrics for a task, although it's not recommended
334- if metrics is not None :
335- metrics_per_run .append (metrics )
336-
337- except Exception as e :
338- job_manager .complete_job (job , metrics = {}, status = Status .FAILED )
339- print (f"Job { job .name } failed with error: { e } " )
340- print (f"Traceback: { traceback .format_exc ()} " )
341- if not args_cli .continue_on_error :
342- raise
343-
344- finally :
345- try :
346- _close_job_resources (policy , env )
347- finally :
348- policy = None
349- env = None
350- collect_garbage_and_clear_cuda_cache ()
351-
352- # Aggregate the metrics from the different experiments into a single view.
353- if metrics_per_run :
354- aggregated_metrics = aggregate_metrics (metrics_per_run )
355- metrics_logger .append_job_metrics (job .name , aggregated_metrics )
356-
357- job_manager .print_jobs_info ()
358- metrics_logger .print_metrics ()
359-
360- # Write HTML report.
361- report_path = build_report (run_output_dir )
362- if args_cli .serve_evaluation_report :
363- serve_until_ctrl_c (report_path .parent , args_cli .evaluation_report_port , report_path .name )
382+ evaluate_jobs (args_cli , eval_jobs_config ["jobs" ])
364383
365384
366385if __name__ == "__main__" :
0 commit comments