valetudo-map-parser 0.1.9b55__py3-none-any.whl → 0.1.9b57__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.
@@ -21,11 +21,13 @@ from .config.types import (
21
21
  Colors,
22
22
  RoomsProperties,
23
23
  RoomStore,
24
+ WebPBytes,
24
25
  )
25
26
  from .config.utils import (
26
27
  BaseHandler,
27
28
  initialize_drawing_config,
28
29
  manage_drawable_elements,
30
+ numpy_to_webp_bytes,
29
31
  prepare_resize_params,
30
32
  )
31
33
  from .hypfer_draw import ImageDraw as ImDraw
@@ -81,7 +83,11 @@ class HypferMapImageHandler(BaseHandler, AutoCrop):
81
83
  self.rooms_pos = []
82
84
  for room_id, room_data in room_properties.items():
83
85
  self.rooms_pos.append(
84
- {"name": room_data["name"], "outline": room_data["outline"]}
86
+ {
87
+ "id": room_id,
88
+ "name": room_data["name"],
89
+ "outline": room_data["outline"],
90
+ }
85
91
  )
86
92
  else:
87
93
  LOGGER.debug("%s: Rooms data not available!", self.file_name)
@@ -92,12 +98,14 @@ class HypferMapImageHandler(BaseHandler, AutoCrop):
92
98
  async def async_get_image_from_json(
93
99
  self,
94
100
  m_json: json | None,
95
- ) -> Image.Image | None:
101
+ return_webp: bool = False,
102
+ ) -> WebPBytes | Image.Image | None:
96
103
  """Get the image from the JSON data.
97
104
  It uses the ImageDraw class to draw some of the elements of the image.
98
105
  The robot itself will be drawn in this function as per some of the values are needed for other tasks.
99
106
  @param m_json: The JSON data to use to draw the image.
100
- @return Image.Image: The image to display.
107
+ @param return_webp: If True, return WebP bytes; if False, return PIL Image (default).
108
+ @return WebPBytes | Image.Image: WebP bytes or PIL Image depending on return_webp parameter.
101
109
  """
102
110
  # Initialize the colors.
103
111
  colors: Colors = {
@@ -131,6 +139,8 @@ class HypferMapImageHandler(BaseHandler, AutoCrop):
131
139
  # Get the pixels size and layers from the JSON data
132
140
  pixel_size = int(m_json["pixelSize"])
133
141
  layers, active = self.data.find_layers(m_json["layers"], {}, [])
142
+ # Populate active_zones from the JSON data
143
+ self.active_zones = active
134
144
  new_frame_hash = await self.calculate_array_hash(layers, active)
135
145
  if self.frame_number == 0:
136
146
  self.img_hash = new_frame_hash
@@ -240,20 +250,30 @@ class HypferMapImageHandler(BaseHandler, AutoCrop):
240
250
 
241
251
  # Draw obstacles if enabled
242
252
  if self.drawing_config.is_enabled(DrawableElement.OBSTACLE):
243
- img_np_array = await self.imd.async_draw_obstacle(
244
- img_np_array, entity_dict, colors["no_go"]
245
- )
253
+ self.shared.obstacles_pos = self.data.get_obstacles(entity_dict)
254
+ if self.shared.obstacles_pos:
255
+ img_np_array = await self.imd.async_draw_obstacle(
256
+ img_np_array, self.shared.obstacles_pos, colors["no_go"]
257
+ )
246
258
  # Robot and rooms position
247
259
  if (room_id > 0) and not self.room_propriety:
248
260
  self.room_propriety = await self.async_extract_room_properties(
249
261
  self.json_data
250
262
  )
251
- if self.rooms_pos and robot_position and robot_position_angle:
252
- self.robot_pos = await self.imd.async_get_robot_in_room(
253
- robot_x=(robot_position[0]),
254
- robot_y=(robot_position[1]),
255
- angle=robot_position_angle,
256
- )
263
+
264
+ # Ensure room data is available for robot room detection (even if not extracted above)
265
+ if not self.rooms_pos and not self.room_propriety:
266
+ self.room_propriety = await self.async_extract_room_properties(
267
+ self.json_data
268
+ )
269
+
270
+ # Always check robot position for zooming (moved outside the condition)
271
+ if self.rooms_pos and robot_position and robot_position_angle:
272
+ self.robot_pos = await self.imd.async_get_robot_in_room(
273
+ robot_x=(robot_position[0]),
274
+ robot_y=(robot_position[1]),
275
+ angle=robot_position_angle,
276
+ )
257
277
  LOGGER.info("%s: Completed base Layers", self.file_name)
258
278
  # Copy the new array in base layer.
259
279
  self.img_base_layer = await self.async_copy_array(img_np_array)
@@ -332,6 +352,9 @@ class HypferMapImageHandler(BaseHandler, AutoCrop):
332
352
  robot_position,
333
353
  DrawableElement.ROBOT,
334
354
  )
