diff --git a/CMD.md b/CMD.md
index d5b0732..89d62fe 100644
--- a/CMD.md
+++ b/CMD.md
@@ -15,6 +15,20 @@ python robogauge/scripts/run.py \
--num-processes 5 \
--seeds 0 1 2 3 4 \
--frictions 0.5 1.0 1.5 2.0 2.5 \
+ --compress-logs \
+ --headless
+
+# Terrain with Level, need specify goals (default is target_pos)
+python robogauge/scripts/run.py \
+ --task go2_moe.slope \
+ --experiment-name debug \
+ --multi \
+ --num-processes 5 \
+ --seeds 0 1 2 3 4 \
+ --frictions 1.0 \
+ --level 3 \
+ --goals max_velocity diagonal_velocity\
+ --compress-logs \
--headless
```
@@ -25,8 +39,9 @@ python robogauge/scripts/run.py \
--task go2_moe.slope \
--experiment-name debug \
--search-max-level \
- --seeds 0 1 2 \
+ --seeds 0 1 2 3 4 \
--frictions 1.0 \
+ --compress-logs \
--headless
```
@@ -39,8 +54,9 @@ python robogauge/scripts/run.py \
--stress-terrain-names flat slope stairs_up stairs_down wave \
--stress-num-processes 2 \
--num-processes 3 \
- --seeds 0 1 2 \
+ --seeds 0 1 2 3 4 \
--frictions 0.5 1.0 1.5 2.0 2.5 \
+ --compress-logs \
--headless
```
diff --git a/README.md b/README.md
index 757b820..a491ef4 100644
--- a/README.md
+++ b/README.md
@@ -12,6 +12,14 @@
| - | - | - |
| Unitree go2 | 四足 | ✅ |
+## 安装
+参考[PyTorch官网](https://pytorch.org/get-started/locally/)安装2.0以上任意版本, 安装本仓库
+
+```bash
+git clone https://github.com/wty-yy/RoboGauge.git
+pip install -e .
+```
+
## 目录结构
- `robogauge/scripts`: 启动指标测试程序
- `robogauge/tasks`: 定义测试任务
diff --git a/UPDATE.md b/UPDATE.md
index 41c64e1..37fcc83 100644
--- a/UPDATE.md
+++ b/UPDATE.md
@@ -1,4 +1,12 @@
# UPDATE
+## 20251227
+### v0.1.17
+1. args中添加`goals`配置, 在LevelPipeline和MultiPipeline中指定goals, 修正压力测试中评估的目标不对的问题
+2. 评估必须使用5个种子, 否则LevelPipeline中的80%通过率就没有意义了, 并且提高5个种子最终的评级影响可能非常大 (moe.stairs_up Friction 2.0从Lv 7->10)
+3. 在StressPipeline测试中, 不再保存
+4. 添加obstacles地形
+5. 优化分数计算方法, 按照`(level-1)*0.1+metric_score`计算
+6. 加入日志文件夹递归保存, StressPipeline -> LevelPipeline -> MultiPipeline, 加入压缩参数`--compress-logs`, 将日志文件夹压缩为`subtasks.tar.xz`, 日志文件压缩`4M->100kb`
## 20251226
### v0.1.16
1. 基本完成StressPipeline, 加入绘制进度条的线程, 其他进程通过Queue更新主进程的进度条
diff --git a/resources/terrains/obstacle/obstacle_1.xml b/resources/terrains/obstacle/obstacle_1.xml
new file mode 100644
index 0000000..264e3e5
--- /dev/null
+++ b/resources/terrains/obstacle/obstacle_1.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/resources/terrains/obstacle/obstacle_10.xml b/resources/terrains/obstacle/obstacle_10.xml
new file mode 100644
index 0000000..ac0bbd6
--- /dev/null
+++ b/resources/terrains/obstacle/obstacle_10.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/resources/terrains/obstacle/obstacle_2.xml b/resources/terrains/obstacle/obstacle_2.xml
new file mode 100644
index 0000000..5f29b65
--- /dev/null
+++ b/resources/terrains/obstacle/obstacle_2.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/resources/terrains/obstacle/obstacle_3.xml b/resources/terrains/obstacle/obstacle_3.xml
new file mode 100644
index 0000000..37e649b
--- /dev/null
+++ b/resources/terrains/obstacle/obstacle_3.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/resources/terrains/obstacle/obstacle_4.xml b/resources/terrains/obstacle/obstacle_4.xml
new file mode 100644
index 0000000..670fdfd
--- /dev/null
+++ b/resources/terrains/obstacle/obstacle_4.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/resources/terrains/obstacle/obstacle_5.xml b/resources/terrains/obstacle/obstacle_5.xml
new file mode 100644
index 0000000..e9a0977
--- /dev/null
+++ b/resources/terrains/obstacle/obstacle_5.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/resources/terrains/obstacle/obstacle_6.xml b/resources/terrains/obstacle/obstacle_6.xml
new file mode 100644
index 0000000..17ab25f
--- /dev/null
+++ b/resources/terrains/obstacle/obstacle_6.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/resources/terrains/obstacle/obstacle_7.xml b/resources/terrains/obstacle/obstacle_7.xml
new file mode 100644
index 0000000..94fba34
--- /dev/null
+++ b/resources/terrains/obstacle/obstacle_7.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/resources/terrains/obstacle/obstacle_8.xml b/resources/terrains/obstacle/obstacle_8.xml
new file mode 100644
index 0000000..c3df2ff
--- /dev/null
+++ b/resources/terrains/obstacle/obstacle_8.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/resources/terrains/obstacle/obstacle_9.xml b/resources/terrains/obstacle/obstacle_9.xml
new file mode 100644
index 0000000..d51cc22
--- /dev/null
+++ b/resources/terrains/obstacle/obstacle_9.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/robogauge/tasks/__init__.py b/robogauge/tasks/__init__.py
index 5a2c480..d2f7242 100644
--- a/robogauge/tasks/__init__.py
+++ b/robogauge/tasks/__init__.py
@@ -15,6 +15,7 @@ task_register.register('go2.slope', BasePipeline, MujocoConfig, Go2SlopeGaugeCon
task_register.register('go2.wave', BasePipeline, MujocoConfig, Go2WaveGaugeConfig, Go2TerrainConfig)
task_register.register('go2.stairs_up', BasePipeline, MujocoConfig, Go2StairsUpGaugeConfig, Go2TerrainConfig)
task_register.register('go2.stairs_down', BasePipeline, MujocoConfig, Go2StairsDownGaugeConfig, Go2TerrainConfig)
+task_register.register('go2.obstacle', BasePipeline, MujocoConfig, Go2ObstacleGaugeConfig, Go2TerrainConfig)
# Go2 MoE
task_register.register('go2_moe.flat', BasePipeline, MujocoConfig, Go2FlatGaugeConfig, Go2MoEConfig)
@@ -22,3 +23,4 @@ task_register.register('go2_moe.slope', BasePipeline, MujocoConfig, Go2SlopeGaug
task_register.register('go2_moe.wave', BasePipeline, MujocoConfig, Go2WaveGaugeConfig, Go2MoETerrainConfig)
task_register.register('go2_moe.stairs_up', BasePipeline, MujocoConfig, Go2StairsUpGaugeConfig, Go2MoETerrainConfig)
task_register.register('go2_moe.stairs_down', BasePipeline, MujocoConfig, Go2StairsDownGaugeConfig, Go2MoETerrainConfig)
+task_register.register('go2_moe.obstacle', BasePipeline, MujocoConfig, Go2ObstacleGaugeConfig, Go2MoETerrainConfig)
diff --git a/robogauge/tasks/custom/go2/__init__.py b/robogauge/tasks/custom/go2/__init__.py
index b1b0b6b..8cd836d 100644
--- a/robogauge/tasks/custom/go2/__init__.py
+++ b/robogauge/tasks/custom/go2/__init__.py
@@ -11,3 +11,4 @@ from .go2_slope_task import Go2SlopeGaugeConfig
from .go2_wave_task import Go2WaveGaugeConfig
from .go2_stairs_up_task import Go2StairsUpGaugeConfig
from .go2_stairs_down_task import Go2StairsDownGaugeConfig
+from .go2_obstacle_task import Go2ObstacleGaugeConfig
diff --git a/robogauge/tasks/custom/go2/go2_obstacle_task.py b/robogauge/tasks/custom/go2/go2_obstacle_task.py
new file mode 100644
index 0000000..638a3a8
--- /dev/null
+++ b/robogauge/tasks/custom/go2/go2_obstacle_task.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+'''
+@File : go2_obstacle_task.py
+@Time : 2025/12/27 14:32:22
+@Author : wty-yy
+@Version : 1.0
+@Blog : https://wty-yy.github.io/
+@Desc : Go2 Obstacle Task Configuration
+'''
+from robogauge.tasks.robots import Go2Config, Go2MoEConfig
+from robogauge.tasks.gauge import ObstacleGaugeConfig
+from robogauge.tasks.simulator.mujoco_config import MujocoConfig
+
+class Go2ObstacleGaugeConfig(ObstacleGaugeConfig):
+ class metrics(ObstacleGaugeConfig.metrics):
+ class dof_limits(ObstacleGaugeConfig.metrics.dof_limits):
+ enabled = True
+ soft_dof_limit_ratio = 0.7
+ dof_names = ['hip', 'thigh'] # List of DOF names to monitor, None for all
diff --git a/robogauge/tasks/gauge/__init__.py b/robogauge/tasks/gauge/__init__.py
index e0fcddb..4fb00cd 100644
--- a/robogauge/tasks/gauge/__init__.py
+++ b/robogauge/tasks/gauge/__init__.py
@@ -5,3 +5,4 @@ from .gauge_configs.slope_gauge_config import SlopeGaugeConfig
from .gauge_configs.wave_gauge_config import WaveGaugeConfig
from .gauge_configs.stairs_up_gauge_config import StairsUpGaugeConfig
from .gauge_configs.stairs_down_gauge_config import StairsDownGaugeConfig
+from .gauge_configs.obstacle_gauge_config import ObstacleGaugeConfig
diff --git a/robogauge/tasks/gauge/gauge_configs/obstacle_gauge_config.py b/robogauge/tasks/gauge/gauge_configs/obstacle_gauge_config.py
new file mode 100644
index 0000000..1213d48
--- /dev/null
+++ b/robogauge/tasks/gauge/gauge_configs/obstacle_gauge_config.py
@@ -0,0 +1,32 @@
+# -*- coding: utf-8 -*-
+'''
+@File : obstacle_gauge_config.py
+@Time : 2025/12/27 14:33:14
+@Author : wty-yy
+@Version : 1.0
+@Blog : https://wty-yy.github.io/
+@Desc : Obstacle Gauge Configuration
+'''
+from robogauge.tasks.gauge.base_gauge_config import BaseGaugeConfig
+
+class ObstacleGaugeConfig(BaseGaugeConfig):
+ gauge_class = 'BaseGauge'
+
+ class assets(BaseGaugeConfig.assets):
+ terrain_name = "obstacle"
+ terrain_level = 10 # 1-10
+ terrain_xmls = [
+ '{ROBOGAUGE_ROOT_DIR}/resources/terrains/obstacle/obstacle_10.xml',
+ '{ROBOGAUGE_ROOT_DIR}/resources/terrains/wall/10x10_wall.xml',
+ ]
+ terrain_spawn_pos = [0.5, 2.0, 0.0] # x y z [m], robot freejoint spawn position on the terrain
+
+ class goals(BaseGaugeConfig.goals):
+ class target_pos_velocity: # goal to reach a target position by velocity command
+ enabled = True
+ target_pos = [6.5, -2.0, 0.3] # x y z [m], target position in the environment, used for target position goal
+ lin_vel_x = 0.8 # +/- m/s
+ lin_vel_y = 1.0 # +/- m/s
+ ang_vel_yaw = 1.5 # +/- rad/s
+ max_cmd_duration = 30.0 # [s] maximum duration to reach the target position
+ reach_threshold = 0.1
diff --git a/robogauge/tasks/gauge/gauge_configs/terrain_levels_config.py b/robogauge/tasks/gauge/gauge_configs/terrain_levels_config.py
index bc4219f..82a10d8 100644
--- a/robogauge/tasks/gauge/gauge_configs/terrain_levels_config.py
+++ b/robogauge/tasks/gauge/gauge_configs/terrain_levels_config.py
@@ -87,6 +87,9 @@ class TerrainSearchLevelsConfig(Config):
[1.1, 3.0, 7],
]
+ class obstacle:
+ levels = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
+
class TerrainEvalLevelsConfig(Config):
class slope:
levels = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
diff --git a/robogauge/tasks/gauge/gauge_configs/wave_gauge_config.py b/robogauge/tasks/gauge/gauge_configs/wave_gauge_config.py
index 98854eb..2880ff7 100644
--- a/robogauge/tasks/gauge/gauge_configs/wave_gauge_config.py
+++ b/robogauge/tasks/gauge/gauge_configs/wave_gauge_config.py
@@ -21,7 +21,7 @@ class WaveGaugeConfig(BaseGaugeConfig):
]
terrain_spawn_pos = [1.5, 1.25, 0.0] # x y z [m], robot freejoint spawn position on the terrain
- class goals:
+ class goals(BaseGaugeConfig.goals):
class target_pos_velocity: # goal to reach a target position by velocity command
enabled = True
target_pos = [6.5, -2.65, 0.8] # x y z [m], target position in the environment, used for target position goal
diff --git a/robogauge/tasks/pipeline/level_pipeline.py b/robogauge/tasks/pipeline/level_pipeline.py
index 874c6e6..e3f8867 100644
--- a/robogauge/tasks/pipeline/level_pipeline.py
+++ b/robogauge/tasks/pipeline/level_pipeline.py
@@ -12,6 +12,7 @@ import yaml
from robogauge.tasks.pipeline.multi_pipeline import MultiPipeline
from robogauge.utils.logger import Logger
from robogauge.utils.progress_monitor import report_progress, ProgressTypes, ProgressData
+from robogauge.utils.file_utils import compress_directory
level_logger = Logger() # LevelPipeline logger
@@ -21,7 +22,11 @@ class LevelPipeline:
self.seeds = args.seeds
self.console_output = console_output
self.progress_data = progress_data
- level_logger.create(args.experiment_name+'_level', args.run_name, console_output=console_output)
+ parent_log_dir = getattr(args, 'parent_log_dir', None)
+ level_logger.create(args.experiment_name+'_level', args.run_name, console_output=console_output, parent_log_dir=parent_log_dir)
+ self.args.parent_log_dir = str(level_logger.log_dir / "subtasks")
+ self.compress_logs = args.compress_logs
+ args.compress_logs = False # Disable child log compression
def run(self):
level_logger.info(f"🚀 Starting Level Searcher for '{self.args.experiment_name}'.")
@@ -53,6 +58,9 @@ class LevelPipeline:
level_logger.info(f"❌ No valid level found [1-10].")
with open(level_logger.log_dir / "level_search_results.yaml", 'w') as f:
yaml.dump(level_results, f, allow_unicode=True, sort_keys=False)
+ level_logger.logger.info(f"📂 Level search results saved to: {level_logger.log_dir / 'level_search_results.yaml'}")
+ if self.compress_logs:
+ compress_directory(level_logger.log_dir / "subtasks", delete_original=True, logger=level_logger)
return level, level_results
def test_level(self, level: int):
@@ -63,7 +71,7 @@ class LevelPipeline:
success_mean = float(aggregated_results['summary']['success']['mean'].split(' ')[0])
all_success = success_mean >= 0.8
if all_success:
- level_logger.info(f"✅ Level {level} passed all tests.")
+ level_logger.info(f"✅ Level {level} passed all tests, success mean: {success_mean}.")
else:
- level_logger.info(f"❌ Level {level} failed some tests.")
+ level_logger.info(f"❌ Level {level} failed some tests, success mean: {success_mean}.")
return all_success, aggregated_results
diff --git a/robogauge/tasks/pipeline/multi_pipeline.py b/robogauge/tasks/pipeline/multi_pipeline.py
index 93cad20..6247b4d 100644
--- a/robogauge/tasks/pipeline/multi_pipeline.py
+++ b/robogauge/tasks/pipeline/multi_pipeline.py
@@ -24,6 +24,7 @@ from robogauge.utils.task_register import task_register
from robogauge.utils.logger import Logger
from robogauge.utils.process_utils import NoDaemonPool
from robogauge.utils.progress_monitor import report_progress, ProgressTypes, ProgressData
+from robogauge.utils.file_utils import compress_directory
multi_logger = Logger() # MultiPipeline logger
@@ -38,7 +39,8 @@ def run_single_process(args, data):
logger.create(
experiment_name=local_args.experiment_name,
run_name=run_name,
- console_output=False
+ console_output=False,
+ parent_log_dir=args.parent_log_dir
)
pipeline = task_register.make_pipeline(args=local_args, create_logger=False)
results, warning, error = pipeline.run()
@@ -73,7 +75,10 @@ class MultiPipeline:
self.progress_data = progress_data
self.num_processes = args.num_processes
self.static_info = {}
- multi_logger.create(args.experiment_name+'_multi', args.run_name+'_multi', console_output=console_output)
+ parent_log_dir = getattr(args, 'parent_log_dir', None)
+ multi_logger.create(args.experiment_name+'_multi', args.run_name+'_multi', console_output=console_output, parent_log_dir=parent_log_dir)
+ self.args.parent_log_dir = str(multi_logger.log_dir / "subtasks")
+ self.compress_logs = args.compress_logs
def add_static_info(self, key: str, value):
if key not in self.static_info:
@@ -153,6 +158,8 @@ class MultiPipeline:
multi_logger.info("✅ Aggregated execution finished.")
multi_logger.info(f"📁 Aggregated results saved to: {save_path}")
+ if self.compress_logs:
+ compress_directory(multi_logger.log_dir / "subtasks", delete_original=True, logger=multi_logger)
# multi_logger.info(
# f"""\n{'='*20} Multi-Run Summary {'='*20}\n"""
# f"""{yaml.dump(summary, allow_unicode=True)}"""
diff --git a/robogauge/tasks/pipeline/stress_pipeline.py b/robogauge/tasks/pipeline/stress_pipeline.py
index a52ee0c..c9b441d 100644
--- a/robogauge/tasks/pipeline/stress_pipeline.py
+++ b/robogauge/tasks/pipeline/stress_pipeline.py
@@ -7,6 +7,12 @@
@Blog : https://wty-yy.github.io/
@Desc : Stress Pipeline for Robogauge
'''
+# MuJoCo/XLA warnings suppression
+import os
+os.environ['ABSL_LOG_LEVEL'] = 'error'
+import absl.logging
+absl.logging.set_verbosity(absl.logging.ERROR)
+
import yaml
import traceback
import functools
@@ -22,9 +28,15 @@ from robogauge.utils.process_utils import NoDaemonPool
from robogauge.utils.progress_monitor import report_progress, ProgressTypes, start_progress_monitor_thread, ProgressData
from robogauge.tasks.pipeline import MultiPipeline, LevelPipeline
from robogauge.tasks.gauge.gauge_configs.terrain_levels_config import TerrainSearchLevelsConfig
+from robogauge.utils.file_utils import compress_directory
stress_logger = Logger() # StressPipeline logger
+GOALS = {
+ 'level_pipeline': ['target_pos_velocity'],
+ 'multi_pipeline': ['max_velocity', 'diagonal_velocity']
+}
+
def run_pipeline(args, progress_queue, data):
args = deepcopy(args)
task_id = data['task_id']
@@ -44,6 +56,7 @@ def run_pipeline(args, progress_queue, data):
args.base_masses = [data['base_mass']]
args.task_name = f"{data['task_robot_model']}.{data['terrain_name']}"
args.experiment_name = f"{args.experiment_name}_{data['terrain_name']}_M{data['base_mass']}_F{data['friction']}"
+ args.goals = GOALS['level_pipeline']
level, level_results = LevelPipeline(args, console_output=False, progress_data=progress_data).run()
if level == 0: # no valid level found
@@ -63,6 +76,7 @@ def run_pipeline(args, progress_queue, data):
args.experiment_name = f"{args.experiment_name}_{data['terrain_name']}"
args.level = level
+ args.goals = GOALS['multi_pipeline']
results = {
'success': True,
'results': MultiPipeline(args, console_output=False, progress_data=progress_data).run(),
@@ -81,6 +95,9 @@ class StressPipeline:
args.experiment_name = self.task_robot_model + '_stress' + ('' if args.cli_experiment_name is None else '_' + args.cli_experiment_name)
self.static_info = {}
stress_logger.create(args.experiment_name, args.run_name)
+ self.args.parent_log_dir = str(stress_logger.log_dir / "subtasks")
+ self.compress_logs = args.compress_logs
+ args.compress_logs = False # Disable child log compression
def add_static_info(self, key: str, value):
if key not in self.static_info:
@@ -183,7 +200,9 @@ class StressPipeline:
for metric, means in result['results']['summary'].items():
for mean_name, mean_value in means.items():
- value_collections[metric][mean_name].append(float(mean_value.split(' ')[0]))
+ value = float(mean_value.split(' ')[0])
+ value += (terrain_level - 1) * 0.1 if terrain_level is not None else 0.0
+ value_collections[metric][mean_name].append(value)
for metric, means in value_collections.items():
summary['summary'][metric] = {}
@@ -196,4 +215,7 @@ class StressPipeline:
yaml.dump(summary, file, allow_unicode=True, sort_keys=False)
stress_logger.info(f"✅ Stress benchmark aggregated execution finished.")
stress_logger.info(f"📁 Stress benchmark results saved to: {save_path}")
+
+ if self.compress_logs:
+ compress_directory(stress_logger.log_dir / "subtasks", delete_original=True, logger=stress_logger)
return summary
diff --git a/robogauge/utils/file_utils.py b/robogauge/utils/file_utils.py
new file mode 100644
index 0000000..be4fa18
--- /dev/null
+++ b/robogauge/utils/file_utils.py
@@ -0,0 +1,57 @@
+# -*- coding: utf-8 -*-
+'''
+@File : file_utils.py
+@Time : 2025/12/27 17:14:29
+@Author : wty-yy
+@Version : 1.0
+@Blog : https://wty-yy.github.io/
+@Desc : Common File Utilities
+'''
+import tarfile
+import shutil
+import os
+from pathlib import Path
+import logging
+
+def log_msg(logger: logging.Logger, msg: str, level: str):
+ if logger:
+ getattr(logger, level)(msg)
+ else:
+ print(msg)
+
+def compress_directory(source_dir, output_filename=None, delete_original=True, logger: logging.Logger=None):
+ """
+ Compress a directory into a .tar.xz file using LZMA compression.
+
+ Args:
+ source_dir (str | Path): Path to the directory to compress.
+ output_filename (str | Path, optional): Output path. Defaults to source_dir + ".tar.xz".
+ delete_original (bool): Whether to delete the source directory after successful compression.
+ """
+ source_dir = Path(source_dir)
+ if not source_dir.exists():
+ log_msg(logger, f"⚠️ Source directory for compression not found: {source_dir}", "warning")
+ return
+
+ if output_filename is None:
+ output_filename = source_dir.with_suffix('.tar.xz')
+ else:
+ output_filename = Path(output_filename)
+
+ log_msg(logger, f"📦 Compressing logs: {source_dir.name} -> {output_filename.name} ...", "info")
+
+ try:
+ with tarfile.open(output_filename, "w:xz") as tar:
+ tar.add(source_dir, arcname=source_dir.name)
+
+ log_msg(logger, f"✅ Compression finished: {output_filename}", "info")
+
+ if delete_original:
+ shutil.rmtree(source_dir)
+ log_msg(logger, f"🗑️ Deleted original directory: {source_dir}", "info")
+
+ except Exception as e:
+ log_msg(logger, f"❌ Failed to compress directory {source_dir}: {e}", "error")
+ # If compression failed, ensure we don't leave a half-baked file
+ if output_filename.exists():
+ os.remove(output_filename)
diff --git a/robogauge/utils/helpers.py b/robogauge/utils/helpers.py
index 93fa544..bfb2ca8 100644
--- a/robogauge/utils/helpers.py
+++ b/robogauge/utils/helpers.py
@@ -76,6 +76,7 @@ def parse_args():
{"name": "--base-mass", "type": float, "default": 0.0, "help": "Set the base mass of the robot."},
{"name": "--friction", "type": float, "default": 1.0, "help": "Set the ground friction coefficient."},
{"name": "--level", "type": int, "help": "Set the difficulty level of the environment, range 1-10 (flat is 0)."},
+ {"name": "--goals", "type": str, "nargs": "+", "help": "List of goal names to evaluate."},
# Multiprocessing parameters, with different seeds
{"name": "--multi", "action": "store_true", "default": False, "help": "Enable multiprocessing."},
@@ -91,6 +92,8 @@ def parse_args():
{"name": "--stress-benchmark", "action": "store_true", "default": False, "help": "Use stress pipeline to benchmark model robustness."},
{"name": "--stress-terrain-names", "type": str, "nargs": "+", "default": ["flat", "slope", "wave", "stairs_up", "stairs_down"], "help": "List of terrain names for stress benchmark."},
{"name": "--stress-num-processes", "type": int, "default": 2, "help": "Number of parallel processes for stress benchmark."},
+
+ {"name": "--compress-logs", "action": "store_true", "default": False, "help": "Compress and delete logs after run."},
]
for param in parameters:
parser.add_argument(param['name'], **{k: v for k, v in param.items() if k != 'name'})
diff --git a/robogauge/utils/logger.py b/robogauge/utils/logger.py
index 74f72bd..12d7682 100644
--- a/robogauge/utils/logger.py
+++ b/robogauge/utils/logger.py
@@ -58,7 +58,8 @@ class Logger:
experiment_name,
run_name,
console_output=True, color_output=True,
- log_level=logging.DEBUG, save_file_mode='a'
+ log_level=logging.DEBUG, save_file_mode='a',
+ parent_log_dir=None
):
"""
Create customed Logger
@@ -69,6 +70,7 @@ class Logger:
color_output (bool, optional): Whether use color output. Defaults to True.
log_level (int, optional): Defaults to logging.DEBUG.
save_file_mode (str, optional): The mode of saving to path_log_file
+ parent_log_dir (Path | str, optional): If specified, log_dir will be created under this directory.
Returns:
logging.Logger: logger
@@ -102,7 +104,11 @@ class Logger:
sh.setFormatter(console_formatter)
self.logger.addHandler(sh)
- self.log_dir = Path(ROBOGAUGE_LOGS_DIR) / experiment_name / self.tag
+ if parent_log_dir:
+ baes_dir = Path(parent_log_dir)
+ else:
+ baes_dir = Path(ROBOGAUGE_LOGS_DIR)
+ self.log_dir = baes_dir / experiment_name / self.tag
self.log_dir.mkdir(parents=True, exist_ok=True)
path_log_file = self.log_dir / "stdout.log"
if path_log_file:
diff --git a/robogauge/utils/task_register.py b/robogauge/utils/task_register.py
index f898dd4..eb324cd 100644
--- a/robogauge/utils/task_register.py
+++ b/robogauge/utils/task_register.py
@@ -9,7 +9,7 @@
'''
from robogauge import ROBOGAUGE_ROOT_DIR
from robogauge.utils.logger import logger
-from robogauge.utils.helpers import parse_args, set_seed
+from robogauge.utils.helpers import parse_args, set_seed, class_to_dict
from robogauge.tasks.gauge.gauge_configs.terrain_levels_config import TerrainSearchLevelsConfig
class TaskRegister():
@@ -73,14 +73,23 @@ class TaskRegister():
if args.level is not None:
gauger_cfg.assets.terrain_level = args.level
levels_cfg = TerrainSearchLevelsConfig()
- cfg = getattr(levels_cfg, gauger_cfg.assets.terrain_name)
+ cfg = getattr(levels_cfg, gauger_cfg.assets.terrain_name, None)
assert cfg is not None, f"Level {args.level} configuration not found in TerrainLevelsConfig."
assert args.level in cfg.levels, f"Level must be in {cfg.levels}."
- gauger_cfg.goals.target_pos_velocity.target_pos = cfg.targets[cfg.levels.index(args.level)]
+ if hasattr(cfg, 'targets'):
+ gauger_cfg.goals.target_pos_velocity.target_pos = cfg.targets[cfg.levels.index(args.level)]
if hasattr(cfg, 'spawns'):
gauger_cfg.assets.terrain_spawn_pos = cfg.spawns[cfg.levels.index(args.level)]
xml = gauger_cfg.assets.terrain_xmls[0]
xml = xml.rsplit('/', 1)[0] + f"/{gauger_cfg.assets.terrain_name}_{args.level}.xml"
gauger_cfg.assets.terrain_xmls[0] = xml
+ if args.goals is not None:
+ keys = class_to_dict(gauger_cfg.goals).keys()
+ enable_count = 0
+ for key in keys:
+ flag = key in args.goals
+ getattr(gauger_cfg.goals, key).enabled = flag
+ enable_count += int(flag)
+ assert enable_count > 0, f"At least one goal must be enabled from '{args.goals}', available goals are {list(keys)}."
task_register = TaskRegister()
diff --git a/setup.py b/setup.py
index 4cf7fa8..c2e3170 100644
--- a/setup.py
+++ b/setup.py
@@ -10,14 +10,12 @@ setup(
packages=find_packages(),
install_requires=[
"numpy>=1.20.0",
- "mujoco>=3.0.0", # 必须依赖
- "dm_control>=1.0.14", # 之前推荐用来组合地形的库
- "scipy", # 计算信号处理、统计指标常用
- "matplotlib", # 用于画出评估图表
- "pandas", # 用于生成评估报告表格
- "tqdm", # 显示评估进度条
- # "gymnasium", # 如果你的接口兼容 gym
- # "torch", # 如果你需要加载 pytorch 模型
+ "mujoco>=3.0.0",
+ "dm_control>=1.0.14",
+ "scipy",
+ "matplotlib",
+ "tqdm",
+ "imageio[ffmpeg]",
],
python_requires=">=3.8",