miniworld-maze 1.0.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.

@@ -1,155 +1,42 @@
1
- """Factory for creating Nine Rooms environment variants."""
2
-
3
- import gymnasium as gym
4
- import numpy as np
1
+ """Gymnasium environment registrations for Nine Rooms environment variants."""
5
2
 
3
+ from gymnasium.envs.registration import register
6
4
  from ..core import ObservationLevel
7
5
  from ..core.constants import FACTORY_DOOR_SIZE, FACTORY_ROOM_SIZE
8
- from ..wrappers.image_transforms import ImageToPyTorch
9
- from .nine_rooms import NineRooms
10
- from .spiral_nine_rooms import SpiralNineRooms
11
- from .twenty_five_rooms import TwentyFiveRooms
12
-
13
-
14
- class NineRoomsEnvironmentWrapper(gym.Wrapper):
15
- """Unified wrapper for all Nine Rooms environment variants."""
16
-
17
- def __init__(
18
- self,
19
- variant="NineRooms",
20
- obs_level=ObservationLevel.TOP_DOWN_PARTIAL,
21
- continuous=False,
22
- size=64,
23
- room_size=FACTORY_ROOM_SIZE,
24
- door_size=FACTORY_DOOR_SIZE,
25
- agent_mode=None,
26
- ):
27
- """
28
- Create a Nine Rooms environment variant.
29
-
30
- Args:
31
- variant: Environment variant ("NineRooms", "SpiralNineRooms", "TwentyFiveRooms")
32
- obs_level: Observation level (ObservationLevel enum)
33
- continuous: Whether to use continuous actions
34
- size: Observation image size (rendered directly at this size to avoid resizing)
35
- room_size: Size of each room in environment units
36
- door_size: Size of doors between rooms
37
- agent_mode: Agent rendering mode ('empty', 'circle', 'triangle', or None for default)
38
- """
39
- self.variant = variant
40
-
41
- # Select the appropriate environment class
42
- env_classes = {
43
- "NineRooms": NineRooms,
44
- "SpiralNineRooms": SpiralNineRooms,
45
- "TwentyFiveRooms": TwentyFiveRooms,
46
- }
47
-
48
- if variant not in env_classes:
49
- raise ValueError(
50
- f"Unknown variant '{variant}'. Available: {list(env_classes.keys())}"
51
- )
52
-
53
- env_class = env_classes[variant]
54
-
55
- # Create base environment with direct rendering size
56
- base_env = env_class(
57
- room_size=room_size,
58
- door_size=door_size,
59
- obs_level=obs_level,
60
- continuous=continuous,
61
- obs_width=size,
62
- obs_height=size,
63
- agent_mode=agent_mode,
64
- )
65
-
66
- # Apply wrappers - no resize needed since we render at target size
67
- env = ImageToPyTorch(base_env)
68
-
69
- # Initialize gym.Wrapper with the wrapped environment
70
- super().__init__(env)
71
-
72
- def render_on_pos(self, pos):
73
- """Render observation from a specific position."""
74
- # Get access to the base environment
75
- base_env = self.env
76
- while hasattr(base_env, "env") or hasattr(base_env, "_env"):
77
- if hasattr(base_env, "env"):
78
- base_env = base_env.env
79
- elif hasattr(base_env, "_env"):
80
- base_env = base_env._env
81
- else:
82
- break
83
-
84
- # Store original position
85
- original_pos = base_env.agent.pos.copy()
86
-
87
- # Move agent to target position
88
- base_env.place_agent(pos=pos)
89
-
90
- # Get first-person observation from the agent's perspective at this position
91
- obs = base_env.render_obs()
92
-
93
- # Restore original position
94
- base_env.place_agent(pos=original_pos)
95
-
96
- # Apply wrapper transformations manually for consistency
97
- # Convert to PyTorch format (CHW) - no resize needed since we render at target size
98
- obs = np.transpose(obs, (2, 0, 1))
99
-
100
- return obs
101
-
102
-
103
- def create_drstrategy_env(variant="NineRooms", **kwargs):
104
- """
105
- Factory function to create DrStrategy environment variants.
106
-
107
- Args:
108
- variant: Environment variant ("NineRooms", "SpiralNineRooms", "TwentyFiveRooms")
109
- **kwargs: Additional arguments passed to NineRoomsEnvironmentWrapper
110
-
111
- Returns:
112
- NineRoomsEnvironmentWrapper instance
113
- """
114
- return NineRoomsEnvironmentWrapper(variant=variant, **kwargs)
115
-
116
-
117
- # Backward compatibility alias
118
- def create_nine_rooms_env(variant="NineRooms", **kwargs):
119
- """
120
- Legacy factory function for backward compatibility.
121
-
122
- Deprecated: Use create_drstrategy_env() instead.
123
- """
124
- import warnings
125
-
126
- warnings.warn(
127
- "create_nine_rooms_env() is deprecated. Use create_drstrategy_env() instead.",
128
- DeprecationWarning,
129
- stacklevel=2,
130
- )
131
- return create_drstrategy_env(variant=variant, **kwargs)
132
-
133
-
134
- # Legacy function - deprecated
135
- def NineRoomsFullyPureGymnasium(
136
- name="NineRooms",
137
- obs_level=ObservationLevel.TOP_DOWN_PARTIAL,
138
- continuous=False,
139
- size=64,
140
- ):
141
- """
142
- Legacy function for backward compatibility.
143
-
144
- Deprecated: Use create_drstrategy_env() instead.
145
- """
146
- import warnings
147
6
 