355
+ # Synchronize zooming state from ImageDraw to handler before auto-crop
356
+ self.zooming = self.imd.img_h.zooming
357
+
335
358
  # Resize the image
336
359
  img_np_array = await self.async_auto_trim_and_zoom_image(
337
360
  img_np_array,
@@ -345,16 +368,43 @@ class HypferMapImageHandler(BaseHandler, AutoCrop):
345
368
  LOGGER.warning("%s: Image array is None.", self.file_name)
346
369
  return None
347
370
 
348
- # Convert the numpy array to a PIL image
349
- pil_img = Image.fromarray(img_np_array, mode="RGBA")
350
- del img_np_array
351
- # reduce the image size if the zoomed image is bigger then the original.
371
+ # Handle resizing if needed, then return based on format preference
352
372
  if self.check_zoom_and_aspect_ratio():
373
+ # Convert to PIL for resizing
374
+ pil_img = Image.fromarray(img_np_array, mode="RGBA")
375
+ del img_np_array
353
376
  resize_params = prepare_resize_params(self, pil_img, False)
354
377
  resized_image = await self.async_resize_images(resize_params)
355
- return resized_image
356
- LOGGER.debug("%s: Frame Completed.", self.file_name)
357
- return pil_img
378
+
379
+ # Return WebP bytes or PIL Image based on parameter
380
+ if return_webp:
381
+ from .config.utils import pil_to_webp_bytes
382
+ webp_bytes = await pil_to_webp_bytes(
383
+ resized_image,
384
+ quality=90,
385
+ lossless=False
386
+ )
387
+ return webp_bytes
388
+ else:
389
+ return resized_image
390
+ else:
391
+ # Return WebP bytes or PIL Image based on parameter
392
+ if return_webp:
393
+ # Convert directly from NumPy to WebP for better performance
394
+ webp_bytes = await numpy_to_webp_bytes(
395
+ img_np_array,
396
+ quality=90,
397
+ lossless=False
398
+ )
399
+ del img_np_array
400
+ LOGGER.debug("%s: Frame Completed.", self.file_name)
401
+ return webp_bytes
402
+ else:
403
+ # Convert to PIL Image (original behavior)
404
+ pil_img = Image.fromarray(img_np_array, mode="RGBA")
405
+ del img_np_array
406
+ LOGGER.debug("%s: Frame Completed.", self.file_name)
407
+ return pil_img
358
408
  except (RuntimeError, RuntimeWarning) as e:
359
409
  LOGGER.warning(
360
410
  "%s: Error %s during image creation.",
@@ -10,7 +10,7 @@ from __future__ import annotations
10
10
 
11
11
  import numpy as np
12
12
 
13
- from .config.types import Colors, ImageSize, JsonType, NumpyArray
13
+ from .config.types import ImageSize, JsonType
14
14
 
15
15
 
16
16
  class ImageData:
@@ -34,6 +34,30 @@ class ImageData:
34
34
  # list the specific Layers, Paths, Zones and Pints in the
35
35
  # Vacuums Json in parallel.
36
36
 
37
+ @staticmethod
38
+ def get_obstacles(entity_dict: dict) -> list:
39
+ """Get the obstacles positions from the entity data."""
40
+ try:
41
+ obstacle_data = entity_dict.get("obstacle")
42
+ except KeyError:
43
+ return []
44
+ obstacle_positions = []
45
+ if obstacle_data:
46
+ for obstacle in obstacle_data:
47
+ label = obstacle.get("metaData", {}).get("label")
48
+ points = obstacle.get("points", [])
49
+ image_id = obstacle.get("metaData", {}).get("id")
50
+
51
+ if label and points:
52
+ obstacle_pos = {
53
+ "label": label,
54
+ "points": {"x": points[0], "y": points[1]},
55
+ "id": image_id,
56
+ }
57
+ obstacle_positions.append(obstacle_pos)
58
+ return obstacle_positions
59
+ return []
60
+
37
61
  @staticmethod
38
62
  def find_layers(
39
63
  json_obj: JsonType, layer_dict: dict, active_list: list
@@ -284,7 +308,7 @@ class RandImageData:
284
308
  Return the calculated angle and original angle.
285
309
  """
286
310
  angle_c = round(json_data.get("robot_angle", 0))
287
- angle = (360 - angle_c + 80) if angle_c < 0 else (180 - angle_c - 80)
311
+ angle = (360 - angle_c + 95) if angle_c < 0 else (180 - angle_c - 85)
288
312
  return angle % 360, json_data.get("robot_angle", 0)
289
313
 
290
314
  @staticmethod