valetudo-map-parser 0.1.9b100__py3-none-any.whl → 0.1.10__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.
Files changed (33) hide show
  1. valetudo_map_parser/__init__.py +24 -8
  2. valetudo_map_parser/config/auto_crop.py +2 -27
  3. valetudo_map_parser/config/color_utils.py +3 -4
  4. valetudo_map_parser/config/colors.py +2 -2
  5. valetudo_map_parser/config/drawable.py +102 -153
  6. valetudo_map_parser/config/drawable_elements.py +0 -2
  7. valetudo_map_parser/config/fonts/FiraSans.ttf +0 -0
  8. valetudo_map_parser/config/fonts/Inter-VF.ttf +0 -0
  9. valetudo_map_parser/config/fonts/Lato-Regular.ttf +0 -0
  10. valetudo_map_parser/config/fonts/MPLUSRegular.ttf +0 -0
  11. valetudo_map_parser/config/fonts/NotoKufiArabic-VF.ttf +0 -0
  12. valetudo_map_parser/config/fonts/NotoSansCJKhk-VF.ttf +0 -0
  13. valetudo_map_parser/config/fonts/NotoSansKhojki.ttf +0 -0
  14. valetudo_map_parser/config/rand256_parser.py +169 -44
  15. valetudo_map_parser/config/shared.py +103 -101
  16. valetudo_map_parser/config/status_text/status_text.py +96 -0
  17. valetudo_map_parser/config/status_text/translations.py +280 -0
  18. valetudo_map_parser/config/types.py +42 -13
  19. valetudo_map_parser/config/utils.py +221 -181
  20. valetudo_map_parser/hypfer_draw.py +6 -169
  21. valetudo_map_parser/hypfer_handler.py +40 -130
  22. valetudo_map_parser/map_data.py +403 -84
  23. valetudo_map_parser/rand256_handler.py +53 -197
  24. valetudo_map_parser/reimg_draw.py +14 -24
  25. valetudo_map_parser/rooms_handler.py +3 -18
  26. {valetudo_map_parser-0.1.9b100.dist-info → valetudo_map_parser-0.1.10.dist-info}/METADATA +7 -4
  27. valetudo_map_parser-0.1.10.dist-info/RECORD +34 -0
  28. {valetudo_map_parser-0.1.9b100.dist-info → valetudo_map_parser-0.1.10.dist-info}/WHEEL +1 -1
  29. valetudo_map_parser/config/enhanced_drawable.py +0 -324
  30. valetudo_map_parser/hypfer_rooms_handler.py +0 -599
  31. valetudo_map_parser-0.1.9b100.dist-info/RECORD +0 -27
  32. {valetudo_map_parser-0.1.9b100.dist-info → valetudo_map_parser-0.1.10.dist-info/licenses}/LICENSE +0 -0
  33. {valetudo_map_parser-0.1.9b100.dist-info → valetudo_map_parser-0.1.10.dist-info/licenses}/NOTICE.txt +0 -0
@@ -1,7 +1,7 @@
1
1
  """
2
2
  Image Draw Class for Valetudo Hypfer Image Handling.
3
3
  This class is used to simplify the ImageHandler class.
4
- Version: 0.1.9
4
+ Version: 0.1.10
5
5
  """
6
6
 
7
7
  from __future__ import annotations
@@ -10,6 +10,7 @@ import logging
10
10
 
11
11
  from .config.drawable_elements import DrawableElement
12
12
  from .config.types import Color, JsonType, NumpyArray, RobotPosition, RoomStore
13
+ from .config.utils import point_in_polygon
13
14
 
14
15
 
15
16
  _LOGGER = logging.getLogger(__name__)
@@ -23,51 +24,6 @@ class ImageDraw:
23
24
  self.img_h = image_handler
24
25
  self.file_name = self.img_h.shared.file_name
25
26
 
