valetudo-map-parser 0.1.9b50__py3-none-any.whl → 0.1.9b52__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 +2 -0
- valetudo_map_parser/config/color_utils.py +69 -13
- valetudo_map_parser/config/colors.py +55 -70
- valetudo_map_parser/config/drawable_elements.py +0 -676
- valetudo_map_parser/config/enhanced_drawable.py +75 -193
- valetudo_map_parser/config/shared.py +0 -1
- valetudo_map_parser/config/types.py +19 -33
- valetudo_map_parser/config/utils.py +0 -67
- valetudo_map_parser/hypfer_draw.py +222 -57
- valetudo_map_parser/hypfer_handler.py +48 -155
- valetudo_map_parser/hypfer_rooms_handler.py +406 -0
- valetudo_map_parser/map_data.py +0 -30
- valetudo_map_parser/rand25_handler.py +2 -84
- {valetudo_map_parser-0.1.9b50.dist-info → valetudo_map_parser-0.1.9b52.dist-info}/METADATA +1 -1
- valetudo_map_parser-0.1.9b52.dist-info/RECORD +26 -0
- valetudo_map_parser-0.1.9b50.dist-info/RECORD +0 -25
- {valetudo_map_parser-0.1.9b50.dist-info → valetudo_map_parser-0.1.9b52.dist-info}/LICENSE +0 -0
- {valetudo_map_parser-0.1.9b50.dist-info → valetudo_map_parser-0.1.9b52.dist-info}/NOTICE.txt +0 -0
- {valetudo_map_parser-0.1.9b50.dist-info → valetudo_map_parser-0.1.9b52.dist-info}/WHEEL +0 -0
@@ -0,0 +1,406 @@
|
|
1
|
+
"""
|
2
|
+
Hipfer Rooms Handler Module.
|
3
|
+
Handles room data extraction and processing for Valetudo Hipfer vacuum maps.
|
4
|
+
Provides async methods for room outline extraction and properties management.
|
5
|
+
Version: 0.1.9
|
6
|
+
"""
|
7
|
+
|
8
|
+
from __future__ import annotations
|
9
|
+
|
10
|
+
from math import sqrt
|
11
|
+
from typing import Any, Dict, Optional, List, Tuple
|
12
|
+
|
13
|
+
import numpy as np
|
14
|
+
|
15
|
+
from .config.drawable_elements import DrawableElement, DrawingConfig
|
16
|
+
from .config.types import LOGGER, RoomsProperties, RoomStore
|
17
|
+
|
18
|
+
|
19
|
+
class HypferRoomsHandler:
|
20
|
+
"""
|
21
|
+
Handler for extracting and managing room data from Hipfer vacuum maps.
|
22
|
+
|
23
|
+
This class provides methods to:
|
24
|
+
- Extract room outlines using the Ramer-Douglas-Peucker algorithm
|
25
|
+
- Process room properties from JSON data
|
26
|
+
- Generate room masks and extract contours
|
27
|
+
|
28
|
+
All methods are async for better integration with the rest of the codebase.
|
29
|
+
"""
|
30
|
+
|
31
|
+
def __init__(self, vacuum_id: str, drawing_config: Optional[DrawingConfig] = None):
|
32
|
+
"""
|
33
|
+
Initialize the HipferRoomsHandler.
|
34
|
+
|
35
|
+
Args:
|
36
|
+
vacuum_id: Identifier for the vacuum
|
37
|
+
drawing_config: Configuration for which elements to draw (optional)
|
38
|
+
"""
|
39
|
+
self.vacuum_id = vacuum_id
|
40
|
+
self.drawing_config = drawing_config
|
41
|
+
|
42
|
+
@staticmethod
|
43
|
+
def sublist(data: list, chunk_size: int) -> list:
|
44
|
+
return [data[i : i + chunk_size] for i in range(0, len(data), chunk_size)]
|
45
|
+
|
46
|
+
@staticmethod
|
47
|
+
def perpendicular_distance(
|
48
|
+
point: tuple[int, int], line_start: tuple[int, int], line_end: tuple[int, int]
|
49
|
+
) -> float:
|
50
|
+
"""Calculate the perpendicular distance from a point to a line."""
|
51
|
+
if line_start == line_end:
|
52
|
+
return sqrt(
|
53
|
+
(point[0] - line_start[0]) ** 2 + (point[1] - line_start[1]) ** 2
|
54
|
+
)
|
55
|
+
|
56
|
+
x, y = point
|
57
|
+
x1, y1 = line_start
|
58
|
+
x2, y2 = line_end
|
59
|
+
|
60
|
+
# Calculate the line length
|
61
|
+
line_length = sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)
|
62
|
+
if line_length == 0:
|
63
|
+
return 0
|
64
|
+
|
65
|
+
# Calculate the distance from the point to the line
|
66
|
+
return abs((y2 - y1) * x - (x2 - x1) * y + x2 * y1 - y2 * x1) / line_length
|
67
|
+
|
68
|
+
async def rdp(
|
69
|
+
self, points: List[Tuple[int, int]], epsilon: float
|
70
|
+
) -> List[Tuple[int, int]]:
|
71
|
+
"""Ramer-Douglas-Peucker algorithm for simplifying a curve."""
|
72
|
+
if len(points) <= 2:
|
73
|
+
return points
|
74
|
+
|
75
|
+
# Find the point with the maximum distance
|
76
|
+
dmax = 0
|
77
|
+
index = 0
|
78
|
+
for i in range(1, len(points) - 1):
|
79
|
+
d = self.perpendicular_distance(points[i], points[0], points[-1])
|
80
|
+
if d > dmax:
|
81
|
+
index = i
|
82
|
+
dmax = d
|
83
|
+
|
84
|
+
# If max distance is greater than epsilon, recursively simplify
|
85
|
+
if dmax > epsilon:
|
86
|
+
# Recursive call
|
87
|
+
first_segment = await self.rdp(points[: index + 1], epsilon)
|
88
|
+
second_segment = await self.rdp(points[index:], epsilon)
|
89
|
+
|
90
|
+
# Build the result list (avoiding duplicating the common point)
|
91
|
+
return first_segment[:-1] + second_segment
|
92
|
+
else:
|
93
|
+
return [points[0], points[-1]]
|
94
|
+
|
95
|
+
async def async_get_corners(
|
96
|
+
self, mask: np.ndarray, epsilon_factor: float = 0.05
|
97
|
+
) -> List[Tuple[int, int]]:
|
98
|
+
"""
|
99
|
+
Get the corners of a room shape as a list of (x, y) tuples.
|
100
|
+
Uses contour detection and Douglas-Peucker algorithm to simplify the contour.
|
101
|
+
|
102
|
+
Args:
|
103
|
+
mask: Binary mask of the room (1 for room, 0 for background)
|
104
|
+
epsilon_factor: Controls the level of simplification (higher = fewer points)
|
105
|
+
|
106
|
+
Returns:
|
107
|
+
List of (x, y) tuples representing the corners of the room
|
108
|
+
"""
|
109
|
+
# Find contours in the mask
|
110
|
+
contour = await self.async_moore_neighbor_trace(mask)
|
111
|
+
|
112
|
+
if not contour:
|
113
|
+
# Fallback to bounding box if contour detection fails
|
114
|
+
y_indices, x_indices = np.where(mask > 0)
|
115
|
+
if len(x_indices) == 0 or len(y_indices) == 0:
|
116
|
+
return []
|
117
|
+
|
118
|
+
x_min, x_max = np.min(x_indices), np.max(x_indices)
|
119
|
+
y_min, y_max = np.min(y_indices), np.max(y_indices)
|
120
|
+
|
121
|
+
return [
|
122
|
+
(x_min, y_min), # Top-left
|
123
|
+
(x_max, y_min), # Top-right
|
124
|
+
(x_max, y_max), # Bottom-right
|
125
|
+
(x_min, y_max), # Bottom-left
|
126
|
+
(x_min, y_min), # Back to top-left to close the polygon
|
127
|
+
]
|
128
|
+
|
129
|
+
# Calculate the perimeter of the contour
|
130
|
+
perimeter = 0
|
131
|
+
for i in range(len(contour) - 1):
|
132
|
+
x1, y1 = contour[i]
|
133
|
+
x2, y2 = contour[i + 1]
|
134
|
+
perimeter += np.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)
|
135
|
+
|
136
|
+
# Apply Douglas-Peucker algorithm to simplify the contour
|
137
|
+
epsilon = epsilon_factor * perimeter
|
138
|
+
simplified_contour = await self.rdp(contour, epsilon=epsilon)
|
139
|
+
|
140
|
+
# Ensure the contour has at least 3 points to form a polygon
|
141
|
+
if len(simplified_contour) < 3:
|
142
|
+
# Fallback to bounding box
|
143
|
+
y_indices, x_indices = np.where(mask > 0)
|
144
|
+
x_min, x_max = int(np.min(x_indices)), int(np.max(x_indices))
|
145
|
+
y_min, y_max = int(np.min(y_indices)), int(np.max(y_indices))
|
146
|
+
|
147
|
+
LOGGER.debug(
|
148
|
+
f"{self.vacuum_id}: Too few points in contour, using bounding box"
|
149
|
+
)
|
150
|
+
return [
|
151
|
+
(x_min, y_min), # Top-left
|
152
|
+
(x_max, y_min), # Top-right
|
153
|
+
(x_max, y_max), # Bottom-right
|
154
|
+
(x_min, y_max), # Bottom-left
|
155
|
+
(x_min, y_min), # Back to top-left to close the polygon
|
156
|
+
]
|
157
|
+
|
158
|
+
# Ensure the contour is closed
|
159
|
+
if simplified_contour[0] != simplified_contour[-1]:
|
160
|
+
simplified_contour.append(simplified_contour[0])
|
161
|
+
|
162
|
+
return simplified_contour
|
163
|
+
|
164
|
+
@staticmethod
|
165
|
+
async def async_moore_neighbor_trace(mask: np.ndarray) -> List[Tuple[int, int]]:
|
166
|
+
"""
|
167
|
+
Trace the contour of a binary mask using an optimized Moore-Neighbor tracing.
|
168
|
+
|
169
|
+
Args:
|
170
|
+
mask: Binary mask of the room (1 for room, 0 for background)
|
171
|
+
|
172
|
+
Returns:
|
173
|
+
List of (x, y) tuples representing the contour
|
174
|
+
"""
|
175
|
+
# Convert to uint8 and pad
|
176
|
+
padded = np.pad(mask.astype(np.uint8), 1, mode="constant")
|
177
|
+
height, width = padded.shape
|
178
|
+
|
179
|
+
# Find the first non-zero point efficiently (scan row by row)
|
180
|
+
# This is much faster than np.argwhere() for large arrays
|
181
|
+
start = None
|
182
|
+
for y in range(height):
|
183
|
+
# Use NumPy's any() to quickly check if there are any 1s in this row
|
184
|
+
if np.any(padded[y]):
|
185
|
+
# Find the first 1 in this row
|
186
|
+
x = np.where(padded[y] == 1)[0][0]
|
187
|
+
start = (int(x), int(y))
|
188
|
+
break
|
189
|
+
|
190
|
+
if start is None:
|
191
|
+
return []
|
192
|
+
|
193
|
+
# Pre-compute directions
|
194
|
+
directions = [
|
195
|
+
(-1, -1),
|
196
|
+
(-1, 0),
|
197
|
+
(-1, 1),
|
198
|
+
(0, 1),
|
199
|
+
(1, 1),
|
200
|
+
(1, 0),
|
201
|
+
(1, -1),
|
202
|
+
(0, -1),
|
203
|
+
]
|
204
|
+
|
205
|
+
# Use a 2D array for visited tracking (faster than set)
|
206
|
+
visited = np.zeros((height, width), dtype=bool)
|
207
|
+
|
208
|
+
# Initialize contour
|
209
|
+
contour = []
|
210
|
+
contour.append((int(start[0] - 1), int(start[1] - 1))) # Adjust for padding
|
211
|
+
|
212
|
+
current = start
|
213
|
+
prev_dir = 7
|
214
|
+
visited[current[1], current[0]] = True
|
215
|
+
|
216
|
+
# Main tracing loop
|
217
|
+
while True:
|
218
|
+
found = False
|
219
|
+
|
220
|
+
# Check all 8 directions
|
221
|
+
for i in range(8):
|
222
|
+
dir_idx = (prev_dir + i) % 8
|
223
|
+
dx, dy = directions[dir_idx]
|
224
|
+
nx, ny = current[0] + dx, current[1] + dy
|
225
|
+
|
226
|
+
# Bounds check and value check
|
227
|
+
if (
|
228
|
+
0 <= ny < height
|
229
|
+
and 0 <= nx < width
|
230
|
+
and padded[ny, nx] == 1
|
231
|
+
and not visited[ny, nx]
|
232
|
+
):
|
233
|
+
current = (nx, ny)
|
234
|
+
visited[ny, nx] = True
|
235
|
+
contour.append(
|
236
|
+
(int(nx - 1), int(ny - 1))
|
237
|
+
) # Adjust for padding and convert to int
|
238
|
+
prev_dir = (dir_idx + 5) % 8
|
239
|
+
found = True
|
240
|
+
break
|
241
|
+
|
242
|
+
# Check termination conditions
|
243
|
+
if not found or (
|
244
|
+
len(contour) > 3
|
245
|
+
and (int(current[0] - 1), int(current[1] - 1)) == contour[0]
|
246
|
+
):
|
247
|
+
break
|
248
|
+
|
249
|
+
return contour
|
250
|
+
|
251
|
+
async def async_extract_room_properties(
|
252
|
+
self, json_data: Dict[str, Any]
|
253
|
+
) -> RoomsProperties:
|
254
|
+
"""
|
255
|
+
Extract room properties from the JSON data.
|
256
|
+
|
257
|
+
Args:
|
258
|
+
json_data: JSON data from the vacuum
|
259
|
+
|
260
|
+
Returns:
|
261
|
+
Dictionary of room properties
|
262
|
+
"""
|
263
|
+
room_properties = {}
|
264
|
+
pixel_size = json_data.get("pixelSize", 5)
|
265
|
+
height = json_data["size"]["y"]
|
266
|
+
width = json_data["size"]["x"]
|
267
|
+
vacuum_id = self.vacuum_id
|
268
|
+
room_id_counter = 0
|
269
|
+
|
270
|
+
for layer in json_data.get("layers", []):
|
271
|
+
if layer.get("__class") == "MapLayer" and layer.get("type") == "segment":
|
272
|
+
meta_data = layer.get("metaData", {})
|
273
|
+
segment_id = meta_data.get("segmentId")
|
274
|
+
name = meta_data.get("name", f"Room {segment_id}")
|
275
|
+
|
276
|
+
# Check if this room is disabled in the drawing configuration
|
277
|
+
# The room_id_counter is 0-based, but DrawableElement.ROOM_X is 1-based
|
278
|
+
current_room_id = room_id_counter + 1
|
279
|
+
room_id_counter = (
|
280
|
+
room_id_counter + 1
|
281
|
+
) % 16 # Cycle room_id back to 0 after 15
|
282
|
+
|
283
|
+
if 1 <= current_room_id <= 15 and self.drawing_config is not None:
|
284
|
+
room_element = getattr(
|
285
|
+
DrawableElement, f"ROOM_{current_room_id}", None
|
286
|
+
)
|
287
|
+
if room_element and not self.drawing_config.is_enabled(
|
288
|
+
room_element
|
289
|
+
):
|
290
|
+
LOGGER.debug(
|
291
|
+
"%s: Room %d is disabled and will be skipped",
|
292
|
+
self.vacuum_id,
|
293
|
+
current_room_id,
|
294
|
+
)
|
295
|
+
continue
|
296
|
+
|
297
|
+
compressed_pixels = layer.get("compressedPixels", [])
|
298
|
+
pixels = self.sublist(compressed_pixels, 3)
|
299
|
+
|
300
|
+
# Create a binary mask for the room
|
301
|
+
if not pixels:
|
302
|
+
LOGGER.warning(f"Skipping segment {segment_id}: no pixels found")
|
303
|
+
continue
|
304
|
+
|
305
|
+
mask = np.zeros((height, width), dtype=np.uint8)
|
306
|
+
for x, y, length in pixels:
|
307
|
+
if 0 <= y < height and 0 <= x < width and x + length <= width:
|
308
|
+
mask[y, x : x + length] = 1
|
309
|
+
|
310
|
+
# Find the room outline using the improved get_corners function
|
311
|
+
# Adjust epsilon_factor to control the level of simplification (higher = fewer points)
|
312
|
+
outline = await self.async_get_corners(mask, epsilon_factor=0.05)
|
313
|
+
|
314
|
+
if not outline:
|
315
|
+
LOGGER.warning(
|
316
|
+
f"Skipping segment {segment_id}: failed to generate outline"
|
317
|
+
)
|
318
|
+
continue
|
319
|
+
|
320
|
+
# Calculate the center of the room
|
321
|
+
xs, ys = zip(*outline)
|
322
|
+
x_min, x_max = min(xs), max(xs)
|
323
|
+
y_min, y_max = min(ys), max(ys)
|
324
|
+
|
325
|
+
# Scale coordinates by pixel_size
|
326
|
+
scaled_outline = [(x * pixel_size, y * pixel_size) for x, y in outline]
|
327
|
+
|
328
|
+
room_id = str(segment_id)
|
329
|
+
room_properties[room_id] = {
|
330
|
+
"number": segment_id,
|
331
|
+
"outline": scaled_outline, # Already includes the closing point
|
332
|
+
"name": name,
|
333
|
+
"x": ((x_min + x_max) * pixel_size) // 2,
|
334
|
+
"y": ((y_min + y_max) * pixel_size) // 2,
|
335
|
+
}
|
336
|
+
|
337
|
+
RoomStore(vacuum_id, room_properties)
|
338
|
+
return room_properties
|
339
|
+
|
340
|
+
async def get_room_at_position(
|
341
|
+
self, x: int, y: int, room_properties: Optional[RoomsProperties] = None
|
342
|
+
) -> Optional[Dict[str, Any]]:
|
343
|
+
"""
|
344
|
+
Get the room at a specific position.
|
345
|
+
|
346
|
+
Args:
|
347
|
+
x: X coordinate
|
348
|
+
y: Y coordinate
|
349
|
+
room_properties: Room properties dictionary (optional)
|
350
|
+
|
351
|
+
Returns:
|
352
|
+
Room data dictionary or None if no room at position
|
353
|
+
"""
|
354
|
+
if room_properties is None:
|
355
|
+
room_store = RoomStore(self.vacuum_id)
|
356
|
+
room_properties = room_store.get_rooms()
|
357
|
+
|
358
|
+
if not room_properties:
|
359
|
+
return None
|
360
|
+
|
361
|
+
for room_id, room_data in room_properties.items():
|
362
|
+
outline = room_data.get("outline", [])
|
363
|
+
if not outline or len(outline) < 3:
|
364
|
+
continue
|
365
|
+
|
366
|
+
# Check if point is inside the polygon
|
367
|
+
if self.point_in_polygon(x, y, outline):
|
368
|
+
return {
|
369
|
+
"id": room_id,
|
370
|
+
"name": room_data.get("name", f"Room {room_id}"),
|
371
|
+
"x": room_data.get("x", 0),
|
372
|
+
"y": room_data.get("y", 0),
|
373
|
+
}
|
374
|
+
|
375
|
+
return None
|
376
|
+
|
377
|
+
@staticmethod
|
378
|
+
def point_in_polygon(x: int, y: int, polygon: List[Tuple[int, int]]) -> bool:
|
379
|
+
"""
|
380
|
+
Check if a point is inside a polygon using ray casting algorithm.
|
381
|
+
|
382
|
+
Args:
|
383
|
+
x: X coordinate of the point
|
384
|
+
y: Y coordinate of the point
|
385
|
+
polygon: List of (x, y) tuples forming the polygon
|
386
|
+
|
387
|
+
Returns:
|
388
|
+
True if the point is inside the polygon, False otherwise
|
389
|
+
"""
|
390
|
+
n = len(polygon)
|
391
|
+
inside = False
|
392
|
+
|
393
|
+
p1x, p1y = polygon[0]
|
394
|
+
xinters = None # Initialize with default value
|
395
|
+
for i in range(1, n + 1):
|
396
|
+
p2x, p2y = polygon[i % n]
|
397
|
+
if y > min(p1y, p2y):
|
398
|
+
if y <= max(p1y, p2y):
|
399
|
+
if x <= max(p1x, p2x):
|
400
|
+
if p1y != p2y:
|
401
|
+
xinters = (y - p1y) * (p2x - p1x) / (p2y - p1y) + p1x
|
402
|
+
if p1x == p2x or x <= xinters:
|
403
|
+
inside = not inside
|
404
|
+
p1x, p1y = p2x, p2y
|
405
|
+
|
406
|
+
return inside
|
valetudo_map_parser/map_data.py
CHANGED
@@ -16,36 +16,6 @@ from .config.types import Colors, ImageSize, JsonType, NumpyArray
|
|
16
16
|
class ImageData:
|
17
17
|
"""Class to handle the image data."""
|
18
18
|
|
19
|
-
@staticmethod
|
20
|
-
async def async_extract_color_coordinates(
|
21
|
-
source_array: NumpyArray, search_for_colours_list: Colors
|
22
|
-
) -> list:
|
23
|
-
"""Search for specific colors in an image array and return their coordinates."""
|
24
|
-
# Initialize an empty list to store color and coordinates tuples
|
25
|
-
color_coordinates_list = []
|
26
|
-
|
27
|
-
# Iterate over the search_for_colours_list
|
28
|
-
for color_to_search in search_for_colours_list:
|
29
|
-
# Initialize an empty list to store coordinates for the current color
|
30
|
-
color_coordinates = []
|
31
|
-
|
32
|
-
# Iterate over the image array
|
33
|
-
for y in range(source_array.shape[0]):
|
34
|
-
for x in range(source_array.shape[1]):
|
35
|
-
# Extract the pixel color at the current coordinates
|
36
|
-
pixel_color = source_array[y, x]
|
37
|
-
|
38
|
-
# Check if the current pixel color matches the color_to_search
|
39
|
-
if np.all(pixel_color == color_to_search):
|
40
|
-
# Record the coordinates for the current color
|
41
|
-
color_coordinates.append((x, y))
|
42
|
-
|
43
|
-
# Append the color and its coordinates to the final list
|
44
|
-
color_coordinates_list.append((color_to_search, color_coordinates))
|
45
|
-
|
46
|
-
# Return the final list of color and coordinates tuples
|
47
|
-
return color_coordinates_list
|
48
|
-
|
49
19
|
@staticmethod
|
50
20
|
def sublist(lst, n):
|
51
21
|
"""Sub lists of specific n number of elements"""
|
@@ -29,13 +29,10 @@ from .config.types import (
|
|
29
29
|
)
|
30
30
|
from .config.utils import (
|
31
31
|
BaseHandler,
|
32
|
-
get_element_at_position,
|
33
|
-
get_room_at_position,
|
34
32
|
initialize_drawing_config,
|
35
33
|
manage_drawable_elements,
|
36
34
|
prepare_resize_params,
|
37
35
|
async_extract_room_outline,
|
38
|
-
update_element_map_with_robot,
|
39
36
|
)
|
40
37
|
from .map_data import RandImageData
|
41
38
|
from .reimg_draw import ImageDraw
|
@@ -64,7 +61,6 @@ class ReImageHandler(BaseHandler, AutoCrop):
|
|
64
61
|
self.drawing_config, self.draw, self.enhanced_draw = initialize_drawing_config(
|
65
62
|
self
|
66
63
|
)
|
67
|
-
self.element_map = None # Map of element codes
|
68
64
|
self.go_to = None # Go to position data
|
69
65
|
self.img_base_layer = None # Base image layer
|
70
66
|
self.img_rotate = shared_data.image_rotate # Image rotation
|
@@ -96,36 +92,8 @@ class ReImageHandler(BaseHandler, AutoCrop):
|
|
96
92
|
min_x, max_x = min(x_values), max(x_values)
|
97
93
|
min_y, max_y = min(y_values), max(y_values)
|
98
94
|
|
99
|
-
#
|
100
|
-
|
101
|
-
# Return rectangular outline
|
102
|
-
return [(min_x, min_y), (max_x, min_y), (max_x, max_y), (min_x, max_y)]
|
103
|
-
|
104
|
-
# Create a binary mask for this room using the pixel data
|
105
|
-
# This is more reliable than using the element_map since we're directly using the pixel data
|
106
|
-
height, width = self.element_map.shape
|
107
|
-
room_mask = np.zeros((height, width), dtype=np.uint8)
|
108
|
-
|
109
|
-
# Fill the mask with room pixels using the pixel data
|
110
|
-
for x, y, _ in pixels: # Using _ instead of z since z is unused
|
111
|
-
# Make sure we're within bounds
|
112
|
-
if 0 <= y < height and 0 <= x < width:
|
113
|
-
# Mark a pixel at this position
|
114
|
-
room_mask[y, x] = 1
|
115
|
-
|
116
|
-
# Debug log to check if we have any room pixels
|
117
|
-
num_room_pixels = np.sum(room_mask)
|
118
|
-
_LOGGER.debug(
|
119
|
-
"%s: Room %s mask has %d pixels",
|
120
|
-
self.file_name,
|
121
|
-
str(room_id_int),
|
122
|
-
int(num_room_pixels),
|
123
|
-
)
|
124
|
-
|
125
|
-
# Use the shared utility function to extract the room outline
|
126
|
-
return await async_extract_room_outline(
|
127
|
-
room_mask, min_x, min_y, max_x, max_y, self.file_name, room_id_int
|
128
|
-
)
|
95
|
+
# Always return a rectangular outline since element_map is removed
|
96
|
+
return [(min_x, min_y), (max_x, min_y), (max_x, max_y), (min_x, max_y)]
|
129
97
|
|
130
98
|
async def extract_room_properties(
|
131
99
|
self, json_data: JsonType, destinations: JsonType
|
@@ -322,20 +290,11 @@ class ReImageHandler(BaseHandler, AutoCrop):
|
|
322
290
|
async def _draw_map_elements(
|
323
291
|
self, img_np_array, m_json, colors, robot_position, robot_position_angle
|
324
292
|
):
|
325
|
-
# Create element map for tracking what's drawn where if it doesn't exist
|
326
|
-
if self.element_map is None:
|
327
|
-
self.element_map = np.zeros(
|
328
|
-
(img_np_array.shape[0], img_np_array.shape[1]), dtype=np.int32
|
329
|
-
)
|
330
|
-
self.element_map[:] = DrawableElement.FLOOR
|
331
|
-
|
332
293
|
# Draw charger if enabled
|
333
294
|
if self.drawing_config.is_enabled(DrawableElement.CHARGER):
|
334
295
|
img_np_array, self.charger_pos = await self.imd.async_draw_charger(
|
335
296
|
img_np_array, m_json, colors["charger"]
|
336
297
|
)
|
337
|
-
# Update element map for charger position
|
338
|
-
self._update_element_map_for_charger()
|
339
298
|
|
340
299
|
# Draw zones if enabled
|
341
300
|
if self.drawing_config.is_enabled(DrawableElement.RESTRICTED_AREA):
|
@@ -372,10 +331,6 @@ class ReImageHandler(BaseHandler, AutoCrop):
|
|
372
331
|
img_np_array, robot_position, robot_position_angle, robot_color
|
373
332
|
)
|
374
333
|
|
375
|
-
# Update element map for robot position
|
376
|
-
update_element_map_with_robot(
|
377
|
-
self.element_map, robot_position, DrawableElement.ROBOT
|
378
|
-
)
|
379
334
|
img_np_array = await self.async_auto_trim_and_zoom_image(
|
380
335
|
img_np_array,
|
381
336
|
detect_colour=colors["background"],
|
@@ -536,40 +491,3 @@ class ReImageHandler(BaseHandler, AutoCrop):
|
|
536
491
|
property_name=property_name,
|
537
492
|
value=value,
|
538
493
|
)
|
539
|
-
|
540
|
-
def get_element_at_position(self, x: int, y: int) -> DrawableElement:
|
541
|
-
"""Get the element code at a specific position."""
|
542
|
-
return get_element_at_position(self.element_map, x, y)
|
543
|
-
|
544
|
-
def get_room_at_position(self, x: int, y: int) -> int:
|
545
|
-
"""Get the room ID at a specific position, or None if not a room."""
|
546
|
-
return get_room_at_position(self.element_map, x, y, DrawableElement.ROOM_1)
|
547
|
-
|
548
|
-
def _update_element_map_for_charger(self):
|
549
|
-
"""Helper method to update the element map for the charger position."""
|
550
|
-
if not self.charger_pos or self.element_map is None:
|
551
|
-
return
|
552
|
-
|
553
|
-
charger_radius = 15
|
554
|
-
# Handle both dictionary format {'x': x, 'y': y} and list format [x, y]
|
555
|
-
charger_x = (
|
556
|
-
self.charger_pos.get("x")
|
557
|
-
if isinstance(self.charger_pos, dict)
|
558
|
-
else self.charger_pos[0]
|
559
|
-
)
|
560
|
-
charger_y = (
|
561
|
-
self.charger_pos.get("y")
|
562
|
-
if isinstance(self.charger_pos, dict)
|
563
|
-
else self.charger_pos[1]
|
564
|
-
)
|
565
|
-
|
566
|
-
for dy in range(-charger_radius, charger_radius + 1):
|
567
|
-
for dx in range(-charger_radius, charger_radius + 1):
|
568
|
-
# Check if the point is within the circular charger area
|
569
|
-
if dx * dx + dy * dy <= charger_radius * charger_radius:
|
570
|
-
cx, cy = int(charger_x + dx), int(charger_y + dy)
|
571
|
-
# Check if the coordinates are within the element map bounds
|
572
|
-
if (0 <= cy < self.element_map.shape[0]) and (
|
573
|
-
0 <= cx < self.element_map.shape[1]
|
574
|
-
):
|
575
|
-
self.element_map[cy, cx] = DrawableElement.CHARGER
|
@@ -0,0 +1,26 @@
|
|
1
|
+
valetudo_map_parser/__init__.py,sha256=cewtLadNSOg3X2Ts2SuG8mTJqo0ncsFRg_sQ4VkM4ow,1037
|
2
|
+
valetudo_map_parser/config/__init__.py,sha256=DQ9plV3ZF_K25Dp5ZQHPDoG-40dQoJNdNi-dfNeR3Zc,48
|
3
|
+
valetudo_map_parser/config/auto_crop.py,sha256=6OvRsWzXMXBaSEvgwpaaisNdozDKiDyTmPjknFxoUMc,12624
|
4
|
+
valetudo_map_parser/config/color_utils.py,sha256=D4NXRhuPdQ7UDKM3vLNYR0HnACl9AB75EnfCp5tGliI,4502
|
5
|
+
valetudo_map_parser/config/colors.py,sha256=5nk8QbNomv_WeE2rOLIswroGsxV4RZMd0slXrPlt-MY,29297
|
6
|
+
valetudo_map_parser/config/drawable.py,sha256=VhaNK62EBCxcQdNg7-dyUNepsYX6IFzej3raOt7_rVU,34268
|
7
|
+
valetudo_map_parser/config/drawable_elements.py,sha256=bkEwdbx1upt9vaPaqE_VR1rtwRsaiH-IVKc3mHNa8IY,12065
|
8
|
+
valetudo_map_parser/config/enhanced_drawable.py,sha256=6yGoOq_dLf2VCghO_URSyGfLAshFyzS3iEPeHw1PeDo,12586
|
9
|
+
valetudo_map_parser/config/optimized_element_map.py,sha256=52BCnkvVv9bre52LeVIfT8nhnEIpc0TuWTv1xcNu0Rk,15744
|
10
|
+
valetudo_map_parser/config/rand25_parser.py,sha256=kIayyqVZBfQfAMkiArzqrrj9vqZB3pkgT0Y5ufrQmGA,16448
|
11
|
+
valetudo_map_parser/config/room_outline.py,sha256=D20D-yeyKnlmVbW9lI7bsPtQGn2XkcWow6YNOEPnWVg,4800
|
12
|
+
valetudo_map_parser/config/shared.py,sha256=GIEMF-M6BVA6SFBrql7chV7TciWNMLJ8geqwHB0NrW8,11253
|
13
|
+
valetudo_map_parser/config/types.py,sha256=e-eZSwbPm3m5JfCDaKhnUFspmcRFSv74huxegkSBDXM,17566
|
14
|
+
valetudo_map_parser/config/utils.py,sha256=RsMjpjVqNbkI502yhLiRaB0GjCADqmRRcz-TkC6zklQ,31073
|
15
|
+
valetudo_map_parser/hypfer_draw.py,sha256=P8CrKysLaBb63ZArfqxN2Og6JCU6sPHPFHOte5noCGg,26654
|
16
|
+
valetudo_map_parser/hypfer_handler.py,sha256=WYFrp-q5wBsy0cTcVQUCXXVGTtW30z2W2dYvjKz2el8,23292
|
17
|
+
valetudo_map_parser/hypfer_rooms_handler.py,sha256=3QL8QZc6aMXxYn_L6gv1iL204I0rAzutWPVzc_4MfJ4,14505
|
18
|
+
valetudo_map_parser/map_data.py,sha256=zQKE8EzWxR0r0qyfD1QQq51T1wFrpcIeXtnpm92-LXQ,17743
|
19
|
+
valetudo_map_parser/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
20
|
+
valetudo_map_parser/rand25_handler.py,sha256=eLFX_gmGLaWQwvp8hVj8CgcNOfLsYNIdE1OLRcQy_yM,19988
|
21
|
+
valetudo_map_parser/reimg_draw.py,sha256=1q8LkNTPHEA9Tsapc_JnVw51kpPYNhaBU-KmHkefCQY,12507
|
22
|
+
valetudo_map_parser-0.1.9b52.dist-info/LICENSE,sha256=Lh-qBbuRV0-jiCIBhfV7NgdwFxQFOXH3BKOzK865hRs,10480
|
23
|
+
valetudo_map_parser-0.1.9b52.dist-info/METADATA,sha256=jd0uF9vdxA_LxkXlWoLtOCyWvXJD3knHXVTQYDNn3E4,3321
|
24
|
+
valetudo_map_parser-0.1.9b52.dist-info/NOTICE.txt,sha256=5lTOuWiU9aiEnJ2go8sc7lTJ7ntMBx0g0GFnNrswCY4,2533
|
25
|
+
valetudo_map_parser-0.1.9b52.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
26
|
+
valetudo_map_parser-0.1.9b52.dist-info/RECORD,,
|
@@ -1,25 +0,0 @@
|
|
1
|
-
valetudo_map_parser/__init__.py,sha256=SOxmq7LkS7eMa2N4atW7ZBbqhGEL7fpj6MsyXZpCMsk,958
|
2
|
-
valetudo_map_parser/config/__init__.py,sha256=DQ9plV3ZF_K25Dp5ZQHPDoG-40dQoJNdNi-dfNeR3Zc,48
|
3
|
-
valetudo_map_parser/config/auto_crop.py,sha256=6OvRsWzXMXBaSEvgwpaaisNdozDKiDyTmPjknFxoUMc,12624
|
4
|
-
valetudo_map_parser/config/color_utils.py,sha256=1IlFKq-otK__Q-MdfMfeLKhdYfL850qJV_qs9pt5BVw,1609
|
5
|
-
valetudo_map_parser/config/colors.py,sha256=us2v693jPHfW5V7Q55xAyMcTTwUAs23eXfFVsz_n3S8,30277
|
6
|
-
valetudo_map_parser/config/drawable.py,sha256=VhaNK62EBCxcQdNg7-dyUNepsYX6IFzej3raOt7_rVU,34268
|
7
|
-
valetudo_map_parser/config/drawable_elements.py,sha256=Ulfgf8B4LuLCfx-FfmC7LrP8o9ll_Sncg9mR774_3KE,43140
|
8
|
-
valetudo_map_parser/config/enhanced_drawable.py,sha256=xNgFUNccstP245VgLFEA9gjB3-VvlSAJSjRgSZ3YFL0,16641
|
9
|
-
valetudo_map_parser/config/optimized_element_map.py,sha256=52BCnkvVv9bre52LeVIfT8nhnEIpc0TuWTv1xcNu0Rk,15744
|
10
|
-
valetudo_map_parser/config/rand25_parser.py,sha256=kIayyqVZBfQfAMkiArzqrrj9vqZB3pkgT0Y5ufrQmGA,16448
|
11
|
-
valetudo_map_parser/config/room_outline.py,sha256=D20D-yeyKnlmVbW9lI7bsPtQGn2XkcWow6YNOEPnWVg,4800
|
12
|
-
valetudo_map_parser/config/shared.py,sha256=WSl5rYSiTqE6YGAiwi9RILMZIQdFZRzVS8DwqzTZBbw,11309
|
13
|
-
valetudo_map_parser/config/types.py,sha256=uEJY-yYHHJWW3EZjg7hERSFrC2XuKzzRGT3C0z31Aw0,18359
|
14
|
-
valetudo_map_parser/config/utils.py,sha256=MP5_s9VFSdDERymujvDuGB8nYCXVuJcqg5tR5H9HCgY,33167
|
15
|
-
valetudo_map_parser/hypfer_draw.py,sha256=oL_RbX0LEcPvOlMrfBA38qpJkMqqVwR-oAEbZeHqLWM,19898
|
16
|
-
valetudo_map_parser/hypfer_handler.py,sha256=-nwGlfd-fqmNAHEeQFgCarr1t5v8gJTvb2ngDz_8PbM,27616
|
17
|
-
valetudo_map_parser/map_data.py,sha256=lSKD-CG-RENOcNUDnUIIpqh74DuGnLmrH46IF1_EjwQ,19117
|
18
|
-
valetudo_map_parser/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
19
|
-
valetudo_map_parser/rand25_handler.py,sha256=F9o1J6JZRV3CZTS4CG3AHNHZweKRM0nzd4HLmdPZe4w,23617
|
20
|
-
valetudo_map_parser/reimg_draw.py,sha256=1q8LkNTPHEA9Tsapc_JnVw51kpPYNhaBU-KmHkefCQY,12507
|
21
|
-
valetudo_map_parser-0.1.9b50.dist-info/LICENSE,sha256=Lh-qBbuRV0-jiCIBhfV7NgdwFxQFOXH3BKOzK865hRs,10480
|
22
|
-
valetudo_map_parser-0.1.9b50.dist-info/METADATA,sha256=0Io7-FU4r1-5UhYjcV5LRs_oAcWQFCb_IX4_rJM0oNs,3321
|
23
|
-
valetudo_map_parser-0.1.9b50.dist-info/NOTICE.txt,sha256=5lTOuWiU9aiEnJ2go8sc7lTJ7ntMBx0g0GFnNrswCY4,2533
|
24
|
-
valetudo_map_parser-0.1.9b50.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
25
|
-
valetudo_map_parser-0.1.9b50.dist-info/RECORD,,
|
File without changes
|
{valetudo_map_parser-0.1.9b50.dist-info → valetudo_map_parser-0.1.9b52.dist-info}/NOTICE.txt
RENAMED
File without changes
|
File without changes
|