valetudo-map-parser 0.1.10rc7__py3-none-any.whl → 0.1.11b1__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.
@@ -2,7 +2,7 @@
2
2
  Image Handler Module for Valetudo Re Vacuums.
3
3
  It returns the PIL PNG image frame relative to the Map Data extrapolated from the vacuum json.
4
4
  It also returns calibration, rooms data to the card and other images information to the camera.
5
- Version: 0.1.9.a6
5
+ Version: 0.1.10
6
6
  """
7
7
 
8
8
  from __future__ import annotations
@@ -11,22 +11,22 @@ import uuid
11
11
  from typing import Any
12
12
 
13
13
  import numpy as np
14
+ from mvcrender.autocrop import AutoCrop
14
15
 
15
16
  from .config.async_utils import AsyncPIL
16
-
17
- from mvcrender.autocrop import AutoCrop
18
17
  from .config.drawable_elements import DrawableElement
19
18
  from .config.types import (
20
19
  COLORS,
21
20
  DEFAULT_IMAGE_SIZE,
22
21
  DEFAULT_PIXEL_SIZE,
22
+ LOGGER,
23
23
  Colors,
24
+ Destinations,
24
25
  JsonType,
25
26
  PilPNG,
26
27
  RobotPosition,
27
28
  RoomsProperties,
28
29
  RoomStore,
29
- LOGGER,
30
30
  )
31
31
  from .config.utils import (
32
32
  BaseHandler,
@@ -55,11 +55,10 @@ class ReImageHandler(BaseHandler, AutoCrop):
55
55
  self.data = RandImageData # Image Data
56
56
 
57
57
  # Initialize drawing configuration using the shared utility function
58
- self.drawing_config, self.draw, self.enhanced_draw = initialize_drawing_config(
59
- self
60
- )
58
+ self.drawing_config, self.draw = initialize_drawing_config(self)
61
59
  self.go_to = None # Go to position data
62
60
  self.img_base_layer = None # Base image layer
61
+ self.img_work_layer = None # Persistent working buffer (reused across frames)
63
62
  self.img_rotate = shared_data.image_rotate # Image rotation
64
63
  self.room_propriety = None # Room propriety data
65
64
  self.active_zones = None # Active zones
@@ -70,7 +69,9 @@ class ReImageHandler(BaseHandler, AutoCrop):
70
69
  ) # Room data handler
71
70
 
72
71
  async def extract_room_properties(
73
- self, json_data: JsonType, destinations: JsonType
72
+ self,
73
+ json_data: JsonType,
74
+ destinations: Destinations | None = None,
74
75
  ) -> RoomsProperties:
75
76
  """Extract the room properties."""
76
77
  # unsorted_id = RandImageData.get_rrm_segments_ids(json_data)
@@ -85,9 +86,10 @@ class ReImageHandler(BaseHandler, AutoCrop):
85
86
  json_data, size_x, size_y, top, left, True
86
87
  )
87
88
 
88
- dest_json = destinations
89
- zones_data = dict(dest_json).get("zones", [])
90
- points_data = dict(dest_json).get("spots", [])
89
+
90
+ dest_json = destinations if destinations else {}
91
+ zones_data = dest_json.get("zones", [])
92
+ points_data = dest_json.get("spots", [])
91
93
 
92
94
  # Use the RandRoomsHandler to extract room properties
93
95
  room_properties = await self.rooms_handler.async_extract_room_properties(
@@ -96,17 +98,13 @@ class ReImageHandler(BaseHandler, AutoCrop):
96
98
 
97
99
  # Update self.rooms_pos from room_properties for compatibility with other methods
98
100
  self.rooms_pos = []
99
- room_ids = [] # Collect room IDs for shared.map_rooms
100
101
  for room_id, room_data in room_properties.items():
101
102
  self.rooms_pos.append(
102
103
  {"name": room_data["name"], "outline": room_data["outline"]}
103
104
  )
104
- # Store the room number (segment ID) for MQTT active zone mapping
105
- room_ids.append(room_data["number"])
106
-
107
- # Update shared.map_rooms with the room IDs for MQTT active zone mapping
108
- self.shared.map_rooms = room_ids
109
105
 
106
+ # Update shared.map_rooms with the full room properties (consistent with Hypfer)
107
+ self.shared.map_rooms = room_properties
110
108
  # get the zones and points data
111
109
  self.shared.map_pred_zones = await self.async_zone_propriety(zones_data)
112
110
  # get the points data
@@ -115,7 +113,7 @@ class ReImageHandler(BaseHandler, AutoCrop):
115
113
  if not (room_properties or self.shared.map_pred_zones):
116
114
  self.rooms_pos = None
117
115
 
118
- rooms = RoomStore(self.file_name, room_properties)
116
+ _ = RoomStore(self.file_name, room_properties)
119
117
  return room_properties
120
118
  except (RuntimeError, ValueError) as e:
121
119
  LOGGER.warning(
@@ -123,12 +121,12 @@ class ReImageHandler(BaseHandler, AutoCrop):
123
121
  e,
124
122
  exc_info=True,
125
123
  )
126
- return None, None, None
124
+ return None
127
125
 
128
126
  async def get_image_from_rrm(
129
127
  self,
130
128
  m_json: JsonType, # json data
131
- destinations: None = None, # MQTT destinations for labels
129
+ destinations: Destinations | None = None, # MQTT destinations for labels
132
130
  ) -> PilPNG | None:
133
131
  """Generate Images from the json data.