26
- @staticmethod
27
- def point_in_polygon(x: int, y: int, polygon: list) -> bool:
28
- """
29
- Check if a point is inside a polygon using ray casting algorithm.
30
- Enhanced version with better handling of edge cases.
31
-
32
- Args:
33
- x: X coordinate of the point
34
- y: Y coordinate of the point
35
- polygon: List of (x, y) tuples forming the polygon
36
-
37
- Returns:
38
- True if the point is inside the polygon, False otherwise
39
- """
40
- # Ensure we have a valid polygon with at least 3 points
41
- if len(polygon) < 3:
42
- return False
43
-
44
- # Make sure the polygon is closed (last point equals first point)
45
- if polygon[0] != polygon[-1]:
46
- polygon = polygon + [polygon[0]]
47
-
48
- # Use winding number algorithm for better accuracy
49
- wn = 0 # Winding number counter
50
-
51
- # Loop through all edges of the polygon
52
- for i in range(len(polygon) - 1): # Last vertex is first vertex
53
- p1x, p1y = polygon[i]
54
- p2x, p2y = polygon[i + 1]
55
-
56
- # Test if a point is left/right/on the edge defined by two vertices
57
- if p1y <= y: # Start y <= P.y
58
- if p2y > y: # End y > P.y (upward crossing)
59
- # Point left of edge
60
- if ((p2x - p1x) * (y - p1y) - (x - p1x) * (p2y - p1y)) > 0:
61
- wn += 1 # Valid up intersect
62
- else: # Start y > P.y
63
- if p2y <= y: # End y <= P.y (downward crossing)
64
- # Point right of edge
65
- if ((p2x - p1x) * (y - p1y) - (x - p1x) * (p2y - p1y)) < 0:
66
- wn -= 1 # Valid down intersect
67
-
68
- # If winding number is not 0, the point is inside the polygon
69
- return wn != 0
70
-
71
27
  async def draw_go_to_flag(
72
28
  self, np_array: NumpyArray, entity_dict: dict, color_go_to: Color
73
29
  ) -> NumpyArray:
@@ -191,8 +147,7 @@ class ImageDraw:
191
147
  Returns:
192
148
  The updated image array
