valetudo-map-parser 0.1.9b54__py3-none-any.whl → 0.1.9b55__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.
@@ -17,11 +17,12 @@ from .config.types import (
17
17
  )
18
18
  from .hypfer_handler import HypferMapImageHandler
19
19
  from .rand25_handler import ReImageHandler
20
- from .rooms_handler import RoomsHandler
20
+ from .rooms_handler import RoomsHandler, RandRoomsHandler
21
21
 
22
22
 
23
23
  __all__ = [
24
24
  "RoomsHandler",
25
+ "RandRoomsHandler",
25
26
  "HypferMapImageHandler",
26
27
  "ReImageHandler",
27
28
  "RRMapParser",
@@ -88,6 +88,7 @@ class CameraShared:
88
88
  self.vac_json_id = None # Vacuum json id
89
89
  self.margins = "100" # Image margins
90
90
  self.obstacles_data = None # Obstacles data
91
+ self.obstacles_pos = None # Obstacles position
91
92
  self.offset_top = 0 # Image offset top
92
93
  self.offset_down = 0 # Image offset down
93
94
  self.offset_left = 0 # Image offset left
@@ -303,6 +303,13 @@ class ImageDraw:
303
303
  await self.img_h.draw.async_draw_obstacles(
304
304
  np_array, obstacle_positions, color_no_go
305
305
  )
306
+
307
+ # Update both obstacles_pos and obstacles_data
308
+ self.img_h.shared.obstacles_pos = obstacle_positions
309
+ # Only update obstacles_data if it's None or if the number of obstacles has changed
310
+ if (self.img_h.shared.obstacles_data is None or
311
+ len(self.img_h.shared.obstacles_data) != len(obstacle_positions)):
312
+ self.img_h.shared.obstacles_data = obstacle_positions
306
313
  return np_array
307
314
 
308
315
  async def async_draw_charger(
@@ -29,13 +29,14 @@ from .config.types import (
29
29
  )
30
30
  from .config.utils import (
31
31
  BaseHandler,
32
- async_extract_room_outline,
32
+ # async_extract_room_outline,
33
33
  initialize_drawing_config,
34
34
  manage_drawable_elements,
35
35
  prepare_resize_params,
36
36
  )
37
37
  from .map_data import RandImageData
38
38
  from .reimg_draw import ImageDraw
39
+ from .rooms_handler import RandRoomsHandler
39
40
 
40
41
 
41
42
  _LOGGER = logging.getLogger(__name__)
@@ -68,6 +69,7 @@ class ReImageHandler(BaseHandler, AutoCrop):
68
69
  self.active_zones = None # Active zones
69
70
  self.file_name = self.shared.file_name # File name
70
71
  self.imd = ImageDraw(self) # Image Draw
72
+ self.rooms_handler = RandRoomsHandler(self.file_name, self.drawing_config) # Room data handler
71
73
 
72
74
  async def extract_room_outline_from_map(self, room_id_int, pixels):
73
75
  """Extract the outline of a room using the pixel data and element map.
@@ -110,62 +112,45 @@ class ReImageHandler(BaseHandler, AutoCrop):
110
112
  ) = await RandImageData.async_get_rrm_segments(
111
113
  json_data, size_x, size_y, top, left, True
112
114
  )
115
+
113
116
  dest_json = destinations
114
- room_data = dict(dest_json).get("rooms", [])
115
117
  zones_data = dict(dest_json).get("zones", [])
116
118
  points_data = dict(dest_json).get("spots", [])
117
- room_id_to_data = {room["id"]: room for room in room_data}
119
+
120
+ # Use the RandRoomsHandler to extract room properties
121
+ room_properties = await self.rooms_handler.async_extract_room_properties(
122
+ json_data, dest_json
123
+ )
124
+
125
+ # Update self.rooms_pos from room_properties for compatibility with other methods
118
126
  self.rooms_pos = []
119
- room_properties = {}
120
- if self.outlines:
121
- for id_x, room_id in enumerate(unsorted_id):
122
- if room_id in room_id_to_data:
123
- room_info = room_id_to_data[room_id]
124
- name = room_info.get("name")
125
- # Calculate x and y min/max from outlines
126
- x_min = self.outlines[id_x][0][0]
127
- x_max = self.outlines[id_x][1][0]
128
- y_min = self.outlines[id_x][0][1]
129
- y_max = self.outlines[id_x][1][1]
130
- corners = self.get_corners(x_max, x_min, y_max, y_min)
131
- # rand256 vacuums accept int(room_id) or str(name)
132
- # the card will soon support int(room_id) but the camera will send name
133
- # this avoids the manual change of the values in the card.
134
- self.rooms_pos.append(
135
- {
136
- "name": name,
137
- "corners": corners,
138
- }
139
- )
140
- room_properties[int(room_id)] = {
141
- "number": int(room_id),
142
- "outline": corners,
143
- "name": name,
144
- "x": (x_min + x_max) // 2,
145
- "y": (y_min + y_max) // 2,
146
- }
147
- # get the zones and points data
148
- zone_properties = await self.async_zone_propriety(zones_data)
149
- # get the points data
150
- point_properties = await self.async_points_propriety(points_data)
151
- if room_properties or zone_properties:
152
- extracted_data = [
153
- f"{len(room_properties)} Rooms" if room_properties else None,
154
- f"{len(zone_properties)} Zones" if zone_properties else None,
155
- ]
156
- extracted_data = ", ".join(filter(None, extracted_data))
157
- _LOGGER.debug("Extracted data: %s", extracted_data)
158
- else:
159
- self.rooms_pos = None
160
- _LOGGER.debug(
161
- "%s: Rooms and Zones data not available!", self.file_name
162
- )
163
- rooms = RoomStore(self.file_name, room_properties)
164
- _LOGGER.debug("Rooms Data: %s", rooms.get_rooms())
165
- return room_properties, zone_properties, point_properties
127
+ for room_id, props in room_properties.items():
128
+ self.rooms_pos.append({
129
+ "name": props["name"],
130
+ "corners": props["outline"], # Use the enhanced outline
131
+ })
132
+
133
+ # get the zones and points data
134
+ zone_properties = await self.async_zone_propriety(zones_data)
135
+ # get the points data
136
+ point_properties = await self.async_points_propriety(points_data)
137
+
138
+ if room_properties or zone_properties:
139
+ extracted_data = [
140
+ f"{len(room_properties)} Rooms" if room_properties else None,
141
+ f"{len(zone_properties)} Zones" if zone_properties else None,
142
+ ]
143
+ extracted_data = ", ".join(filter(None, extracted_data))
144
+ _LOGGER.debug("Extracted data: %s", extracted_data)
166
145
  else:
167
- _LOGGER.debug("%s: No outlines available", self.file_name)
168
- return None, None, None
146
+ self.rooms_pos = None
147
+ _LOGGER.debug(
148
+ "%s: Rooms and Zones data not available!", self.file_name
149
+ )
150
+
151
+ rooms = RoomStore(self.file_name, room_properties)
152
+ _LOGGER.debug("Rooms Data: %s", rooms.get_rooms())
153
+ return room_properties, zone_properties, point_properties
169
154
  except (RuntimeError, ValueError) as e:
170
155
  _LOGGER.debug(
171
156
  "No rooms Data or Error in extract_room_properties: %s",
@@ -8,7 +8,7 @@ Version: 0.1.9
8
8
  from __future__ import annotations
9
9
 
10
10
  import time
11
- from typing import Any, Dict, Optional, Tuple
11
+ from typing import Any, Dict, List, Optional, Tuple
12
12
 
13
13
  import numpy as np
14
14
  from scipy.ndimage import binary_dilation, binary_erosion
@@ -17,6 +17,7 @@ from scipy.spatial import ConvexHull
17
17
  from .config.drawable_elements import DrawableElement, DrawingConfig
18
18
  from .config.types import LOGGER, RoomsProperties
19
19
 
20
+ from .map_data import RandImageData, ImageData
20
21
 
21
22
  class RoomsHandler:
22
23
  """
@@ -223,3 +224,247 @@ class RoomsHandler:
223
224
  total_time = time.time() - start_total
224
225
  LOGGER.debug("Room extraction Total time: %.3fs", total_time)
225
226
  return room_properties
227
+
228
+ class RandRoomsHandler:
229
+ """
230
+ Handler for extracting and managing room data from Rand25 vacuum maps.
231
+
232
+ This class provides methods to:
233
+ - Extract room outlines using the Convex Hull algorithm
234
+ - Process room properties from JSON data and destinations JSON
235
+ - Generate room masks and extract contours
236
+
237
+ All methods are async for better integration with the rest of the codebase.
238
+ """
239
+
240
+ def __init__(self, vacuum_id: str, drawing_config: Optional[DrawingConfig] = None):
241
+ """
242
+ Initialize the RandRoomsHandler.
243
+
244
+ Args:
245
+ vacuum_id: Identifier for the vacuum
246
+ drawing_config: Configuration for which elements to draw (optional)
247
+ """
248
+ self.vacuum_id = vacuum_id
249
+ self.drawing_config = drawing_config
250
+ self.current_json_data = None # Will store the current JSON data being processed
251
+ self.segment_data = None # Segment data
252
+ self.outlines = None # Outlines data
253
+
254
+ @staticmethod
255
+ def sublist(data: list, chunk_size: int) -> list:
256
+ """Split a list into chunks of specified size."""
257
+ return [data[i : i + chunk_size] for i in range(0, len(data), chunk_size)]
258
+
259
+ @staticmethod
260
+ def convex_hull_outline(points: List[Tuple[int, int]]) -> List[Tuple[int, int]]:
261
+ """
262
+ Generate a convex hull outline from a set of points.
263
+
264
+ Args:
265
+ points: List of (x, y) coordinate tuples
266
+
267
+ Returns:
268
+ List of (x, y) tuples forming the convex hull outline
269
+ """
270
+ if len(points) == 0:
271
+ return []
272
+
273
+ # Convert to numpy array for processing
274
+ points_array = np.array(points)
275
+
276
+ if len(points) < 3:
277
+ # Not enough points for a convex hull, return the points as is
278
+ return [(int(x), int(y)) for x, y in points_array]
279
+
280
+ try:
281
+ # Calculate the convex hull
282
+ hull = ConvexHull(points_array)
283
+
284
+ # Extract the vertices in order
285
+ hull_points = [
286
+ (int(points_array[vertex][0]), int(points_array[vertex][1]))
287
+ for vertex in hull.vertices
288
+ ]
289
+
290
+ # Close the polygon by adding the first point at the end
291
+ if hull_points[0] != hull_points[-1]:
292
+ hull_points.append(hull_points[0])
293
+
294
+ return hull_points
295
+
296
+ except Exception as e:
297
+ LOGGER.warning(f"Error calculating convex hull: {e}")
298
+
299
+ # Fallback to bounding box if convex hull fails
300
+ x_min, y_min = np.min(points_array, axis=0)
301
+ x_max, y_max = np.max(points_array, axis=0)
302
+
303
+ return [
304
+ (int(x_min), int(y_min)), # Top-left
305
+ (int(x_max), int(y_min)), # Top-right
306
+ (int(x_max), int(y_max)), # Bottom-right
307
+ (int(x_min), int(y_max)), # Bottom-left
308
+ (int(x_min), int(y_min)), # Back to top-left to close the polygon
309
+ ]
310
+
311
+ async def _process_segment_data(
312
+ self, segment_data: List, segment_id: int, pixel_size: int
313
+ ) -> Tuple[Optional[str], Optional[Dict[str, Any]]]:
314
+ """
315
+ Process a single segment and extract its outline.
316
+
317
+ Args:
318
+ segment_data: The segment pixel data
319
+ segment_id: The ID of the segment
320
+ pixel_size: The size of each pixel
321
+
322
+ Returns:
323
+ Tuple of (room_id, room_data) or (None, None) if processing failed
324
+ """
325
+ # Check if this room is enabled in the drawing configuration
326
+ if self.drawing_config is not None:
327
+ try:
328
+ # Convert segment_id to room element (ROOM_1 to ROOM_15)
329
+ room_element_id = int(segment_id)
330
+ if 1 <= room_element_id <= 15:
331
+ room_element = getattr(
332
+ DrawableElement, f"ROOM_{room_element_id}", None
333
+ )
334
+ if room_element:
335
+ is_enabled = self.drawing_config.is_enabled(room_element)
336
+ if not is_enabled:
337
+ # Skip this room if it's disabled
338
+ LOGGER.debug("Skipping disabled room %s", segment_id)
339
+ return None, None
340
+ except (ValueError, TypeError):
341
+ # If segment_id is not a valid integer, we can't map it to a room element
342
+ # In this case, we'll include the room (fail open)
343
+ LOGGER.debug(
344
+ "Could not convert segment_id %s to room element", segment_id
345
+ )
346
+
347
+ # Skip if no pixels
348
+ if not segment_data:
349
+ return None, None
350
+
351
+ # Extract points from segment data
352
+ points = []
353
+ for x, y, _ in segment_data:
354
+ points.append((int(x), int(y)))
355
+
356
+ if not points:
357
+ return None, None
358
+
359
+ # Use convex hull to get the outline
360
+ outline = self.convex_hull_outline(points)
361
+ if not outline:
362
+ return None, None
363
+
364
+ # Calculate bounding box for the room
365
+ xs, ys = zip(*outline)
366
+ x_min, x_max = min(xs), max(xs)
367
+ y_min, y_max = min(ys), max(ys)
368
+
369
+ # Scale coordinates by pixel_size
370
+ scaled_outline = [
371
+ (int(x * pixel_size), int(y * pixel_size)) for x, y in outline
372
+ ]
373
+
374
+ room_id = str(segment_id)
375
+ room_data = {
376
+ "number": segment_id,
377
+ "outline": scaled_outline,
378
+ "name": f"Room {segment_id}", # Default name, will be updated from destinations
379
+ "x": int(((x_min + x_max) * pixel_size) // 2),
380
+ "y": int(((y_min + y_max) * pixel_size) // 2),
381
+ }
382
+
383
+ return room_id, room_data
384
+
385
+ async def async_extract_room_properties(
386
+ self, json_data: Dict[str, Any], destinations: Dict[str, Any]
387
+ ) -> RoomsProperties:
388
+ """
389
+ Extract room properties from the JSON data and destinations.
390
+
391
+ Args:
392
+ json_data: The JSON data from the vacuum
393
+ destinations: The destinations JSON containing room names and IDs
394
+
395
+ Returns:
396
+ Dictionary of room properties
397
+ """
398
+ start_total = time.time()
399
+ room_properties = {}
400
+
401
+ # Get basic map information
402
+ unsorted_id = RandImageData.get_rrm_segments_ids(json_data)
403
+ size_x, size_y = RandImageData.get_rrm_image_size(json_data)
404
+ top, left = RandImageData.get_rrm_image_position(json_data)
405
+ pixel_size = 50 # Rand25 vacuums use a larger pixel size to match the original implementation
406
+
407
+ # Get segment data and outlines if not already available
408
+ if not self.segment_data or not self.outlines:
409
+ (
410
+ self.segment_data,
411
+ self.outlines,
412
+ ) = await RandImageData.async_get_rrm_segments(
413
+ json_data, size_x, size_y, top, left, True
414
+ )
415
+
416
+ # Process destinations JSON to get room names
417
+ dest_json = destinations
418
+ room_data = dest_json.get("rooms", [])
419
+ room_id_to_data = {room["id"]: room for room in room_data}
420
+
421
+ # Process each segment
422
+ if unsorted_id and self.segment_data and self.outlines:
423
+ for idx, segment_id in enumerate(unsorted_id):
424
+ # Extract points from segment data
425
+ points = []
426
+ for x, y, _ in self.segment_data[idx]:
427
+ points.append((int(x), int(y)))
428
+
429
+ if not points:
430
+ continue
431
+
432
+ # Use convex hull to get the outline
433
+ outline = self.convex_hull_outline(points)
434
+ if not outline:
435
+ continue
436
+
437
+ # Scale coordinates by pixel_size
438
+ scaled_outline = [
439
+ (int(x * pixel_size), int(y * pixel_size)) for x, y in outline
440
+ ]
441
+
442
+ # Calculate center point
443
+ xs, ys = zip(*outline)
444
+ x_min, x_max = min(xs), max(xs)
445
+ y_min, y_max = min(ys), max(ys)
446
+ center_x = int(((x_min + x_max) * pixel_size) // 2)
447
+ center_y = int(((y_min + y_max) * pixel_size) // 2)
448
+
449
+ # Create room data
450
+ room_id = str(segment_id)
451
+ room_data = {
452
+ "number": segment_id,
453
+ "outline": scaled_outline,
454
+ "name": f"Room {segment_id}", # Default name, will be updated from destinations
455
+ "x": center_x,
456
+ "y": center_y,
457
+ }
458
+
459
+ # Update room name from destinations if available
460
+ if segment_id in room_id_to_data:
461
+ room_info = room_id_to_data[segment_id]
462
+ room_data["name"] = room_info.get("name", room_data["name"])
463
+
464
+ room_properties[room_id] = room_data
465
+
466
+ # Log timing information
467
+ total_time = time.time() - start_total
468
+ LOGGER.debug("Room extraction Total time: %.3fs", total_time)
469
+
470
+ return room_properties
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: valetudo-map-parser
3
- Version: 0.1.9b54
3
+ Version: 0.1.9b55
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
@@ -1,4 +1,4 @@
1
- valetudo_map_parser/__init__.py,sha256=A_rCyIP5ll-ovzHMEsGMgbNmB39vDtqgL5hpZQkNbPQ,1018
1
+ valetudo_map_parser/__init__.py,sha256=Fz-gtKf_OlZcDQqVfGlBwIWi5DJAiRucMbBMdQ2tX_U,1060
2
2
  valetudo_map_parser/config/__init__.py,sha256=DQ9plV3ZF_K25Dp5ZQHPDoG-40dQoJNdNi-dfNeR3Zc,48
3
3
  valetudo_map_parser/config/auto_crop.py,sha256=6xt_wJQqphddWhlrr7MNUkodCi8ZYdRk42qvAaxlYCM,13546
4
4
  valetudo_map_parser/config/color_utils.py,sha256=nXD6WeNmdFdoMxPDW-JFpjnxJSaZR1jX-ouNfrx6zvE,4502
@@ -9,19 +9,19 @@ valetudo_map_parser/config/enhanced_drawable.py,sha256=QlGxlUMVgECUXPtFwIslyjubW
9
9
  valetudo_map_parser/config/optimized_element_map.py,sha256=52BCnkvVv9bre52LeVIfT8nhnEIpc0TuWTv1xcNu0Rk,15744
10
10
  valetudo_map_parser/config/rand25_parser.py,sha256=kIayyqVZBfQfAMkiArzqrrj9vqZB3pkgT0Y5ufrQmGA,16448
11
11
  valetudo_map_parser/config/room_outline.py,sha256=D20D-yeyKnlmVbW9lI7bsPtQGn2XkcWow6YNOEPnWVg,4800
12
- valetudo_map_parser/config/shared.py,sha256=LJPDE8MhmbY0CXXMbtDff-JVtmLKGjoWE6c4mpsaEc4,10419
12
+ valetudo_map_parser/config/shared.py,sha256=Vr4bicL7aJoRQbwbXyjEpiWhfzZ-cakLlfRqL3LBhpM,10475
13
13
  valetudo_map_parser/config/types.py,sha256=TaRKoo7G7WIUw7ljOz2Vn5oYzKaLyQH-7Eb8ZYql8Ls,17464
14
14
  valetudo_map_parser/config/utils.py,sha256=CFuuiS5IufEu9aeaZwi7xa1jEF1z6yDZB0mcyVX79Xo,29261
15
- valetudo_map_parser/hypfer_draw.py,sha256=afFJ9woTVYOfETbxFLU74r4H2PUW_eTfEvhKuYKH8h0,26226
15
+ valetudo_map_parser/hypfer_draw.py,sha256=bwNTYopTJFY0nElrHquQrSfGHgN_-6t5E-8xBxDsRXA,26660
16
16
  valetudo_map_parser/hypfer_handler.py,sha256=wvkZt6MsUF0gkHZDAwiUgOOawkdvakRzYBV3NtjxuJQ,19938
17
17
  valetudo_map_parser/hypfer_rooms_handler.py,sha256=NkpOA6Gdq-2D3lLAxvtNuuWMvPXHxeMY2TO5RZLSHlU,22652
18
18
  valetudo_map_parser/map_data.py,sha256=zQKE8EzWxR0r0qyfD1QQq51T1wFrpcIeXtnpm92-LXQ,17743
19
19
  valetudo_map_parser/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
- valetudo_map_parser/rand25_handler.py,sha256=vK4lF7RYKKeh_6IyvUWDmwFqULHdDa3pz_Wb00KAsXs,19988
20
+ valetudo_map_parser/rand25_handler.py,sha256=VOBzx0AY61AggOkiX-P4c73MrPrQBGfsDQTEbVL1wCQ,18855
21
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,,
22
+ valetudo_map_parser/rooms_handler.py,sha256=YP8OLotBH-RXluv398l7TTT2zIBHJp91b8THWxl3NdI,17794
23
+ valetudo_map_parser-0.1.9b55.dist-info/LICENSE,sha256=Lh-qBbuRV0-jiCIBhfV7NgdwFxQFOXH3BKOzK865hRs,10480
24
+ valetudo_map_parser-0.1.9b55.dist-info/METADATA,sha256=Af2KutWU3EHPE8F1HNCWWKlIGKqr9bz1nTbC1-d3ZCo,3321
25
+ valetudo_map_parser-0.1.9b55.dist-info/NOTICE.txt,sha256=5lTOuWiU9aiEnJ2go8sc7lTJ7ntMBx0g0GFnNrswCY4,2533
26
+ valetudo_map_parser-0.1.9b55.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
27
+ valetudo_map_parser-0.1.9b55.dist-info/RECORD,,