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 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
@@ -0,0 +1,3 @@
1
+ from deepracer.envs.env import DeepracerGymEnv
2
+
3
+ __all__ = ["DeepracerGymEnv"]
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()