193
149
  """
194
- # Log the wall color to verify alpha is being passed correctly
195
- _LOGGER.debug("%s: Drawing walls with color %s", self.file_name, color_wall)
150
+ # Draw walls
196
151
 
197
152
  # If there are no disabled rooms, draw all walls
198
153
  if not disabled_rooms:
@@ -202,9 +157,6 @@ class ImageDraw:
202
157
 
203
158
  # If there are disabled rooms, we need to check each wall pixel
204
159
  # to see if it belongs to a disabled room
205
- _LOGGER.debug(
206
- "%s: Filtering walls for disabled rooms: %s", self.file_name, disabled_rooms
207
- )
208
160
 
209
161
  # Get the element map if available
210
162
  element_map = getattr(self.img_h, "element_map", None)
@@ -247,7 +199,7 @@ class ImageDraw:
247
199
  # Get the element at this position
248
200
  element = element_map[check_y, check_x]
249
201
 
250
- # Check if this element is a disabled room
202
+ # Check if this element is a disabled one
251
203
  # Room elements are in the range 101-115 (ROOM_1 to ROOM_15)
252
204
  if 101 <= element <= 115:
253
205
  room_id = element - 101 # Convert to 0-based index
@@ -263,12 +215,6 @@ class ImageDraw:
263
215
  filtered_pixels.append((x, y, z))
264
216
 
265
217
  # Draw the filtered walls
266
- _LOGGER.debug(
267
- "%s: Drawing %d of %d wall pixels after filtering",
268
- self.file_name,
269
- len(filtered_pixels),
270
- len(pixels),
271
- )
272
218
  if filtered_pixels:
273
219
  return await self.img_h.draw.from_json_to_image(
274
220
  img_np_array, filtered_pixels, pixel_size, color_wall
@@ -310,15 +256,6 @@ class ImageDraw:
310
256
  return np_array
311
257
  return np_array
312
258
 
313
- async def async_get_json_id(self, my_json: JsonType) -> str | None:
314
- """Return the JSON ID from the image."""
315
- try:
316
- json_id = my_json["metaData"]["nonce"]
317
- except (ValueError, KeyError) as e:
318
- _LOGGER.debug("%s: No JsonID provided: %s", self.file_name, str(e))
319
- json_id = None
320
- return json_id
321
-
322
259
  async def async_draw_zones(
323
260
  self,
324
261
  m_json: JsonType,
@@ -417,15 +354,6 @@ class ImageDraw:
417
354
  )
418
355
  return np_array
419
356
 
420
- async def async_get_entity_data(self, m_json: JsonType) -> dict or None:
421
- """Get the entity data from the JSON data."""
422
- try:
423
- entity_dict = self.img_h.data.find_points_entities(m_json)
424
- except (ValueError, KeyError):
425
- return None
426
- _LOGGER.info("%s: Got the points in the json.", self.file_name)
427
- return entity_dict
428
-
429
357
  def _check_active_zone_and_set_zooming(self) -> None:
430
358
  """Helper function to check active zones and set zooming state."""
431
359
  if self.img_h.active_zones and self.img_h.robot_in_room:
@@ -433,26 +361,8 @@ class ImageDraw:
433
361
  room_store = RoomStore(self.file_name)
434
362
  room_keys = list(room_store.get_rooms().keys())
435
363
 
436
- _LOGGER.debug(
437
- "%s: Active zones debug - segment_id: %s, room_keys: %s, active_zones: %s",
438
- self.file_name,
439
- segment_id,
440
- room_keys,
441
- self.img_h.active_zones,
442
- )
443
-
444
364
  if segment_id in room_keys:
445
365
  position = room_keys.index(segment_id)
446
- _LOGGER.debug(
447
- "%s: Segment ID %s found at position %s, active_zones[%s] = %s",
448
- self.file_name,
449
- segment_id,
450
- position,
451
- position,
452
- self.img_h.active_zones[position]
453
- if position < len(self.img_h.active_zones)
454
- else "OUT_OF_BOUNDS",
455
- )
456
366
  if position < len(self.img_h.active_zones):
457
367
  self.img_h.zooming = bool(self.img_h.active_zones[position])
458
368
  else:
@@ -468,37 +378,6 @@ class ImageDraw:
468
378
  else:
469
379
  self.img_h.zooming = False
470
380
 
471
- @staticmethod
472
- def point_in_polygon(x: int, y: int, polygon: list) -> bool:
473
- """
474
- Check if a point is inside a polygon using ray casting algorithm.
475
-
476
- Args:
477
- x: X coordinate of the point
478
- y: Y coordinate of the point
479
- polygon: List of (x, y) tuples forming the polygon
480
-
481
- Returns:
482
- True if the point is inside the polygon, False otherwise
483
- """
484
- n = len(polygon)
485
- inside = False
486
-
487
- p1x, p1y = polygon[0]
488
- xinters = None # Initialize with default value
489
- for i in range(1, n + 1):
490
- p2x, p2y = polygon[i % n]
491
- if y > min(p1y, p2y):
492
- if y <= max(p1y, p2y):
493
- if x <= max(p1x, p2x):
494
- if p1y != p2y:
495
- xinters = (y - p1y) * (p2x - p1x) / (p2y - p1y) + p1x
496
- if p1x == p2x or (xinters is not None and x <= xinters):
497
- inside = not inside
498
- p1x, p1y = p2x, p2y
499
-
500
- return inside
501
-
502
381
  async def async_get_robot_in_room(
503
382
  self, robot_y: int = 0, robot_x: int = 0, angle: float = 0.0
504
383
  ) -> RobotPosition:
@@ -508,7 +387,7 @@ class ImageDraw:
508
387
  # If we have outline data, use point_in_polygon for accurate detection
509
388
  if "outline" in self.img_h.robot_in_room:
510
389
  outline = self.img_h.robot_in_room["outline"]
511
- if self.point_in_polygon(int(robot_x), int(robot_y), outline):
390
+ if point_in_polygon(int(robot_x), int(robot_y), outline):
512
391
  temp = {
513
392
  "x": robot_x,
514
393
  "y": robot_y,
@@ -549,12 +428,6 @@ class ImageDraw:
549
428
  # This helps prevent false positives for points very far from any room
550
429
  map_boundary = 20000 # Typical map size is around 5000-10000 units
551
430
  if abs(robot_x) > map_boundary or abs(robot_y) > map_boundary:
552
- _LOGGER.debug(
553
- "%s robot position (%s, %s) is far outside map boundaries.",
554
- self.file_name,
555
- robot_x,
556
- robot_y,
557
- )
558
431
  self.img_h.robot_in_room = last_room
559
432
  self.img_h.zooming = False
560
433
  temp = {
@@ -567,10 +440,6 @@ class ImageDraw:
567
440
 
568
441
  # Search through all rooms to find which one contains the robot
569
442
  if self.img_h.rooms_pos is None:
570
- _LOGGER.debug(
571
- "%s: No rooms data available for robot position detection.",
572
- self.file_name,
573
- )
574
443
  self.img_h.robot_in_room = last_room
575
444
  self.img_h.zooming = False
576
445
  temp = {
@@ -586,7 +455,7 @@ class ImageDraw:
586
455
  if "outline" in room:
587
456
  outline = room["outline"]
588
457
  # Use point_in_polygon for accurate detection with complex shapes
589
- if self.point_in_polygon(int(robot_x), int(robot_y), outline):
458
+ if point_in_polygon(int(robot_x), int(robot_y), outline):
590
459
  # Robot is in this room
591
460
  self.img_h.robot_in_room = {
592
461
  "id": room.get(
@@ -608,26 +477,8 @@ class ImageDraw:
608
477
  room_store = RoomStore(self.file_name)
609
478
  room_keys = list(room_store.get_rooms().keys())
610
479
 
611
- _LOGGER.debug(
612
- "%s: Active zones debug - segment_id: %s, room_keys: %s, active_zones: %s",
613
- self.file_name,
614
- segment_id,
615
- room_keys,
616
- self.img_h.active_zones,
617
- )
618
-
619
480
  if segment_id in room_keys:
620
481
  position = room_keys.index(segment_id)
621
- _LOGGER.debug(
622
- "%s: Segment ID %s found at position %s, active_zones[%s] = %s",
623
- self.file_name,
624
- segment_id,
625
- position,
626
- position,
627
- self.img_h.active_zones[position]
628
- if position < len(self.img_h.active_zones)
629
- else "OUT_OF_BOUNDS",
630
- )
631
482
  if position < len(self.img_h.active_zones):
632
483
  self.img_h.zooming = bool(
633
484
  self.img_h.active_zones[position]
@@ -645,11 +496,6 @@ class ImageDraw:
645
496
  else:
646
497
  self.img_h.zooming = False
647
498
 
648
- _LOGGER.debug(
649
- "%s is in %s room (polygon detection).",
650
- self.file_name,
651
- self.img_h.robot_in_room["room"],
652
- )
653
499
  return temp
654
500
  # Fallback to bounding box if no outline is available
655
501
  elif "corners" in room:
@@ -683,19 +529,10 @@ class ImageDraw:
683
529
  # Handle active zones
684
530
  self._check_active_zone_and_set_zooming()
685
531
 
686
- _LOGGER.debug(
687
- "%s is in %s room (bounding box detection).",
688
- self.file_name,
689
- self.img_h.robot_in_room["room"],
690
- )
691
532
  return temp
692
533
  room_count += 1
693
534
 
694
535
  # Robot not found in any room
695
- _LOGGER.debug(
696
- "%s not located within any room coordinates.",
697
- self.file_name,
698
- )
699
536
  self.img_h.robot_in_room = last_room
700
537
  self.img_h.zooming = False
701
538
  temp = {
@@ -2,37 +2,32 @@
2
2
  Hypfer Image Handler Class.
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
5
+ Version: 0.1.10
6
6
  """