134
132
  @param m_json: The JSON data to use to draw the image.
@@ -159,10 +157,24 @@ class ReImageHandler(BaseHandler, AutoCrop):
159
157
 
160
158
  # Increment frame number
161
159
  self.frame_number += 1
162
- img_np_array = await self.async_copy_array(self.img_base_layer)
163
160
  if self.frame_number > 5:
164
161
  self.frame_number = 0
165
162
 
163
+ # Ensure persistent working buffer exists and matches base (allocate only when needed)
164
+ if (
165
+ self.img_work_layer is None
166
+ or self.img_work_layer.shape != self.img_base_layer.shape
167
+ or self.img_work_layer.dtype != self.img_base_layer.dtype
168
+ ):
169
+ # Delete old buffer before creating new one to free memory
170
+ if self.img_work_layer is not None:
171
+ del self.img_work_layer
172
+ self.img_work_layer = np.empty_like(self.img_base_layer)
173
+
174
+ # Copy the base layer into the persistent working buffer (no new allocation per frame)
175
+ np.copyto(self.img_work_layer, self.img_base_layer)
176
+ img_np_array = self.img_work_layer
177
+
166
178
  # Draw map elements
167
179
  img_np_array = await self._draw_map_elements(
168
180
  img_np_array, m_json, colors, robot_position, robot_position_angle
@@ -170,7 +182,7 @@ class ReImageHandler(BaseHandler, AutoCrop):
170
182
 
171
183
  # Return PIL Image using async utilities
172
184
  pil_img = await AsyncPIL.async_fromarray(img_np_array, mode="RGBA")
173
- del img_np_array # free memory
185
+ # Note: Don't delete img_np_array here as it's the persistent work buffer
174
186
  return await self._finalize_image(pil_img)
175
187
 
176
188
  except (RuntimeError, RuntimeWarning) as e:
@@ -188,6 +200,7 @@ class ReImageHandler(BaseHandler, AutoCrop):
188
200
  async def _setup_robot_and_image(
189
201
  self, m_json, size_x, size_y, colors, destinations
190
202
  ):
203
+ """Set up the elements of the map and the image."""
191
204
  (
192
205
  _,
193
206
  robot_position,
@@ -212,12 +225,6 @@ class ReImageHandler(BaseHandler, AutoCrop):
212
225
  )
213
226
  LOGGER.info("%s: Completed base Layers", self.file_name)
214
227
 
215
- # Update element map for rooms
216
- if 0 < room_id <= 15:
217
- # This is a simplification - in a real implementation we would
218
- # need to identify the exact pixels that belong to each room
219
- pass
220
-
221
228
  if room_id > 0 and not self.room_propriety:
222
229
  self.room_propriety = await self.get_rooms_attributes(destinations)
223
230
 
@@ -225,14 +232,23 @@ class ReImageHandler(BaseHandler, AutoCrop):
225
232
  if not self.rooms_pos and not self.room_propriety:
226
233
  self.room_propriety = await self.get_rooms_attributes(destinations)
227
234
 
228
- # Always check robot position for zooming (fallback)
229
- if self.rooms_pos and robot_position and not hasattr(self, "robot_pos"):
235
+ # Always check robot position for zooming (update if room info is missing)
236
+ if (
237
+ self.rooms_pos
238
+ and robot_position
239
+ and (self.robot_pos is None or "in_room" not in self.robot_pos)
240
+ ):
230
241
  self.robot_pos = await self.async_get_robot_in_room(
231
242
  (robot_position[0] * 10),
232
243
  (robot_position[1] * 10),
233
244
  robot_position_angle,
234
245
  )
246
+ # Delete old base layer before creating new one to free memory
247
+ if self.img_base_layer is not None:
248
+ del self.img_base_layer
235
249
  self.img_base_layer = await self.async_copy_array(img_np_array)
250
+ # Delete source array after copying to free memory
251
+ del img_np_array
236
252
  else:
237
253
  # If floor is disabled, create an empty image
238
254
  background_color = self.drawing_config.get_property(
@@ -241,7 +257,12 @@ class ReImageHandler(BaseHandler, AutoCrop):
241
257
  img_np_array = await self.draw.create_empty_image(
242
258
  size_x, size_y, background_color
243
259
  )
260
+ # Delete old base layer before creating new one to free memory
261
+ if self.img_base_layer is not None:
262
+ del self.img_base_layer
244
263
  self.img_base_layer = await self.async_copy_array(img_np_array)
264
+ # Delete source array after copying to free memory
265
+ del img_np_array
245
266
 
246
267
  # Check active zones BEFORE auto-crop to enable proper zoom functionality
247
268
  # This needs to run on every frame, not just frame 0
@@ -273,7 +294,7 @@ class ReImageHandler(BaseHandler, AutoCrop):
273
294
  # Restore original rooms_pos
274
295
  self.rooms_pos = original_rooms_pos
275
296
 
276
- except Exception as e:
297
+ except (ValueError, KeyError, TypeError):
277
298
  # Fallback to robot-position-based zoom if room extraction fails
278
299
  if (
279
300
  self.shared.image_auto_zoom
@@ -287,6 +308,7 @@ class ReImageHandler(BaseHandler, AutoCrop):
287
308
  async def _draw_map_elements(
288
309
  self, img_np_array, m_json, colors, robot_position, robot_position_angle
289
310
  ):
311
+ """Draw map elements on the image."""
290
312
  # Draw charger if enabled
291
313
  if self.drawing_config.is_enabled(DrawableElement.CHARGER):
292
314
  img_np_array, self.charger_pos = await self.imd.async_draw_charger(
@@ -357,22 +379,24 @@ class ReImageHandler(BaseHandler, AutoCrop):
357
379
  return img_np_array
358
380
 
359
381
  async def _finalize_image(self, pil_img):
360
- if not self.shared.image_ref_width or not self.shared.image_ref_height:
361
- LOGGER.warning(
362
- "Image finalization failed: Invalid image dimensions. Returning original image."
363
- )
364
- return pil_img
382
+ """Finalize the image by resizing if needed."""
383
+ if pil_img is None:
384
+ LOGGER.warning("%s: Image is None. Returning None.", self.file_name)
385
+ return None
365
386
  if self.check_zoom_and_aspect_ratio():
366
387
  resize_params = self.prepare_resize_params(pil_img, True)
367
388
  pil_img = await self.async_resize_images(resize_params)
389
+ else:
390
+ LOGGER.warning(
391
+ "%s: Invalid image dimensions. Returning original image.",
392
+ self.file_name,
393
+ )
368
394
  return pil_img
369
395
 
370
396
  async def get_rooms_attributes(
371
397
  self, destinations: JsonType = None
372
398
  ) -> tuple[RoomsProperties, Any, Any]:
373
399
  """Return the rooms attributes."""
374
- if self.room_propriety:
375
- return self.room_propriety
376
400
  if self.json_data and destinations:
377
401
  self.room_propriety = await self.extract_room_properties(
378
402
  self.json_data, destinations
@@ -397,6 +421,12 @@ class ReImageHandler(BaseHandler, AutoCrop):
397
421
  }
398
422
  # Handle active zones
399
423
  self.active_zones = self.shared.rand256_active_zone
424
+ LOGGER.debug(
425
+ "%s: Robot is in %s room (polygon detection). %s",
426
+ self.file_name,
427
+ self.robot_in_room["room"],
428
+ self.active_zones,
429
+ )
400
430
  self.zooming = False
401
431
  if self.active_zones and (
402
432
  self.robot_in_room["id"] in range(len(self.active_zones))
@@ -8,7 +8,7 @@ from __future__ import annotations
8
8
 
9
9
  from .config.drawable import Drawable
10
10
  from .config.drawable_elements import DrawableElement
11
- from .config.types import Color, JsonType, NumpyArray, LOGGER
11
+ from .config.types import LOGGER, Color, JsonType, NumpyArray
12
12
  from .map_data import ImageData, RandImageData
13
13
 
14
14
 
@@ -7,7 +7,6 @@ Version: 0.1.9
7
7
 
8
8
  from __future__ import annotations
9
9
 
10
- import time
11
10
  from typing import Any, Dict, List, Optional, Tuple
12
11
 
13
12
  import numpy as np
@@ -16,8 +15,7 @@ from scipy.spatial import ConvexHull
16
15
 
17
16
  from .config.drawable_elements import DrawableElement, DrawingConfig
18
17
  from .config.types import LOGGER, RoomsProperties
19
-
20
- from .map_data import RandImageData, ImageData
18
+ from .map_data import RandImageData
21
19
 
22
20
 
23
21
  class RoomsHandler:
@@ -163,8 +161,17 @@ class RoomsHandler:
163
161
  np.uint8
164
162
  )
165
163
 
164
+ # Free intermediate arrays to reduce memory usage
165
+ del local_mask
166
+ del struct_elem
167
+ del eroded
168
+
166
169
  # Extract contour from the mask
167
170
  outline = self.convex_hull_outline(mask)
171
+
172
+ # Free mask after extracting outline
173
+ del mask
174
+
168
175
  if not outline:
169
176
  return None, None
170
177
 
@@ -204,7 +211,6 @@ class RoomsHandler:
204
211
  Returns:
205
212
  Dictionary of room properties
206
213
  """
207
- start_total = time.time()
208
214
  room_properties = {}
209
215
  pixel_size = json_data.get("pixelSize", 5)
210
216
  height = json_data["size"]["y"]
@@ -217,9 +223,6 @@ class RoomsHandler:
217
223
  )
218
224
  if room_id is not None and room_data is not None:
219
225
  room_properties[room_id] = room_data
220
-
221
- # Log timing information (kept internal, no debug output)
222
- total_time = time.time() - start_total
223
226
  return room_properties
224
227
 
225
228
 
@@ -395,7 +398,6 @@ class RandRoomsHandler:
395
398
  Returns:
396
399
  Dictionary of room properties
397
400
  """
398
- start_total = time.time()
399
401
  room_properties = {}
400
402
 
401
403
  # Get basic map information
@@ -463,6 +465,4 @@ class RandRoomsHandler:
463
465
 
464
466
  room_properties[room_id] = room_data
465
467
 
466
- # Log timing information (kept internal, no debug output)
467
- total_time = time.time() - start_total
468
468
  return room_properties
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: valetudo-map-parser
3
- Version: 0.1.10rc7
3
+ Version: 0.1.11b1
4
4
  Summary: A Python library to parse Valetudo map data returning a PIL Image object.
5
5
  License: Apache-2.0
6
6
  License-File: LICENSE
@@ -13,7 +13,7 @@ Classifier: Programming Language :: Python :: 3
13
13
  Classifier: Programming Language :: Python :: 3.13
14
14
  Classifier: Programming Language :: Python :: 3.14
15
15
  Requires-Dist: Pillow (>=10.3.0)
16
- Requires-Dist: mvcrender (>=0.0.4)
16
+ Requires-Dist: mvcrender (==0.0.6)
17
17
  Requires-Dist: numpy (>=1.26.4)
18
18
  Requires-Dist: scipy (>=1.12.0)
19
19
  Project-URL: Bug Tracker, https://github.com/sca075/Python-package-valetudo-map-parser/issues
@@ -0,0 +1,32 @@
1
+ valetudo_map_parser/__init__.py,sha256=l_O2iyvj49KkUtuW7lMIZJcEaV_6_-YFaVZkiVeSh9g,1743
2
+ valetudo_map_parser/config/__init__.py,sha256=DQ9plV3ZF_K25Dp5ZQHPDoG-40dQoJNdNi-dfNeR3Zc,48
3
+ valetudo_map_parser/config/async_utils.py,sha256=e1j9uTtg4dhPVWvB2_XgqaH4aeSjRAPz-puRMbGoOs8,3204
4
+ valetudo_map_parser/config/colors.py,sha256=DMY5aHDS-alW2CZ2j8U7QVGHK_H8M0fBDX2OGuryirc,29914
5
+ valetudo_map_parser/config/drawable.py,sha256=s0bA3yeIXgultFbDD_BXDbzDPMpkGVHzq5IyXnH_sW8,23984
6
+ valetudo_map_parser/config/drawable_elements.py,sha256=_iHdAuzbAoIOeT8JEZrvSu5fVojIdhAyH5ABUVi-x3Y,11496
7
+ valetudo_map_parser/config/fonts/FiraSans.ttf,sha256=Pavz1Iv0WZ-Vz_2S-Z6kJqAU1TEfUqXrXsOvJl6XzZc,440984
8
+ valetudo_map_parser/config/fonts/Inter-VF.ttf,sha256=zzy0OwNm4txt9g4RMrHJpMFXd_DNjlpT4MFRJAA-ntQ,804612
9
+ valetudo_map_parser/config/fonts/Lato-Regular.ttf,sha256=6CVCrtgpP0n8g8Sq6lZrH2tPx6mrXaEeb7m8CXO1Mks,75152
10
+ valetudo_map_parser/config/fonts/MPLUSRegular.ttf,sha256=IGdcNSRP4dDxQskbE3Ybuwz7T0flhCBuzwfhLMcPt9s,3380812
11
+ valetudo_map_parser/config/fonts/NotoKufiArabic-VF.ttf,sha256=NaIy40eLx7d3ts0kuenp0GjWd-YN24J6DpSvX2L3vLA,433800
12
+ valetudo_map_parser/config/fonts/NotoSansCJKhk-VF.ttf,sha256=xIXXLKCJzmWoPEg8HdvxeRgotMjjxF6l6ugGP-IWRJU,36135040
13
+ valetudo_map_parser/config/fonts/NotoSansKhojki.ttf,sha256=XJWzSmpN-Ql6jTfTvFojP_JkCHOztQvixQc1_7hPWrc,107388
14
+ valetudo_map_parser/config/optimized_element_map.py,sha256=52BCnkvVv9bre52LeVIfT8nhnEIpc0TuWTv1xcNu0Rk,15744
15
+ valetudo_map_parser/config/rand256_parser.py,sha256=UZ0UlcNIjDhMpImA_2jSei0HeLftpb9fLDwzliJJpz8,21161
16
+ valetudo_map_parser/config/shared.py,sha256=PyP3IpgrztOM9B-qjNDHKcQL1_No_rAbPhvfuQJXOBE,11829
17
+ valetudo_map_parser/config/status_text/status_text.py,sha256=29E8b3adl_Rr_BH8-J35ia2cOS716sPzVk_cezFiWQ4,4053
18
+ valetudo_map_parser/config/status_text/translations.py,sha256=mmPbJkl_2A59w49wnesQf3ocXqwZxBsrqNX-yt5FSCQ,9132
19
+ valetudo_map_parser/config/types.py,sha256=rYdqOsUX9dtre8M7s8o1S5Ag8Ixvab13Stpk3Hfw_54,18027
20
+ valetudo_map_parser/config/utils.py,sha256=IP64VqYW_oWGAxSXexGHtLGaHTn7r4eICd0JGZDqG9A,35696
21
+ valetudo_map_parser/hypfer_draw.py,sha256=0-ZBCZw9H7G5Mu2cukTdOfbu83tZPL2cTdzPjRXzZsQ,22422
22
+ valetudo_map_parser/hypfer_handler.py,sha256=O2eR4nNbmEkcDc-5EcWCKHLMOQGeQBMAdpClEM1fGnI,20446
23
+ valetudo_map_parser/map_data.py,sha256=1-kxE82o8BfaJojYrD0LXoUAgCNpD6d50yLGc4w7ZWc,27433
24
+ valetudo_map_parser/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
25
+ valetudo_map_parser/rand256_handler.py,sha256=z7ofWheIy7UvC120rvhP1nNjWdW9JqCIqnGV3T5OoNs,23048
26
+ valetudo_map_parser/reimg_draw.py,sha256=Pwm_QUHiJd4o657qr-mlsWQ_TtGCBB-ucsA84t-IwOg,12474
27
+ valetudo_map_parser/rooms_handler.py,sha256=ZbSdxG-uwoskYBbzN5zzAqK_lTtrNrcpjZmCVmxFWpw,17364
28
+ valetudo_map_parser-0.1.11b1.dist-info/METADATA,sha256=YyvgIGxYawa9mD6hJ0wWOoChSED2xO3Dh8W4VJmrS2I,3403
29
+ valetudo_map_parser-0.1.11b1.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
30
+ valetudo_map_parser-0.1.11b1.dist-info/licenses/LICENSE,sha256=Lh-qBbuRV0-jiCIBhfV7NgdwFxQFOXH3BKOzK865hRs,10480
31
+ valetudo_map_parser-0.1.11b1.dist-info/licenses/NOTICE.txt,sha256=5lTOuWiU9aiEnJ2go8sc7lTJ7ntMBx0g0GFnNrswCY4,2533
32
+ valetudo_map_parser-0.1.11b1.dist-info/RECORD,,