148
- warnings.warn(
149
- "NineRoomsFullyPureGymnasium() is deprecated. Use create_drstrategy_env() instead.",
150
- DeprecationWarning,
151
- stacklevel=2,
152
- )
153
- return create_drstrategy_env(
154
- variant="NineRooms", obs_level=obs_level, continuous=continuous, size=size
155
- )
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,22 +47,18 @@ 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
51
+
52
+ # Initialize goal positions for each room (2 goals per room)
53
+ goal_positions = GridRoomsEnvironment._generate_goal_positions(
54
+ 3, room_size, goals_per_room=2
55
+ )
60
56
 
61
57
  super().__init__(
62
58
  grid_size=3,
63
59
  connections=connections or default_connections,
64
60
  textures=textures or default_textures,
61
+ goal_positions=goal_positions,
65
62
  placed_room=placed_room,
66
63
  obs_level=obs_level,
67
64
  continuous=continuous,
@@ -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,22 +43,18 @@ 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
47
+
48
+ # Initialize goal positions for each room (2 goals per room)
49
+ goal_positions = GridRoomsEnvironment._generate_goal_positions(
50
+ 3, room_size, goals_per_room=2
51
+ )
56
52
 
57
53
  super().__init__(
58
54
  grid_size=3,
59
55
  connections=connections or default_connections,
60
56
  textures=textures or default_textures,
57
+ goal_positions=goal_positions,
61
58
  placed_room=placed_room,
62
59
  obs_level=obs_level,
63
60
  continuous=continuous,
@@ -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,38 +79,18 @@ 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
83
+
84
+ # Initialize goal positions for each room (1 goal per room at center)
85
+ goal_positions = GridRoomsEnvironment._generate_goal_positions(
86
+ 5, room_size, goals_per_room=1
87
+ )
108
88
 
109
89
  super().__init__(
110
90
  grid_size=5,
111
91
  connections=connections or default_connections,
112
92
  textures=textures or default_textures,
93
+ goal_positions=goal_positions,
113
94
  placed_room=placed_room,
114
95
  obs_level=obs_level,
115
96
  continuous=continuous,
@@ -1,5 +1,3 @@
1
1
  """Tools for Nine Rooms environments."""
2
2
 
3
- from .generate_observations import generate_observations, main
4
-
5
- __all__ = ["generate_observations", "main"]
3
+ __all__ = []
@@ -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