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.
- valetudo_map_parser/__init__.py +24 -8
- valetudo_map_parser/config/auto_crop.py +2 -27
- valetudo_map_parser/config/color_utils.py +3 -4
- valetudo_map_parser/config/colors.py +2 -2
- valetudo_map_parser/config/drawable.py +102 -153
- valetudo_map_parser/config/drawable_elements.py +0 -2
- valetudo_map_parser/config/fonts/FiraSans.ttf +0 -0
- valetudo_map_parser/config/fonts/Inter-VF.ttf +0 -0
- valetudo_map_parser/config/fonts/Lato-Regular.ttf +0 -0
- valetudo_map_parser/config/fonts/MPLUSRegular.ttf +0 -0
- valetudo_map_parser/config/fonts/NotoKufiArabic-VF.ttf +0 -0
- valetudo_map_parser/config/fonts/NotoSansCJKhk-VF.ttf +0 -0
- valetudo_map_parser/config/fonts/NotoSansKhojki.ttf +0 -0
- valetudo_map_parser/config/rand256_parser.py +169 -44
- valetudo_map_parser/config/shared.py +103 -101
- valetudo_map_parser/config/status_text/status_text.py +96 -0
- valetudo_map_parser/config/status_text/translations.py +280 -0
- valetudo_map_parser/config/types.py +42 -13
- valetudo_map_parser/config/utils.py +221 -181
- valetudo_map_parser/hypfer_draw.py +6 -169
- valetudo_map_parser/hypfer_handler.py +40 -130
- valetudo_map_parser/map_data.py +403 -84
- valetudo_map_parser/rand256_handler.py +53 -197
- valetudo_map_parser/reimg_draw.py +14 -24
- valetudo_map_parser/rooms_handler.py +3 -18
- {valetudo_map_parser-0.1.9b100.dist-info → valetudo_map_parser-0.1.10.dist-info}/METADATA +7 -4
- valetudo_map_parser-0.1.10.dist-info/RECORD +34 -0
- {valetudo_map_parser-0.1.9b100.dist-info → valetudo_map_parser-0.1.10.dist-info}/WHEEL +1 -1
- valetudo_map_parser/config/enhanced_drawable.py +0 -324
- valetudo_map_parser/hypfer_rooms_handler.py +0 -599
- valetudo_map_parser-0.1.9b100.dist-info/RECORD +0 -27
- {valetudo_map_parser-0.1.9b100.dist-info → valetudo_map_parser-0.1.10.dist-info/licenses}/LICENSE +0 -0
- {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.
|
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
|
-
#
|
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
|
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
|
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
|
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.
|
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
|
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
|
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 =
|
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
|
-
|
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
|
-
|
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
|
-
@
|
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
|
-
|
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 =
|
112
|
+
self.json_id = self.json_data.json_id
|
137
113
|
# Check entity data.
|
138
|
-
entity_dict =
|
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
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
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
|
-
|
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
|
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
|
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 =
|
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(
|
366
|
+
resize_params = self.prepare_resize_params(pil_img)
|
404
367
|
resized_image = await self.async_resize_images(resize_params)
|
405
368
|
|
406
|
-
# Return
|
407
|
-
|
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
|
414
|
-
|
415
|
-
|
416
|
-
|
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
|