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.
@@ -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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: valetudo-map-parser
3
- Version: 0.1.9b52
3
+ Version: 0.1.9b54
4
4
  Summary: A Python library to parse Valetudo map data returning a PIL Image object.
5
5
  License: Apache-2.0
6
6
  Author: Sandro Cantarella
@@ -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,,