valetudo-map-parser 0.1.11b2__py3-none-any.whl → 0.1.12b0__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.
- valetudo_map_parser/__init__.py +124 -15
- valetudo_map_parser/config/async_utils.py +1 -1
- valetudo_map_parser/config/colors.py +2 -419
- valetudo_map_parser/config/drawable.py +10 -73
- valetudo_map_parser/config/rand256_parser.py +129 -111
- valetudo_map_parser/config/shared.py +15 -12
- valetudo_map_parser/config/status_text/__init__.py +6 -0
- valetudo_map_parser/config/status_text/status_text.py +74 -49
- valetudo_map_parser/config/types.py +74 -408
- valetudo_map_parser/config/utils.py +99 -73
- valetudo_map_parser/const.py +288 -0
- valetudo_map_parser/hypfer_draw.py +121 -150
- valetudo_map_parser/hypfer_handler.py +210 -183
- valetudo_map_parser/map_data.py +31 -0
- valetudo_map_parser/rand256_handler.py +207 -218
- valetudo_map_parser/rooms_handler.py +4 -5
- {valetudo_map_parser-0.1.11b2.dist-info → valetudo_map_parser-0.1.12b0.dist-info}/METADATA +1 -1
- valetudo_map_parser-0.1.12b0.dist-info/RECORD +34 -0
- valetudo_map_parser-0.1.11b2.dist-info/RECORD +0 -32
- {valetudo_map_parser-0.1.11b2.dist-info → valetudo_map_parser-0.1.12b0.dist-info}/WHEEL +0 -0
- {valetudo_map_parser-0.1.11b2.dist-info → valetudo_map_parser-0.1.12b0.dist-info}/licenses/LICENSE +0 -0
- {valetudo_map_parser-0.1.11b2.dist-info → valetudo_map_parser-0.1.12b0.dist-info}/licenses/NOTICE.txt +0 -0
|
@@ -16,8 +16,8 @@ from PIL import Image
|
|
|
16
16
|
from .config.async_utils import AsyncPIL
|
|
17
17
|
from .config.drawable_elements import DrawableElement
|
|
18
18
|
from .config.shared import CameraShared
|
|
19
|
+
from .const import COLORS
|
|
19
20
|
from .config.types import (
|
|
20
|
-
COLORS,
|
|
21
21
|
LOGGER,
|
|
22
22
|
CalibrationPoints,
|
|
23
23
|
Colors,
|
|
@@ -88,6 +88,189 @@ class HypferMapImageHandler(BaseHandler, AutoCrop):
|
|
|
88
88
|
self.rooms_pos = None
|
|
89
89
|
return room_properties
|
|
90
90
|
|
|
91
|
+
def _identify_disabled_rooms(self) -> set:
|
|
92
|
+
"""Identify which rooms are disabled in the drawing configuration."""
|
|
93
|
+
disabled_rooms = set()
|
|
94
|
+
room_id = 0
|
|
95
|
+
for layer_type, _ in self.json_data.layers.items():
|
|
96
|
+
if layer_type == "segment":
|
|
97
|
+
current_room_id = room_id + 1
|
|
98
|
+
if 1 <= current_room_id <= 15:
|
|
99
|
+
room_element = getattr(
|
|
100
|
+
DrawableElement, f"ROOM_{current_room_id}", None
|
|
101
|
+
)
|
|
102
|
+
if room_element and not self.drawing_config.is_enabled(
|
|
103
|
+
room_element
|
|
104
|
+
):
|
|
105
|
+
disabled_rooms.add(room_id)
|
|
106
|
+
room_id = (room_id + 1) % 16
|
|
107
|
+
return disabled_rooms
|
|
108
|
+
|
|
109
|
+
async def _draw_layer_if_enabled(
|
|
110
|
+
self,
|
|
111
|
+
img_np_array,
|
|
112
|
+
layer_type,
|
|
113
|
+
compressed_pixels_list,
|
|
114
|
+
colors,
|
|
115
|
+
pixel_size,
|
|
116
|
+
disabled_rooms,
|
|
117
|
+
room_id,
|
|
118
|
+
):
|
|
119
|
+
"""Draw a layer if it's enabled in the drawing configuration."""
|
|
120
|
+
is_room_layer = layer_type == "segment"
|
|
121
|
+
|
|
122
|
+
if is_room_layer:
|
|
123
|
+
current_room_id = room_id + 1
|
|
124
|
+
if 1 <= current_room_id <= 15:
|
|
125
|
+
room_element = getattr(DrawableElement, f"ROOM_{current_room_id}", None)
|
|
126
|
+
if not self.drawing_config.is_enabled(room_element):
|
|
127
|
+
return room_id + 1, img_np_array # Skip this room
|
|
128
|
+
|
|
129
|
+
is_wall_layer = layer_type == "wall"
|
|
130
|
+
if is_wall_layer and not self.drawing_config.is_enabled(DrawableElement.WALL):
|
|
131
|
+
return room_id, img_np_array # Skip walls
|
|
132
|
+
|
|
133
|
+
# Draw the layer
|
|
134
|
+
room_id, img_np_array = await self.imd.async_draw_base_layer(
|
|
135
|
+
img_np_array,
|
|
136
|
+
compressed_pixels_list,
|
|
137
|
+
layer_type,
|
|
138
|
+
colors["wall"],
|
|
139
|
+
colors["zone_clean"],
|
|
140
|
+
pixel_size,
|
|
141
|
+
disabled_rooms if layer_type == "wall" else None,
|
|
142
|
+
)
|
|
143
|
+
return room_id, img_np_array
|
|
144
|
+
|
|
145
|
+
async def _draw_base_layers(self, img_np_array, colors, pixel_size):
|
|
146
|
+
"""Draw all base layers (rooms, walls, floors)."""
|
|
147
|
+
disabled_rooms = self._identify_disabled_rooms()
|
|
148
|
+
room_id = 0
|
|
149
|
+
|
|
150
|
+
for layer_type, compressed_pixels_list in self.json_data.layers.items():
|
|
151
|
+
room_id, img_np_array = await self._draw_layer_if_enabled(
|
|
152
|
+
img_np_array,
|
|
153
|
+
layer_type,
|
|
154
|
+
compressed_pixels_list,
|
|
155
|
+
colors,
|
|
156
|
+
pixel_size,
|
|
157
|
+
disabled_rooms,
|
|
158
|
+
room_id,
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
return img_np_array, room_id
|
|
162
|
+
|
|
163
|
+
async def _draw_additional_elements(
|
|
164
|
+
self, img_np_array, m_json, entity_dict, colors
|
|
165
|
+
):
|
|
166
|
+
"""Draw additional elements like walls, charger, and obstacles."""
|
|
167
|
+
if self.drawing_config.is_enabled(DrawableElement.VIRTUAL_WALL):
|
|
168
|
+
img_np_array = await self.imd.async_draw_virtual_walls(
|
|
169
|
+
m_json, img_np_array, colors["no_go"]
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
if self.drawing_config.is_enabled(DrawableElement.CHARGER):
|
|
173
|
+
img_np_array = await self.imd.async_draw_charger(
|
|
174
|
+
img_np_array, entity_dict, colors["charger"]
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
if self.drawing_config.is_enabled(DrawableElement.OBSTACLE):
|
|
178
|
+
self.shared.obstacles_pos = self.data.get_obstacles(entity_dict)
|
|
179
|
+
if self.shared.obstacles_pos:
|
|
180
|
+
img_np_array = await self.imd.async_draw_obstacle(
|
|
181
|
+
img_np_array, self.shared.obstacles_pos, colors["no_go"]
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
return img_np_array
|
|
185
|
+
|
|
186
|
+
async def _setup_room_and_robot_data(
|
|
187
|
+
self, room_id, robot_position, robot_position_angle
|
|
188
|
+
):
|
|
189
|
+
"""Setup room properties and robot position data."""
|
|
190
|
+
if (room_id > 0) and not self.room_propriety:
|
|
191
|
+
self.room_propriety = await self.async_extract_room_properties(
|
|
192
|
+
self.json_data.json_data
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
if not self.rooms_pos and not self.room_propriety:
|
|
196
|
+
self.room_propriety = await self.async_extract_room_properties(
|
|
197
|
+
self.json_data.json_data
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
if self.rooms_pos and robot_position and robot_position_angle:
|
|
201
|
+
self.robot_pos = await self.imd.async_get_robot_in_room(
|
|
202
|
+
robot_x=(robot_position[0]),
|
|
203
|
+
robot_y=(robot_position[1]),
|
|
204
|
+
angle=robot_position_angle,
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
async def _prepare_data_tasks(self, m_json, entity_dict):
|
|
208
|
+
"""Prepare and execute data extraction tasks in parallel."""
|
|
209
|
+
data_tasks = []
|
|
210
|
+
|
|
211
|
+
if self.drawing_config.is_enabled(DrawableElement.RESTRICTED_AREA):
|
|
212
|
+
data_tasks.append(self._prepare_zone_data(m_json))
|
|
213
|
+
|
|
214
|
+
if self.drawing_config.is_enabled(DrawableElement.GO_TO_TARGET):
|
|
215
|
+
data_tasks.append(self._prepare_goto_data(entity_dict))
|
|
216
|
+
|
|
217
|
+
path_enabled = self.drawing_config.is_enabled(DrawableElement.PATH)
|
|
218
|
+
LOGGER.info("%s: PATH element enabled: %s", self.file_name, path_enabled)
|
|
219
|
+
if path_enabled:
|
|
220
|
+
LOGGER.info("%s: Drawing path", self.file_name)
|
|
221
|
+
data_tasks.append(self._prepare_path_data(m_json))
|
|
222
|
+
|
|
223
|
+
if data_tasks:
|
|
224
|
+
await asyncio.gather(*data_tasks)
|
|
225
|
+
|
|
226
|
+
return path_enabled
|
|
227
|
+
|
|
228
|
+
async def _draw_dynamic_elements(
|
|
229
|
+
self, img_np_array, m_json, entity_dict, colors, path_enabled
|
|
230
|
+
):
|
|
231
|
+
"""Draw dynamic elements like zones, paths, and go-to targets."""
|
|
232
|
+
if self.drawing_config.is_enabled(DrawableElement.RESTRICTED_AREA):
|
|
233
|
+
img_np_array = await self.imd.async_draw_zones(
|
|
234
|
+
m_json, img_np_array, colors["zone_clean"], colors["no_go"]
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
if self.drawing_config.is_enabled(DrawableElement.GO_TO_TARGET):
|
|
238
|
+
img_np_array = await self.imd.draw_go_to_flag(
|
|
239
|
+
img_np_array, entity_dict, colors["go_to"]
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
if path_enabled:
|
|
243
|
+
img_np_array = await self.imd.async_draw_paths(
|
|
244
|
+
img_np_array, m_json, colors["move"], self.color_grey
|
|
245
|
+
)
|
|
246
|
+
else:
|
|
247
|
+
LOGGER.info("%s: Skipping path drawing", self.file_name)
|
|
248
|
+
|
|
249
|
+
return img_np_array
|
|
250
|
+
|
|
251
|
+
async def _draw_robot_if_enabled(
|
|
252
|
+
self, img_np_array, robot_pos, robot_position, robot_position_angle, colors
|
|
253
|
+
):
|
|
254
|
+
"""Draw the robot on the map if enabled."""
|
|
255
|
+
if self.shared.vacuum_state == "docked":
|
|
256
|
+
robot_position_angle -= 180
|
|
257
|
+
|
|
258
|
+
if robot_pos and self.drawing_config.is_enabled(DrawableElement.ROBOT):
|
|
259
|
+
robot_color = self.drawing_config.get_property(
|
|
260
|
+
DrawableElement.ROBOT, "color", colors["robot"]
|
|
261
|
+
)
|
|
262
|
+
img_np_array = await self.draw.robot(
|
|
263
|
+
layers=img_np_array,
|
|
264
|
+
x=robot_position[0],
|
|
265
|
+
y=robot_position[1],
|
|
266
|
+
angle=robot_position_angle,
|
|
267
|
+
fill=robot_color,
|
|
268
|
+
radius=self.shared.robot_size,
|
|
269
|
+
robot_state=self.shared.vacuum_state,
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
return img_np_array
|
|
273
|
+
|
|
91
274
|
# noinspection PyUnresolvedReferences,PyUnboundLocalVariable
|
|
92
275
|
async def async_get_image_from_json(
|
|
93
276
|
self,
|
|
@@ -132,126 +315,21 @@ class HypferMapImageHandler(BaseHandler, AutoCrop):
|
|
|
132
315
|
img_np_array = await self.draw.create_empty_image(
|
|
133
316
|
self.img_size["x"], self.img_size["y"], colors["background"]
|
|
134
317
|
)
|
|
135
|
-
# Draw layers and segments if enabled
|
|
136
318
|
room_id = 0
|
|
137
|
-
# Keep track of disabled rooms to skip their walls later
|
|
138
|
-
disabled_rooms = set()
|
|
139
319
|
|
|
140
320
|
if self.drawing_config.is_enabled(DrawableElement.FLOOR):
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
layer_type,
|
|
144
|
-
compressed_pixels_list,
|
|
145
|
-
) in self.json_data.layers.items():
|
|
146
|
-
# Check if this is a room layer
|
|
147
|
-
if layer_type == "segment":
|
|
148
|
-
# The room_id is the current room being processed (0-based index)
|
|
149
|
-
# We need to check if ROOM_{room_id+1} is enabled (1-based in DrawableElement)
|
|
150
|
-
current_room_id = room_id + 1
|
|
151
|
-
if 1 <= current_room_id <= 15:
|
|
152
|
-
room_element = getattr(
|
|
153
|
-
DrawableElement, f"ROOM_{current_room_id}", None
|
|
154
|
-
)
|
|
155
|
-
if (
|
|
156
|
-
room_element
|
|
157
|
-
and not self.drawing_config.is_enabled(
|
|
158
|
-
room_element
|
|
159
|
-
)
|
|
160
|
-
):
|
|
161
|
-
# Add this room to the disabled rooms set
|
|
162
|
-
disabled_rooms.add(room_id)
|
|
163
|
-
room_id = (
|
|
164
|
-
room_id + 1
|
|
165
|
-
) % 16 # Cycle room_id back to 0 after 15
|
|
166
|
-
|
|
167
|
-
# Reset room_id for the actual drawing pass
|
|
168
|
-
room_id = 0
|
|
169
|
-
|
|
170
|
-
# Second pass: draw enabled rooms and walls
|
|
171
|
-
for (
|
|
172
|
-
layer_type,
|
|
173
|
-
compressed_pixels_list,
|
|
174
|
-
) in self.json_data.layers.items():
|
|
175
|
-
# Check if this is a room layer
|
|
176
|
-
is_room_layer = layer_type == "segment"
|
|
177
|
-
|
|
178
|
-
# If it's a room layer, check if the specific room is enabled
|
|
179
|
-
if is_room_layer:
|
|
180
|
-
# The room_id is the current room being processed (0-based index)
|
|
181
|
-
# We need to check if ROOM_{room_id+1} is enabled (1-based in DrawableElement)
|
|
182
|
-
current_room_id = room_id + 1
|
|
183
|
-
if 1 <= current_room_id <= 15:
|
|
184
|
-
room_element = getattr(
|
|
185
|
-
DrawableElement, f"ROOM_{current_room_id}", None
|
|
186
|
-
)
|
|
187
|
-
|
|
188
|
-
# Skip this room if it's disabled
|
|
189
|
-
if not self.drawing_config.is_enabled(room_element):
|
|
190
|
-
room_id = (
|
|
191
|
-
room_id + 1
|
|
192
|
-
) % 16 # Increment room_id even if we skip
|
|
193
|
-
continue
|
|
194
|
-
|
|
195
|
-
# Draw the layer ONLY if enabled
|
|
196
|
-
is_wall_layer = layer_type == "wall"
|
|
197
|
-
if is_wall_layer:
|
|
198
|
-
# Skip walls entirely if disabled
|
|
199
|
-
if not self.drawing_config.is_enabled(
|
|
200
|
-
DrawableElement.WALL
|
|
201
|
-
):
|
|
202
|
-
continue
|
|
203
|
-
# Draw the layer
|
|
204
|
-
(
|
|
205
|
-
room_id,
|
|
206
|
-
img_np_array,
|
|
207
|
-
) = await self.imd.async_draw_base_layer(
|
|
208
|
-
img_np_array,
|
|
209
|
-
compressed_pixels_list,
|
|
210
|
-
layer_type,
|
|
211
|
-
colors["wall"],
|
|
212
|
-
colors["zone_clean"],
|
|
213
|
-
pixel_size,
|
|
214
|
-
disabled_rooms if layer_type == "wall" else None,
|
|
215
|
-
)
|
|
216
|
-
|
|
217
|
-
# Draw the virtual walls if enabled
|
|
218
|
-
if self.drawing_config.is_enabled(DrawableElement.VIRTUAL_WALL):
|
|
219
|
-
img_np_array = await self.imd.async_draw_virtual_walls(
|
|
220
|
-
m_json, img_np_array, colors["no_go"]
|
|
321
|
+
img_np_array, room_id = await self._draw_base_layers(
|
|
322
|
+
img_np_array, colors, pixel_size
|
|
221
323
|
)
|
|
222
324
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
img_np_array, entity_dict, colors["charger"]
|
|
227
|
-
)
|
|
228
|
-
|
|
229
|
-
# Draw obstacles if enabled
|
|
230
|
-
if self.drawing_config.is_enabled(DrawableElement.OBSTACLE):
|
|
231
|
-
self.shared.obstacles_pos = self.data.get_obstacles(entity_dict)
|
|
232
|
-
if self.shared.obstacles_pos:
|
|
233
|
-
img_np_array = await self.imd.async_draw_obstacle(
|
|
234
|
-
img_np_array, self.shared.obstacles_pos, colors["no_go"]
|
|
235
|
-
)
|
|
236
|
-
# Robot and rooms position
|
|
237
|
-
if (room_id > 0) and not self.room_propriety:
|
|
238
|
-
self.room_propriety = await self.async_extract_room_properties(
|
|
239
|
-
self.json_data.json_data
|
|
240
|
-
)
|
|
325
|
+
img_np_array = await self._draw_additional_elements(
|
|
326
|
+
img_np_array, m_json, entity_dict, colors
|
|
327
|
+
)
|
|
241
328
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
self.json_data.json_data
|
|
246
|
-
)
|
|
329
|
+
await self._setup_room_and_robot_data(
|
|
330
|
+
room_id, robot_position, robot_position_angle
|
|
331
|
+
)
|
|
247
332
|
|
|
248
|
-
# Always check robot position for zooming (moved outside the condition)
|
|
249
|
-
if self.rooms_pos and robot_position and robot_position_angle:
|
|
250
|
-
self.robot_pos = await self.imd.async_get_robot_in_room(
|
|
251
|
-
robot_x=(robot_position[0]),
|
|
252
|
-
robot_y=(robot_position[1]),
|
|
253
|
-
angle=robot_position_angle,
|
|
254
|
-
)
|
|
255
333
|
LOGGER.info("%s: Completed base Layers", self.file_name)
|
|
256
334
|
# Copy the new array in base layer.
|
|
257
335
|
# Delete old base layer before creating new one to free memory
|
|
@@ -282,73 +360,22 @@ class HypferMapImageHandler(BaseHandler, AutoCrop):
|
|
|
282
360
|
np.copyto(self.img_work_layer, self.img_base_layer)
|
|
283
361
|
img_np_array = self.img_work_layer
|
|
284
362
|
|
|
285
|
-
# Prepare
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
# Prepare zone data extraction
|
|
289
|
-
if self.drawing_config.is_enabled(DrawableElement.RESTRICTED_AREA):
|
|
290
|
-
data_tasks.append(self._prepare_zone_data(m_json))
|
|
363
|
+
# Prepare and execute data extraction tasks
|
|
364
|
+
path_enabled = await self._prepare_data_tasks(m_json, entity_dict)
|
|
291
365
|
|
|
292
|
-
#
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
# Prepare path data extraction
|
|
297
|
-
path_enabled = self.drawing_config.is_enabled(DrawableElement.PATH)
|
|
298
|
-
LOGGER.info(
|
|
299
|
-
"%s: PATH element enabled: %s", self.file_name, path_enabled
|
|
366
|
+
# Draw dynamic elements
|
|
367
|
+
img_np_array = await self._draw_dynamic_elements(
|
|
368
|
+
img_np_array, m_json, entity_dict, colors, path_enabled
|
|
300
369
|
)
|
|
301
|
-
if path_enabled:
|
|
302
|
-
LOGGER.info("%s: Drawing path", self.file_name)
|
|
303
|
-
data_tasks.append(self._prepare_path_data(m_json))
|
|
304
|
-
|
|
305
|
-
# Await all data preparation tasks if any were created
|
|
306
|
-
if data_tasks:
|
|
307
|
-
await asyncio.gather(*data_tasks)
|
|
308
|
-
|
|
309
|
-
# Process drawing operations sequentially (since they modify the same array)
|
|
310
|
-
# Draw zones if enabled
|
|
311
|
-
if self.drawing_config.is_enabled(DrawableElement.RESTRICTED_AREA):
|
|
312
|
-
img_np_array = await self.imd.async_draw_zones(
|
|
313
|
-
m_json, img_np_array, colors["zone_clean"], colors["no_go"]
|
|
314
|
-
)
|
|
315
|
-
|
|
316
|
-
# Draw the go_to target flag if enabled
|
|
317
|
-
if self.drawing_config.is_enabled(DrawableElement.GO_TO_TARGET):
|
|
318
|
-
img_np_array = await self.imd.draw_go_to_flag(
|
|
319
|
-
img_np_array, entity_dict, colors["go_to"]
|
|
320
|
-
)
|
|
321
370
|
|
|
322
|
-
# Draw
|
|
323
|
-
|
|
324
|
-
img_np_array
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
# Check if the robot is docked.
|
|
331
|
-
if self.shared.vacuum_state == "docked":
|
|
332
|
-
# Adjust the robot angle.
|
|
333
|
-
robot_position_angle -= 180
|
|
334
|
-
|
|
335
|
-
# Draw the robot if enabled
|
|
336
|
-
if robot_pos and self.drawing_config.is_enabled(DrawableElement.ROBOT):
|
|
337
|
-
# Get robot color (allows for customization)
|
|
338
|
-
robot_color = self.drawing_config.get_property(
|
|
339
|
-
DrawableElement.ROBOT, "color", colors["robot"]
|
|
340
|
-
)
|
|
341
|
-
|
|
342
|
-
# Draw the robot
|
|
343
|
-
img_np_array = await self.draw.robot(
|
|
344
|
-
layers=img_np_array,
|
|
345
|
-
x=robot_position[0],
|
|
346
|
-
y=robot_position[1],
|
|
347
|
-
angle=robot_position_angle,
|
|
348
|
-
fill=robot_color,
|
|
349
|
-
radius=self.shared.robot_size,
|
|
350
|
-
robot_state=self.shared.vacuum_state,
|
|
351
|
-
)
|
|
371
|
+
# Draw robot
|
|
372
|
+
img_np_array = await self._draw_robot_if_enabled(
|
|
373
|
+
img_np_array,
|
|
374
|
+
robot_pos,
|
|
375
|
+
robot_position,
|
|
376
|
+
robot_position_angle,
|
|
377
|
+
colors,
|
|
378
|
+
)
|
|
352
379
|
|
|
353
380
|
# Synchronize zooming state from ImageDraw to handler before auto-crop
|
|
354
381
|
self.zooming = self.imd.img_h.zooming
|
|
@@ -376,11 +403,11 @@ class HypferMapImageHandler(BaseHandler, AutoCrop):
|
|
|
376
403
|
|
|
377
404
|
# Return PIL Image
|
|
378
405
|
return resized_image
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
406
|
+
|
|
407
|
+
# Return PIL Image (convert from NumPy)
|
|
408
|
+
pil_img = await AsyncPIL.async_fromarray(img_np_array, mode="RGBA")
|
|
409
|
+
del img_np_array
|
|
410
|
+
return pil_img
|
|
384
411
|
except (RuntimeError, RuntimeWarning) as e:
|
|
385
412
|
LOGGER.warning(
|
|
386
413
|
"%s: Error %s during image creation.",
|
valetudo_map_parser/map_data.py
CHANGED
|
@@ -30,6 +30,8 @@ T = TypeVar("T")
|
|
|
30
30
|
|
|
31
31
|
|
|
32
32
|
class RangeStats(TypedDict):
|
|
33
|
+
"""Statistics for a range of values (min, max, mid, avg)."""
|
|
34
|
+
|
|
33
35
|
min: int
|
|
34
36
|
max: int
|
|
35
37
|
mid: int
|
|
@@ -37,6 +39,8 @@ class RangeStats(TypedDict):
|
|
|
37
39
|
|
|
38
40
|
|
|
39
41
|
class Dimensions(TypedDict):
|
|
42
|
+
"""Dimensions with x/y range statistics and pixel count."""
|
|
43
|
+
|
|
40
44
|
x: RangeStats
|
|
41
45
|
y: RangeStats
|
|
42
46
|
pixelCount: int
|
|
@@ -46,10 +50,14 @@ class Dimensions(TypedDict):
|
|
|
46
50
|
|
|
47
51
|
|
|
48
52
|
class FloorWallMeta(TypedDict, total=False):
|
|
53
|
+
"""Metadata for floor and wall layers."""
|
|
54
|
+
|
|
49
55
|
area: int
|
|
50
56
|
|
|
51
57
|
|
|
52
58
|
class SegmentMeta(TypedDict, total=False):
|
|
59
|
+
"""Metadata for segment layers including segment ID and active state."""
|
|
60
|
+
|
|
53
61
|
segmentId: str
|
|
54
62
|
active: bool
|
|
55
63
|
source: str
|
|
@@ -57,6 +65,8 @@ class SegmentMeta(TypedDict, total=False):
|
|
|
57
65
|
|
|
58
66
|
|
|
59
67
|
class MapLayerBase(TypedDict):
|
|
68
|
+
"""Base structure for map layers with pixels and dimensions."""
|
|
69
|
+
|
|
60
70
|
__class__: Literal["MapLayer"]
|
|
61
71
|
type: str
|
|
62
72
|
pixels: list[int]
|
|
@@ -65,11 +75,15 @@ class MapLayerBase(TypedDict):
|
|
|
65
75
|
|
|
66
76
|
|
|
67
77
|
class FloorWallLayer(MapLayerBase):
|
|
78
|
+
"""Map layer representing floor or wall areas."""
|
|
79
|
+
|
|
68
80
|
metaData: FloorWallMeta
|
|
69
81
|
type: Literal["floor", "wall"]
|
|
70
82
|
|
|
71
83
|
|
|
72
84
|
class SegmentLayer(MapLayerBase):
|
|
85
|
+
"""Map layer representing a room segment."""
|
|
86
|
+
|
|
73
87
|
metaData: SegmentMeta
|
|
74
88
|
type: Literal["segment"]
|
|
75
89
|
|
|
@@ -78,12 +92,16 @@ class SegmentLayer(MapLayerBase):
|
|
|
78
92
|
|
|
79
93
|
|
|
80
94
|
class PointMeta(TypedDict, total=False):
|
|
95
|
+
"""Metadata for point entities including angle, label, and ID."""
|
|
96
|
+
|
|
81
97
|
angle: float
|
|
82
98
|
label: str
|
|
83
99
|
id: str
|
|
84
100
|
|
|
85
101
|
|
|
86
102
|
class PointMapEntity(TypedDict):
|
|
103
|
+
"""Point-based map entity (robot, charger, obstacle, etc.)."""
|
|
104
|
+
|
|
87
105
|
__class__: Literal["PointMapEntity"]
|
|
88
106
|
type: str
|
|
89
107
|
points: list[int]
|
|
@@ -91,6 +109,8 @@ class PointMapEntity(TypedDict):
|
|
|
91
109
|
|
|
92
110
|
|
|
93
111
|
class PathMapEntity(TypedDict):
|
|
112
|
+
"""Path-based map entity representing robot movement paths."""
|
|
113
|
+
|
|
94
114
|
__class__: Literal["PathMapEntity"]
|
|
95
115
|
type: str
|
|
96
116
|
points: list[int]
|
|
@@ -103,16 +123,22 @@ Entity = PointMapEntity | PathMapEntity
|
|
|
103
123
|
|
|
104
124
|
|
|
105
125
|
class MapMeta(TypedDict, total=False):
|
|
126
|
+
"""Metadata for the Valetudo map including version and total area."""
|
|
127
|
+
|
|
106
128
|
version: int
|
|
107
129
|
totalLayerArea: int
|
|
108
130
|
|
|
109
131
|
|
|
110
132
|
class Size(TypedDict):
|
|
133
|
+
"""Map size with x and y dimensions."""
|
|
134
|
+
|
|
111
135
|
x: int
|
|
112
136
|
y: int
|
|
113
137
|
|
|
114
138
|
|
|
115
139
|
class ValetudoMap(TypedDict):
|
|
140
|
+
"""Complete Valetudo map structure with layers and entities."""
|
|
141
|
+
|
|
116
142
|
__class__: Literal["ValetudoMap"]
|
|
117
143
|
metaData: MapMeta
|
|
118
144
|
size: Size
|
|
@@ -673,6 +699,11 @@ class RandImageData:
|
|
|
673
699
|
img = RandImageData.get_rrm_image(json_data)
|
|
674
700
|
seg_data = img.get("segments", {})
|
|
675
701
|
seg_ids = seg_data.get("id")
|
|
702
|
+
|
|
703
|
+
# Handle missing or invalid segment IDs gracefully
|
|
704
|
+
if not seg_ids:
|
|
705
|
+
return ([], []) if out_lines else []
|
|
706
|
+
|
|
676
707
|
segments = []
|
|
677
708
|
outlines = []
|
|
678
709
|
count_seg = 0
|