valetudo-map-parser 0.1.9b43__py3-none-any.whl → 0.1.9b45__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/config/colors.py +88 -8
- valetudo_map_parser/config/drawable.py +83 -5
- valetudo_map_parser/config/drawable_elements.py +591 -16
- valetudo_map_parser/config/enhanced_drawable.py +4 -9
- valetudo_map_parser/config/optimized_element_map.py +363 -0
- valetudo_map_parser/config/room_outline.py +148 -0
- valetudo_map_parser/config/shared.py +1 -0
- valetudo_map_parser/config/utils.py +82 -65
- valetudo_map_parser/hypfer_draw.py +30 -32
- valetudo_map_parser/hypfer_handler.py +88 -100
- valetudo_map_parser/map_data.py +0 -9
- valetudo_map_parser/rand25_handler.py +173 -132
- valetudo_map_parser/utils/__init__.py +5 -0
- valetudo_map_parser/utils/color_utils.py +62 -0
- {valetudo_map_parser-0.1.9b43.dist-info → valetudo_map_parser-0.1.9b45.dist-info}/METADATA +2 -1
- valetudo_map_parser-0.1.9b45.dist-info/RECORD +27 -0
- valetudo_map_parser-0.1.9b43.dist-info/RECORD +0 -23
- {valetudo_map_parser-0.1.9b43.dist-info → valetudo_map_parser-0.1.9b45.dist-info}/LICENSE +0 -0
- {valetudo_map_parser-0.1.9b43.dist-info → valetudo_map_parser-0.1.9b45.dist-info}/NOTICE.txt +0 -0
- {valetudo_map_parser-0.1.9b43.dist-info → valetudo_map_parser-0.1.9b45.dist-info}/WHEEL +0 -0
@@ -2,18 +2,16 @@
|
|
2
2
|
|
3
3
|
import hashlib
|
4
4
|
import json
|
5
|
-
import logging
|
6
|
-
import numpy as np
|
7
5
|
from dataclasses import dataclass
|
8
|
-
from
|
9
|
-
from typing import Callable, Dict, List, Optional, Tuple, Union
|
6
|
+
from typing import Callable, List, Optional
|
10
7
|
|
8
|
+
import numpy as np
|
11
9
|
from PIL import ImageOps
|
12
10
|
|
13
|
-
from .
|
14
|
-
|
15
|
-
|
16
|
-
|
11
|
+
from .drawable import Drawable
|
12
|
+
from .drawable_elements import DrawableElement, DrawingConfig
|
13
|
+
from .enhanced_drawable import EnhancedDrawable
|
14
|
+
from .types import LOGGER, ChargerPosition, ImageSize, NumpyArray, PilPNG, RobotPosition
|
17
15
|
|
18
16
|
|
19
17
|
@dataclass
|
@@ -114,7 +112,7 @@ class BaseHandler:
|
|
114
112
|
elif rotation in [90, 270]:
|
115
113
|
self.offset_y = (self.crop_img_size[0] - width) // 2
|
116
114
|
self.offset_x = self.crop_img_size[1] - height
|
117
|
-
|
115
|
+
LOGGER.debug(
|
118
116
|
"%s Image Coordinates Offsets (x,y): %s. %s",
|
119
117
|
self.file_name,
|
120
118
|
self.offset_x,
|
@@ -142,7 +140,7 @@ class BaseHandler:
|
|
142
140
|
self.offset_x = width - self.crop_img_size[0]
|
143
141
|
self.offset_y = height - self.crop_img_size[1]
|
144
142
|
|
145
|
-
|
143
|
+
LOGGER.debug(
|
146
144
|
"%s Image Coordinates Offsets (x,y): %s. %s",
|
147
145
|
self.file_name,
|
148
146
|
self.offset_x,
|
@@ -173,7 +171,7 @@ class BaseHandler:
|
|
173
171
|
self.offset_y = (self.crop_img_size[0] - width) // 2
|
174
172
|
self.offset_x = self.crop_img_size[1] - height
|
175
173
|
|
176
|
-
|
174
|
+
LOGGER.debug(
|
177
175
|
"%s Image Coordinates Offsets (x,y): %s. %s",
|
178
176
|
self.file_name,
|
179
177
|
self.offset_x,
|
@@ -205,7 +203,7 @@ class BaseHandler:
|
|
205
203
|
self.offset_y = (self.crop_img_size[0] - width) // 2
|
206
204
|
self.offset_x = self.crop_img_size[1] - height
|
207
205
|
|
208
|
-
|
206
|
+
LOGGER.debug(
|
209
207
|
"%s Image Coordinates Offsets (x,y): %s. %s",
|
210
208
|
self.file_name,
|
211
209
|
self.offset_x,
|
@@ -233,7 +231,7 @@ class BaseHandler:
|
|
233
231
|
self.offset_x = width - self.crop_img_size[0]
|
234
232
|
self.offset_y = height - self.crop_img_size[1]
|
235
233
|
|
236
|
-
|
234
|
+
LOGGER.debug(
|
237
235
|
"%s Image Coordinates Offsets (x,y): %s. %s",
|
238
236
|
self.file_name,
|
239
237
|
self.offset_x,
|
@@ -261,7 +259,7 @@ class BaseHandler:
|
|
261
259
|
self.offset_x = width - self.crop_img_size[0]
|
262
260
|
self.offset_y = height - self.crop_img_size[1]
|
263
261
|
|
264
|
-
|
262
|
+
LOGGER.debug(
|
265
263
|
"%s Image Coordinates Offsets (x,y): %s. %s",
|
266
264
|
self.file_name,
|
267
265
|
self.offset_x,
|
@@ -446,7 +444,7 @@ class BaseHandler:
|
|
446
444
|
}
|
447
445
|
id_count += 1
|
448
446
|
if id_count > 1:
|
449
|
-
|
447
|
+
LOGGER.debug("%s: Zones Properties updated.", self.file_name)
|
450
448
|
return zone_properties
|
451
449
|
|
452
450
|
async def async_points_propriety(self, points_data) -> dict:
|
@@ -467,7 +465,7 @@ class BaseHandler:
|
|
467
465
|
}
|
468
466
|
id_count += 1
|
469
467
|
if id_count > 1:
|
470
|
-
|
468
|
+
LOGGER.debug("%s: Point Properties updated.", self.file_name)
|
471
469
|
return point_properties
|
472
470
|
|
473
471
|
@staticmethod
|
@@ -489,7 +487,7 @@ async def async_resize_image(params: ResizeParams):
|
|
489
487
|
wsf, hsf = [int(x) for x in params.aspect_ratio.split(",")]
|
490
488
|
|
491
489
|
if wsf == 0 or hsf == 0 or params.width <= 0 or params.height <= 0:
|
492
|
-
|
490
|
+
LOGGER.warning(
|
493
491
|
"Invalid aspect ratio parameters: width=%s, height=%s, wsf=%s, hsf=%s. Returning original image.",
|
494
492
|
params.width,
|
495
493
|
params.height,
|
@@ -509,8 +507,8 @@ async def async_resize_image(params: ResizeParams):
|
|
509
507
|
new_width = params.pil_img.width
|
510
508
|
new_height = int(params.pil_img.width / new_aspect_ratio)
|
511
509
|
|
512
|
-
|
513
|
-
|
510
|
+
LOGGER.debug("Resizing image to aspect ratio: %s, %s", wsf, hsf)
|
511
|
+
LOGGER.debug("New image size: %s x %s", new_width, new_height)
|
514
512
|
|
515
513
|
if (params.crop_size is not None) and (params.offset_func is not None):
|
516
514
|
offset = OffsetParams(wsf, hsf, new_width, new_height, params.is_rand)
|
@@ -544,50 +542,47 @@ def initialize_drawing_config(handler):
|
|
544
542
|
Returns:
|
545
543
|
Tuple of (DrawingConfig, Drawable, EnhancedDrawable)
|
546
544
|
"""
|
547
|
-
from .drawable import Drawable
|
548
|
-
from .drawable_elements import DrawableElement, DrawingConfig
|
549
|
-
from .enhanced_drawable import EnhancedDrawable
|
550
545
|
|
551
546
|
# Initialize drawing configuration
|
552
547
|
drawing_config = DrawingConfig()
|
553
548
|
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
|
549
|
+
if (
|
550
|
+
hasattr(handler.shared, "device_info")
|
551
|
+
and handler.shared.device_info is not None
|
552
|
+
):
|
553
|
+
LOGGER.info(
|
559
554
|
"%s: Initializing drawing config from device_info", handler.file_name
|
560
555
|
)
|
561
|
-
|
556
|
+
LOGGER.info(
|
562
557
|
"%s: device_info contains disable_obstacles: %s",
|
563
558
|
handler.file_name,
|
564
559
|
"disable_obstacles" in handler.shared.device_info,
|
565
560
|
)
|
566
|
-
|
561
|
+
LOGGER.info(
|
567
562
|
"%s: device_info contains disable_path: %s",
|
568
563
|
handler.file_name,
|
569
564
|
"disable_path" in handler.shared.device_info,
|
570
565
|
)
|
571
|
-
|
566
|
+
LOGGER.info(
|
572
567
|
"%s: device_info contains disable_elements: %s",
|
573
568
|
handler.file_name,
|
574
569
|
"disable_elements" in handler.shared.device_info,
|
575
570
|
)
|
576
571
|
|
577
572
|
if "disable_obstacles" in handler.shared.device_info:
|
578
|
-
|
573
|
+
LOGGER.info(
|
579
574
|
"%s: disable_obstacles value: %s",
|
580
575
|
handler.file_name,
|
581
576
|
handler.shared.device_info["disable_obstacles"],
|
582
577
|
)
|
583
578
|
if "disable_path" in handler.shared.device_info:
|
584
|
-
|
579
|
+
LOGGER.info(
|
585
580
|
"%s: disable_path value: %s",
|
586
581
|
handler.file_name,
|
587
582
|
handler.shared.device_info["disable_path"],
|
588
583
|
)
|
589
584
|
if "disable_elements" in handler.shared.device_info:
|
590
|
-
|
585
|
+
LOGGER.info(
|
591
586
|
"%s: disable_elements value: %s",
|
592
587
|
handler.file_name,
|
593
588
|
handler.shared.device_info["disable_elements"],
|
@@ -596,12 +591,12 @@ def initialize_drawing_config(handler):
|
|
596
591
|
drawing_config.update_from_device_info(handler.shared.device_info)
|
597
592
|
|
598
593
|
# Verify elements are disabled
|
599
|
-
|
594
|
+
LOGGER.info(
|
600
595
|
"%s: After initialization, PATH enabled: %s",
|
601
596
|
handler.file_name,
|
602
597
|
drawing_config.is_enabled(DrawableElement.PATH),
|
603
598
|
)
|
604
|
-
|
599
|
+
LOGGER.info(
|
605
600
|
"%s: After initialization, OBSTACLE enabled: %s",
|
606
601
|
handler.file_name,
|
607
602
|
drawing_config.is_enabled(DrawableElement.OBSTACLE),
|
@@ -738,7 +733,9 @@ def get_room_at_position(element_map, x, y, room_base=101):
|
|
738
733
|
return None
|
739
734
|
|
740
735
|
|
741
|
-
def update_element_map_with_robot(
|
736
|
+
def update_element_map_with_robot(
|
737
|
+
element_map, robot_position, robot_element=3, robot_radius=25
|
738
|
+
):
|
742
739
|
"""
|
743
740
|
Update the element map with the robot position.
|
744
741
|
|
@@ -762,14 +759,18 @@ def update_element_map_with_robot(element_map, robot_position, robot_element=3,
|
|
762
759
|
int(robot_position[0] + dx),
|
763
760
|
int(robot_position[1] + dy),
|
764
761
|
)
|
765
|
-
if
|
766
|
-
0 <= ry < element_map.shape[0]
|
767
|
-
and 0 <= rx < element_map.shape[1]
|
768
|
-
):
|
762
|
+
if 0 <= ry < element_map.shape[0] and 0 <= rx < element_map.shape[1]:
|
769
763
|
element_map[ry, rx] = robot_element
|
770
764
|
|
771
765
|
|
772
|
-
def manage_drawable_elements(
|
766
|
+
def manage_drawable_elements(
|
767
|
+
handler,
|
768
|
+
action,
|
769
|
+
element_code=None,
|
770
|
+
element_codes=None,
|
771
|
+
property_name=None,
|
772
|
+
value=None,
|
773
|
+
):
|
773
774
|
"""
|
774
775
|
Manage drawable elements (enable, disable, set elements, set properties).
|
775
776
|
|
@@ -793,11 +794,15 @@ def manage_drawable_elements(handler, action, element_code=None, element_codes=N
|
|
793
794
|
handler.drawing_config.disable_element(element_code)
|
794
795
|
elif action == "set_elements" and element_codes is not None:
|
795
796
|
handler.drawing_config.set_elements(element_codes)
|
796
|
-
elif
|
797
|
+
elif (
|
798
|
+
action == "set_property"
|
799
|
+
and element_code is not None
|
800
|
+
and property_name is not None
|
801
|
+
):
|
797
802
|
handler.drawing_config.set_property(element_code, property_name, value)
|
798
803
|
|
799
804
|
|
800
|
-
def handle_room_outline_error(file_name, room_id, error
|
805
|
+
def handle_room_outline_error(file_name, room_id, error):
|
801
806
|
"""
|
802
807
|
Handle errors during room outline extraction.
|
803
808
|
|
@@ -805,20 +810,22 @@ def handle_room_outline_error(file_name, room_id, error, logger=None):
|
|
805
810
|
file_name: Name of the file for logging
|
806
811
|
room_id: Room ID for logging
|
807
812
|
error: The error that occurred
|
808
|
-
logger: Logger instance (optional)
|
809
813
|
|
810
814
|
Returns:
|
811
815
|
None
|
812
816
|
"""
|
813
|
-
_LOGGER = logger or logging.getLogger(__name__)
|
814
817
|
|
815
|
-
|
818
|
+
LOGGER.warning(
|
816
819
|
"%s: Failed to trace outline for room %s: %s",
|
817
|
-
file_name,
|
820
|
+
file_name,
|
821
|
+
str(room_id),
|
822
|
+
str(error),
|
818
823
|
)
|
819
824
|
|
820
825
|
|
821
|
-
async def async_extract_room_outline(
|
826
|
+
async def async_extract_room_outline(
|
827
|
+
room_mask, min_x, min_y, max_x, max_y, file_name, room_id_int
|
828
|
+
):
|
822
829
|
"""
|
823
830
|
Extract the outline of a room from a binary mask.
|
824
831
|
|
@@ -830,13 +837,9 @@ async def async_extract_room_outline(room_mask, min_x, min_y, max_x, max_y, file
|
|
830
837
|
max_y: Maximum y coordinate of the room
|
831
838
|
file_name: Name of the file for logging
|
832
839
|
room_id_int: Room ID for logging
|
833
|
-
logger: Logger instance (optional)
|
834
|
-
|
835
840
|
Returns:
|
836
841
|
List of (x, y) points forming the room outline
|
837
842
|
"""
|
838
|
-
# Use the provided logger or create a new one
|
839
|
-
_LOGGER = logger or logging.getLogger(__name__)
|
840
843
|
|
841
844
|
# Get the dimensions of the mask
|
842
845
|
height, width = room_mask.shape
|
@@ -874,24 +877,33 @@ async def async_extract_room_outline(room_mask, min_x, min_y, max_x, max_y, file
|
|
874
877
|
is_boundary = False
|
875
878
|
for dy, dx in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
|
876
879
|
ny, nx = y + dy, x + dx
|
877
|
-
if (
|
878
|
-
|
880
|
+
if (
|
881
|
+
ny < 0
|
882
|
+
or ny >= height
|
883
|
+
or nx < 0
|
884
|
+
or nx >= width
|
885
|
+
or room_mask[ny, nx] == 0
|
886
|
+
):
|
879
887
|
is_boundary = True
|
880
888
|
break
|
881
889
|
if is_boundary:
|
882
890
|
boundary_points.append((x, y))
|
883
891
|
|
884
892
|
# Log the number of boundary points found
|
885
|
-
|
893
|
+
LOGGER.debug(
|
886
894
|
"%s: Room %s has %d boundary points",
|
887
|
-
file_name,
|
895
|
+
file_name,
|
896
|
+
str(room_id_int),
|
897
|
+
len(boundary_points),
|
888
898
|
)
|
889
899
|
|
890
900
|
# If we found too few boundary points, use the rectangular outline
|
891
901
|
if len(boundary_points) < 8: # Need at least 8 points for a meaningful shape
|
892
|
-
|
902
|
+
LOGGER.debug(
|
893
903
|
"%s: Room %s has too few boundary points (%d), using rectangular outline",
|
894
|
-
file_name,
|
904
|
+
file_name,
|
905
|
+
str(room_id_int),
|
906
|
+
len(boundary_points),
|
895
907
|
)
|
896
908
|
return rect_outline
|
897
909
|
|
@@ -903,7 +915,7 @@ async def async_extract_room_outline(room_mask, min_x, min_y, max_x, max_y, file
|
|
903
915
|
|
904
916
|
# Calculate angles from centroid
|
905
917
|
def calculate_angle(point):
|
906
|
-
return np.arctan2(point[1] - centroid_y, point[0] - centroid_x)
|
918
|
+
return np.arctan2(point[1] - int(centroid_y), point[0] - int(centroid_x))
|
907
919
|
|
908
920
|
# Sort boundary points by angle
|
909
921
|
boundary_points.sort(key=calculate_angle)
|
@@ -912,10 +924,12 @@ async def async_extract_room_outline(room_mask, min_x, min_y, max_x, max_y, file
|
|
912
924
|
if len(boundary_points) > 20:
|
913
925
|
# Take every Nth point to simplify
|
914
926
|
step = len(boundary_points) // 20
|
915
|
-
simplified_outline = [
|
927
|
+
simplified_outline = [
|
928
|
+
boundary_points[i] for i in range(0, len(boundary_points), step)
|
929
|
+
]
|
916
930
|
# Make sure we have at least 8 points
|
917
931
|
if len(simplified_outline) < 8:
|
918
|
-
simplified_outline = boundary_points[::len(boundary_points)//8]
|
932
|
+
simplified_outline = boundary_points[:: len(boundary_points) // 8]
|
919
933
|
else:
|
920
934
|
simplified_outline = boundary_points
|
921
935
|
|
@@ -926,16 +940,19 @@ async def async_extract_room_outline(room_mask, min_x, min_y, max_x, max_y, file
|
|
926
940
|
# Convert NumPy int64 values to regular Python integers
|
927
941
|
simplified_outline = [(int(x), int(y)) for x, y in simplified_outline]
|
928
942
|
|
929
|
-
|
943
|
+
LOGGER.debug(
|
930
944
|
"%s: Room %s outline has %d points",
|
931
|
-
file_name,
|
945
|
+
file_name,
|
946
|
+
str(room_id_int),
|
947
|
+
len(simplified_outline),
|
932
948
|
)
|
933
949
|
|
934
950
|
return simplified_outline
|
935
951
|
|
936
952
|
except (ValueError, IndexError, TypeError, ArithmeticError) as e:
|
937
|
-
|
953
|
+
LOGGER.warning(
|
938
954
|
"%s: Error tracing room outline: %s. Using rectangular outline instead.",
|
939
|
-
file_name,
|
955
|
+
file_name,
|
956
|
+
str(e),
|
940
957
|
)
|
941
958
|
return rect_outline
|
@@ -252,27 +252,26 @@ class ImageDraw:
|
|
252
252
|
except KeyError:
|
253
253
|
_LOGGER.info("%s No obstacle found.", self.file_name)
|
254
254
|
return np_array
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
return np_array
|
255
|
+
obstacle_positions = []
|
256
|
+
if obstacle_data:
|
257
|
+
for obstacle in obstacle_data:
|
258
|
+
label = obstacle.get("metaData", {}).get("label")
|
259
|
+
points = obstacle.get("points", [])
|
260
|
+
|
261
|
+
if label and points:
|
262
|
+
obstacle_pos = {
|
263
|
+
"label": label,
|
264
|
+
"points": {"x": points[0], "y": points[1]},
|
265
|
+
}
|
266
|
+
obstacle_positions.append(obstacle_pos)
|
267
|
+
|
268
|
+
# List of dictionaries containing label and points for each obstacle
|
269
|
+
# and draw obstacles on the map
|
270
|
+
if obstacle_positions:
|
271
|
+
await self.img_h.draw.async_draw_obstacles(
|
272
|
+
np_array, obstacle_positions, color_no_go
|
273
|
+
)
|
274
|
+
return np_array
|
276
275
|
|
277
276
|
async def async_draw_charger(
|
278
277
|
self,
|
@@ -286,18 +285,17 @@ class ImageDraw:
|
|
286
285
|
except KeyError:
|
287
286
|
_LOGGER.warning("%s: No charger position found.", self.file_name)
|
288
287
|
return np_array
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
np_array
|
297
|
-
|
298
|
-
)
|
299
|
-
return np_array
|
288
|
+
if charger_pos:
|
289
|
+
charger_pos = charger_pos[0]["points"]
|
290
|
+
self.img_h.charger_pos = {
|
291
|
+
"x": charger_pos[0],
|
292
|
+
"y": charger_pos[1],
|
293
|
+
}
|
294
|
+
np_array = await self.img_h.draw.battery_charger(
|
295
|
+
np_array, charger_pos[0], charger_pos[1], color_charger
|
296
|
+
)
|
300
297
|
return np_array
|
298
|
+
return np_array
|
301
299
|
|
302
300
|
async def async_get_json_id(self, my_json: JsonType) -> str | None:
|
303
301
|
"""Return the JSON ID from the image."""
|