7
7
 
8
8
  from __future__ import annotations
9
9
 
10
10
  import asyncio
11
- import numpy as np
12
11
 
12
+ import numpy as np
13
+ from mvcrender.autocrop import AutoCrop
13
14
  from PIL import Image
14
15
 
15
- from .config.async_utils import AsyncNumPy, AsyncPIL
16
- from .config.auto_crop import AutoCrop
16
+ from .config.async_utils import AsyncPIL
17
17
  from .config.drawable_elements import DrawableElement
18
18
  from .config.shared import CameraShared
19
- from .config.utils import pil_to_webp_bytes
20
19
  from .config.types import (
21
20
  COLORS,
22
21
  LOGGER,
23
22
  CalibrationPoints,
24
23
  Colors,
24
+ JsonType,
25
25
  RoomsProperties,
26
26
  RoomStore,
27
- WebPBytes,
28
- JsonType,
29
27
  )
30
28
  from .config.utils import (
31
29
  BaseHandler,
32
30
  initialize_drawing_config,
33
- manage_drawable_elements,
34
- numpy_to_webp_bytes,
35
- prepare_resize_params,
36
31
  )
37
32
  from .hypfer_draw import ImageDraw as ImDraw
38
33
  from .map_data import ImageData
@@ -50,11 +45,8 @@ class HypferMapImageHandler(BaseHandler, AutoCrop):
50
45
  AutoCrop.__init__(self, self)
