From d00da52c3c56de36c53be24eefc58cf66b568005 Mon Sep 17 00:00:00 2001 From: Johnny Fernandes Date: Sun, 17 May 2026 02:19:15 +0000 Subject: [PATCH] =?UTF-8?q?Portable=20Python=20env=20+=20360=C2=B0=20LiDAR?= =?UTF-8?q?=20ablation=20flag?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two small features. (1) Portable interpreter * `tools/setup_env.sh` exports HERDING_PYTHON (default points to the project's conda env; override in your shell to retarget). * Both `controllers/*/runtime.ini` files now use Webots' env-var expansion: `COMMAND = $(HERDING_PYTHON)` so the Webots-launched controllers pick up the same interpreter as the bash scripts. * `tools/run_webots.sh`, `tools/webots_sweep{,_gt}.sh` and `tools/calibrate_mecanum.sh` all source `setup_env.sh` at the top instead of hard-coding `/home/jalf/miniconda3/envs/tir/bin`. The hard-coded conda path is now exactly one line in `setup_env.sh`'s fallback default — a single place to edit on a new machine, or override-once via `export HERDING_PYTHON=...`. (2) 360° LiDAR FOV ablation * New `LIDAR_WEBOTS_360` preset matches the existing `protos/ShepherdDog360.proto` (360 rays / 2π FOV / 15 m range). * `tools/run_webots.sh` reads `HERDING_LIDAR=140|360` and swaps the diff-drive proto accordingly (mecanum keeps 140° — the ShepherdDogMecanum proto has its own LiDAR section). The variant is written into `herding_runtime.cfg` so the controller can read it even when Webots strips env vars. * `controllers/shepherd_dog/shepherd_dog.py` picks the matching `lidar_cfg` (`HERDING_WEBOTS.lidar` for 140°, `LIDAR_WEBOTS_360` otherwise) and feeds it to `detections_from_scan` so the perception pipeline interprets ray angles + max range correctly. Smoke test: `HERDING_LIDAR=360 tools/run_webots.sh 5 strombom differential field` launches with `ShepherdDog360.proto`, the controller logs the new mode/drive/world line, and the dog is penning sheep through 360° perception (4/5 at step 19200 before I killed the test). No retraining required because the gym already trains under `LIDAR_FULL` (360° preset). 126 pytest cases still pass. Co-Authored-By: Claude Opus 4.7 --- controllers/sheep/runtime.ini | 10 ++++++- controllers/shepherd_dog/runtime.ini | 10 ++++++- controllers/shepherd_dog/shepherd_dog.py | 16 ++++++++++-- herding/config.py | 13 ++++++++++ tools/calibrate_mecanum.sh | 2 +- tools/run_webots.sh | 33 +++++++++++++++++++++--- tools/setup_env.sh | 23 +++++++++++++++++ tools/webots_sweep.sh | 5 ++-- tools/webots_sweep_gt.sh | 5 ++-- 9 files changed, 105 insertions(+), 12 deletions(-) create mode 100644 tools/setup_env.sh diff --git a/controllers/sheep/runtime.ini b/controllers/sheep/runtime.ini index 19a4441..01eafc2 100644 --- a/controllers/sheep/runtime.ini +++ b/controllers/sheep/runtime.ini @@ -1,2 +1,10 @@ +# Webots reads this file before starting the controller. It tells +# Webots which Python interpreter to launch (default is system +# `python3`, which usually lacks NumPy). +# +# Webots supports environment-variable expansion in this file, so we +# defer the interpreter path to $HERDING_PYTHON — set that variable +# once in your shell (or `tools/setup_env.sh`) before launching +# Webots and the controllers in this project will pick it up. [python] -COMMAND = /home/jalf/miniconda3/envs/tir/bin/python3 +COMMAND = $(HERDING_PYTHON) diff --git a/controllers/shepherd_dog/runtime.ini b/controllers/shepherd_dog/runtime.ini index 19a4441..fa9f15c 100644 --- a/controllers/shepherd_dog/runtime.ini +++ b/controllers/shepherd_dog/runtime.ini @@ -1,2 +1,10 @@ +# Webots reads this file before starting the controller. It tells +# Webots which Python interpreter to launch (default is system +# `python3`, which usually lacks SB3/PyTorch). +# +# Webots supports environment-variable expansion in this file, so we +# defer the interpreter path to $HERDING_PYTHON — set that variable +# once in your shell (or `tools/setup_env.sh`) before launching +# Webots and the controllers in this project will pick it up. [python] -COMMAND = /home/jalf/miniconda3/envs/tir/bin/python3 +COMMAND = $(HERDING_PYTHON) diff --git a/controllers/shepherd_dog/shepherd_dog.py b/controllers/shepherd_dog/shepherd_dog.py index 97ae6b2..efc18e6 100644 --- a/controllers/shepherd_dog/shepherd_dog.py +++ b/controllers/shepherd_dog/shepherd_dog.py @@ -97,7 +97,7 @@ from herding.world.geometry import ( DOG_SOUTH_LIMIT, PEN_ENTRY, is_penned, ) -from herding.config import HERDING_WEBOTS, RobotConfig +from herding.config import HERDING_WEBOTS, LIDAR_WEBOTS_360, RobotConfig # Robot physical constants come from RobotConfig so they stay in sync with # the training environment. The Webots preset uses action_smooth=0.55. @@ -136,6 +136,18 @@ WORLD = (os.environ.get("HERDING_WORLD") or _runtime_cfg.get("HERDING_WORLD") or "field").lower() +# LiDAR FOV variant — "140" (default, ShepherdDog.proto) or "360" +# (ShepherdDog360.proto, FOV ablation). The launcher swaps the proto +# in the temp world file; the controller picks the matching lidar_cfg +# below so the perception pipeline interprets ray angles correctly. +LIDAR_FOV_VARIANT = (os.environ.get("HERDING_LIDAR") + or _runtime_cfg.get("HERDING_LIDAR") + or "140").lower() +if LIDAR_FOV_VARIANT == "360": + _LIDAR_CFG = LIDAR_WEBOTS_360 +else: + _LIDAR_CFG = HERDING_WEBOTS.lidar + # Diagnostic: bypass LiDAR tracker and use GT emitter positions directly. # Set HERDING_USE_GT=1 to isolate perception sim-to-real gap from policy quality. USE_GT_PERCEPTION = bool(int( @@ -409,7 +421,7 @@ while robot.step(timestep) != -1: detections = detections_from_scan( ranges, dog_xy[0], dog_xy[1], dog_heading, detection_cfg=HERDING_WEBOTS.detection, - lidar_cfg=HERDING_WEBOTS.lidar, + lidar_cfg=_LIDAR_CFG, ) if USE_GT_PERCEPTION and _gt_sheep: # Bypass tracker: feed GT emitter positions directly to policy/teacher. diff --git a/herding/config.py b/herding/config.py index 1d99de6..3bfbb85 100644 --- a/herding/config.py +++ b/herding/config.py @@ -93,6 +93,19 @@ rays), a policy trained here can be deployed on a wider-FOV LiDAR (e.g. which can only improve tracker quality. """ +LIDAR_WEBOTS_360 = LidarConfig( + n_rays=360, + fov_rad=2.0 * math.pi, + max_range=15.0, +) +"""Matches ShepherdDog360.proto (360 rays, 360° FOV, 15 m range). + +Used by the FOV-ablation Webots launch (HERDING_LIDAR=360). The wider +range and full surround visibility hand the tracker more detections +per step, so the trained policy — already trained on 360° gym +perception — sees an observation distribution closer to training. +""" + # --------------------------------------------------------------------------- # Cluster-detection pipeline diff --git a/tools/calibrate_mecanum.sh b/tools/calibrate_mecanum.sh index a4d5fb7..ea6fd79 100755 --- a/tools/calibrate_mecanum.sh +++ b/tools/calibrate_mecanum.sh @@ -21,7 +21,7 @@ set -euo pipefail N_STEPS="${1:-150}" ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )/.." && pwd )" LOG="$ROOT/calibrate_mecanum.log" -export PATH="/home/jalf/miniconda3/envs/tir/bin:$PATH" +source "$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )/setup_env.sh" echo "Running mecanum calibration (N_STEPS=$N_STEPS)..." echo "Results will be written to: $LOG" diff --git a/tools/run_webots.sh b/tools/run_webots.sh index f3f1ebd..33335f2 100755 --- a/tools/run_webots.sh +++ b/tools/run_webots.sh @@ -33,6 +33,12 @@ # WEBOTS_EXTRA_ARGS="--stdout --stderr" WEBOTS_HEADLESS=1 tools/run_webots.sh 10 rl set -e + +# Make sure HERDING_PYTHON is resolved and on PATH so Webots inherits +# the right interpreter (controllers/{shepherd_dog,sheep}/runtime.ini +# both read $HERDING_PYTHON via env-var expansion). +source "$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )/setup_env.sh" + N=${1:-10} MODE=${2:-bc} DRIVE=${3:-differential} @@ -84,12 +90,31 @@ fi cp "$SRC" "$DST" -# Swap robot proto based on drive mode. -# Base worlds reference ShepherdDog (diff-drive). For mecanum we swap in -# ShepherdDogMecanum and inject mecanum contact properties. +# LiDAR FOV variant: HERDING_LIDAR=140 (default) or 360 (ablation). +# 360° is only supported for differential drive; the mecanum proto +# always uses the 140° sensor matching ShepherdDog.proto. +LIDAR_VARIANT="${HERDING_LIDAR:-140}" +if [[ "$LIDAR_VARIANT" != "140" && "$LIDAR_VARIANT" != "360" ]]; then + echo "HERDING_LIDAR must be 140 or 360, got '$LIDAR_VARIANT'" >&2; exit 1 +fi +if [[ "$LIDAR_VARIANT" == "360" && "$DRIVE" == "mecanum" ]]; then + echo "[run_webots] HERDING_LIDAR=360 not available for mecanum drive — falling back to 140." >&2 + LIDAR_VARIANT="140" +fi +export HERDING_LIDAR="$LIDAR_VARIANT" + +# Swap robot proto based on drive mode + LiDAR variant. +# Base worlds reference ShepherdDog (diff-drive 140°). For mecanum we +# swap in ShepherdDogMecanum; for the 360° ablation we swap in +# ShepherdDog360. if [[ "$DRIVE" == "mecanum" ]]; then sed -i 's|"../protos/ShepherdDog.proto"|"../protos/ShepherdDogMecanum.proto"|' "$DST" sed -i 's|^ShepherdDog {|ShepherdDogMecanum {|' "$DST" +elif [[ "$LIDAR_VARIANT" == "360" ]]; then + sed -i 's|"../protos/ShepherdDog.proto"|"../protos/ShepherdDog360.proto"|' "$DST" + sed -i 's|^ShepherdDog {|ShepherdDog360 {|' "$DST" +fi +if [[ "$DRIVE" == "mecanum" ]]; then # Inject mecanum roller contact properties. The proto's rollers are # split into two contact materials so that we can keep the friction # axes oriented along each roller's free-spin direction — but with @@ -152,6 +177,7 @@ HERDING_MODE=$MODE HERDING_POLICY_DIR=$RESOLVED_POLICY_DIR HERDING_DRIVE=$DRIVE HERDING_WORLD=$WORLD +HERDING_LIDAR=$LIDAR_VARIANT HERDING_USE_GT=${HERDING_USE_GT:-0} EOF @@ -159,6 +185,7 @@ export HERDING_MODE="$MODE" export HERDING_POLICY_DIR="$RESOLVED_POLICY_DIR" export HERDING_DRIVE="$DRIVE" export HERDING_WORLD="$WORLD" +export HERDING_LIDAR="$LIDAR_VARIANT" # The controller writes this sentinel when all GT sheep are penned. We # poll for it and kill Webots so the run finishes cleanly instead of diff --git a/tools/setup_env.sh b/tools/setup_env.sh new file mode 100644 index 0000000..ae1683e --- /dev/null +++ b/tools/setup_env.sh @@ -0,0 +1,23 @@ +# Source this from your shell before running the launchers: +# +# source tools/setup_env.sh +# +# The launchers (`tools/run_webots.sh`, `tools/webots_sweep*.sh`, +# `tools/calibrate_mecanum.sh`) and the Webots controllers (via +# `controllers/*/runtime.ini`) all read $HERDING_PYTHON to decide +# which Python interpreter to use. The default below points at the +# project author's conda env — edit this file or override the var in +# your shell to match your own setup. + +: "${HERDING_PYTHON:=/home/jalf/miniconda3/envs/tir/bin/python3}" +export HERDING_PYTHON + +# Prepend the Python's bin/ to PATH so subprocesses pick up the same +# interpreter (used by Webots when it doesn't read runtime.ini, and +# by any Python tooling launched by the bash scripts). +export PATH="$(dirname "$HERDING_PYTHON"):$PATH" + +if [[ ! -x "$HERDING_PYTHON" ]]; then + echo "[setup_env] WARNING: HERDING_PYTHON=$HERDING_PYTHON is not executable." >&2 + echo "[setup_env] Edit tools/setup_env.sh or 'export HERDING_PYTHON=...' yourself." >&2 +fi diff --git a/tools/webots_sweep.sh b/tools/webots_sweep.sh index e131dfe..646de38 100755 --- a/tools/webots_sweep.sh +++ b/tools/webots_sweep.sh @@ -10,8 +10,9 @@ ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )/.." && pwd )" OUT="${1:-$ROOT/webots_sweep.log}" TIMEOUT_S=120 # ~80k steps in fast headless mode — covers slow-converging physics -# Webots uses its own python3; put the conda env first so all deps resolve. -export PATH="/home/jalf/miniconda3/envs/tir/bin:$PATH" +# Source the project python path. Edit tools/setup_env.sh or override +# HERDING_PYTHON in your shell to point at a Python with SB3+PyTorch. +source "$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )/setup_env.sh" # Columns: mode drive world n_sheep success steps printf "%-12s %-14s %-12s %7s %7s %s\n" \ diff --git a/tools/webots_sweep_gt.sh b/tools/webots_sweep_gt.sh index 9f7449d..2ffc71d 100755 --- a/tools/webots_sweep_gt.sh +++ b/tools/webots_sweep_gt.sh @@ -10,8 +10,9 @@ ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )/.." && pwd )" OUT="${1:-$ROOT/webots_sweep.log}" TIMEOUT_S=120 # ~80k steps in fast headless mode — covers slow-converging physics -# Webots uses its own python3; put the conda env first so all deps resolve. -export PATH="/home/jalf/miniconda3/envs/tir/bin:$PATH" +# Source the project python path. Edit tools/setup_env.sh or override +# HERDING_PYTHON in your shell to point at a Python with SB3+PyTorch. +source "$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )/setup_env.sh" # Columns: mode drive world n_sheep success steps printf "%-12s %-14s %-12s %7s %7s %s\n" \