deepracer 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- deepracer/__init__.py +60 -0
- deepracer/clean.py +75 -0
- deepracer/defaults/__init__.py +58 -0
- deepracer/defaults/agent_params.json +28 -0
- deepracer/defaults/environment_params.yaml +22 -0
- deepracer/defaults/reward_function.py +25 -0
- deepracer/defaults/tracks.txt +127 -0
- deepracer/envs/__init__.py +3 -0
- deepracer/envs/env.py +228 -0
- deepracer/envs/utils.py +160 -0
- deepracer/gym_adapter.py +101 -0
- deepracer/service/__init__.py +26 -0
- deepracer/service/backends.py +473 -0
- deepracer/service/identity.py +126 -0
- deepracer/service/manager.py +259 -0
- deepracer/service/spec.py +76 -0
- deepracer/service/validate.py +128 -0
- deepracer/utils.py +27 -0
- deepracer/zmq_client.py +51 -0
- deepracer-0.1.0.dist-info/METADATA +87 -0
- deepracer-0.1.0.dist-info/RECORD +24 -0
- deepracer-0.1.0.dist-info/WHEEL +5 -0
- deepracer-0.1.0.dist-info/licenses/LICENSE +21 -0
- deepracer-0.1.0.dist-info/top_level.txt +1 -0
deepracer/__init__.py
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
from importlib.metadata import PackageNotFoundError
|
|
2
|
+
from importlib.metadata import version as _version
|
|
3
|
+
|
|
4
|
+
from gymnasium.envs.registration import register
|
|
5
|
+
|
|
6
|
+
try:
|
|
7
|
+
__version__ = _version("deepracer")
|
|
8
|
+
except PackageNotFoundError: # running from a source tree without an install
|
|
9
|
+
__version__ = "0.0.0"
|
|
10
|
+
|
|
11
|
+
register(id="deepracer-v0", entry_point="deepracer.envs:DeepracerGymEnv")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def close(env, cache: bool | None = None):
|
|
15
|
+
"""Close a (possibly gym.make-wrapped) DeepRacer env, overriding its cache flag.
|
|
16
|
+
|
|
17
|
+
The container's fate is normally the env's `cache` flag (set at gym.make), and a
|
|
18
|
+
plain `env.close()` already honors it through the wrapper. Use this helper (or
|
|
19
|
+
`env.unwrapped.close(cache=…)`) only to *override* that decision at close time —
|
|
20
|
+
e.g. `deepracer.close(env, cache=False)` to force-stop a cache=True env.
|
|
21
|
+
"""
|
|
22
|
+
env.unwrapped.close(cache=cache)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def shutdown_all():
|
|
26
|
+
"""Stop every DeepRacer container this process is managing, including any
|
|
27
|
+
kept warm. Call when you are completely done (or to reclaim warm
|
|
28
|
+
containers)."""
|
|
29
|
+
from deepracer.service.manager import SimulationManager
|
|
30
|
+
|
|
31
|
+
if SimulationManager._instance is not None:
|
|
32
|
+
SimulationManager._instance.shutdown_all()
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def running() -> list[str]:
|
|
36
|
+
"""Names of DeepRacer simulators currently running on this host (across any
|
|
37
|
+
kernels/processes). Handy to see what is up before/after your work."""
|
|
38
|
+
import shutil
|
|
39
|
+
import subprocess
|
|
40
|
+
|
|
41
|
+
from deepracer.service.spec import LABEL_NS
|
|
42
|
+
|
|
43
|
+
names: list[str] = []
|
|
44
|
+
for engine in ("podman", "docker"):
|
|
45
|
+
if shutil.which(engine) is None:
|
|
46
|
+
continue
|
|
47
|
+
result = subprocess.run(
|
|
48
|
+
[
|
|
49
|
+
engine,
|
|
50
|
+
"ps",
|
|
51
|
+
"--filter",
|
|
52
|
+
f"label={LABEL_NS}.managed=true",
|
|
53
|
+
"--format",
|
|
54
|
+
"{{.Names}}",
|
|
55
|
+
],
|
|
56
|
+
capture_output=True,
|
|
57
|
+
text=True,
|
|
58
|
+
)
|
|
59
|
+
names += [n for n in result.stdout.split() if n]
|
|
60
|
+
return names
|
deepracer/clean.py
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""Maintenance CLI for removing orphaned DeepRacer sims.
|
|
2
|
+
|
|
3
|
+
python -m deepracer.clean
|
|
4
|
+
|
|
5
|
+
Removes managed Docker/Podman containers, DeepRacer Apptainer instances,
|
|
6
|
+
per-instance overlays, and stale Apptainer logs. Best-effort and idempotent.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import glob
|
|
10
|
+
import json
|
|
11
|
+
import os
|
|
12
|
+
import shutil
|
|
13
|
+
import subprocess
|
|
14
|
+
|
|
15
|
+
from deepracer.service.spec import LABEL_NS
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _run(argv):
|
|
19
|
+
return subprocess.run(argv, capture_output=True, text=True)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _clean_oci(binary: str) -> None:
|
|
23
|
+
if shutil.which(binary) is None:
|
|
24
|
+
return
|
|
25
|
+
result = _run([binary, "ps", "-aq", "--filter", f"label={LABEL_NS}.managed=true"])
|
|
26
|
+
ids = [i for i in result.stdout.split() if i]
|
|
27
|
+
for cid in ids:
|
|
28
|
+
_run([binary, "rm", "-f", cid]) # -f stops then removes
|
|
29
|
+
print(f"{binary}: removed {len(ids)} managed container(s).")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _clean_apptainer() -> None:
|
|
33
|
+
if shutil.which("apptainer") is None:
|
|
34
|
+
return
|
|
35
|
+
result = _run(["apptainer", "instance", "list", "--json"])
|
|
36
|
+
try:
|
|
37
|
+
instances = json.loads(result.stdout).get("instances", [])
|
|
38
|
+
except Exception:
|
|
39
|
+
instances = []
|
|
40
|
+
names = [
|
|
41
|
+
i["instance"]
|
|
42
|
+
for i in instances
|
|
43
|
+
if str(i.get("instance", "")).startswith("deepracer-")
|
|
44
|
+
]
|
|
45
|
+
for name in names:
|
|
46
|
+
_run(["apptainer", "instance", "stop", name])
|
|
47
|
+
for overlay in glob.glob("/tmp/deepracer_*"):
|
|
48
|
+
shutil.rmtree(overlay, ignore_errors=True)
|
|
49
|
+
# Apptainer never rotates its per-instance logs; they accumulate forever and
|
|
50
|
+
# a stale FATAL: line poisons a later start of the same name (the backend
|
|
51
|
+
# clears these per-name on start, but sweep leftovers here too).
|
|
52
|
+
log_base = os.path.expanduser("~/.apptainer/instances/logs")
|
|
53
|
+
logs = glob.glob(os.path.join(log_base, "*", "*", "deepracer-*.out")) + glob.glob(
|
|
54
|
+
os.path.join(log_base, "*", "*", "deepracer-*.err")
|
|
55
|
+
)
|
|
56
|
+
for path in logs:
|
|
57
|
+
try:
|
|
58
|
+
os.remove(path)
|
|
59
|
+
except OSError:
|
|
60
|
+
pass
|
|
61
|
+
print(
|
|
62
|
+
f"apptainer: stopped {len(names)} instance(s), cleared overlays + "
|
|
63
|
+
f"{len(logs)} stale log file(s)."
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def main() -> None:
|
|
68
|
+
for binary in ("docker", "podman"):
|
|
69
|
+
_clean_oci(binary)
|
|
70
|
+
_clean_apptainer()
|
|
71
|
+
print("Done.")
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
if __name__ == "__main__":
|
|
75
|
+
main()
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Packaged default configs (agent_params.json, environment_params.yaml,
|
|
2
|
+
# reward_function.py) and the frozen track list (tracks.txt), so the package
|
|
3
|
+
# works from any cwd without the repo's top-level configs/ directory.
|
|
4
|
+
import importlib.resources as resources
|
|
5
|
+
import json
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _read(name: str) -> str:
|
|
9
|
+
return resources.files("deepracer.defaults").joinpath(name).read_text()
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def default_agent_config() -> dict:
|
|
13
|
+
return json.loads(_read("agent_params.json"))
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def default_track_config() -> dict:
|
|
17
|
+
import yaml # pyyaml
|
|
18
|
+
|
|
19
|
+
return yaml.safe_load(_read("environment_params.yaml"))
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def resolve_agent_config(config: dict | None = None) -> dict:
|
|
23
|
+
"""shallow merge agent config with packaged defaults"""
|
|
24
|
+
if config is None:
|
|
25
|
+
return default_agent_config()
|
|
26
|
+
if isinstance(config, dict):
|
|
27
|
+
return {**default_agent_config(), **config}
|
|
28
|
+
raise TypeError(
|
|
29
|
+
f"agent_config must be a dict or None, got {type(config).__name__}."
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def resolve_track_config(config: dict | None = None) -> dict:
|
|
34
|
+
"""shallow merge track config with packaged defaults"""
|
|
35
|
+
if config is None:
|
|
36
|
+
return default_track_config()
|
|
37
|
+
if isinstance(config, dict):
|
|
38
|
+
return {**default_track_config(), **config}
|
|
39
|
+
raise TypeError(
|
|
40
|
+
f"track_config must be a dict or None, got {type(config).__name__}."
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def default_reward_function():
|
|
45
|
+
"""The packaged default reward function (used when the caller passes none).
|
|
46
|
+
|
|
47
|
+
Prefer a repo-local ``configs.reward_function`` if importable (back-compat
|
|
48
|
+
with the current project layout); otherwise fall back to the packaged copy.
|
|
49
|
+
Loaded lazily so importing the package never depends on cwd.
|
|
50
|
+
"""
|
|
51
|
+
try:
|
|
52
|
+
from configs.reward_function import reward_function
|
|
53
|
+
|
|
54
|
+
return reward_function
|
|
55
|
+
except Exception:
|
|
56
|
+
namespace: dict = {}
|
|
57
|
+
exec(_read("reward_function.py"), namespace)
|
|
58
|
+
return namespace["reward_function"]
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"action_space": [
|
|
3
|
+
{
|
|
4
|
+
"steering_angle": 30,
|
|
5
|
+
"speed": 0.6
|
|
6
|
+
},
|
|
7
|
+
{
|
|
8
|
+
"steering_angle": 15,
|
|
9
|
+
"speed": 0.6
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"steering_angle": 0,
|
|
13
|
+
"speed": 0.6
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"steering_angle": -15,
|
|
17
|
+
"speed": 0.6
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"steering_angle": -30,
|
|
21
|
+
"speed": 0.6
|
|
22
|
+
}
|
|
23
|
+
],
|
|
24
|
+
"sensor": ["STEREO_CAMERAS", "LIDAR"],
|
|
25
|
+
"neural_network": "DEEP_CONVOLUTIONAL_NETWORK_SHALLOW",
|
|
26
|
+
"action_space_type": "discrete",
|
|
27
|
+
"version": "6"
|
|
28
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
WORLD_NAME: "reInvent2019_wide"
|
|
2
|
+
CHANGE_START_POSITION: "true"
|
|
3
|
+
ALTERNATE_DRIVING_DIRECTION: "true"
|
|
4
|
+
NUMBER_OF_OBSTACLES: "0"
|
|
5
|
+
IS_OBSTACLE_BOT_CAR: "false"
|
|
6
|
+
RANDOMIZE_OBSTACLE_LOCATIONS: "true"
|
|
7
|
+
# OBJECT_POSITIONS:
|
|
8
|
+
# - 0.1690708037909166, -1
|
|
9
|
+
# - 0.2638102569075569, 1
|
|
10
|
+
# - 0.4072827740044651, -1
|
|
11
|
+
# - 0.5804718430735435, 1
|
|
12
|
+
# - 0.6937442410812812, -1
|
|
13
|
+
# - 0.7864867324330095, 1
|
|
14
|
+
IS_LANE_CHANGE: "false"
|
|
15
|
+
LOWER_LANE_CHANGE_TIME: "3.0"
|
|
16
|
+
UPPER_LANE_CHANGE_TIME: "5.0"
|
|
17
|
+
LANE_CHANGE_DISTANCE: "1.0"
|
|
18
|
+
NUMBER_OF_BOT_CARS: "0"
|
|
19
|
+
MIN_DISTANCE_BETWEEN_BOT_CARS: "2.0"
|
|
20
|
+
RANDOMIZE_BOT_CAR_LOCATIONS: "true"
|
|
21
|
+
BOT_CAR_SPEED: "0.2"
|
|
22
|
+
ENABLE_DOMAIN_RANDOMIZATION: "false"
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
def reward_function(params):
|
|
2
|
+
"""
|
|
3
|
+
Example of rewarding the agent to follow center line
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
# Read input parameters
|
|
7
|
+
track_width = params["track_width"]
|
|
8
|
+
distance_from_center = params["distance_from_center"]
|
|
9
|
+
|
|
10
|
+
# Calculate 3 markers that are at varying distances away from the center line
|
|
11
|
+
marker_1 = 0.1 * track_width
|
|
12
|
+
marker_2 = 0.25 * track_width
|
|
13
|
+
marker_3 = 0.5 * track_width
|
|
14
|
+
|
|
15
|
+
# Give higher reward if the car is closer to center line and vice versa
|
|
16
|
+
if distance_from_center <= marker_1:
|
|
17
|
+
reward = 1.0
|
|
18
|
+
elif distance_from_center <= marker_2:
|
|
19
|
+
reward = 0.5
|
|
20
|
+
elif distance_from_center <= marker_3:
|
|
21
|
+
reward = 0.1
|
|
22
|
+
else:
|
|
23
|
+
reward = 1e-3 # likely crashed/ close to off track
|
|
24
|
+
|
|
25
|
+
return float(reward)
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
2022_april_open
|
|
2
|
+
2022_april_open_ccw
|
|
3
|
+
2022_april_open_cw
|
|
4
|
+
2022_april_pro
|
|
5
|
+
2022_april_pro_ccw
|
|
6
|
+
2022_april_pro_cw
|
|
7
|
+
2022_august_open
|
|
8
|
+
2022_august_open_ccw
|
|
9
|
+
2022_august_open_cw
|
|
10
|
+
2022_august_pro
|
|
11
|
+
2022_august_pro_ccw
|
|
12
|
+
2022_august_pro_cw
|
|
13
|
+
2022_july_open
|
|
14
|
+
2022_july_pro
|
|
15
|
+
2022_july_pro_ccw
|
|
16
|
+
2022_july_pro_cw
|
|
17
|
+
2022_june_open
|
|
18
|
+
2022_june_open_ccw
|
|
19
|
+
2022_june_open_cw
|
|
20
|
+
2022_june_pro
|
|
21
|
+
2022_june_pro_ccw
|
|
22
|
+
2022_june_pro_cw
|
|
23
|
+
2022_march_open
|
|
24
|
+
2022_march_open_ccw
|
|
25
|
+
2022_march_open_cw
|
|
26
|
+
2022_march_pro
|
|
27
|
+
2022_march_pro_ccw
|
|
28
|
+
2022_march_pro_cw
|
|
29
|
+
2022_may_open
|
|
30
|
+
2022_may_open_ccw
|
|
31
|
+
2022_may_open_cw
|
|
32
|
+
2022_may_pro
|
|
33
|
+
2022_may_pro_ccw
|
|
34
|
+
2022_may_pro_cw
|
|
35
|
+
2022_october_open
|
|
36
|
+
2022_october_open_ccw
|
|
37
|
+
2022_october_open_cw
|
|
38
|
+
2022_october_pro
|
|
39
|
+
2022_october_pro_ccw
|
|
40
|
+
2022_october_pro_cw
|
|
41
|
+
2022_reinvent_champ_ccw
|
|
42
|
+
2022_reinvent_champ_cw
|
|
43
|
+
2022_september_open
|
|
44
|
+
2022_september_open_ccw
|
|
45
|
+
2022_september_open_cw
|
|
46
|
+
2022_september_pro
|
|
47
|
+
2022_september_pro_ccw
|
|
48
|
+
2022_september_pro_cw
|
|
49
|
+
2022_summit_speedway
|
|
50
|
+
2022_summit_speedway_ccw
|
|
51
|
+
2022_summit_speedway_cw
|
|
52
|
+
2022_summit_speedway_mini
|
|
53
|
+
2024_reinvent_champ_ccw
|
|
54
|
+
2024_reinvent_champ_cw
|
|
55
|
+
Albert
|
|
56
|
+
AmericasGeneratedInclStart
|
|
57
|
+
Aragon
|
|
58
|
+
arctic_open
|
|
59
|
+
arctic_open_ccw
|
|
60
|
+
arctic_open_cw
|
|
61
|
+
arctic_pro
|
|
62
|
+
arctic_pro_ccw
|
|
63
|
+
arctic_pro_cw
|
|
64
|
+
Austin
|
|
65
|
+
AWS_track
|
|
66
|
+
Belille
|
|
67
|
+
Bowtie_track
|
|
68
|
+
caecer_gp
|
|
69
|
+
caecer_loop
|
|
70
|
+
Canada_Training
|
|
71
|
+
China_track
|
|
72
|
+
dubai_open
|
|
73
|
+
dubai_open_ccw
|
|
74
|
+
dubai_open_cw
|
|
75
|
+
dubai_pro
|
|
76
|
+
FS_June2020
|
|
77
|
+
hamption_open
|
|
78
|
+
hamption_pro
|
|
79
|
+
July_2020
|
|
80
|
+
jyllandsringen_open
|
|
81
|
+
jyllandsringen_open_ccw
|
|
82
|
+
jyllandsringen_open_cw
|
|
83
|
+
jyllandsringen_pro
|
|
84
|
+
jyllandsringen_pro_ccw
|
|
85
|
+
jyllandsringen_pro_cw
|
|
86
|
+
LGSWide
|
|
87
|
+
Mexico_track
|
|
88
|
+
Monaco
|
|
89
|
+
Monaco_building
|
|
90
|
+
morgan_open
|
|
91
|
+
morgan_pro
|
|
92
|
+
New_York_Track
|
|
93
|
+
Oval_track
|
|
94
|
+
penbay_open
|
|
95
|
+
penbay_open_ccw
|
|
96
|
+
penbay_open_cw
|
|
97
|
+
penbay_pro
|
|
98
|
+
penbay_pro_ccw
|
|
99
|
+
penbay_pro_cw
|
|
100
|
+
red_star_open
|
|
101
|
+
red_star_pro
|
|
102
|
+
red_star_pro_ccw
|
|
103
|
+
red_star_pro_cw
|
|
104
|
+
reInvent2019_track
|
|
105
|
+
reInvent2019_track_ccw
|
|
106
|
+
reInvent2019_track_cw
|
|
107
|
+
reInvent2019_wide
|
|
108
|
+
reInvent2019_wide_ccw
|
|
109
|
+
reInvent2019_wide_cw
|
|
110
|
+
reInvent2019_wide_mirrored
|
|
111
|
+
reinvent_base
|
|
112
|
+
reinvent_carpet
|
|
113
|
+
reinvent_concrete
|
|
114
|
+
reinvent_wood
|
|
115
|
+
Singapore
|
|
116
|
+
Singapore_building
|
|
117
|
+
Singapore_f1
|
|
118
|
+
Spain_track
|
|
119
|
+
Spain_track_f1
|
|
120
|
+
Straight_track
|
|
121
|
+
thunder_hill_open
|
|
122
|
+
thunder_hill_pro
|
|
123
|
+
thunder_hill_pro_ccw
|
|
124
|
+
thunder_hill_pro_cw
|
|
125
|
+
Tokyo_Training_track
|
|
126
|
+
Vegas_track
|
|
127
|
+
Virtual_May19_Train_track
|
deepracer/envs/env.py
ADDED
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import weakref
|
|
3
|
+
from collections.abc import Callable
|
|
4
|
+
|
|
5
|
+
import gymnasium as gym
|
|
6
|
+
import matplotlib.pyplot as plt
|
|
7
|
+
import numpy as np
|
|
8
|
+
from gymnasium import spaces
|
|
9
|
+
from loguru import logger
|
|
10
|
+
|
|
11
|
+
from deepracer.defaults import (
|
|
12
|
+
default_reward_function,
|
|
13
|
+
resolve_agent_config,
|
|
14
|
+
resolve_track_config,
|
|
15
|
+
)
|
|
16
|
+
from deepracer.envs.utils import (
|
|
17
|
+
make_action_space,
|
|
18
|
+
make_observation_space,
|
|
19
|
+
num_channels,
|
|
20
|
+
)
|
|
21
|
+
from deepracer.gym_adapter import DeepracerGymAdapter
|
|
22
|
+
from deepracer.service.spec import DEFAULT_IMAGE
|
|
23
|
+
|
|
24
|
+
# validate_configs is imported lazily in __init__ to avoid an import cycle
|
|
25
|
+
# (service.validate -> envs.utils -> envs.__init__ -> this module).
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
type ActionType = int | np.ndarray | list[float]
|
|
29
|
+
HOST: str = "127.0.0.1"
|
|
30
|
+
DEFAULT_PORT: int = 8888
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _default_port() -> int:
|
|
34
|
+
"""Port for the connect-only (manage_container=False) path: GYM_PORT env, else
|
|
35
|
+
a per-user hash (back-compat with the bash scripts), else DEFAULT_PORT."""
|
|
36
|
+
if "GYM_PORT" in os.environ:
|
|
37
|
+
try:
|
|
38
|
+
return int(os.environ["GYM_PORT"])
|
|
39
|
+
except ValueError:
|
|
40
|
+
pass
|
|
41
|
+
try:
|
|
42
|
+
from deepracer.service.identity import current_user, string_to_port
|
|
43
|
+
|
|
44
|
+
return string_to_port(current_user())
|
|
45
|
+
except Exception:
|
|
46
|
+
return DEFAULT_PORT
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _release(manager, handle, keep_warm):
|
|
50
|
+
"""Module-level finalizer target (must not hold a ref to the env instance).
|
|
51
|
+
Honors the env's cache flag: a forgotten close() / GC on a cache=True env
|
|
52
|
+
keeps it warm (reaped at process exit by shutdown_all), else stops+removes."""
|
|
53
|
+
try:
|
|
54
|
+
manager.release(handle, keep_warm=keep_warm)
|
|
55
|
+
except Exception:
|
|
56
|
+
pass
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class DeepracerGymEnv(gym.Env):
|
|
60
|
+
metadata = {"render_modes": ["rgb_array", "human"], "render_fps": 30}
|
|
61
|
+
|
|
62
|
+
def __init__(
|
|
63
|
+
self,
|
|
64
|
+
agent_config: dict | None = None,
|
|
65
|
+
track_config: dict | None = None,
|
|
66
|
+
reward_function: Callable | None = None,
|
|
67
|
+
world_name: str | None = None,
|
|
68
|
+
evaluation: bool = False,
|
|
69
|
+
render_mode: str = "rgb_array",
|
|
70
|
+
image: str | None = None,
|
|
71
|
+
cpus: float = 3.0,
|
|
72
|
+
memory: str = "6g",
|
|
73
|
+
cache: bool = False,
|
|
74
|
+
manage_container: bool = True,
|
|
75
|
+
host: str = HOST,
|
|
76
|
+
port: int | None = None,
|
|
77
|
+
**kwargs,
|
|
78
|
+
):
|
|
79
|
+
super().__init__(**kwargs)
|
|
80
|
+
self.render_mode = render_mode
|
|
81
|
+
|
|
82
|
+
agent_config = resolve_agent_config(agent_config)
|
|
83
|
+
track_config = resolve_track_config(track_config)
|
|
84
|
+
# Non-eval world_name replaces WORLD_NAME; eval mode passes EVAL_WORLD_NAME.
|
|
85
|
+
if world_name and not evaluation:
|
|
86
|
+
track_config = {**track_config, "WORLD_NAME": world_name}
|
|
87
|
+
from deepracer.service.validate import validate_configs
|
|
88
|
+
|
|
89
|
+
validate_configs(
|
|
90
|
+
agent_config,
|
|
91
|
+
track_config,
|
|
92
|
+
world_name=world_name,
|
|
93
|
+
evaluation=evaluation,
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
self.action_space, self._action_metadata = make_action_space(agent_config)
|
|
97
|
+
self.observation_space, self._observation_metadata = make_observation_space(
|
|
98
|
+
agent_config
|
|
99
|
+
)
|
|
100
|
+
self.reward_function = (
|
|
101
|
+
reward_function
|
|
102
|
+
if reward_function is not None
|
|
103
|
+
else default_reward_function()
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
self._cache = cache
|
|
107
|
+
self._manager = None
|
|
108
|
+
self._handle = None
|
|
109
|
+
self._finalizer = None
|
|
110
|
+
self._closed = False
|
|
111
|
+
|
|
112
|
+
if manage_container:
|
|
113
|
+
from deepracer.service.manager import SimulationManager
|
|
114
|
+
|
|
115
|
+
self._manager = SimulationManager.instance()
|
|
116
|
+
self._handle = self._manager.acquire(
|
|
117
|
+
agent_config=agent_config,
|
|
118
|
+
track_config=track_config,
|
|
119
|
+
image=image or DEFAULT_IMAGE,
|
|
120
|
+
cpus=cpus,
|
|
121
|
+
memory=memory,
|
|
122
|
+
evaluation=evaluation,
|
|
123
|
+
world_name=world_name,
|
|
124
|
+
)
|
|
125
|
+
connect_port = self._handle.port
|
|
126
|
+
# Safety net for a forgotten close(); the documented API is close().
|
|
127
|
+
self._finalizer = weakref.finalize(
|
|
128
|
+
self,
|
|
129
|
+
_release,
|
|
130
|
+
self._manager,
|
|
131
|
+
self._handle,
|
|
132
|
+
self._cache,
|
|
133
|
+
)
|
|
134
|
+
else:
|
|
135
|
+
connect_port = port if port is not None else _default_port()
|
|
136
|
+
|
|
137
|
+
logger.info(f"Using port {connect_port} for deepracer server.")
|
|
138
|
+
|
|
139
|
+
if isinstance(self.action_space, spaces.Discrete):
|
|
140
|
+
action_space_type = "discrete"
|
|
141
|
+
elif isinstance(self.action_space, spaces.Box):
|
|
142
|
+
action_space_type = "continuous"
|
|
143
|
+
self.deepracer_adapter = DeepracerGymAdapter(
|
|
144
|
+
action_space_type, host=host, port=connect_port
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
def reset(self, **kwargs):
|
|
148
|
+
super().reset(**kwargs)
|
|
149
|
+
observation, info = self.deepracer_adapter.env_reset()
|
|
150
|
+
return observation, info
|
|
151
|
+
|
|
152
|
+
def step(self, action: ActionType):
|
|
153
|
+
assert self.action_space.contains(action), (
|
|
154
|
+
f"Infeasible action. Action space does not containr {action}."
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
observation, terminated, truncated, info = self.deepracer_adapter.send_action(
|
|
158
|
+
action
|
|
159
|
+
)
|
|
160
|
+
reward = self.reward_function(info["reward_params"])
|
|
161
|
+
return observation, reward, terminated, truncated, info
|
|
162
|
+
|
|
163
|
+
def render(self, mode="rgb_array"):
|
|
164
|
+
observation, _, _, _ = self.deepracer_adapter._parse_response(
|
|
165
|
+
self.deepracer_adapter.response
|
|
166
|
+
)
|
|
167
|
+
measurement = None
|
|
168
|
+
for sensor in observation:
|
|
169
|
+
if "CAMERA" in sensor:
|
|
170
|
+
measurement = observation[sensor]
|
|
171
|
+
|
|
172
|
+
if measurement is None:
|
|
173
|
+
raise ValueError(
|
|
174
|
+
f"Cannot render output of sensors {list(observation.keys())}."
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
channels = num_channels(measurement)
|
|
178
|
+
if channels == 2:
|
|
179
|
+
# stereo camera
|
|
180
|
+
measurement = np.hstack((measurement[0, :, :], measurement[1, :, :]))
|
|
181
|
+
|
|
182
|
+
channels = num_channels(measurement)
|
|
183
|
+
if channels == 1:
|
|
184
|
+
# greyscale image
|
|
185
|
+
measurement = np.stack(3 * (measurement,), axis=-1)
|
|
186
|
+
elif channels == 3:
|
|
187
|
+
# front facing camera
|
|
188
|
+
measurement = measurement.transpose(1, 2, 0)
|
|
189
|
+
|
|
190
|
+
if mode == "human":
|
|
191
|
+
plt.imshow(np.asarray(measurement))
|
|
192
|
+
plt.axis("off")
|
|
193
|
+
elif mode == "rgb_array":
|
|
194
|
+
return np.asarray(measurement)
|
|
195
|
+
|
|
196
|
+
def close(self, cache: bool | None = None):
|
|
197
|
+
"""Close the ZMQ client and release the sim container. Idempotent.
|
|
198
|
+
|
|
199
|
+
The container's fate is the env's `cache` flag (set at gym.make): with
|
|
200
|
+
cache=True it is kept warm so a later env in *this same process* with a
|
|
201
|
+
matching config re-attaches (faster boot); with cache=False (default) it
|
|
202
|
+
is stopped and removed. A warm container does NOT survive process exit.
|
|
203
|
+
Pass cache=… here only to override the constructor value at close time.
|
|
204
|
+
|
|
205
|
+
Because `cache` is a constructor argument, gym.make forwards it, so a
|
|
206
|
+
plain `env.close()` (through the gym.make wrapper) honors it — no
|
|
207
|
+
`.unwrapped` needed. The override path still needs the unwrapped env:
|
|
208
|
+
`deepracer.close(env, cache=False)` or `env.unwrapped.close(cache=…)`.
|
|
209
|
+
"""
|
|
210
|
+
if self._closed:
|
|
211
|
+
return
|
|
212
|
+
self._closed = True
|
|
213
|
+
try:
|
|
214
|
+
self.deepracer_adapter.zmq_client.socket.close()
|
|
215
|
+
except Exception:
|
|
216
|
+
pass
|
|
217
|
+
if self._manager is not None and self._handle is not None:
|
|
218
|
+
use_cache = self._cache if cache is None else cache
|
|
219
|
+
if self._finalizer is not None:
|
|
220
|
+
self._finalizer.detach()
|
|
221
|
+
self._manager.release(self._handle, keep_warm=use_cache)
|
|
222
|
+
super().close()
|
|
223
|
+
|
|
224
|
+
def __enter__(self):
|
|
225
|
+
return self
|
|
226
|
+
|
|
227
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
228
|
+
self.close()
|