valetudo-map-parser 0.1.9b52__py3-none-any.whl → 0.1.9b54__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 -2
- valetudo_map_parser/config/auto_crop.py +27 -7
- valetudo_map_parser/config/color_utils.py +2 -2
- valetudo_map_parser/config/colors.py +15 -4
- valetudo_map_parser/config/drawable.py +1 -5
- valetudo_map_parser/config/drawable_elements.py +4 -19
- valetudo_map_parser/config/enhanced_drawable.py +1 -1
- valetudo_map_parser/config/shared.py +0 -20
- valetudo_map_parser/config/types.py +1 -5
- valetudo_map_parser/config/utils.py +1 -51
- valetudo_map_parser/hypfer_draw.py +0 -13
- valetudo_map_parser/hypfer_handler.py +28 -88
- valetudo_map_parser/hypfer_rooms_handler.py +295 -102
- valetudo_map_parser/rand25_handler.py +1 -1
- valetudo_map_parser/rooms_handler.py +225 -0
- {valetudo_map_parser-0.1.9b52.dist-info → valetudo_map_parser-0.1.9b54.dist-info}/METADATA +1 -1
- valetudo_map_parser-0.1.9b54.dist-info/RECORD +27 -0
- valetudo_map_parser-0.1.9b52.dist-info/RECORD +0 -26
- {valetudo_map_parser-0.1.9b52.dist-info → valetudo_map_parser-0.1.9b54.dist-info}/LICENSE +0 -0
- {valetudo_map_parser-0.1.9b52.dist-info → valetudo_map_parser-0.1.9b54.dist-info}/NOTICE.txt +0 -0
- {valetudo_map_parser-0.1.9b52.dist-info → valetudo_map_parser-0.1.9b54.dist-info}/WHEEL +0 -0
@@ -0,0 +1,225 @@
|
|
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
|
+
import time
|
11
|
+
from typing import Any, Dict, Optional, Tuple
|
12
|
+
|
13
|
+
import numpy as np
|
14
|
+
from scipy.ndimage import binary_dilation, binary_erosion
|
15
|
+
from scipy.spatial import ConvexHull
|
16
|
+
|
17
|
+
from .config.drawable_elements import DrawableElement, DrawingConfig
|
18
|
+
from .config.types import LOGGER, RoomsProperties
|
19
|
+
|
20
|
+
|
21
|
+
class RoomsHandler:
|
22
|
+
"""
|
23
|
+
Handler for extracting and managing room data from Hipfer vacuum maps.
|
24
|
+
|
25
|
+
This class provides methods to:
|
26
|
+
- Extract room outlines using the Ramer-Douglas-Peucker algorithm
|
27
|
+
- Process room properties from JSON data
|
28
|
+
- Generate room masks and extract contours
|
29
|
+
|
30
|
+
All methods are async for better integration with the rest of the codebase.
|
31
|
+
"""
|
32
|
+
|
33
|
+
def __init__(self, vacuum_id: str, drawing_config: Optional[DrawingConfig] = None):
|
34
|
+
"""
|
35
|
+
Initialize the HipferRoomsHandler.
|
36
|
+
|
37
|
+
Args:
|
38
|
+
vacuum_id: Identifier for the vacuum
|
39
|
+
drawing_config: Configuration for which elements to draw (optional)
|
40
|
+
"""
|
41
|
+
self.vacuum_id = vacuum_id
|
42
|
+
self.drawing_config = drawing_config
|
43
|
+
self.current_json_data = (
|
44
|
+
None # Will store the current JSON data being processed
|
45
|
+
)
|
46
|
+
|
47
|
+
@staticmethod
|
48
|
+
def sublist(data: list, chunk_size: int) -> list:
|
49
|
+
return [data[i : i + chunk_size] for i in range(0, len(data), chunk_size)]
|
50
|
+
|
51
|
+
@staticmethod
|
52
|
+
def convex_hull_outline(mask: np.ndarray) -> list[tuple[int, int]]:
|
53
|
+
y_indices, x_indices = np.where(mask > 0)
|
54
|
+
if len(x_indices) == 0 or len(y_indices) == 0:
|
55
|
+
return []
|
56
|
+
|
57
|
+
points = np.column_stack((x_indices, y_indices))
|
58
|
+
if len(points) < 3:
|
59
|
+
return [(int(x), int(y)) for x, y in points]
|
60
|
+
|
61
|
+
hull = ConvexHull(points)
|
62
|
+
# Convert numpy.int64 values to regular Python integers
|
63
|
+
hull_points = [
|
64
|
+
(int(points[vertex][0]), int(points[vertex][1])) for vertex in hull.vertices
|
65
|
+
]
|
66
|
+
if hull_points[0] != hull_points[-1]:
|
67
|
+
hull_points.append(hull_points[0])
|
68
|
+
return hull_points
|
69
|
+
|
70
|
+
async def _process_room_layer(
|
71
|
+
self, layer: Dict[str, Any], width: int, height: int, pixel_size: int
|
72
|
+
) -> Tuple[Optional[str], Optional[Dict[str, Any]]]:
|
73
|
+
"""Process a single room layer and extract its outline.
|
74
|
+
|
75
|
+
Args:
|
76
|
+
layer: The layer data from the JSON
|
77
|
+
width: The width of the map
|
78
|
+
height: The height of the map
|
79
|
+
pixel_size: The size of each pixel
|
80
|
+
|
81
|
+
Returns:
|
82
|
+
Tuple of (room_id, room_data) or (None, None) if processing failed
|
83
|
+
"""
|
84
|
+
meta_data = layer.get("metaData", {})
|
85
|
+
segment_id = meta_data.get("segmentId")
|
86
|
+
name = meta_data.get("name", "Room {}".format(segment_id))
|
87
|
+
compressed_pixels = layer.get("compressedPixels", [])
|
88
|
+
pixels = self.sublist(compressed_pixels, 3)
|
89
|
+
|
90
|
+
# Check if this room is enabled in the drawing configuration
|
91
|
+
if self.drawing_config is not None:
|
92
|
+
# Convert segment_id to room element (ROOM_1 to ROOM_15)
|
93
|
+
try:
|
94
|
+
# Segment IDs might not be sequential, so we need to map them to room elements
|
95
|
+
# We'll use a simple approach: if segment_id is an integer, use it directly
|
96
|
+
room_element_id = int(segment_id)
|
97
|
+
if 1 <= room_element_id <= 15:
|
98
|
+
room_element = getattr(
|
99
|
+
DrawableElement, f"ROOM_{room_element_id}", None
|
100
|
+
)
|
101
|
+
if room_element:
|
102
|
+
is_enabled = self.drawing_config.is_enabled(room_element)
|
103
|
+
if not is_enabled:
|
104
|
+
# Skip this room if it's disabled
|
105
|
+
LOGGER.debug("Skipping disabled room %s", segment_id)
|
106
|
+
return None, None
|
107
|
+
except (ValueError, TypeError):
|
108
|
+
# If segment_id is not a valid integer, we can't map it to a room element
|
109
|
+
# In this case, we'll include the room (fail open)
|
110
|
+
LOGGER.debug(
|
111
|
+
"Could not convert segment_id %s to room element", segment_id
|
112
|
+
)
|
113
|
+
|
114
|
+
# Optimization: Create a smaller mask for just the room area
|
115
|
+
if not pixels:
|
116
|
+
# Skip if no pixels
|
117
|
+
return None, None
|
118
|
+
|
119
|
+
# Convert to numpy arrays for vectorized operations
|
120
|
+
pixel_data = np.array(pixels)
|
121
|
+
|
122
|
+
if pixel_data.size == 0:
|
123
|
+
return None, None
|
124
|
+
|
125
|
+
# Find the actual bounds of the room to create a smaller mask
|
126
|
+
# Add padding to ensure we don't lose edge details
|
127
|
+
padding = 10 # Add padding pixels around the room
|
128
|
+
min_x = max(0, int(np.min(pixel_data[:, 0])) - padding)
|
129
|
+
max_x = min(
|
130
|
+
width, int(np.max(pixel_data[:, 0]) + np.max(pixel_data[:, 2])) + padding
|
131
|
+
)
|
132
|
+
min_y = max(0, int(np.min(pixel_data[:, 1])) - padding)
|
133
|
+
max_y = min(height, int(np.max(pixel_data[:, 1]) + 1) + padding)
|
134
|
+
|
135
|
+
# Create a smaller mask for just the room area (much faster)
|
136
|
+
local_width = max_x - min_x
|
137
|
+
local_height = max_y - min_y
|
138
|
+
|
139
|
+
# Skip if dimensions are invalid
|
140
|
+
if local_width <= 0 or local_height <= 0:
|
141
|
+
return None, None
|
142
|
+
|
143
|
+
# Create a smaller mask
|
144
|
+
local_mask = np.zeros((local_height, local_width), dtype=np.uint8)
|
145
|
+
|
146
|
+
# Fill the mask efficiently
|
147
|
+
for x, y, length in pixel_data:
|
148
|
+
x, y, length = int(x), int(y), int(length)
|
149
|
+
# Adjust coordinates to local mask
|
150
|
+
local_x = x - min_x
|
151
|
+
local_y = y - min_y
|
152
|
+
|
153
|
+
# Ensure we're within bounds
|
154
|
+
if 0 <= local_y < local_height and 0 <= local_x < local_width:
|
155
|
+
# Calculate the end point, clamping to mask width
|
156
|
+
end_x = min(local_x + length, local_width)
|
157
|
+
if end_x > local_x: # Only process if there's a valid segment
|
158
|
+
local_mask[local_y, local_x:end_x] = 1
|
159
|
+
|
160
|
+
# Apply morphological operations
|
161
|
+
struct_elem = np.ones((3, 3), dtype=np.uint8)
|
162
|
+
eroded = binary_erosion(local_mask, structure=struct_elem, iterations=1)
|
163
|
+
mask = binary_dilation(eroded, structure=struct_elem, iterations=1).astype(
|
164
|
+
np.uint8
|
165
|
+
)
|
166
|
+
|
167
|
+
# Extract contour from the mask
|
168
|
+
outline = self.convex_hull_outline(mask)
|
169
|
+
if not outline:
|
170
|
+
return None, None
|
171
|
+
|
172
|
+
# Adjust coordinates back to global space
|
173
|
+
outline = [(x + min_x, y + min_y) for (x, y) in outline]
|
174
|
+
|
175
|
+
# Use coordinates as-is without flipping Y coordinates
|
176
|
+
xs, ys = zip(*outline)
|
177
|
+
x_min, x_max = min(xs), max(xs)
|
178
|
+
y_min, y_max = min(ys), max(ys)
|
179
|
+
|
180
|
+
room_id = str(segment_id)
|
181
|
+
|
182
|
+
# Scale coordinates by pixel_size and convert to regular Python integers
|
183
|
+
scaled_outline = [
|
184
|
+
(int(x * pixel_size), int(y * pixel_size)) for x, y in outline
|
185
|
+
]
|
186
|
+
room_data = {
|
187
|
+
"number": segment_id,
|
188
|
+
"outline": scaled_outline,
|
189
|
+
"name": name,
|
190
|
+
"x": int(((x_min + x_max) * pixel_size) // 2),
|
191
|
+
"y": int(((y_min + y_max) * pixel_size) // 2),
|
192
|
+
}
|
193
|
+
|
194
|
+
return room_id, room_data
|
195
|
+
|
196
|
+
async def async_extract_room_properties(self, json_data) -> RoomsProperties:
|
197
|
+
"""Extract room properties from the JSON data.
|
198
|
+
|
199
|
+
This method processes all room layers in the JSON data and extracts their outlines.
|
200
|
+
It respects the drawing configuration, skipping rooms that are disabled.
|
201
|
+
|
202
|
+
Args:
|
203
|
+
json_data: The JSON data from the vacuum
|
204
|
+
|
205
|
+
Returns:
|
206
|
+
Dictionary of room properties
|
207
|
+
"""
|
208
|
+
start_total = time.time()
|
209
|
+
room_properties = {}
|
210
|
+
pixel_size = json_data.get("pixelSize", 5)
|
211
|
+
height = json_data["size"]["y"]
|
212
|
+
width = json_data["size"]["x"]
|
213
|
+
|
214
|
+
for layer in json_data.get("layers", []):
|
215
|
+
if layer.get("__class") == "MapLayer" and layer.get("type") == "segment":
|
216
|
+
room_id, room_data = await self._process_room_layer(
|
217
|
+
layer, width, height, pixel_size
|
218
|
+
)
|
219
|
+
if room_id is not None and room_data is not None:
|
220
|
+
room_properties[room_id] = room_data
|
221
|
+
|
222
|
+
# Log timing information
|
223
|
+
total_time = time.time() - start_total
|
224
|
+
LOGGER.debug("Room extraction Total time: %.3fs", total_time)
|
225
|
+
return room_properties
|
@@ -0,0 +1,27 @@
|
|
1
|
+
valetudo_map_parser/__init__.py,sha256=A_rCyIP5ll-ovzHMEsGMgbNmB39vDtqgL5hpZQkNbPQ,1018
|
2
|
+
valetudo_map_parser/config/__init__.py,sha256=DQ9plV3ZF_K25Dp5ZQHPDoG-40dQoJNdNi-dfNeR3Zc,48
|
3
|
+
valetudo_map_parser/config/auto_crop.py,sha256=6xt_wJQqphddWhlrr7MNUkodCi8ZYdRk42qvAaxlYCM,13546
|
4
|
+
valetudo_map_parser/config/color_utils.py,sha256=nXD6WeNmdFdoMxPDW-JFpjnxJSaZR1jX-ouNfrx6zvE,4502
|
5
|
+
valetudo_map_parser/config/colors.py,sha256=DG-oPQoN5gsnwDbEsuFr8a0hRCxmbFHObWa4_5pr-70,29910
|
6
|
+
valetudo_map_parser/config/drawable.py,sha256=2MeVHXqZuVuJk3eerMJYGwo25rVetHx3xB_vxecEFOQ,34168
|
7
|
+
valetudo_map_parser/config/drawable_elements.py,sha256=o-5oiXmfqPwNQLzKIhkEcZD_A47rIU9E0CqKgWipxgc,11516
|
8
|
+
valetudo_map_parser/config/enhanced_drawable.py,sha256=QlGxlUMVgECUXPtFwIslyjubWxQuhIixsRymWV3lEvk,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=LJPDE8MhmbY0CXXMbtDff-JVtmLKGjoWE6c4mpsaEc4,10419
|
13
|
+
valetudo_map_parser/config/types.py,sha256=TaRKoo7G7WIUw7ljOz2Vn5oYzKaLyQH-7Eb8ZYql8Ls,17464
|
14
|
+
valetudo_map_parser/config/utils.py,sha256=CFuuiS5IufEu9aeaZwi7xa1jEF1z6yDZB0mcyVX79Xo,29261
|
15
|
+
valetudo_map_parser/hypfer_draw.py,sha256=afFJ9woTVYOfETbxFLU74r4H2PUW_eTfEvhKuYKH8h0,26226
|
16
|
+
valetudo_map_parser/hypfer_handler.py,sha256=wvkZt6MsUF0gkHZDAwiUgOOawkdvakRzYBV3NtjxuJQ,19938
|
17
|
+
valetudo_map_parser/hypfer_rooms_handler.py,sha256=NkpOA6Gdq-2D3lLAxvtNuuWMvPXHxeMY2TO5RZLSHlU,22652
|
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=vK4lF7RYKKeh_6IyvUWDmwFqULHdDa3pz_Wb00KAsXs,19988
|
21
|
+
valetudo_map_parser/reimg_draw.py,sha256=1q8LkNTPHEA9Tsapc_JnVw51kpPYNhaBU-KmHkefCQY,12507
|
22
|
+
valetudo_map_parser/rooms_handler.py,sha256=GKqDji8LWAowQMDAbSk-MYwzAHVj25rcF60GyTWeSpY,8687
|
23
|
+
valetudo_map_parser-0.1.9b54.dist-info/LICENSE,sha256=Lh-qBbuRV0-jiCIBhfV7NgdwFxQFOXH3BKOzK865hRs,10480
|
24
|
+
valetudo_map_parser-0.1.9b54.dist-info/METADATA,sha256=B7aiLJ5yxR6ye5YI6VlOKVTJCfZ2oU1YHdkYloViZA0,3321
|
25
|
+
valetudo_map_parser-0.1.9b54.dist-info/NOTICE.txt,sha256=5lTOuWiU9aiEnJ2go8sc7lTJ7ntMBx0g0GFnNrswCY4,2533
|
26
|
+
valetudo_map_parser-0.1.9b54.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
27
|
+
valetudo_map_parser-0.1.9b54.dist-info/RECORD,,
|
@@ -1,26 +0,0 @@
|
|
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,,
|
File without changes
|
{valetudo_map_parser-0.1.9b52.dist-info → valetudo_map_parser-0.1.9b54.dist-info}/NOTICE.txt
RENAMED
File without changes
|
File without changes
|