51
46
  self.calibration_data = None # camera shared data.
52
47
  self.data = ImageData # imported Image Data Module.
53
-
54
48
  # Initialize drawing configuration using the shared utility function
55
- self.drawing_config, self.draw, self.enhanced_draw = initialize_drawing_config(
56
- self
57
- )
49
+ self.drawing_config, self.draw = initialize_drawing_config(self)
58
50
 
59
51
  self.go_to = None # vacuum go to data
60
52
  self.img_hash = None # hash of the image calculated to check differences.
@@ -62,8 +54,7 @@ class HypferMapImageHandler(BaseHandler, AutoCrop):
62
54
  self.img_work_layer = (
63
55
  None # persistent working buffer to avoid per-frame allocations
64
56
  )
65
- self.active_zones = None # vacuum active zones.
66
- self.svg_wait = False # SVG image creation wait.
57
+ self.active_zones = [] # vacuum active zones.
67
58
  self.imd = ImDraw(self) # Image Draw class.
68
59
  self.color_grey = (128, 128, 128, 255)
69
60
  self.file_name = self.shared.file_name # file name of the vacuum.
@@ -82,10 +73,7 @@ class HypferMapImageHandler(BaseHandler, AutoCrop):
82
73
  json_data
83
74
  )
84
75
  if room_properties:
85
- rooms = RoomStore(self.file_name, room_properties)
86
- LOGGER.debug(
87
- "%s: Rooms data extracted! %s", self.file_name, rooms.get_rooms()
88
- )
76
+ _ = RoomStore(self.file_name, room_properties)
89
77
  # Convert room_properties to the format expected by async_get_robot_in_room
90
78
  self.rooms_pos = []
91
79
  for room_id, room_data in room_properties.items():
@@ -97,7 +85,6 @@ class HypferMapImageHandler(BaseHandler, AutoCrop):
97
85
  }
98
86
  )
99
87
  else:
100
- LOGGER.debug("%s: Rooms data not available!", self.file_name)
101
88
  self.rooms_pos = None
102
89
  return room_properties
103
90
 
