miniworld-maze 1.1.0__py3-none-any.whl → 1.2.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.
Potentially problematic release.
This version of miniworld-maze might be problematic. Click here for more details.
- miniworld_maze/__init__.py +17 -5
- miniworld_maze/core/constants.py +55 -14
- miniworld_maze/core/miniworld_gymnasium/__init__.py +1 -1
- miniworld_maze/core/miniworld_gymnasium/unified_env.py +13 -18
- miniworld_maze/environments/__init__.py +0 -2
- miniworld_maze/environments/base_grid_rooms.py +87 -15
- miniworld_maze/environments/factory.py +38 -101
- miniworld_maze/environments/nine_rooms.py +5 -23
- miniworld_maze/environments/spiral_nine_rooms.py +5 -23
- miniworld_maze/environments/twenty_five_rooms.py +5 -38
- miniworld_maze/tools/__init__.py +1 -3
- miniworld_maze/utils.py +286 -0
- {miniworld_maze-1.1.0.dist-info → miniworld_maze-1.2.0.dist-info}/METADATA +25 -14
- {miniworld_maze-1.1.0.dist-info → miniworld_maze-1.2.0.dist-info}/RECORD +15 -16
- miniworld_maze/tools/generate_observations.py +0 -199
- miniworld_maze-1.1.0.dist-info/entry_points.txt +0 -3
- {miniworld_maze-1.1.0.dist-info → miniworld_maze-1.2.0.dist-info}/WHEEL +0 -0
miniworld_maze/__init__.py
CHANGED
|
@@ -15,17 +15,29 @@ Main modules:
|
|
|
15
15
|
- tools: Observation generation and utilities
|
|
16
16
|
"""
|
|
17
17
|
|
|
18
|
+
import os
|
|
19
|
+
import warnings
|
|
20
|
+
|
|
21
|
+
# Set PYGLET_HEADLESS=1 by default if not already set
|
|
22
|
+
if "PYGLET_HEADLESS" not in os.environ:
|
|
23
|
+
os.environ["PYGLET_HEADLESS"] = "1"
|
|
24
|
+
warnings.warn(
|
|
25
|
+
"Automatically set PYGLET_HEADLESS=1 for headless rendering. "
|
|
26
|
+
"Set PYGLET_HEADLESS=0 before importing miniworld_maze to override this behavior.",
|
|
27
|
+
UserWarning,
|
|
28
|
+
stacklevel=2
|
|
29
|
+
)
|
|
30
|
+
|
|
18
31
|
from .core import ObservationLevel
|
|
19
|
-
from .environments.factory import (
|
|
20
|
-
NineRoomsEnvironmentWrapper,
|
|
21
|
-
)
|
|
22
32
|
from .environments.nine_rooms import NineRooms
|
|
23
33
|
from .environments.spiral_nine_rooms import SpiralNineRooms
|
|
24
34
|
from .environments.twenty_five_rooms import TwentyFiveRooms
|
|
25
35
|
|
|
26
|
-
|
|
36
|
+
# Import factory to register environments
|
|
37
|
+
from .environments import factory # noqa: F401
|
|
38
|
+
|
|
39
|
+
__version__ = "1.1.0"
|
|
27
40
|
__all__ = [
|
|
28
|
-
"NineRoomsEnvironmentWrapper",
|
|
29
41
|
"NineRooms",
|
|
30
42
|
"SpiralNineRooms",
|
|
31
43
|
"TwentyFiveRooms",
|
miniworld_maze/core/constants.py
CHANGED
|
@@ -57,23 +57,64 @@ DEFAULT_BENCHMARK_STEPS: Final[int] = 100
|
|
|
57
57
|
DEFAULT_WARMUP_STEPS: Final[int] = 10
|
|
58
58
|
|
|
59
59
|
# ========================
|
|
60
|
-
#
|
|
60
|
+
# TEXTURE THEMES
|
|
61
61
|
# ========================
|
|
62
62
|
|
|
63
|
+
class TextureThemes:
|
|
64
|
+
"""Pre-defined texture themes for different environments."""
|
|
65
|
+
|
|
66
|
+
NINE_ROOMS = [
|
|
67
|
+
"beige",
|
|
68
|
+
"lightbeige",
|
|
69
|
+
"lightgray",
|
|
70
|
+
"copperred",
|
|
71
|
+
"skyblue",
|
|
72
|
+
"lightcobaltgreen",
|
|
73
|
+
"oakbrown",
|
|
74
|
+
"navyblue",
|
|
75
|
+
"cobaltgreen",
|
|
76
|
+
]
|
|
77
|
+
|
|
78
|
+
SPIRAL_NINE_ROOMS = [
|
|
79
|
+
"beige",
|
|
80
|
+
"lightbeige",
|
|
81
|
+
"lightgray",
|
|
82
|
+
"copperred",
|
|
83
|
+
"skyblue",
|
|
84
|
+
"lightcobaltgreen",
|
|
85
|
+
"oakbrown",
|
|
86
|
+
"navyblue",
|
|
87
|
+
"cobaltgreen",
|
|
88
|
+
]
|
|
89
|
+
|
|
90
|
+
TWENTY_FIVE_ROOMS = [
|
|
91
|
+
"crimson",
|
|
92
|
+
"beanpaste",
|
|
93
|
+
"cobaltgreen",
|
|
94
|
+
"lightnavyblue",
|
|
95
|
+
"skyblue",
|
|
96
|
+
"lightcobaltgreen",
|
|
97
|
+
"oakbrown",
|
|
98
|
+
"copperred",
|
|
99
|
+
"lightgray",
|
|
100
|
+
"lime",
|
|
101
|
+
"turquoise",
|
|
102
|
+
"violet",
|
|
103
|
+
"beige",
|
|
104
|
+
"morningglory",
|
|
105
|
+
"silver",
|
|
106
|
+
"magenta",
|
|
107
|
+
"sunnyyellow",
|
|
108
|
+
"blueberry",
|
|
109
|
+
"lightbeige",
|
|
110
|
+
"seablue",
|
|
111
|
+
"lemongrass",
|
|
112
|
+
"orchid",
|
|
113
|
+
"redbean",
|
|
114
|
+
"orange",
|
|
115
|
+
"realblueberry",
|
|
116
|
+
]
|
|
63
117
|
|
|
64
|
-
# Convenient position calculations for standard room layouts
|
|
65
|
-
class RoomPositions:
|
|
66
|
-
"""Pre-calculated positions for standard room layouts."""
|
|
67
|
-
|
|
68
|
-
# NineRooms (3x3) strategic positions
|
|
69
|
-
NINE_ROOMS_CENTER: Final[tuple[float, float, float]] = (22.5, 0.0, 22.5)
|
|
70
|
-
NINE_ROOMS_TOP_LEFT: Final[tuple[float, float, float]] = (7.5, 0.0, 7.5)
|
|
71
|
-
NINE_ROOMS_BOTTOM_RIGHT: Final[tuple[float, float, float]] = (37.5, 0.0, 37.5)
|
|
72
|
-
|
|
73
|
-
# TwentyFiveRooms (5x5) strategic positions
|
|
74
|
-
TWENTY_FIVE_CENTER: Final[tuple[float, float, float]] = (75.0, 0.0, 75.0)
|
|
75
|
-
TWENTY_FIVE_CORNER: Final[tuple[float, float, float]] = (37.5, 0.0, 37.5)
|
|
76
|
-
TWENTY_FIVE_FAR_CORNER: Final[tuple[float, float, float]] = (112.5, 0.0, 112.5)
|
|
77
118
|
|
|
78
119
|
|
|
79
120
|
# ========================
|
|
@@ -332,34 +332,32 @@ class UnifiedMiniWorldEnv(gym.Env):
|
|
|
332
332
|
self._render_static()
|
|
333
333
|
|
|
334
334
|
# Generate the first camera image
|
|
335
|
-
obs = self._generate_observation()
|
|
335
|
+
obs = self._generate_observation(self.obs_level)
|
|
336
336
|
|
|
337
337
|
# Generate additional observations for info dictionary if specified
|
|
338
338
|
info = {}
|
|
339
339
|
if self.info_obs is not None:
|
|
340
340
|
for obs_level in self.info_obs:
|
|
341
|
-
#
|
|
342
|
-
|
|
343
|
-
self.obs_level = obs_level
|
|
344
|
-
info_obs = self._generate_observation()
|
|
345
|
-
self.obs_level = original_obs_level
|
|
341
|
+
# Generate observation with the specified level
|
|
342
|
+
info_obs = self._generate_observation(observation_level=obs_level)
|
|
346
343
|
# Use the observation level name as key
|
|
347
344
|
info[str(obs_level)] = info_obs
|
|
348
345
|
|
|
349
346
|
# Return first observation with info dict for Gymnasium compatibility
|
|
350
347
|
return obs, info
|
|
351
348
|
|
|
352
|
-
def _generate_observation(self, render_agent: bool = None):
|
|
353
|
-
"""Generate observation based on
|
|
349
|
+
def _generate_observation(self, observation_level, render_agent: bool = None):
|
|
350
|
+
"""Generate observation based on specified observation level.
|
|
354
351
|
|
|
355
352
|
Args:
|
|
353
|
+
observation_level: Observation level to use.
|
|
356
354
|
render_agent: Whether to render the agent in the observation.
|
|
357
355
|
If None, uses default behavior based on observation level.
|
|
358
356
|
"""
|
|
359
357
|
# Import ObservationLevel here to avoid circular imports
|
|
360
358
|
from ..observation_types import ObservationLevel
|
|
361
359
|
|
|
362
|
-
if
|
|
360
|
+
if observation_level == ObservationLevel.TOP_DOWN_PARTIAL:
|
|
363
361
|
if self.agent_mode == "empty":
|
|
364
362
|
# Agent mode 'empty' always renders without agent
|
|
365
363
|
render_ag = False
|
|
@@ -371,19 +369,19 @@ class UnifiedMiniWorldEnv(gym.Env):
|
|
|
371
369
|
render_ag = True
|
|
372
370
|
return self.render_top_view(POMDP=True, render_ag=render_ag)
|
|
373
371
|
|
|
374
|
-
elif
|
|
372
|
+
elif observation_level == ObservationLevel.TOP_DOWN_FULL:
|
|
375
373
|
# Use explicit render_agent parameter or default to True
|
|
376
374
|
render_ag = render_agent if render_agent is not None else True
|
|
377
375
|
return self.render_top_view(POMDP=False, render_ag=render_ag)
|
|
378
376
|
|
|
379
|
-
elif
|
|
377
|
+
elif observation_level == ObservationLevel.FIRST_PERSON:
|
|
380
378
|
# First person view doesn't include the agent anyway
|
|
381
379
|
return self.render_obs()
|
|
382
380
|
|
|
383
381
|
else:
|
|
384
382
|
valid_levels = list(ObservationLevel)
|
|
385
383
|
raise ValueError(
|
|
386
|
-
f"Invalid obs_level {
|
|
384
|
+
f"Invalid obs_level {observation_level}. Must be one of {valid_levels}"
|
|
387
385
|
)
|
|
388
386
|
|
|
389
387
|
def _calculate_carried_object_position(self, agent_pos, ent):
|
|
@@ -507,7 +505,7 @@ class UnifiedMiniWorldEnv(gym.Env):
|
|
|
507
505
|
self._process_action(action)
|
|
508
506
|
|
|
509
507
|
# Generate observation
|
|
510
|
-
observation = self._generate_observation()
|
|
508
|
+
observation = self._generate_observation(self.obs_level)
|
|
511
509
|
|
|
512
510
|
# Calculate step results
|
|
513
511
|
reward, terminated, info = self._calculate_step_results(observation)
|
|
@@ -586,11 +584,8 @@ class UnifiedMiniWorldEnv(gym.Env):
|
|
|
586
584
|
info = {}
|
|
587
585
|
if self.info_obs is not None:
|
|
588
586
|
for obs_level in self.info_obs:
|
|
589
|
-
#
|
|
590
|
-
|
|
591
|
-
self.obs_level = obs_level
|
|
592
|
-
info_obs = self._generate_observation()
|
|
593
|
-
self.obs_level = original_obs_level
|
|
587
|
+
# Generate observation with the specified level
|
|
588
|
+
info_obs = self._generate_observation(observation_level=obs_level)
|
|
594
589
|
# Use the observation level name as key
|
|
595
590
|
info[str(obs_level)] = info_obs
|
|
596
591
|
|
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
"""Nine Rooms environment implementations."""
|
|
2
2
|
|
|
3
3
|
from .base_grid_rooms import GridRoomsEnvironment
|
|
4
|
-
from .factory import NineRoomsEnvironmentWrapper
|
|
5
4
|
from .nine_rooms import NineRooms
|
|
6
5
|
from .spiral_nine_rooms import SpiralNineRooms
|
|
7
6
|
from .twenty_five_rooms import TwentyFiveRooms
|
|
8
7
|
|
|
9
8
|
__all__ = [
|
|
10
9
|
"GridRoomsEnvironment",
|
|
11
|
-
"NineRoomsEnvironmentWrapper",
|
|
12
10
|
"NineRooms",
|
|
13
11
|
"SpiralNineRooms",
|
|
14
12
|
"TwentyFiveRooms",
|
|
@@ -77,7 +77,8 @@ class GridRoomsEnvironment(UnifiedMiniWorldEnv):
|
|
|
77
77
|
|
|
78
78
|
# Validate and set textures
|
|
79
79
|
assert len(textures) == self.total_rooms, (
|
|
80
|
-
f"Textures for floor should be same as the number of the rooms
|
|
80
|
+
f"Textures for floor should be same as the number of the rooms "
|
|
81
|
+
f"({self.total_rooms})"
|
|
81
82
|
)
|
|
82
83
|
self.textures = textures
|
|
83
84
|
|
|
@@ -226,16 +227,20 @@ class GridRoomsEnvironment(UnifiedMiniWorldEnv):
|
|
|
226
227
|
obs, reward, terminated, truncated, info = super().step(action)
|
|
227
228
|
|
|
228
229
|
# Check if goal is achieved
|
|
229
|
-
if self.
|
|
230
|
+
if self._is_goal_achieved():
|
|
230
231
|
terminated = True
|
|
231
232
|
reward = 1.0 # Positive reward for achieving goal
|
|
232
233
|
|
|
234
|
+
# Add agent and goal positions to info dictionary
|
|
235
|
+
agent_pos = self.agent.pos
|
|
236
|
+
info["agent_position"] = np.array([agent_pos[0], agent_pos[2]]) # x, z
|
|
237
|
+
|
|
238
|
+
if hasattr(self, "_current_goal_position"):
|
|
239
|
+
goal_pos = self._current_goal_position
|
|
240
|
+
info["goal_position"] = np.array([goal_pos[0], goal_pos[2]]) # x, z
|
|
241
|
+
|
|
233
242
|
# Return observation as dict
|
|
234
|
-
obs_dict =
|
|
235
|
-
"observation": obs,
|
|
236
|
-
"desired_goal": self.desired_goal,
|
|
237
|
-
"achieved_goal": obs,
|
|
238
|
-
}
|
|
243
|
+
obs_dict = self._build_observation_dict(obs)
|
|
239
244
|
return obs_dict, reward, terminated, truncated, info
|
|
240
245
|
|
|
241
246
|
def reset(self, seed=None, options=None, pos=None):
|
|
@@ -254,17 +259,21 @@ class GridRoomsEnvironment(UnifiedMiniWorldEnv):
|
|
|
254
259
|
obs, info = super().reset(seed=seed, options=options, pos=pos)
|
|
255
260
|
|
|
256
261
|
# Generate goal
|
|
257
|
-
self.desired_goal = self.
|
|
262
|
+
self.desired_goal = self._get_goal()
|
|
263
|
+
|
|
264
|
+
# Add agent and goal positions to info dictionary
|
|
265
|
+
agent_pos = self.agent.pos
|
|
266
|
+
info["agent_position"] = np.array([agent_pos[0], agent_pos[2]]) # x, z
|
|
267
|
+
|
|
268
|
+
if hasattr(self, "_current_goal_position"):
|
|
269
|
+
goal_pos = self._current_goal_position
|
|
270
|
+
info["goal_position"] = np.array([goal_pos[0], goal_pos[2]]) # x, z
|
|
258
271
|
|
|
259
272
|
# Return observation as dict with desired_goal and achieved_goal
|
|
260
|
-
obs_dict =
|
|
261
|
-
"observation": obs,
|
|
262
|
-
"desired_goal": self.desired_goal,
|
|
263
|
-
"achieved_goal": obs,
|
|
264
|
-
}
|
|
273
|
+
obs_dict = self._build_observation_dict(obs)
|
|
265
274
|
return obs_dict, info
|
|
266
275
|
|
|
267
|
-
def
|
|
276
|
+
def _get_goal(self):
|
|
268
277
|
"""
|
|
269
278
|
Generate a goal by randomly selecting a room and goal position.
|
|
270
279
|
|
|
@@ -318,7 +327,7 @@ class GridRoomsEnvironment(UnifiedMiniWorldEnv):
|
|
|
318
327
|
|
|
319
328
|
return obs
|
|
320
329
|
|
|
321
|
-
def
|
|
330
|
+
def _is_goal_achieved(self, pos=None, threshold=0.5):
|
|
322
331
|
"""
|
|
323
332
|
Check if the agent has achieved the current goal.
|
|
324
333
|
|
|
@@ -341,3 +350,66 @@ class GridRoomsEnvironment(UnifiedMiniWorldEnv):
|
|
|
341
350
|
distance = np.linalg.norm(pos_array - goal_array)
|
|
342
351
|
|
|
343
352
|
return bool(distance < threshold)
|
|
353
|
+
|
|
354
|
+
@staticmethod
|
|
355
|
+
def _generate_goal_positions(
|
|
356
|
+
grid_size: int, room_size: Union[int, float], goals_per_room: int = 2
|
|
357
|
+
) -> List[List[List[float]]]:
|
|
358
|
+
"""
|
|
359
|
+
Generate goal positions for grid layout.
|
|
360
|
+
Args:
|
|
361
|
+
grid_size: Size of the grid (e.g., 3 for 3x3, 5 for 5x5)
|
|
362
|
+
room_size: Size of each room
|
|
363
|
+
goals_per_room: Number of goals per room (1 or 2)
|
|
364
|
+
Returns:
|
|
365
|
+
List of goal positions for each room
|
|
366
|
+
"""
|
|
367
|
+
goal_positions = []
|
|
368
|
+
for i in range(grid_size): # rows
|
|
369
|
+
for j in range(grid_size): # columns
|
|
370
|
+
center_x = room_size * j + room_size / 2
|
|
371
|
+
center_z = room_size * i + room_size / 2
|
|
372
|
+
if goals_per_room == 1:
|
|
373
|
+
# One goal per room at the center
|
|
374
|
+
goal_positions.append([[center_x, 0.0, center_z]])
|
|
375
|
+
else:
|
|
376
|
+
# Two goals per room: center-left and center-right
|
|
377
|
+
goal_positions.append([
|
|
378
|
+
[center_x - 1.0, 0.0, center_z], # left goal
|
|
379
|
+
[center_x + 1.0, 0.0, center_z], # right goal
|
|
380
|
+
])
|
|
381
|
+
return goal_positions
|
|
382
|
+
|
|
383
|
+
def get_extent(self, padding: float = 1.0) -> Tuple[float, float, float, float]:
|
|
384
|
+
"""
|
|
385
|
+
Get the scene extent for use with matplotlib imshow.
|
|
386
|
+
|
|
387
|
+
Returns the scene bounds with padding in the format expected by
|
|
388
|
+
matplotlib's imshow(extent=...) parameter: (left, right, bottom, top).
|
|
389
|
+
|
|
390
|
+
Args:
|
|
391
|
+
padding: Padding to add around environment bounds (default: 1.0)
|
|
392
|
+
|
|
393
|
+
Returns:
|
|
394
|
+
Tuple[float, float, float, float]: (min_x, max_x, min_z, max_z) with padding
|
|
395
|
+
"""
|
|
396
|
+
return (
|
|
397
|
+
self.min_x - padding,
|
|
398
|
+
self.max_x + padding,
|
|
399
|
+
self.min_z - padding,
|
|
400
|
+
self.max_z + padding
|
|
401
|
+
)
|
|
402
|
+
|
|
403
|
+
def _build_observation_dict(self, obs: np.ndarray) -> dict:
|
|
404
|
+
"""
|
|
405
|
+
Build the standard observation dictionary format.
|
|
406
|
+
Args:
|
|
407
|
+
obs: The observation array
|
|
408
|
+
Returns:
|
|
409
|
+
Dictionary with observation, desired_goal, and achieved_goal
|
|
410
|
+
"""
|
|
411
|
+
return {
|
|
412
|
+
"observation": obs,
|
|
413
|
+
"desired_goal": self.desired_goal,
|
|
414
|
+
"achieved_goal": obs,
|
|
415
|
+
}
|
|
@@ -1,105 +1,42 @@
|
|
|
1
|
-
"""
|
|
2
|
-
|
|
3
|
-
from typing import List
|
|
4
|
-
|
|
5
|
-
import gymnasium as gym
|
|
6
|
-
import numpy as np
|
|
1
|
+
"""Gymnasium environment registrations for Nine Rooms environment variants."""
|
|
7
2
|
|
|
3
|
+
from gymnasium.envs.registration import register
|
|
8
4
|
from ..core import ObservationLevel
|
|
9
5
|
from ..core.constants import FACTORY_DOOR_SIZE, FACTORY_ROOM_SIZE
|
|
10
|
-
from .nine_rooms import NineRooms
|
|
11
|
-
from .spiral_nine_rooms import SpiralNineRooms
|
|
12
|
-
from .twenty_five_rooms import TwentyFiveRooms
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
class NineRoomsEnvironmentWrapper(gym.Wrapper):
|
|
16
|
-
"""Unified wrapper for all Nine Rooms environment variants."""
|
|
17
|
-
|
|
18
|
-
def __init__(
|
|
19
|
-
self,
|
|
20
|
-
variant="NineRooms",
|
|
21
|
-
obs_level=ObservationLevel.TOP_DOWN_PARTIAL,
|
|
22
|
-
continuous=False,
|
|
23
|
-
size=64,
|
|
24
|
-
room_size=FACTORY_ROOM_SIZE,
|
|
25
|
-
door_size=FACTORY_DOOR_SIZE,
|
|
26
|
-
agent_mode=None,
|
|
27
|
-
info_obs: List[ObservationLevel] = None,
|
|
28
|
-
):
|
|
29
|
-
"""
|
|
30
|
-
Create a Nine Rooms environment variant.
|
|
31
|
-
|
|
32
|
-
Args:
|
|
33
|
-
variant: Environment variant ("NineRooms", "SpiralNineRooms", "TwentyFiveRooms")
|
|
34
|
-
obs_level: Observation level (ObservationLevel enum)
|
|
35
|
-
continuous: Whether to use continuous actions
|
|
36
|
-
size: Observation image size (rendered directly at this size to avoid resizing)
|
|
37
|
-
room_size: Size of each room in environment units
|
|
38
|
-
door_size: Size of doors between rooms
|
|
39
|
-
agent_mode: Agent rendering mode ('empty', 'circle', 'triangle', or None for default)
|
|
40
|
-
info_obs: List of observation levels to include in info dictionary
|
|
41
|
-
"""
|
|
42
|
-
self.variant = variant
|
|
43
|
-
|
|
44
|
-
# Select the appropriate environment class
|
|
45
|
-
env_classes = {
|
|
46
|
-
"NineRooms": NineRooms,
|
|
47
|
-
"SpiralNineRooms": SpiralNineRooms,
|
|
48
|
-
"TwentyFiveRooms": TwentyFiveRooms,
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
if variant not in env_classes:
|
|
52
|
-
raise ValueError(
|
|
53
|
-
f"Unknown variant '{variant}'. Available: {list(env_classes.keys())}"
|
|
54
|
-
)
|
|
55
|
-
|
|
56
|
-
env_class = env_classes[variant]
|
|
57
|
-
|
|
58
|
-
# Create base environment with direct rendering size
|
|
59
|
-
base_env = env_class(
|
|
60
|
-
room_size=room_size,
|
|
61
|
-
door_size=door_size,
|
|
62
|
-
obs_level=obs_level,
|
|
63
|
-
continuous=continuous,
|
|
64
|
-
obs_width=size,
|
|
65
|
-
obs_height=size,
|
|
66
|
-
agent_mode=agent_mode,
|
|
67
|
-
info_obs=info_obs,
|
|
68
|
-
)
|
|
69
|
-
|
|
70
|
-
# Apply wrappers - no resize needed since we render at target size
|
|
71
|
-
|
|
72
|
-
# Initialize gym.Wrapper with the base environment
|
|
73
|
-
super().__init__(base_env)
|
|
74
|
-
|
|
75
|
-
def render_on_pos(self, pos):
|
|
76
|
-
"""Render observation from a specific position."""
|
|
77
|
-
# Get access to the base environment
|
|
78
|
-
base_env = self.env
|
|
79
|
-
while hasattr(base_env, "env") or hasattr(base_env, "_env"):
|
|
80
|
-
if hasattr(base_env, "env"):
|
|
81
|
-
base_env = base_env.env
|
|
82
|
-
elif hasattr(base_env, "_env"):
|
|
83
|
-
base_env = base_env._env
|
|
84
|
-
else:
|
|
85
|
-
break
|
|
86
|
-
|
|
87
|
-
# Store original position
|
|
88
|
-
original_pos = base_env.agent.pos.copy()
|
|
89
|
-
|
|
90
|
-
# Move agent to target position
|
|
91
|
-
base_env.place_agent(pos=pos)
|
|
92
|
-
|
|
93
|
-
# Get first-person observation from the agent's perspective at this position
|
|
94
|
-
obs = base_env.render_obs()
|
|
95
|
-
|
|
96
|
-
# Restore original position
|
|
97
|
-
base_env.place_agent(pos=original_pos)
|
|
98
|
-
|
|
99
|
-
# Apply wrapper transformations manually for consistency
|
|
100
|
-
# Convert to PyTorch format (CHW) - no resize needed since we render at target size
|
|
101
|
-
obs = np.transpose(obs, (2, 0, 1))
|
|
102
|
-
|
|
103
|
-
return obs
|
|
104
|
-
|
|
105
6
|
|
|
7
|
+
# Register environment variants with factory defaults matching the original wrapper
|
|
8
|
+
register(
|
|
9
|
+
id="NineRooms-v0",
|
|
10
|
+
entry_point="miniworld_maze.environments.nine_rooms:NineRooms",
|
|
11
|
+
max_episode_steps=1000,
|
|
12
|
+
kwargs={
|
|
13
|
+
"room_size": FACTORY_ROOM_SIZE,
|
|
14
|
+
"door_size": FACTORY_DOOR_SIZE,
|
|
15
|
+
"obs_level": ObservationLevel.TOP_DOWN_PARTIAL,
|
|
16
|
+
"agent_mode": None, # becomes "empty" by default
|
|
17
|
+
},
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
register(
|
|
21
|
+
id="SpiralNineRooms-v0",
|
|
22
|
+
entry_point="miniworld_maze.environments.spiral_nine_rooms:SpiralNineRooms",
|
|
23
|
+
max_episode_steps=1000,
|
|
24
|
+
kwargs={
|
|
25
|
+
"room_size": FACTORY_ROOM_SIZE,
|
|
26
|
+
"door_size": FACTORY_DOOR_SIZE,
|
|
27
|
+
"obs_level": ObservationLevel.TOP_DOWN_PARTIAL,
|
|
28
|
+
"agent_mode": None,
|
|
29
|
+
},
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
register(
|
|
33
|
+
id="TwentyFiveRooms-v0",
|
|
34
|
+
entry_point="miniworld_maze.environments.twenty_five_rooms:TwentyFiveRooms",
|
|
35
|
+
max_episode_steps=1000,
|
|
36
|
+
kwargs={
|
|
37
|
+
"room_size": FACTORY_ROOM_SIZE,
|
|
38
|
+
"door_size": FACTORY_DOOR_SIZE,
|
|
39
|
+
"obs_level": ObservationLevel.TOP_DOWN_PARTIAL,
|
|
40
|
+
"agent_mode": None,
|
|
41
|
+
},
|
|
42
|
+
)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""NineRooms environment implementation."""
|
|
2
2
|
|
|
3
3
|
from ..core import ObservationLevel
|
|
4
|
+
from ..core.constants import TextureThemes
|
|
4
5
|
from .base_grid_rooms import GridRoomsEnvironment
|
|
5
6
|
|
|
6
7
|
|
|
@@ -46,31 +47,12 @@ class NineRooms(GridRoomsEnvironment):
|
|
|
46
47
|
(6, 7),
|
|
47
48
|
(7, 8),
|
|
48
49
|
]
|
|
49
|
-
default_textures =
|
|
50
|
-
"beige",
|
|
51
|
-
"lightbeige",
|
|
52
|
-
"lightgray",
|
|
53
|
-
"copperred",
|
|
54
|
-
"skyblue",
|
|
55
|
-
"lightcobaltgreen",
|
|
56
|
-
"oakbrown",
|
|
57
|
-
"navyblue",
|
|
58
|
-
"cobaltgreen",
|
|
59
|
-
]
|
|
50
|
+
default_textures = TextureThemes.NINE_ROOMS
|
|
60
51
|
|
|
61
52
|
# Initialize goal positions for each room (2 goals per room)
|
|
62
|
-
goal_positions =
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
center_x = room_size * j + room_size / 2
|
|
66
|
-
center_z = room_size * i + room_size / 2
|
|
67
|
-
# Two goals per room: center-left and center-right
|
|
68
|
-
goal_positions.append(
|
|
69
|
-
[
|
|
70
|
-
[center_x - 1.0, 0.0, center_z], # left goal
|
|
71
|
-
[center_x + 1.0, 0.0, center_z], # right goal
|
|
72
|
-
]
|
|
73
|
-
)
|
|
53
|
+
goal_positions = GridRoomsEnvironment._generate_goal_positions(
|
|
54
|
+
3, room_size, goals_per_room=2
|
|
55
|
+
)
|
|
74
56
|
|
|
75
57
|
super().__init__(
|
|
76
58
|
grid_size=3,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""SpiralNineRooms environment implementation."""
|
|
2
2
|
|
|
3
3
|
from ..core import ObservationLevel
|
|
4
|
+
from ..core.constants import TextureThemes
|
|
4
5
|
from .base_grid_rooms import GridRoomsEnvironment
|
|
5
6
|
|
|
6
7
|
|
|
@@ -42,31 +43,12 @@ class SpiralNineRooms(GridRoomsEnvironment):
|
|
|
42
43
|
(6, 7),
|
|
43
44
|
(7, 8),
|
|
44
45
|
]
|
|
45
|
-
default_textures =
|
|
46
|
-
"beige",
|
|
47
|
-
"lightbeige",
|
|
48
|
-
"lightgray",
|
|
49
|
-
"copperred",
|
|
50
|
-
"skyblue",
|
|
51
|
-
"lightcobaltgreen",
|
|
52
|
-
"oakbrown",
|
|
53
|
-
"navyblue",
|
|
54
|
-
"cobaltgreen",
|
|
55
|
-
]
|
|
46
|
+
default_textures = TextureThemes.SPIRAL_NINE_ROOMS
|
|
56
47
|
|
|
57
48
|
# Initialize goal positions for each room (2 goals per room)
|
|
58
|
-
goal_positions =
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
center_x = room_size * j + room_size / 2
|
|
62
|
-
center_z = room_size * i + room_size / 2
|
|
63
|
-
# Two goals per room: center-left and center-right
|
|
64
|
-
goal_positions.append(
|
|
65
|
-
[
|
|
66
|
-
[center_x - 1.0, 0.0, center_z], # left goal
|
|
67
|
-
[center_x + 1.0, 0.0, center_z], # right goal
|
|
68
|
-
]
|
|
69
|
-
)
|
|
49
|
+
goal_positions = GridRoomsEnvironment._generate_goal_positions(
|
|
50
|
+
3, room_size, goals_per_room=2
|
|
51
|
+
)
|
|
70
52
|
|
|
71
53
|
super().__init__(
|
|
72
54
|
grid_size=3,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""TwentyFiveRooms environment implementation."""
|
|
2
2
|
|
|
3
3
|
from ..core import ObservationLevel
|
|
4
|
+
from ..core.constants import TextureThemes
|
|
4
5
|
from .base_grid_rooms import GridRoomsEnvironment
|
|
5
6
|
|
|
6
7
|
|
|
@@ -78,46 +79,12 @@ class TwentyFiveRooms(GridRoomsEnvironment):
|
|
|
78
79
|
(22, 23),
|
|
79
80
|
(23, 24),
|
|
80
81
|
]
|
|
81
|
-
default_textures =
|
|
82
|
-
"crimson",
|
|
83
|
-
"beanpaste",
|
|
84
|
-
"cobaltgreen",
|
|
85
|
-
"lightnavyblue",
|
|
86
|
-
"skyblue",
|
|
87
|
-
"lightcobaltgreen",
|
|
88
|
-
"oakbrown",
|
|
89
|
-
"copperred",
|
|
90
|
-
"lightgray",
|
|
91
|
-
"lime",
|
|
92
|
-
"turquoise",
|
|
93
|
-
"violet",
|
|
94
|
-
"beige",
|
|
95
|
-
"morningglory",
|
|
96
|
-
"silver",
|
|
97
|
-
"magenta",
|
|
98
|
-
"sunnyyellow",
|
|
99
|
-
"blueberry",
|
|
100
|
-
"lightbeige",
|
|
101
|
-
"seablue",
|
|
102
|
-
"lemongrass",
|
|
103
|
-
"orchid",
|
|
104
|
-
"redbean",
|
|
105
|
-
"orange",
|
|
106
|
-
"realblueberry",
|
|
107
|
-
]
|
|
82
|
+
default_textures = TextureThemes.TWENTY_FIVE_ROOMS
|
|
108
83
|
|
|
109
84
|
# Initialize goal positions for each room (1 goal per room at center)
|
|
110
|
-
goal_positions =
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
center_x = room_size * j + room_size / 2
|
|
114
|
-
center_z = room_size * i + room_size / 2
|
|
115
|
-
# One goal per room at the center
|
|
116
|
-
goal_positions.append(
|
|
117
|
-
[
|
|
118
|
-
[center_x, 0.0, center_z],
|
|
119
|
-
]
|
|
120
|
-
)
|
|
85
|
+
goal_positions = GridRoomsEnvironment._generate_goal_positions(
|
|
86
|
+
5, room_size, goals_per_room=1
|
|
87
|
+
)
|
|
121
88
|
|
|
122
89
|
super().__init__(
|
|
123
90
|
grid_size=5,
|
miniworld_maze/tools/__init__.py
CHANGED
miniworld_maze/utils.py
ADDED
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
"""Utility functions for miniworld_maze package."""
|
|
2
|
+
|
|
3
|
+
from typing import Tuple, Union
|
|
4
|
+
import numpy as np
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def environment_to_pixel_coords(
|
|
8
|
+
env_pos: np.ndarray,
|
|
9
|
+
env_min: np.ndarray,
|
|
10
|
+
env_max: np.ndarray,
|
|
11
|
+
image_size: Union[int, Tuple[int, int]],
|
|
12
|
+
) -> Tuple[int, int]:
|
|
13
|
+
"""
|
|
14
|
+
Convert environment coordinates to pixel coordinates.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
env_pos: Environment position as ndarray [x, z] or [x, y]
|
|
18
|
+
env_min: Environment minimum bounds as ndarray [min_x, min_z] or [min_x, min_y]
|
|
19
|
+
env_max: Environment maximum bounds as ndarray [max_x, max_z] or [max_x, max_y]
|
|
20
|
+
image_size: Image size (width, height) or single size for square image
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
Tuple of (pixel_x, pixel_y) coordinates
|
|
24
|
+
"""
|
|
25
|
+
env_x, env_y = env_pos[:2]
|
|
26
|
+
env_min_x, env_min_y = env_min[:2]
|
|
27
|
+
env_max_x, env_max_y = env_max[:2]
|
|
28
|
+
|
|
29
|
+
if isinstance(image_size, int):
|
|
30
|
+
width = height = image_size
|
|
31
|
+
else:
|
|
32
|
+
width, height = image_size
|
|
33
|
+
|
|
34
|
+
# Normalize to [0, 1] range and scale to pixel coordinates
|
|
35
|
+
pixel_x = int((env_x - env_min_x) / (env_max_x - env_min_x) * width)
|
|
36
|
+
pixel_y = int((env_y - env_min_y) / (env_max_y - env_min_y) * height)
|
|
37
|
+
|
|
38
|
+
return pixel_x, pixel_y
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def pixel_to_environment_coords(
|
|
42
|
+
pixel_pos: np.ndarray,
|
|
43
|
+
env_min: np.ndarray,
|
|
44
|
+
env_max: np.ndarray,
|
|
45
|
+
image_size: Union[int, Tuple[int, int]],
|
|
46
|
+
) -> Tuple[float, float]:
|
|
47
|
+
"""
|
|
48
|
+
Convert pixel coordinates to environment coordinates.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
pixel_pos: Pixel position as ndarray [x, y]
|
|
52
|
+
env_min: Environment minimum bounds as ndarray [min_x, min_z] or [min_x, min_y]
|
|
53
|
+
env_max: Environment maximum bounds as ndarray [max_x, max_z] or [max_x, max_y]
|
|
54
|
+
image_size: Image size (width, height) or single size for square image
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
Tuple of (env_x, env_y) coordinates
|
|
58
|
+
"""
|
|
59
|
+
pixel_x, pixel_y = pixel_pos[:2]
|
|
60
|
+
env_min_x, env_min_y = env_min[:2]
|
|
61
|
+
env_max_x, env_max_y = env_max[:2]
|
|
62
|
+
|
|
63
|
+
if isinstance(image_size, int):
|
|
64
|
+
width = height = image_size
|
|
65
|
+
else:
|
|
66
|
+
width, height = image_size
|
|
67
|
+
|
|
68
|
+
# Convert to normalized [0, 1] range and scale to environment coordinates
|
|
69
|
+
env_x = pixel_x / width * (env_max_x - env_min_x) + env_min_x
|
|
70
|
+
env_y = pixel_y / height * (env_max_y - env_min_y) + env_min_y
|
|
71
|
+
|
|
72
|
+
return env_x, env_y
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def clamp_to_bounds(
|
|
76
|
+
value: Union[int, float, np.ndarray],
|
|
77
|
+
min_val: Union[int, float, np.ndarray],
|
|
78
|
+
max_val: Union[int, float, np.ndarray],
|
|
79
|
+
) -> Union[int, float, np.ndarray]:
|
|
80
|
+
"""
|
|
81
|
+
Clamp a value or array of values to specified bounds.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
value: Value(s) to clamp (scalar or ndarray)
|
|
85
|
+
min_val: Minimum bound(s) (scalar or ndarray)
|
|
86
|
+
max_val: Maximum bound(s) (scalar or ndarray)
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
Clamped value(s)
|
|
90
|
+
"""
|
|
91
|
+
if isinstance(value, np.ndarray):
|
|
92
|
+
return np.clip(value, min_val, max_val)
|
|
93
|
+
else:
|
|
94
|
+
return max(min_val, min(max_val, value))
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def clamp_pixel_coords(
|
|
98
|
+
pixel_x: int, pixel_y: int, image_size: Union[int, Tuple[int, int]]
|
|
99
|
+
) -> Tuple[int, int]:
|
|
100
|
+
"""
|
|
101
|
+
Clamp pixel coordinates to image bounds.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
pixel_x: X pixel coordinate
|
|
105
|
+
pixel_y: Y pixel coordinate
|
|
106
|
+
image_size: Image size (width, height) or single size for square image
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
Tuple of clamped (pixel_x, pixel_y) coordinates
|
|
110
|
+
"""
|
|
111
|
+
if isinstance(image_size, int):
|
|
112
|
+
width = height = image_size
|
|
113
|
+
else:
|
|
114
|
+
width, height = image_size
|
|
115
|
+
|
|
116
|
+
clamped_x = max(0, min(width - 1, pixel_x))
|
|
117
|
+
clamped_y = max(0, min(height - 1, pixel_y))
|
|
118
|
+
|
|
119
|
+
return clamped_x, clamped_y
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def normalize_coordinates(
|
|
123
|
+
coords: np.ndarray,
|
|
124
|
+
min_bounds: np.ndarray,
|
|
125
|
+
max_bounds: np.ndarray,
|
|
126
|
+
) -> Tuple[float, float]:
|
|
127
|
+
"""
|
|
128
|
+
Normalize coordinates to [0, 1] range based on given bounds.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
coords: Coordinates to normalize as ndarray
|
|
132
|
+
min_bounds: Minimum bounds as ndarray
|
|
133
|
+
max_bounds: Maximum bounds as ndarray
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
Normalized coordinates as (x, y) tuple
|
|
137
|
+
"""
|
|
138
|
+
x, y = coords[:2]
|
|
139
|
+
min_x, min_y = min_bounds[:2]
|
|
140
|
+
max_x, max_y = max_bounds[:2]
|
|
141
|
+
|
|
142
|
+
norm_x = (x - min_x) / (max_x - min_x)
|
|
143
|
+
norm_y = (y - min_y) / (max_y - min_y)
|
|
144
|
+
|
|
145
|
+
return norm_x, norm_y
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def denormalize_coordinates(
|
|
149
|
+
normalized_coords: np.ndarray,
|
|
150
|
+
min_bounds: np.ndarray,
|
|
151
|
+
max_bounds: np.ndarray,
|
|
152
|
+
) -> Tuple[float, float]:
|
|
153
|
+
"""
|
|
154
|
+
Convert normalized [0, 1] coordinates back to original coordinate space.
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
normalized_coords: Normalized coordinates in [0, 1] range as ndarray
|
|
158
|
+
min_bounds: Original minimum bounds as ndarray
|
|
159
|
+
max_bounds: Original maximum bounds as ndarray
|
|
160
|
+
|
|
161
|
+
Returns:
|
|
162
|
+
Denormalized coordinates as (x, y) tuple
|
|
163
|
+
"""
|
|
164
|
+
norm_x, norm_y = normalized_coords[:2]
|
|
165
|
+
min_x, min_y = min_bounds[:2]
|
|
166
|
+
max_x, max_y = max_bounds[:2]
|
|
167
|
+
|
|
168
|
+
x = norm_x * (max_x - min_x) + min_x
|
|
169
|
+
y = norm_y * (max_y - min_y) + min_y
|
|
170
|
+
|
|
171
|
+
return x, y
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def get_environment_bounds(env) -> Tuple[Tuple[float, float], Tuple[float, float]]:
|
|
175
|
+
"""
|
|
176
|
+
Extract environment bounds from an environment object.
|
|
177
|
+
|
|
178
|
+
Args:
|
|
179
|
+
env: Environment object (may be wrapped)
|
|
180
|
+
|
|
181
|
+
Returns:
|
|
182
|
+
Tuple of ((min_x, min_z), (max_x, max_z))
|
|
183
|
+
"""
|
|
184
|
+
# Unwrap environment to access base environment
|
|
185
|
+
base_env = env
|
|
186
|
+
while hasattr(base_env, "env"):
|
|
187
|
+
base_env = base_env.env
|
|
188
|
+
|
|
189
|
+
min_bounds = (base_env.min_x, base_env.min_z)
|
|
190
|
+
max_bounds = (base_env.max_x, base_env.max_z)
|
|
191
|
+
|
|
192
|
+
return min_bounds, max_bounds
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def calculate_view_size_from_bounds(
|
|
196
|
+
min_bounds: np.ndarray,
|
|
197
|
+
max_bounds: np.ndarray,
|
|
198
|
+
) -> Tuple[float, float]:
|
|
199
|
+
"""
|
|
200
|
+
Calculate view size (width, height) from coordinate bounds.
|
|
201
|
+
|
|
202
|
+
Args:
|
|
203
|
+
min_bounds: Minimum bounds as ndarray (min_x, min_y)
|
|
204
|
+
max_bounds: Maximum bounds as ndarray (max_x, max_y)
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
Tuple of (width, height)
|
|
208
|
+
"""
|
|
209
|
+
min_x, min_y = min_bounds[:2]
|
|
210
|
+
max_x, max_y = max_bounds[:2]
|
|
211
|
+
|
|
212
|
+
width = max_x - min_x
|
|
213
|
+
height = max_y - min_y
|
|
214
|
+
|
|
215
|
+
return width, height
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def scale_coordinates(
|
|
219
|
+
coords: np.ndarray,
|
|
220
|
+
scale_factor: Union[float, Tuple[float, float]],
|
|
221
|
+
) -> Tuple[float, float]:
|
|
222
|
+
"""
|
|
223
|
+
Scale coordinates by a given factor.
|
|
224
|
+
|
|
225
|
+
Args:
|
|
226
|
+
coords: Coordinates to scale as ndarray
|
|
227
|
+
scale_factor: Scale factor (uniform) or (scale_x, scale_y)
|
|
228
|
+
|
|
229
|
+
Returns:
|
|
230
|
+
Scaled coordinates as (x, y) tuple
|
|
231
|
+
"""
|
|
232
|
+
x, y = coords[:2]
|
|
233
|
+
|
|
234
|
+
if isinstance(scale_factor, (tuple, list)):
|
|
235
|
+
scale_x, scale_y = scale_factor
|
|
236
|
+
else:
|
|
237
|
+
scale_x = scale_y = scale_factor
|
|
238
|
+
|
|
239
|
+
return x * scale_x, y * scale_y
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def distance_2d(
|
|
243
|
+
pos1: np.ndarray,
|
|
244
|
+
pos2: np.ndarray,
|
|
245
|
+
) -> float:
|
|
246
|
+
"""
|
|
247
|
+
Calculate 2D Euclidean distance between two positions.
|
|
248
|
+
|
|
249
|
+
Args:
|
|
250
|
+
pos1: First position as ndarray (x, y)
|
|
251
|
+
pos2: Second position as ndarray (x, y)
|
|
252
|
+
|
|
253
|
+
Returns:
|
|
254
|
+
Euclidean distance
|
|
255
|
+
"""
|
|
256
|
+
x1, y1 = pos1[:2]
|
|
257
|
+
x2, y2 = pos2[:2]
|
|
258
|
+
|
|
259
|
+
return np.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def lerp_2d(
|
|
263
|
+
pos1: np.ndarray,
|
|
264
|
+
pos2: np.ndarray,
|
|
265
|
+
t: float,
|
|
266
|
+
) -> Tuple[float, float]:
|
|
267
|
+
"""
|
|
268
|
+
Linear interpolation between two 2D positions.
|
|
269
|
+
|
|
270
|
+
Args:
|
|
271
|
+
pos1: Start position as ndarray (x, y)
|
|
272
|
+
pos2: End position as ndarray (x, y)
|
|
273
|
+
t: Interpolation parameter [0, 1]
|
|
274
|
+
|
|
275
|
+
Returns:
|
|
276
|
+
Interpolated position as (x, y) tuple
|
|
277
|
+
"""
|
|
278
|
+
x1, y1 = pos1[:2]
|
|
279
|
+
x2, y2 = pos2[:2]
|
|
280
|
+
|
|
281
|
+
t = clamp_to_bounds(t, 0.0, 1.0)
|
|
282
|
+
|
|
283
|
+
x = x1 + t * (x2 - x1)
|
|
284
|
+
y = y1 + t * (y2 - y1)
|
|
285
|
+
|
|
286
|
+
return x, y
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: miniworld-maze
|
|
3
|
-
Version: 1.
|
|
4
|
-
Summary: Multi-room maze environments from the DrStrategy paper
|
|
3
|
+
Version: 1.2.0
|
|
4
|
+
Summary: Multi-room maze environments from the DrStrategy paper. Provides NineRooms-v0, SpiralNineRooms-v0, and TwentyFiveRooms-v0 gymnasium environments.
|
|
5
5
|
Keywords: reinforcement-learning,environment,gymnasium,multi-room-maze,drstrategy,maze-navigation,partial-observability,3d-environments
|
|
6
6
|
Author: Tim Joseph
|
|
7
7
|
Author-email: Tim Joseph <tim@mctigger.com>
|
|
@@ -46,9 +46,6 @@ Requires-Dist: opencv-python>=4.5.0,<5.0.0
|
|
|
46
46
|
Requires-Dist: pillow>=8.0.0,<11.0.0
|
|
47
47
|
Requires-Dist: pyopengl>=3.1.0,<4.0.0
|
|
48
48
|
Requires-Dist: pyglet>=1.5.0,<2.0.0
|
|
49
|
-
Requires-Dist: black>=22.0,<25.0 ; extra == 'dev'
|
|
50
|
-
Requires-Dist: isort>=5.10,<6.0 ; extra == 'dev'
|
|
51
|
-
Requires-Dist: flake8>=4.0,<7.0 ; extra == 'dev'
|
|
52
49
|
Requires-Dist: ruff>=0.1.0,<1.0.0 ; extra == 'dev'
|
|
53
50
|
Requires-Dist: build>=0.8.0,<2.0.0 ; extra == 'dev'
|
|
54
51
|
Requires-Dist: twine>=4.0.0,<6.0.0 ; extra == 'dev'
|
|
@@ -83,6 +80,18 @@ pip install miniworld-maze
|
|
|
83
80
|
|
|
84
81
|
## Usage
|
|
85
82
|
|
|
83
|
+
### Registered Environments
|
|
84
|
+
|
|
85
|
+
This package registers the following gymnasium environments:
|
|
86
|
+
|
|
87
|
+
| Environment ID | Description | Rooms | Max Steps |
|
|
88
|
+
|---|---|---|---|
|
|
89
|
+
| `NineRooms-v0` | Standard 3×3 grid with adjacent room connections | 9 | 1000 |
|
|
90
|
+
| `SpiralNineRooms-v0` | 3×3 grid with spiral connection pattern | 9 | 1000 |
|
|
91
|
+
| `TwentyFiveRooms-v0` | Large 5×5 grid with complex navigation | 25 | 1000 |
|
|
92
|
+
|
|
93
|
+
All environments use `TOP_DOWN_PARTIAL` observation level and factory default room/door sizes by default.
|
|
94
|
+
|
|
86
95
|
### Basic Usage
|
|
87
96
|
|
|
88
97
|
See `examples/basic_usage.py` for a complete working example:
|
|
@@ -95,12 +104,13 @@ Basic usage example for miniworld-maze environments.
|
|
|
95
104
|
This is a minimal example showing how to create and interact with the environment.
|
|
96
105
|
"""
|
|
97
106
|
|
|
98
|
-
|
|
107
|
+
import gymnasium as gym
|
|
108
|
+
import miniworld_maze # noqa: F401
|
|
99
109
|
|
|
100
110
|
|
|
101
111
|
def main():
|
|
102
|
-
# Create environment
|
|
103
|
-
env =
|
|
112
|
+
# Create environment using gymnasium registry
|
|
113
|
+
env = gym.make("NineRooms-v0", obs_width=64, obs_height=64)
|
|
104
114
|
obs, info = env.reset()
|
|
105
115
|
|
|
106
116
|
# obs is a dictionary containing:
|
|
@@ -212,14 +222,15 @@ Each environment supports three different observation modes:
|
|
|
212
222
|
All environments can be customized with the following parameters:
|
|
213
223
|
|
|
214
224
|
```python
|
|
215
|
-
|
|
216
|
-
from miniworld_maze
|
|
225
|
+
import gymnasium as gym
|
|
226
|
+
from miniworld_maze import ObservationLevel
|
|
227
|
+
import miniworld_maze # noqa: F401
|
|
217
228
|
|
|
218
|
-
env =
|
|
219
|
-
|
|
229
|
+
env = gym.make(
|
|
230
|
+
"NineRooms-v0", # Environment variant
|
|
220
231
|
obs_level=ObservationLevel.TOP_DOWN_PARTIAL, # Observation type
|
|
221
|
-
|
|
222
|
-
|
|
232
|
+
obs_width=64, # Observation image width
|
|
233
|
+
obs_height=64, # Observation image height
|
|
223
234
|
room_size=5, # Size of each room in environment units
|
|
224
235
|
door_size=2, # Size of doors between rooms
|
|
225
236
|
agent_mode="empty", # Agent rendering: "empty", "circle", "triangle"
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
miniworld_maze/__init__.py,sha256=
|
|
1
|
+
miniworld_maze/__init__.py,sha256=Dfq3558bJ5kgxDZxPxvlHu1531zfBhtiDfKbVL_4zCE,1430
|
|
2
2
|
miniworld_maze/core/__init__.py,sha256=5BA4WKXQjrG55TNaEid2JGrnf1KQniJZ1HhRqovM1Q0,293
|
|
3
|
-
miniworld_maze/core/constants.py,sha256=
|
|
3
|
+
miniworld_maze/core/constants.py,sha256=UXqmuvsT9ww1GljW74lWPPP9XxVmvti-8JfM_RBSoWQ,4314
|
|
4
4
|
miniworld_maze/core/miniworld_gymnasium/README.md,sha256=kkZgkRKBdgixpot3uHuiBFlRIKRFIVBVfXwu68XTEv0,74
|
|
5
|
-
miniworld_maze/core/miniworld_gymnasium/__init__.py,sha256=
|
|
5
|
+
miniworld_maze/core/miniworld_gymnasium/__init__.py,sha256=ALOqr4SCVk9I9bmxyoU0Du8LUPE_57jIyZuV4QhOcBc,159
|
|
6
6
|
miniworld_maze/core/miniworld_gymnasium/base_env.py,sha256=heoJxEw_GGEHSzU75MWgqRkWARY3lgTgwNE9Zp1P_YA,1795
|
|
7
7
|
miniworld_maze/core/miniworld_gymnasium/entities/__init__.py,sha256=gbMJcFeasvlRSIuXWyOYO-MhW0JKKY5ZNxF5TMsWJ5c,262
|
|
8
8
|
miniworld_maze/core/miniworld_gymnasium/entities/agent.py,sha256=U3Zt2Hk21rJSdzPCBj46-N930n7XAkA9qo_fKbezn2k,3153
|
|
@@ -260,19 +260,18 @@ miniworld_maze/core/miniworld_gymnasium/textures/white_1.png,sha256=wRrgs92I_Ids
|
|
|
260
260
|
miniworld_maze/core/miniworld_gymnasium/textures/wood_1.png,sha256=XRZyIN34HFo14olbxRcsHGrzCAFqUlowc6nLR22IFBE,184713
|
|
261
261
|
miniworld_maze/core/miniworld_gymnasium/textures/wood_2.png,sha256=qSDHB-ZO11JJLQuiQse-0edpbuTg1YO-eIBhdTvNUhc,93121
|
|
262
262
|
miniworld_maze/core/miniworld_gymnasium/textures/wood_planks_1.png,sha256=E4SNN1s4yOtkLfZFQy905eip6KvDWnnPUrpS82FxMAg,847259
|
|
263
|
-
miniworld_maze/core/miniworld_gymnasium/unified_env.py,sha256=
|
|
263
|
+
miniworld_maze/core/miniworld_gymnasium/unified_env.py,sha256=B7kfG6axiL05o3kLiBvE6O1Q7kB8PaLdlAt1-Sksv0I,46529
|
|
264
264
|
miniworld_maze/core/miniworld_gymnasium/utils.py,sha256=9cfpg4qYz-Esxvu8nTMPFJc-Tl0TRxTrX6cfg0YuK_o,1007
|
|
265
265
|
miniworld_maze/core/miniworld_gymnasium/wrappers.py,sha256=cD0nGSJYNU96zoWv63aEiKd986POhtHfGGEpNpRL5ec,122
|
|
266
266
|
miniworld_maze/core/observation_types.py,sha256=Co8mEIXzIgk0MLx6tqeBd1EE0PuZOL1gbZwobiEde08,1316
|
|
267
|
-
miniworld_maze/environments/__init__.py,sha256=
|
|
268
|
-
miniworld_maze/environments/base_grid_rooms.py,sha256=
|
|
269
|
-
miniworld_maze/environments/factory.py,sha256=
|
|
270
|
-
miniworld_maze/environments/nine_rooms.py,sha256=
|
|
271
|
-
miniworld_maze/environments/spiral_nine_rooms.py,sha256=
|
|
272
|
-
miniworld_maze/environments/twenty_five_rooms.py,sha256=
|
|
273
|
-
miniworld_maze/tools/__init__.py,sha256=
|
|
274
|
-
miniworld_maze/
|
|
275
|
-
miniworld_maze-1.
|
|
276
|
-
miniworld_maze-1.
|
|
277
|
-
miniworld_maze-1.
|
|
278
|
-
miniworld_maze-1.1.0.dist-info/RECORD,,
|
|
267
|
+
miniworld_maze/environments/__init__.py,sha256=DKld5MQU7x9eNL6BlxIettA44bCiIn2zIpYECDCNxoQ,331
|
|
268
|
+
miniworld_maze/environments/base_grid_rooms.py,sha256=j4LnyUv0YjHlCLfjqX3fyoXUOOyCr4e3_Pdj-sM3FHo,14753
|
|
269
|
+
miniworld_maze/environments/factory.py,sha256=Zk26JawsMgSLMgvSnQxFhQCD8yMH76HgqFtogzWFfqY,1333
|
|
270
|
+
miniworld_maze/environments/nine_rooms.py,sha256=Ct96cKtSt1_nLNI5RBUhwqdNUQq1rHfBJ3aB5Igbdow,1794
|
|
271
|
+
miniworld_maze/environments/spiral_nine_rooms.py,sha256=a_pUuv-ghez8h76Z7YsHkQoLXsQ-w9azKLjEjO4uKmA,1749
|
|
272
|
+
miniworld_maze/environments/twenty_five_rooms.py,sha256=MewKPDHDilscQGTT3aGRrSHvo4uFgHHAOrnJMrHaezQ,2598
|
|
273
|
+
miniworld_maze/tools/__init__.py,sha256=XiReXrJIcBKvDVyPZrKPq1mckJs0_WC7q_RmdXXcKHs,55
|
|
274
|
+
miniworld_maze/utils.py,sha256=HTOkfRq72oOC844gVXjWMH1ox7wdSxfCS6oTrWBw05Q,7523
|
|
275
|
+
miniworld_maze-1.2.0.dist-info/WHEEL,sha256=NHRAbdxxzyL9K3IO2LjmlNqKSyPZnKv2BD16YYVKo18,79
|
|
276
|
+
miniworld_maze-1.2.0.dist-info/METADATA,sha256=QHJBCKSCHQr2u-Utcck1qMOffK1Cc6T0sV2k6_H7OAE,9657
|
|
277
|
+
miniworld_maze-1.2.0.dist-info/RECORD,,
|
|
@@ -1,199 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
Generate comprehensive observations for Nine Rooms environment variants.
|
|
4
|
-
Supports: NineRooms, SpiralNineRooms, TwentyFiveRooms
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
import argparse
|
|
8
|
-
import os
|
|
9
|
-
|
|
10
|
-
import numpy as np
|
|
11
|
-
from PIL import Image
|
|
12
|
-
|
|
13
|
-
from ..core import FrameBuffer
|
|
14
|
-
from ..environments.factory import NineRoomsEnvironmentWrapper
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
def generate_observations(variant, output_dir=None, high_res_full_views=False):
|
|
18
|
-
"""Generate comprehensive observations for the specified environment variant."""
|
|
19
|
-
if output_dir is None:
|
|
20
|
-
output_dir = f"{variant.lower()}_observations"
|
|
21
|
-
|
|
22
|
-
os.makedirs(output_dir, exist_ok=True)
|
|
23
|
-
|
|
24
|
-
# Create environment
|
|
25
|
-
env = NineRoomsEnvironmentWrapper(variant=variant, size=64)
|
|
26
|
-
|
|
27
|
-
# Get base environment for direct render access
|
|
28
|
-
base_env = getattr(env, "env", getattr(env, "_env", env))
|
|
29
|
-
while hasattr(base_env, "env") or hasattr(base_env, "_env"):
|
|
30
|
-
if hasattr(base_env, "env"):
|
|
31
|
-
base_env = base_env.env
|
|
32
|
-
elif hasattr(base_env, "_env"):
|
|
33
|
-
base_env = base_env._env
|
|
34
|
-
else:
|
|
35
|
-
break
|
|
36
|
-
|
|
37
|
-
# Reset environment
|
|
38
|
-
obs, _ = env.reset(seed=42)
|
|
39
|
-
|
|
40
|
-
# Create high-resolution frame buffer if requested
|
|
41
|
-
high_res_fb = None
|
|
42
|
-
if high_res_full_views:
|
|
43
|
-
high_res_fb = FrameBuffer(512, 512, 8)
|
|
44
|
-
|
|
45
|
-
# === FULL ENVIRONMENT OBSERVATIONS ===
|
|
46
|
-
|
|
47
|
-
# 1. Full view with agent at starting position
|
|
48
|
-
if high_res_full_views:
|
|
49
|
-
full_view_start = base_env.render_top_view(
|
|
50
|
-
frame_buffer=high_res_fb, POMDP=False
|
|
51
|
-
)
|
|
52
|
-
else:
|
|
53
|
-
full_view_start = base_env.render_top_view(POMDP=False)
|
|
54
|
-
Image.fromarray(full_view_start).save(f"{output_dir}/full_view_start.png")
|
|
55
|
-
|
|
56
|
-
# 2. Full view without agent (clean maze view)
|
|
57
|
-
if high_res_full_views:
|
|
58
|
-
full_view_clean = base_env.render_top_view(
|
|
59
|
-
frame_buffer=high_res_fb, POMDP=False, render_ag=False
|
|
60
|
-
)
|
|
61
|
-
else:
|
|
62
|
-
full_view_clean = base_env.render_top_view(POMDP=False, render_ag=False)
|
|
63
|
-
Image.fromarray(full_view_clean).save(f"{output_dir}/full_view_clean.png")
|
|
64
|
-
|
|
65
|
-
# 3. Full view with agent in center
|
|
66
|
-
center_x = (base_env.min_x + base_env.max_x) / 2
|
|
67
|
-
center_z = (base_env.min_z + base_env.max_z) / 2
|
|
68
|
-
base_env.place_agent(pos=[center_x, 0.0, center_z])
|
|
69
|
-
if high_res_full_views:
|
|
70
|
-
full_view_center = base_env.render_top_view(
|
|
71
|
-
frame_buffer=high_res_fb, POMDP=False
|
|
72
|
-
)
|
|
73
|
-
else:
|
|
74
|
-
full_view_center = base_env.render_top_view(POMDP=False)
|
|
75
|
-
Image.fromarray(full_view_center).save(f"{output_dir}/full_view_center.png")
|
|
76
|
-
|
|
77
|
-
# === PARTIAL OBSERVATIONS ===
|
|
78
|
-
|
|
79
|
-
# Reset agent to start position
|
|
80
|
-
base_env.place_agent(pos=[2.5, 0.0, 2.5])
|
|
81
|
-
|
|
82
|
-
# 1. Partial view from starting position
|
|
83
|
-
partial_start = base_env.render_top_view(POMDP=True)
|
|
84
|
-
Image.fromarray(partial_start).save(f"{output_dir}/partial_start.png")
|
|
85
|
-
|
|
86
|
-
# 2. Partial view from center
|
|
87
|
-
base_env.place_agent(pos=[center_x, 0.0, center_z])
|
|
88
|
-
partial_center = base_env.render_top_view(POMDP=True)
|
|
89
|
-
Image.fromarray(partial_center).save(f"{output_dir}/partial_center.png")
|
|
90
|
-
|
|
91
|
-
# 3. Partial view from corner/edge
|
|
92
|
-
corner_x = base_env.max_x - 2.5
|
|
93
|
-
corner_z = base_env.max_z - 2.5
|
|
94
|
-
base_env.place_agent(pos=[corner_x, 0.0, corner_z])
|
|
95
|
-
partial_corner = base_env.render_top_view(POMDP=True)
|
|
96
|
-
Image.fromarray(partial_corner).save(f"{output_dir}/partial_corner.png")
|
|
97
|
-
|
|
98
|
-
# 4. Partial view at strategic location
|
|
99
|
-
if variant == "NineRooms":
|
|
100
|
-
strategic_x, strategic_z = 15.0, 7.5 # Room boundary
|
|
101
|
-
elif variant == "SpiralNineRooms":
|
|
102
|
-
strategic_x, strategic_z = 22.5, 15.0 # Center of spiral
|
|
103
|
-
else: # TwentyFiveRooms
|
|
104
|
-
strategic_x, strategic_z = 37.5, 37.5 # Mid-outer area
|
|
105
|
-
|
|
106
|
-
base_env.place_agent(pos=[strategic_x, 0.0, strategic_z])
|
|
107
|
-
partial_strategic = base_env.render_top_view(POMDP=True)
|
|
108
|
-
Image.fromarray(partial_strategic).save(f"{output_dir}/partial_strategic.png")
|
|
109
|
-
|
|
110
|
-
# === WRAPPED OBSERVATIONS ===
|
|
111
|
-
|
|
112
|
-
# Reset environment
|
|
113
|
-
obs, _ = env.reset(seed=42)
|
|
114
|
-
|
|
115
|
-
# 1. Standard gymnasium observation
|
|
116
|
-
obs_hwc = np.transpose(obs, (1, 2, 0))
|
|
117
|
-
Image.fromarray(obs_hwc).save(f"{output_dir}/gymnasium_standard.png")
|
|
118
|
-
|
|
119
|
-
# 2. Observations after movement
|
|
120
|
-
actions = [2, 2, 1] # move_forward, move_forward, turn_right
|
|
121
|
-
action_names = ["move_forward", "move_forward", "turn_right"]
|
|
122
|
-
|
|
123
|
-
for i, action in enumerate(actions):
|
|
124
|
-
obs, _, _, _, _ = env.step(action)
|
|
125
|
-
obs_hwc = np.transpose(obs, (1, 2, 0))
|
|
126
|
-
Image.fromarray(obs_hwc).save(
|
|
127
|
-
f"{output_dir}/gymnasium_step_{i + 1}_{action_names[i]}.png"
|
|
128
|
-
)
|
|
129
|
-
|
|
130
|
-
# === RENDER_ON_POS EXAMPLES ===
|
|
131
|
-
|
|
132
|
-
# Define test positions based on variant
|
|
133
|
-
if variant == "NineRooms":
|
|
134
|
-
test_positions = [
|
|
135
|
-
[7.5, 0.0, 7.5], # top-middle room center
|
|
136
|
-
[37.5, 0.0, 37.5], # bottom-right room center
|
|
137
|
-
[22.5, 0.0, 22.5], # environment center
|
|
138
|
-
[7.5, 0.0, 22.5], # middle-left room center
|
|
139
|
-
]
|
|
140
|
-
elif variant == "SpiralNineRooms":
|
|
141
|
-
test_positions = [
|
|
142
|
-
[7.5, 0.0, 7.5], # top-left room (spiral start)
|
|
143
|
-
[37.5, 0.0, 37.5], # bottom-right room (spiral end)
|
|
144
|
-
[22.5, 0.0, 7.5], # top-right room
|
|
145
|
-
[7.5, 0.0, 37.5], # bottom-left room
|
|
146
|
-
]
|
|
147
|
-
else: # TwentyFiveRooms
|
|
148
|
-
test_positions = [
|
|
149
|
-
[37.5, 0.0, 37.5], # room (1,1) - near corner
|
|
150
|
-
[112.5, 0.0, 112.5], # room (4,4) - far corner
|
|
151
|
-
[75.0, 0.0, 75.0], # center room (2,2)
|
|
152
|
-
[37.5, 0.0, 112.5], # room (1,4) - opposite corner
|
|
153
|
-
]
|
|
154
|
-
|
|
155
|
-
for i, pos in enumerate(test_positions):
|
|
156
|
-
render_obs = env.render_on_pos(pos)
|
|
157
|
-
|
|
158
|
-
# Convert CHW to HWC for PIL
|
|
159
|
-
if len(render_obs.shape) == 3 and render_obs.shape[0] == 3:
|
|
160
|
-
render_obs = np.transpose(render_obs, (1, 2, 0))
|
|
161
|
-
|
|
162
|
-
Image.fromarray(render_obs).save(f"{output_dir}/render_on_pos_{i + 1}.png")
|
|
163
|
-
|
|
164
|
-
env.close()
|
|
165
|
-
return output_dir
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
def main():
|
|
169
|
-
parser = argparse.ArgumentParser(
|
|
170
|
-
description="Generate observations for Nine Rooms environment variants"
|
|
171
|
-
)
|
|
172
|
-
parser.add_argument(
|
|
173
|
-
"variant",
|
|
174
|
-
choices=["NineRooms", "SpiralNineRooms", "TwentyFiveRooms"],
|
|
175
|
-
help="Environment variant to use",
|
|
176
|
-
)
|
|
177
|
-
parser.add_argument(
|
|
178
|
-
"--output-dir",
|
|
179
|
-
type=str,
|
|
180
|
-
default=None,
|
|
181
|
-
help="Output directory for images (default: {variant}_observations)",
|
|
182
|
-
)
|
|
183
|
-
parser.add_argument(
|
|
184
|
-
"--high-res-full",
|
|
185
|
-
action="store_true",
|
|
186
|
-
help="Generate 512x512 high-resolution full environment views",
|
|
187
|
-
)
|
|
188
|
-
|
|
189
|
-
args = parser.parse_args()
|
|
190
|
-
|
|
191
|
-
output_dir = generate_observations(
|
|
192
|
-
args.variant, args.output_dir, args.high_res_full
|
|
193
|
-
)
|
|
194
|
-
|
|
195
|
-
return output_dir
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
if __name__ == "__main__":
|
|
199
|
-
main()
|
|
File without changes
|