@@ -105,14 +92,12 @@ class HypferMapImageHandler(BaseHandler, AutoCrop):
105
92
  async def async_get_image_from_json(
106
93
  self,
107
94
  m_json: JsonType | None,
108
- return_webp: bool = False,
109
- ) -> WebPBytes | Image.Image | None:
95
+ ) -> Image.Image | None:
110
96
  """Get the image from the JSON data.
111
97
  It uses the ImageDraw class to draw some of the elements of the image.
112
98
  The robot itself will be drawn in this function as per some of the values are needed for other tasks.
113
99
  @param m_json: The JSON data to use to draw the image.
114
- @param return_webp: If True, return WebP bytes; if False, return PIL Image (default).
115
- @return WebPBytes | Image.Image: WebP bytes or PIL Image depending on return_webp parameter.
100
+ @return Image.Image: PIL Image.
116
101
  """
117
102
  # Initialize the colors.
118
103
  colors: Colors = {
@@ -121,21 +106,12 @@ class HypferMapImageHandler(BaseHandler, AutoCrop):
121
106
  # Check if the JSON data is not None else process the image.
122
107
  try:
123
108
  if m_json is not None:
124
- LOGGER.debug("%s: Creating Image.", self.file_name)
125
- # buffer json data
126
- self.json_data = m_json
127
109
  # Get the image size from the JSON data
128
- size_x = int(m_json["size"]["x"])
129
- size_y = int(m_json["size"]["y"])
130
- self.img_size = {
131
- "x": size_x,
132
- "y": size_y,
133
- "centre": [(size_x // 2), (size_y // 2)],
134
- }
110
+ self.img_size = self.json_data.image_size
135
111
  # Get the JSON ID from the JSON data.
136
- self.json_id = await self.imd.async_get_json_id(m_json)
112
+ self.json_id = self.json_data.json_id
137
113
  # Check entity data.
138
- entity_dict = await self.imd.async_get_entity_data(m_json)
114
+ entity_dict = self.json_data.entity_dict
139
115
  # Update the Robot position.
140
116
  (
141
117
  robot_pos,
@@ -145,15 +121,16 @@ class HypferMapImageHandler(BaseHandler, AutoCrop):
145
121
 
146
122
  # Get the pixels size and layers from the JSON data
147
123
  pixel_size = int(m_json["pixelSize"])
148
- layers, active = self.data.find_layers(m_json["layers"], {}, [])
149
- # Populate active_zones from the JSON data
150
- self.active_zones = active
151
- new_frame_hash = await self.calculate_array_hash(layers, active)
124
+ self.active_zones = self.json_data.active_zones
125
+
126
+ new_frame_hash = await self.calculate_array_hash(
127
+ self.json_data.layers, self.active_zones
128
+ )
152
129
  if self.frame_number == 0:
153
130
  self.img_hash = new_frame_hash
154
131
  # Create empty image
155
132
  img_np_array = await self.draw.create_empty_image(
156
- size_x, size_y, colors["background"]
133
+ self.img_size["x"], self.img_size["y"], colors["background"]
157
134
  )
158
135
  # Draw layers and segments if enabled
159
136
  room_id = 0
@@ -162,7 +139,10 @@ class HypferMapImageHandler(BaseHandler, AutoCrop):
162
139
 
163
140
  if self.drawing_config.is_enabled(DrawableElement.FLOOR):
164
141
  # First pass: identify disabled rooms
165
- for layer_type, compressed_pixels_list in layers.items():
142
+ for (
143
+ layer_type,
144
+ compressed_pixels_list,
145
+ ) in self.json_data.layers.items():
166
146
  # Check if this is a room layer
167
147
  if layer_type == "segment":
168
148
  # The room_id is the current room being processed (0-based index)
@@ -180,11 +160,6 @@ class HypferMapImageHandler(BaseHandler, AutoCrop):
180
160
  ):
181
161
  # Add this room to the disabled rooms set
182
162
  disabled_rooms.add(room_id)
183
- LOGGER.debug(
184
- "%s: Room %d is disabled and will be skipped",
185
- self.file_name,
186
- current_room_id,
187
- )
188
163
  room_id = (
189
164
  room_id + 1
190
165
  ) % 16 # Cycle room_id back to 0 after 15
@@ -193,7 +168,10 @@ class HypferMapImageHandler(BaseHandler, AutoCrop):
193
168
  room_id = 0
194
169
 
195
170
  # Second pass: draw enabled rooms and walls
196
- for layer_type, compressed_pixels_list in layers.items():
171
+ for (
172
+ layer_type,
173
+ compressed_pixels_list,
174
+ ) in self.json_data.layers.items():
197
175
  # Check if this is a room layer
198
176
  is_room_layer = layer_type == "segment"
199
177
 
@@ -258,13 +236,13 @@ class HypferMapImageHandler(BaseHandler, AutoCrop):
258
236
  # Robot and rooms position
259
237
  if (room_id > 0) and not self.room_propriety:
260
238
  self.room_propriety = await self.async_extract_room_properties(
261
- self.json_data
239
+ self.json_data.json_data
262
240
  )
263
241
 
264
242
  # Ensure room data is available for robot room detection (even if not extracted above)
265
243
  if not self.rooms_pos and not self.room_propriety:
266
244
  self.room_propriety = await self.async_extract_room_properties(
267
- self.json_data
245
+ self.json_data.json_data
268
246
  )
269
247
 
270
248
  # Always check robot position for zooming (moved outside the condition)
@@ -284,12 +262,6 @@ class HypferMapImageHandler(BaseHandler, AutoCrop):
284
262
  new_frame_hash != self.img_hash
285
263
  ):
286
264
  self.frame_number = 0
287
- LOGGER.debug(
288
- "%s: %s at Frame Number: %s",
289
- self.file_name,
290
- str(self.json_id),
291
- str(self.frame_number),
292
- )
293
265
  # Ensure persistent working buffer exists and matches base (allocate only when needed)
294
266
  if (
295
267
  self.img_work_layer is None
@@ -366,24 +338,15 @@ class HypferMapImageHandler(BaseHandler, AutoCrop):
366
338
  y=robot_position[1],
367
339
  angle=robot_position_angle,
368
340
  fill=robot_color,
341
+ radius=self.shared.robot_size,
369
342
  robot_state=self.shared.vacuum_state,
370
343
  )
371
344
 
372
- # Update element map for robot position
373
- if (
374
- hasattr(self.shared, "element_map")
375
- and self.shared.element_map is not None
376
- ):
377
- update_element_map_with_robot(
378
- self.shared.element_map,
379
- robot_position,
380
- DrawableElement.ROBOT,
381
- )
382
345
  # Synchronize zooming state from ImageDraw to handler before auto-crop
383
346
  self.zooming = self.imd.img_h.zooming
384
347
 
385
348
  # Resize the image
386
- img_np_array = await self.async_auto_trim_and_zoom_image(
349
+ img_np_array = self.auto_trim_and_zoom_image(
387
350
  img_np_array,
388
351
  colors["background"],
389
352
  int(self.shared.margins),
@@ -400,29 +363,16 @@ class HypferMapImageHandler(BaseHandler, AutoCrop):
400
363
  # Convert to PIL for resizing
401
364
  pil_img = await AsyncPIL.async_fromarray(img_np_array, mode="RGBA")
402
365
  del img_np_array
403
- resize_params = prepare_resize_params(self, pil_img, False)
366
+ resize_params = self.prepare_resize_params(pil_img)
404
367
  resized_image = await self.async_resize_images(resize_params)
405
368
 
406
- # Return WebP bytes or PIL Image based on parameter
407
- if return_webp:
408
- webp_bytes = await pil_to_webp_bytes(resized_image)
409
- return webp_bytes
410
- else:
411
- return resized_image
369
+ # Return PIL Image
370
+ return resized_image
412
371
  else:
413
- # Return WebP bytes or PIL Image based on parameter
414
- if return_webp:
415
- # Convert directly from NumPy to WebP for better performance
416
- webp_bytes = await numpy_to_webp_bytes(img_np_array)
417
- del img_np_array
418
- LOGGER.debug("%s: Frame Completed.", self.file_name)
419
- return webp_bytes
420
- else:
421
- # Convert to PIL Image (original behavior)
422
- pil_img = await AsyncPIL.async_fromarray(img_np_array, mode="RGBA")
423
- del img_np_array
424
- LOGGER.debug("%s: Frame Completed.", self.file_name)
425
- return pil_img
372
+ # Return PIL Image (convert from NumPy)
373
+ pil_img = await AsyncPIL.async_fromarray(img_np_array, mode="RGBA")
374
+ del img_np_array
375
+ return pil_img
426
376
  except (RuntimeError, RuntimeWarning) as e:
427
377
  LOGGER.warning(
428
378
  "%s: Error %s during image creation.",
@@ -438,19 +388,15 @@ class HypferMapImageHandler(BaseHandler, AutoCrop):
438
388
  if self.room_propriety:
439
389
  return self.room_propriety
440
390
  if self.json_data:
441
- LOGGER.debug("Checking %s Rooms data..", self.file_name)
442
391
  self.room_propriety = await self.async_extract_room_properties(
443
- self.json_data
392
+ self.json_data.json_data
444
393
  )
445
- if self.room_propriety:
446
- LOGGER.debug("Got %s Rooms Attributes.", self.file_name)
447
394
  return self.room_propriety
448
395
 
449
- def get_calibration_data(self) -> CalibrationPoints:
396
+ def get_calibration_data(self, rotation_angle: int = 0) -> CalibrationPoints:
450
397
  """Get the calibration data from the JSON data.
451
398
  this will create the attribute calibration points."""
452
399
  calibration_data = []
453
- rotation_angle = self.shared.image_rotate
454
400
  LOGGER.info("Getting %s Calibrations points.", self.file_name)
455
401
 
456
402
  # Define the map points (fixed)
@@ -465,42 +411,6 @@ class HypferMapImageHandler(BaseHandler, AutoCrop):
465
411
  del vacuum_points, map_points, calibration_point, rotation_angle # free memory.
466
412
  return calibration_data
467
413
 
468
- # Element selection methods
469
- def enable_element(self, element_code: DrawableElement) -> None:
470
- """Enable drawing of a specific element."""
471
- self.drawing_config.enable_element(element_code)
472
- LOGGER.info(
473
- "%s: Enabled element %s, now enabled: %s",
474
- self.file_name,
475
- element_code.name,
476
- self.drawing_config.is_enabled(element_code),
477
- )
478
-
479
- def disable_element(self, element_code: DrawableElement) -> None:
480
- """Disable drawing of a specific element."""
481
- manage_drawable_elements(self, "disable", element_code=element_code)
482
-
483
- def set_elements(self, element_codes: list[DrawableElement]) -> None:
484
- """Enable only the specified elements, disable all others."""
485
- manage_drawable_elements(self, "set_elements", element_codes=element_codes)
486
-
487
- def set_element_property(
488
- self, element_code: DrawableElement, property_name: str, value
489
- ) -> None:
490
- """Set a drawing property for an element."""
491
- manage_drawable_elements(
492
- self,
493
- "set_property",
494
- element_code=element_code,
495
- property_name=property_name,
496
- value=value,
497
- )
498
-
499
- @staticmethod
500
- async def async_copy_array(original_array):
501
- """Copy the array."""
502
- return await AsyncNumPy.async_copy(original_array)
503
-
504
414
  async def _prepare_zone_data(self, m_json):
505
415
  """Prepare zone data for parallel processing."""
506
416
  await asyncio.sleep(0) # Yield control