valetudo-map-parser 0.1.9b3__tar.gz → 0.1.9b5__tar.gz
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-0.1.9b3 → valetudo_map_parser-0.1.9b5}/PKG-INFO +1 -1
- {valetudo_map_parser-0.1.9b3 → valetudo_map_parser-0.1.9b5}/SCR/valetudo_map_parser/config/colors.py +2 -2
- {valetudo_map_parser-0.1.9b3 → valetudo_map_parser-0.1.9b5}/SCR/valetudo_map_parser/config/rand25_parser.py +7 -2
- {valetudo_map_parser-0.1.9b3 → valetudo_map_parser-0.1.9b5}/SCR/valetudo_map_parser/config/shared.py +3 -3
- valetudo_map_parser-0.1.9b5/SCR/valetudo_map_parser/config/utils.py +132 -0
- {valetudo_map_parser-0.1.9b3 → valetudo_map_parser-0.1.9b5}/SCR/valetudo_map_parser/hypfer_draw.py +1 -27
- {valetudo_map_parser-0.1.9b3 → valetudo_map_parser-0.1.9b5}/SCR/valetudo_map_parser/hypfer_handler.py +25 -130
- {valetudo_map_parser-0.1.9b3 → valetudo_map_parser-0.1.9b5}/SCR/valetudo_map_parser/images_utils.py +2 -65
- {valetudo_map_parser-0.1.9b3 → valetudo_map_parser-0.1.9b5}/SCR/valetudo_map_parser/map_data.py +1 -6
- {valetudo_map_parser-0.1.9b3 → valetudo_map_parser-0.1.9b5}/SCR/valetudo_map_parser/rand25_handler.py +21 -99
- {valetudo_map_parser-0.1.9b3 → valetudo_map_parser-0.1.9b5}/SCR/valetudo_map_parser/reimg_draw.py +2 -22
- {valetudo_map_parser-0.1.9b3 → valetudo_map_parser-0.1.9b5}/pyproject.toml +1 -1
- {valetudo_map_parser-0.1.9b3 → valetudo_map_parser-0.1.9b5}/LICENSE +0 -0
- {valetudo_map_parser-0.1.9b3 → valetudo_map_parser-0.1.9b5}/NOTICE.txt +0 -0
- {valetudo_map_parser-0.1.9b3 → valetudo_map_parser-0.1.9b5}/README.md +0 -0
- {valetudo_map_parser-0.1.9b3 → valetudo_map_parser-0.1.9b5}/SCR/valetudo_map_parser/__init__.py +8 -8
- {valetudo_map_parser-0.1.9b3 → valetudo_map_parser-0.1.9b5}/SCR/valetudo_map_parser/config/__init__.py +0 -0
- {valetudo_map_parser-0.1.9b3 → valetudo_map_parser-0.1.9b5}/SCR/valetudo_map_parser/config/auto_crop.py +0 -0
- {valetudo_map_parser-0.1.9b3 → valetudo_map_parser-0.1.9b5}/SCR/valetudo_map_parser/config/drawable.py +1 -1
- {valetudo_map_parser-0.1.9b3 → valetudo_map_parser-0.1.9b5}/SCR/valetudo_map_parser/config/types.py +2 -2
- {valetudo_map_parser-0.1.9b3 → valetudo_map_parser-0.1.9b5}/SCR/valetudo_map_parser/py.typed +0 -0
@@ -5,10 +5,10 @@ Version: v2024.08.2
|
|
5
5
|
- Additional functions are to get in our image_handler the images datas.
|
6
6
|
"""
|
7
7
|
|
8
|
-
from enum import Enum
|
9
8
|
import math
|
10
9
|
import struct
|
11
|
-
from
|
10
|
+
from enum import Enum
|
11
|
+
from typing import Any, Callable, Dict, List, Optional, TypeVar
|
12
12
|
|
13
13
|
_CallableT = TypeVar("_CallableT", bound=Callable[..., Any])
|
14
14
|
|
@@ -387,6 +387,11 @@ class RRMapParser:
|
|
387
387
|
for zone in blocks[RRMapParser.Types.FORBIDDEN_MOP_ZONES.value]
|
388
388
|
]
|
389
389
|
|
390
|
+
if RRMapParser.Types.GOTO_TARGET.value in blocks:
|
391
|
+
parsed_map_data["goto_target"] = blocks[
|
392
|
+
RRMapParser.Types.GOTO_TARGET.value
|
393
|
+
]["position"]
|
394
|
+
|
390
395
|
def parse_data(
|
391
396
|
self, payload: Optional[bytes] = None, pixels: bool = False
|
392
397
|
) -> Optional[Dict[str, Any]]:
|
{valetudo_map_parser-0.1.9b3 → valetudo_map_parser-0.1.9b5}/SCR/valetudo_map_parser/config/shared.py
RENAMED
@@ -9,7 +9,9 @@ import logging
|
|
9
9
|
|
10
10
|
from .types import (
|
11
11
|
ATTR_CALIBRATION_POINTS,
|
12
|
+
ATTR_CAMERA_MODE,
|
12
13
|
ATTR_MARGINS,
|
14
|
+
ATTR_OBSTACLES,
|
13
15
|
ATTR_POINTS,
|
14
16
|
ATTR_ROOMS,
|
15
17
|
ATTR_ROTATE,
|
@@ -19,8 +21,6 @@ from .types import (
|
|
19
21
|
ATTR_VACUUM_POSITION,
|
20
22
|
ATTR_VACUUM_STATUS,
|
21
23
|
ATTR_ZONES,
|
22
|
-
ATTR_CAMERA_MODE,
|
23
|
-
ATTR_OBSTACLES,
|
24
24
|
CONF_ASPECT_RATIO,
|
25
25
|
CONF_AUTO_ZOOM,
|
26
26
|
CONF_OFFSET_BOTTOM,
|
@@ -35,8 +35,8 @@ from .types import (
|
|
35
35
|
CONF_ZOOM_LOCK_RATIO,
|
36
36
|
DEFAULT_VALUES,
|
37
37
|
CameraModes,
|
38
|
+
Colors,
|
38
39
|
)
|
39
|
-
from .types import Colors
|
40
40
|
|
41
41
|
_LOGGER = logging.getLogger(__name__)
|
42
42
|
|
@@ -0,0 +1,132 @@
|
|
1
|
+
"""Utility code for the valetudo map parser."""
|
2
|
+
|
3
|
+
import hashlib
|
4
|
+
import json
|
5
|
+
from logging import getLogger
|
6
|
+
|
7
|
+
from PIL import ImageOps
|
8
|
+
|
9
|
+
from ..images_utils import ImageUtils as ImUtils
|
10
|
+
from .types import ChargerPosition, ImageSize, NumpyArray, RobotPosition
|
11
|
+
|
12
|
+
_LOGGER = getLogger(__name__)
|
13
|
+
|
14
|
+
|
15
|
+
class BaseHandler:
|
16
|
+
"""Avoid Code duplication"""
|
17
|
+
|
18
|
+
def __init__(self):
|
19
|
+
self.file_name = None
|
20
|
+
self.img_size = None
|
21
|
+
self.json_data = None
|
22
|
+
self.json_id = None
|
23
|
+
self.path_pixels = None
|
24
|
+
self.robot_in_room = None
|
25
|
+
self.robot_pos = None
|
26
|
+
self.room_propriety = None
|
27
|
+
self.rooms_pos = None
|
28
|
+
self.charger_pos = None
|
29
|
+
self.frame_number = 0
|
30
|
+
self.max_frames = 1024
|
31
|
+
self.crop_img_size = [0, 0]
|
32
|
+
self.imu = ImUtils(self) # Image Utils
|
33
|
+
|
34
|
+
def get_frame_number(self) -> int:
|
35
|
+
"""Return the frame number of the image."""
|
36
|
+
return self.frame_number
|
37
|
+
|
38
|
+
def get_robot_position(self) -> RobotPosition | None:
|
39
|
+
"""Return the robot position."""
|
40
|
+
return self.robot_pos
|
41
|
+
|
42
|
+
def get_charger_position(self) -> ChargerPosition | None:
|
43
|
+
"""Return the charger position."""
|
44
|
+
return self.charger_pos
|
45
|
+
|
46
|
+
def get_img_size(self) -> ImageSize | None:
|
47
|
+
"""Return the size of the image."""
|
48
|
+
return self.img_size
|
49
|
+
|
50
|
+
def get_json_id(self) -> str | None:
|
51
|
+
"""Return the JSON ID from the image."""
|
52
|
+
return self.json_id
|
53
|
+
|
54
|
+
async def async_resize_image(
|
55
|
+
self, pil_img, width, height, aspect_ratio=None, is_rand=False
|
56
|
+
):
|
57
|
+
"""Resize the image to the given dimensions and aspect ratio."""
|
58
|
+
if aspect_ratio:
|
59
|
+
wsf, hsf = [int(x) for x in aspect_ratio.split(",")]
|
60
|
+
if wsf == 0 or hsf == 0:
|
61
|
+
return pil_img
|
62
|
+
new_aspect_ratio = wsf / hsf
|
63
|
+
if width / height > new_aspect_ratio:
|
64
|
+
new_width = int(pil_img.height * new_aspect_ratio)
|
65
|
+
new_height = pil_img.height
|
66
|
+
else:
|
67
|
+
new_width = pil_img.width
|
68
|
+
new_height = int(pil_img.width / new_aspect_ratio)
|
69
|
+
_LOGGER.debug(
|
70
|
+
"%s: Image Aspect Ratio: %s, %s",
|
71
|
+
self.file_name,
|
72
|
+
str(wsf),
|
73
|
+
str(hsf),
|
74
|
+
)
|
75
|
+
(
|
76
|
+
self.crop_img_size[0],
|
77
|
+
self.crop_img_size[1],
|
78
|
+
) = await self.async_map_coordinates_offset(
|
79
|
+
wsf, hsf, new_width, new_height, is_rand
|
80
|
+
)
|
81
|
+
return ImageOps.pad(pil_img, (new_width, new_height))
|
82
|
+
return ImageOps.pad(pil_img, (width, height))
|
83
|
+
|
84
|
+
async def async_map_coordinates_offset(
|
85
|
+
self, wsf: int, hsf: int, width: int, height: int, rand256: bool = False
|
86
|
+
) -> tuple[int, int]:
|
87
|
+
"""
|
88
|
+
Offset the coordinates to the map.
|
89
|
+
"""
|
90
|
+
|
91
|
+
if wsf == 1 and hsf == 1:
|
92
|
+
self.imu.set_image_offset_ratio_1_1(width, height, rand256)
|
93
|
+
elif wsf == 2 and hsf == 1:
|
94
|
+
self.imu.set_image_offset_ratio_2_1(width, height, rand256)
|
95
|
+
elif wsf == 3 and hsf == 2:
|
96
|
+
self.imu.set_image_offset_ratio_3_2(width, height, rand256)
|
97
|
+
elif wsf == 5 and hsf == 4:
|
98
|
+
self.imu.set_image_offset_ratio_5_4(width, height, rand256)
|
99
|
+
elif wsf == 9 and hsf == 16:
|
100
|
+
self.imu.set_image_offset_ratio_9_16(width, height, rand256=True)
|
101
|
+
elif wsf == 16 and hsf == 9:
|
102
|
+
self.imu.set_image_offset_ratio_16_9(width, height, rand256=True)
|
103
|
+
return width, height
|
104
|
+
|
105
|
+
@staticmethod
|
106
|
+
async def calculate_array_hash(layers: dict, active: list[int] = None) -> str:
|
107
|
+
"""Calculate the hash of the image based on layers and active zones."""
|
108
|
+
if layers and active:
|
109
|
+
data_to_hash = {
|
110
|
+
"layers": len(layers["wall"][0]),
|
111
|
+
"active_segments": tuple(active),
|
112
|
+
}
|
113
|
+
data_json = json.dumps(data_to_hash, sort_keys=True)
|
114
|
+
return hashlib.sha256(data_json.encode()).hexdigest()
|
115
|
+
return None
|
116
|
+
|
117
|
+
@staticmethod
|
118
|
+
async def async_copy_array(original_array: NumpyArray) -> NumpyArray:
|
119
|
+
"""Copy the array."""
|
120
|
+
return NumpyArray.copy(original_array)
|
121
|
+
|
122
|
+
def get_map_points(self) -> dict:
|
123
|
+
"""Return the map points."""
|
124
|
+
return [
|
125
|
+
{"x": 0, "y": 0}, # Top-left corner 0
|
126
|
+
{"x": self.crop_img_size[0], "y": 0}, # Top-right corner 1
|
127
|
+
{
|
128
|
+
"x": self.crop_img_size[0],
|
129
|
+
"y": self.crop_img_size[1],
|
130
|
+
}, # Bottom-right corner 2
|
131
|
+
{"x": 0, "y": self.crop_img_size[1]}, # Bottom-left corner (optional) 3
|
132
|
+
]
|
{valetudo_map_parser-0.1.9b3 → valetudo_map_parser-0.1.9b5}/SCR/valetudo_map_parser/hypfer_draw.py
RENAMED
@@ -6,16 +6,9 @@ Version: 2024.07.2
|
|
6
6
|
|
7
7
|
from __future__ import annotations
|
8
8
|
|
9
|
-
import hashlib
|
10
|
-
import json
|
11
9
|
import logging
|
12
10
|
|
13
|
-
from .config.types import
|
14
|
-
Color,
|
15
|
-
JsonType,
|
16
|
-
NumpyArray,
|
17
|
-
RobotPosition,
|
18
|
-
)
|
11
|
+
from .config.types import Color, JsonType, NumpyArray, RobotPosition
|
19
12
|
|
20
13
|
_LOGGER = logging.getLogger(__name__)
|
21
14
|
|
@@ -288,25 +281,6 @@ class ImageDraw:
|
|
288
281
|
_LOGGER.info("%s: Got the points in the json.", self.file_name)
|
289
282
|
return entity_dict
|
290
283
|
|
291
|
-
@staticmethod
|
292
|
-
async def async_copy_array(original_array: NumpyArray) -> NumpyArray:
|
293
|
-
"""Copy the array."""
|
294
|
-
return NumpyArray.copy(original_array)
|
295
|
-
|
296
|
-
async def calculate_array_hash(self, layers: dict, active: list[int] = None) -> str:
|
297
|
-
"""Calculate the hash of the image based on the layers and active segments walls."""
|
298
|
-
self.img_h.active_zones = active
|
299
|
-
if layers and active:
|
300
|
-
data_to_hash = {
|
301
|
-
"layers": len(layers["wall"][0]),
|
302
|
-
"active_segments": tuple(active),
|
303
|
-
}
|
304
|
-
data_json = json.dumps(data_to_hash, sort_keys=True)
|
305
|
-
hash_value = hashlib.sha256(data_json.encode()).hexdigest()
|
306
|
-
else:
|
307
|
-
hash_value = None
|
308
|
-
return hash_value
|
309
|
-
|
310
284
|
async def async_get_robot_in_room(
|
311
285
|
self, robot_y: int = 0, robot_x: int = 0, angle: float = 0.0
|
312
286
|
) -> RobotPosition:
|
@@ -12,58 +12,35 @@ import logging
|
|
12
12
|
|
13
13
|
from PIL import Image
|
14
14
|
|
15
|
-
from .config.types import (
|
16
|
-
CalibrationPoints,
|
17
|
-
ChargerPosition,
|
18
|
-
ImageSize,
|
19
|
-
RobotPosition,
|
20
|
-
RoomsProperties,
|
21
|
-
)
|
22
15
|
from .config.auto_crop import AutoCrop
|
23
16
|
from .config.drawable import Drawable
|
24
17
|
from .config.shared import CameraShared
|
18
|
+
from .config.types import COLORS, CalibrationPoints, Colors, RoomsProperties
|
19
|
+
from .config.utils import BaseHandler
|
20
|
+
from .hypfer_draw import ImageDraw as ImDraw
|
25
21
|
from .map_data import ImageData
|
26
|
-
from .images_utils import (
|
27
|
-
ImageUtils as ImUtils,
|
28
|
-
resize_to_aspect_ratio,
|
29
|
-
)
|
30
|
-
from .hypfer_draw import (
|
31
|
-
ImageDraw as ImDraw,
|
32
|
-
)
|
33
|
-
from .config.colors import ColorsManagment, SupportedColor
|
34
22
|
|
35
23
|
_LOGGER = logging.getLogger(__name__)
|
36
24
|
|
37
25
|
|
38
|
-
class HypferMapImageHandler:
|
26
|
+
class HypferMapImageHandler(BaseHandler):
|
39
27
|
"""Map Image Handler Class.
|
40
28
|
This class is used to handle the image data and the drawing of the map."""
|
41
29
|
|
42
30
|
def __init__(self, shared_data: CameraShared):
|
43
31
|
"""Initialize the Map Image Handler."""
|
32
|
+
super().__init__()
|
44
33
|
self.shared = shared_data # camera shared data
|
45
|
-
self.file_name = self.shared.file_name # file name of the vacuum.
|
46
34
|
self.auto_crop = None # auto crop data to be calculate once.
|
47
35
|
self.calibration_data = None # camera shared data.
|
48
|
-
self.charger_pos = None # vacuum data charger position.
|
49
36
|
self.crop_area = None # module shared for calibration data.
|
50
|
-
self.crop_img_size = None # size of the image cropped calibration data.
|
51
37
|
self.data = ImageData # imported Image Data Module.
|
52
38
|
self.draw = Drawable # imported Drawing utilities
|
53
39
|
self.go_to = None # vacuum go to data
|
54
40
|
self.img_hash = None # hash of the image calculated to check differences.
|
55
41
|
self.img_base_layer = None # numpy array store the map base layer.
|
56
|
-
self.img_size = None # size of the created image
|
57
|
-
self.json_data = None # local stored and shared json data.
|
58
|
-
self.json_id = None # grabbed data of the vacuum image id.
|
59
|
-
self.path_pixels = None # vacuum path datas.
|
60
|
-
self.robot_in_room = None # vacuum room position.
|
61
|
-
self.robot_pos = None # vacuum coordinates.
|
62
|
-
self.room_propriety = None # vacuum segments data.
|
63
|
-
self.rooms_pos = None # vacuum room coordinates / name list.
|
64
42
|
self.active_zones = None # vacuum active zones.
|
65
43
|
self.frame_number = 0 # frame number of the image.
|
66
|
-
self.max_frames = 1024
|
67
44
|
self.zooming = False # zooming the image.
|
68
45
|
self.svg_wait = False # SVG image creation wait.
|
69
46
|
self.trim_down = 0 # memory stored trims calculated once.
|
@@ -77,11 +54,9 @@ class HypferMapImageHandler:
|
|
77
54
|
self.offset_x = 0 # offset x for the aspect ratio.
|
78
55
|
self.offset_y = 0 # offset y for the aspect ratio.
|
79
56
|
self.imd = ImDraw(self)
|
80
|
-
self.imu = ImUtils(self)
|
81
57
|
self.ac = AutoCrop(self)
|
82
|
-
self.colors_manager = ColorsManagment({})
|
83
|
-
self.rooms_colors = self.colors_manager.get_rooms_colors()
|
84
58
|
self.color_grey = (128, 128, 128, 255)
|
59
|
+
self.file_name = self.shared.file_name # file name of the vacuum.
|
85
60
|
|
86
61
|
async def async_extract_room_properties(self, json_data) -> RoomsProperties:
|
87
62
|
"""Extract room properties from the JSON data."""
|
@@ -132,23 +107,6 @@ class HypferMapImageHandler:
|
|
132
107
|
self.rooms_pos = None
|
133
108
|
return room_properties
|
134
109
|
|
135
|
-
async def _async_initialize_colors(self):
|
136
|
-
"""Initialize and return all required colors."""
|
137
|
-
return {
|
138
|
-
"color_wall": self.colors_manager.get_colour(SupportedColor.WALLS),
|
139
|
-
"color_no_go": self.colors_manager.get_colour(SupportedColor.NO_GO),
|
140
|
-
"color_go_to": self.colors_manager.get_colour(SupportedColor.GO_TO),
|
141
|
-
"color_robot": self.colors_manager.get_colour(SupportedColor.ROBOT),
|
142
|
-
"color_charger": self.colors_manager.get_colour(SupportedColor.CHARGER),
|
143
|
-
"color_move": self.colors_manager.get_colour(SupportedColor.PATH),
|
144
|
-
"color_background": self.colors_manager.get_colour(
|
145
|
-
SupportedColor.MAP_BACKGROUND
|
146
|
-
),
|
147
|
-
"color_zone_clean": self.colors_manager.get_colour(
|
148
|
-
SupportedColor.ZONE_CLEAN
|
149
|
-
),
|
150
|
-
}
|
151
|
-
|
152
110
|
# noinspection PyUnresolvedReferences,PyUnboundLocalVariable
|
153
111
|
async def async_get_image_from_json(
|
154
112
|
self,
|
@@ -161,7 +119,9 @@ class HypferMapImageHandler:
|
|
161
119
|
@return Image.Image: The image to display.
|
162
120
|
"""
|
163
121
|
# Initialize the colors.
|
164
|
-
colors =
|
122
|
+
colors: Colors = {
|
123
|
+
name: self.shared.user_colors[idx] for idx, name in enumerate(COLORS)
|
124
|
+
}
|
165
125
|
# Check if the JSON data is not None else process the image.
|
166
126
|
try:
|
167
127
|
if m_json is not None:
|
@@ -190,12 +150,12 @@ class HypferMapImageHandler:
|
|
190
150
|
# Get the pixels size and layers from the JSON data
|
191
151
|
pixel_size = int(m_json["pixelSize"])
|
192
152
|
layers, active = self.data.find_layers(m_json["layers"], {}, [])
|
193
|
-
new_frame_hash = await self.
|
153
|
+
new_frame_hash = await self.calculate_array_hash(layers, active)
|
194
154
|
if self.frame_number == 0:
|
195
155
|
self.img_hash = new_frame_hash
|
196
156
|
# empty image
|
197
157
|
img_np_array = await self.draw.create_empty_image(
|
198
|
-
size_x, size_y, colors["
|
158
|
+
size_x, size_y, colors["background"]
|
199
159
|
)
|
200
160
|
# overlapping layers and segments
|
201
161
|
for layer_type, compressed_pixels_list in layers.items():
|
@@ -203,21 +163,21 @@ class HypferMapImageHandler:
|
|
203
163
|
img_np_array,
|
204
164
|
compressed_pixels_list,
|
205
165
|
layer_type,
|
206
|
-
colors["
|
207
|
-
colors["
|
166
|
+
colors["wall"],
|
167
|
+
colors["zone_clean"],
|
208
168
|
pixel_size,
|
209
169
|
)
|
210
170
|
# Draw the virtual walls if any.
|
211
171
|
img_np_array = await self.imd.async_draw_virtual_walls(
|
212
|
-
m_json, img_np_array, colors["
|
172
|
+
m_json, img_np_array, colors["no_go"]
|
213
173
|
)
|
214
174
|
# Draw charger.
|
215
175
|
img_np_array = await self.imd.async_draw_charger(
|
216
|
-
img_np_array, entity_dict, colors["
|
176
|
+
img_np_array, entity_dict, colors["charger"]
|
217
177
|
)
|
218
178
|
# Draw obstacles if any.
|
219
179
|
img_np_array = await self.imd.async_draw_obstacle(
|
220
|
-
img_np_array, entity_dict, colors["
|
180
|
+
img_np_array, entity_dict, colors["no_go"]
|
221
181
|
)
|
222
182
|
# Robot and rooms position
|
223
183
|
if (room_id > 0) and not self.room_propriety:
|
@@ -232,7 +192,7 @@ class HypferMapImageHandler:
|
|
232
192
|
)
|
233
193
|
_LOGGER.info("%s: Completed base Layers", self.file_name)
|
234
194
|
# Copy the new array in base layer.
|
235
|
-
self.img_base_layer = await self.
|
195
|
+
self.img_base_layer = await self.async_copy_array(img_np_array)
|
236
196
|
self.shared.frame_number = self.frame_number
|
237
197
|
self.frame_number += 1
|
238
198
|
if (self.frame_number >= self.max_frames) or (
|
@@ -246,18 +206,18 @@ class HypferMapImageHandler:
|
|
246
206
|
str(self.frame_number),
|
247
207
|
)
|
248
208
|
# Copy the base layer to the new image.
|
249
|
-
img_np_array = await self.
|
209
|
+
img_np_array = await self.async_copy_array(self.img_base_layer)
|
250
210
|
# All below will be drawn at each frame.
|
251
211
|
# Draw zones if any.
|
252
|
-
img_np_array = await self.
|
212
|
+
img_np_array = await self.async_draw_zones(
|
253
213
|
m_json,
|
254
214
|
img_np_array,
|
255
|
-
colors["
|
256
|
-
colors["
|
215
|
+
colors["zone_clean"],
|
216
|
+
colors["no_go"],
|
257
217
|
)
|
258
218
|
# Draw the go_to target flag.
|
259
219
|
img_np_array = await self.imd.draw_go_to_flag(
|
260
|
-
img_np_array, entity_dict, colors["
|
220
|
+
img_np_array, entity_dict, colors["go_to"]
|
261
221
|
)
|
262
222
|
# Draw path prediction and paths.
|
263
223
|
img_np_array = await self.imd.async_draw_paths(
|
@@ -304,15 +264,8 @@ class HypferMapImageHandler:
|
|
304
264
|
):
|
305
265
|
width = self.shared.image_ref_width
|
306
266
|
height = self.shared.image_ref_height
|
307
|
-
(
|
308
|
-
|
309
|
-
self.crop_img_size,
|
310
|
-
) = await resize_to_aspect_ratio(
|
311
|
-
pil_img,
|
312
|
-
width,
|
313
|
-
height,
|
314
|
-
self.shared.image_aspect_ratio,
|
315
|
-
self.async_map_coordinates_offset,
|
267
|
+
resized_image = await self.async_resize_image(
|
268
|
+
pil_img, width, height, self.shared.image_aspect_ratio
|
316
269
|
)
|
317
270
|
return resized_image
|
318
271
|
_LOGGER.debug("%s: Frame Completed.", self.file_name)
|
@@ -326,26 +279,6 @@ class HypferMapImageHandler:
|
|
326
279
|
)
|
327
280
|
return None
|
328
281
|
|
329
|
-
def get_frame_number(self) -> int:
|
330
|
-
"""Return the frame number of the image."""
|
331
|
-
return self.frame_number
|
332
|
-
|
333
|
-
def get_robot_position(self) -> RobotPosition | None:
|
334
|
-
"""Return the robot position."""
|
335
|
-
return self.robot_pos
|
336
|
-
|
337
|
-
def get_charger_position(self) -> ChargerPosition | None:
|
338
|
-
"""Return the charger position."""
|
339
|
-
return self.charger_pos
|
340
|
-
|
341
|
-
def get_img_size(self) -> ImageSize | None:
|
342
|
-
"""Return the size of the image."""
|
343
|
-
return self.img_size
|
344
|
-
|
345
|
-
def get_json_id(self) -> str | None:
|
346
|
-
"""Return the JSON ID from the image."""
|
347
|
-
return self.json_id
|
348
|
-
|
349
282
|
async def async_get_rooms_attributes(self) -> RoomsProperties:
|
350
283
|
"""Get the rooms attributes from the JSON data.
|
351
284
|
:return: The rooms attribute's."""
|
@@ -368,15 +301,7 @@ class HypferMapImageHandler:
|
|
368
301
|
_LOGGER.info("Getting %s Calibrations points.", self.file_name)
|
369
302
|
|
370
303
|
# Define the map points (fixed)
|
371
|
-
map_points =
|
372
|
-
{"x": 0, "y": 0}, # Top-left corner 0
|
373
|
-
{"x": self.crop_img_size[0], "y": 0}, # Top-right corner 1
|
374
|
-
{
|
375
|
-
"x": self.crop_img_size[0],
|
376
|
-
"y": self.crop_img_size[1],
|
377
|
-
}, # Bottom-right corner 2
|
378
|
-
{"x": 0, "y": self.crop_img_size[1]}, # Bottom-left corner (optional) 3
|
379
|
-
]
|
304
|
+
map_points = self.get_map_points()
|
380
305
|
# Calculate the calibration points in the vacuum coordinate system
|
381
306
|
vacuum_points = self.imu.get_vacuum_points(rotation_angle)
|
382
307
|
|
@@ -386,33 +311,3 @@ class HypferMapImageHandler:
|
|
386
311
|
calibration_data.append(calibration_point)
|
387
312
|
del vacuum_points, map_points, calibration_point, rotation_angle # free memory.
|
388
313
|
return calibration_data
|
389
|
-
|
390
|
-
async def async_map_coordinates_offset(
|
391
|
-
self, wsf: int, hsf: int, width: int, height: int
|
392
|
-
) -> tuple[int, int]:
|
393
|
-
"""
|
394
|
-
Offset the coordinates to the map.
|
395
|
-
:param wsf: Width scale factor.
|
396
|
-
:param hsf: Height scale factor.
|
397
|
-
:param width: Width of the image.
|
398
|
-
:param height: Height of the image.
|
399
|
-
:return: A tuple containing the adjusted (width, height) values
|
400
|
-
:raises ValueError: If any input parameters are negative
|
401
|
-
"""
|
402
|
-
|
403
|
-
if any(x < 0 for x in (wsf, hsf, width, height)):
|
404
|
-
raise ValueError("All parameters must be positive integers")
|
405
|
-
|
406
|
-
if wsf == 1 and hsf == 1:
|
407
|
-
self.imu.set_image_offset_ratio_1_1(width, height)
|
408
|
-
elif wsf == 2 and hsf == 1:
|
409
|
-
self.imu.set_image_offset_ratio_2_1(width, height)
|
410
|
-
elif wsf == 3 and hsf == 2:
|
411
|
-
self.imu.set_image_offset_ratio_3_2(width, height)
|
412
|
-
elif wsf == 5 and hsf == 4:
|
413
|
-
self.imu.set_image_offset_ratio_5_4(width, height)
|
414
|
-
elif wsf == 9 and hsf == 16:
|
415
|
-
self.imu.set_image_offset_ratio_9_16(width, height)
|
416
|
-
elif wsf == 16 and hsf == 9:
|
417
|
-
self.imu.set_image_offset_ratio_16_9(width, height)
|
418
|
-
return width, height
|
{valetudo_map_parser-0.1.9b3 → valetudo_map_parser-0.1.9b5}/SCR/valetudo_map_parser/images_utils.py
RENAMED
@@ -8,8 +8,6 @@ from __future__ import annotations
|
|
8
8
|
|
9
9
|
import logging
|
10
10
|
|
11
|
-
from PIL import Image, ImageOps
|
12
|
-
|
13
11
|
_LOGGER = logging.getLogger(__name__)
|
14
12
|
|
15
13
|
|
@@ -295,7 +293,7 @@ class ImageUtils:
|
|
295
293
|
)
|
296
294
|
|
297
295
|
async def async_zone_propriety(self, zones_data) -> dict:
|
298
|
-
"""Get the zone
|
296
|
+
"""Get the zone propriety"""
|
299
297
|
zone_properties = {}
|
300
298
|
id_count = 1
|
301
299
|
for zone in zones_data:
|
@@ -316,7 +314,7 @@ class ImageUtils:
|
|
316
314
|
return zone_properties
|
317
315
|
|
318
316
|
async def async_points_propriety(self, points_data) -> dict:
|
319
|
-
"""Get the point
|
317
|
+
"""Get the point propriety"""
|
320
318
|
point_properties = {}
|
321
319
|
id_count = 1
|
322
320
|
for point in points_data:
|
@@ -335,64 +333,3 @@ class ImageUtils:
|
|
335
333
|
if id_count > 1:
|
336
334
|
_LOGGER.debug("%s: Point Properties updated.", self.file_name)
|
337
335
|
return point_properties
|
338
|
-
|
339
|
-
|
340
|
-
async def resize_to_aspect_ratio(
|
341
|
-
pil_img: Image.Image,
|
342
|
-
ref_width: int,
|
343
|
-
ref_height: int,
|
344
|
-
aspect_ratio: str = "None",
|
345
|
-
async_map_coordinates_offset=None,
|
346
|
-
) -> tuple:
|
347
|
-
"""
|
348
|
-
Resize the image to match the given aspect ratio, maintaining the camera's aspect ratio.
|
349
|
-
|
350
|
-
Args:
|
351
|
-
pil_img (PIL.Image): The input image to resize.
|
352
|
-
ref_width (int): The reference width for the image.
|
353
|
-
ref_height (int): The reference height for the image.
|
354
|
-
aspect_ratio (str): Aspect ratio in the format "width,height" or "None" for default.
|
355
|
-
async_map_coordinates_offset (callable): Async function to compute coordinate offsets.
|
356
|
-
|
357
|
-
Returns:
|
358
|
-
tuple: A resized image and crop image size as a tuple (PIL.Image, list).
|
359
|
-
"""
|
360
|
-
crop_img_size = [0, 0]
|
361
|
-
|
362
|
-
if aspect_ratio and aspect_ratio != "None":
|
363
|
-
try:
|
364
|
-
# Parse aspect ratio (e.g., "16,9")
|
365
|
-
wsf, hsf = [int(x) for x in aspect_ratio.split(",")]
|
366
|
-
new_aspect_ratio = wsf / hsf
|
367
|
-
|
368
|
-
# Calculate current aspect ratio
|
369
|
-
current_aspect_ratio = ref_width / ref_height
|
370
|
-
|
371
|
-
# Resize based on aspect ratio comparison
|
372
|
-
if current_aspect_ratio > new_aspect_ratio:
|
373
|
-
new_width = int(pil_img.height * new_aspect_ratio)
|
374
|
-
new_height = pil_img.height
|
375
|
-
else:
|
376
|
-
new_width = pil_img.width
|
377
|
-
new_height = int(pil_img.width / new_aspect_ratio)
|
378
|
-
|
379
|
-
# Resize image using padding
|
380
|
-
resized_img = ImageOps.pad(pil_img, (new_width, new_height))
|
381
|
-
|
382
|
-
# Compute crop image size if mapping offset function is provided
|
383
|
-
if async_map_coordinates_offset:
|
384
|
-
(
|
385
|
-
crop_img_size[0],
|
386
|
-
crop_img_size[1],
|
387
|
-
) = await async_map_coordinates_offset(wsf, hsf, new_width, new_height)
|
388
|
-
|
389
|
-
return resized_img, crop_img_size
|
390
|
-
|
391
|
-
except Exception as e:
|
392
|
-
_LOGGER.debug(
|
393
|
-
"Error resizing image with aspect ratio: %s. %s", aspect_ratio, e
|
394
|
-
)
|
395
|
-
raise ValueError("Error resizing image with aspect ratio") from e
|
396
|
-
|
397
|
-
# If no aspect ratio is provided, return the original image and default crop size
|
398
|
-
return pil_img, crop_img_size
|
@@ -9,13 +9,22 @@ from __future__ import annotations
|
|
9
9
|
|
10
10
|
import logging
|
11
11
|
import uuid
|
12
|
-
|
13
|
-
from PIL import Image, ImageOps
|
14
12
|
from typing import Any
|
15
|
-
|
16
|
-
from
|
13
|
+
|
14
|
+
from PIL import Image
|
15
|
+
|
17
16
|
from .config.auto_crop import AutoCrop
|
18
|
-
from .
|
17
|
+
from .config.types import (
|
18
|
+
COLORS,
|
19
|
+
DEFAULT_IMAGE_SIZE,
|
20
|
+
DEFAULT_PIXEL_SIZE,
|
21
|
+
Colors,
|
22
|
+
JsonType,
|
23
|
+
PilPNG,
|
24
|
+
RobotPosition,
|
25
|
+
RoomsProperties,
|
26
|
+
)
|
27
|
+
from .config.utils import BaseHandler
|
19
28
|
from .map_data import RandImageData
|
20
29
|
from .reimg_draw import ImageDraw
|
21
30
|
|
@@ -23,33 +32,23 @@ _LOGGER = logging.getLogger(__name__)
|
|
23
32
|
|
24
33
|
|
25
34
|
# noinspection PyTypeChecker
|
26
|
-
class ReImageHandler:
|
35
|
+
class ReImageHandler(BaseHandler):
|
27
36
|
"""
|
28
37
|
Image Handler for Valetudo Re Vacuums.
|
29
38
|
"""
|
30
39
|
|
31
40
|
def __init__(self, camera_shared):
|
41
|
+
super().__init__()
|
32
42
|
self.auto_crop = None # Auto crop flag
|
33
43
|
self.segment_data = None # Segment data
|
34
44
|
self.outlines = None # Outlines data
|
35
45
|
self.calibration_data = None # Calibration data
|
36
|
-
self.charger_pos = None # Charger position
|
37
46
|
self.crop_area = None # Crop area
|
38
|
-
self.crop_img_size = [] # Crop image size
|
39
47
|
self.data = RandImageData # Image Data
|
40
|
-
self.frame_number = 0 # Image Frame number
|
41
|
-
self.max_frames = 1024
|
42
48
|
self.go_to = None # Go to position data
|
43
49
|
self.img_base_layer = None # Base image layer
|
44
50
|
self.img_rotate = camera_shared.image_rotate # Image rotation
|
45
|
-
self.img_size = None # Image size
|
46
|
-
self.json_data = None # Json data
|
47
|
-
self.json_id = None # Json id
|
48
|
-
self.path_pixels = None # Path pixels data
|
49
|
-
self.robot_in_room = None # Robot in room data
|
50
|
-
self.robot_pos = None # Robot position
|
51
51
|
self.room_propriety = None # Room propriety data
|
52
|
-
self.rooms_pos = None # Rooms position data
|
53
52
|
self.shared = camera_shared # Shared data
|
54
53
|
self.active_zones = None # Active zones
|
55
54
|
self.trim_down = None # Trim down
|
@@ -65,7 +64,6 @@ class ReImageHandler:
|
|
65
64
|
self.offset_left = self.shared.offset_left # offset left
|
66
65
|
self.offset_right = self.shared.offset_right # offset right
|
67
66
|
self.imd = ImageDraw(self) # Image Draw
|
68
|
-
self.imu = ImUtils(self) # Image Utils
|
69
67
|
self.ac = AutoCrop(self)
|
70
68
|
|
71
69
|
async def extract_room_properties(
|
@@ -177,7 +175,7 @@ class ReImageHandler:
|
|
177
175
|
|
178
176
|
# Increment frame number
|
179
177
|
self.frame_number += 1
|
180
|
-
img_np_array = await self.
|
178
|
+
img_np_array = await self.async_copy_array(self.img_base_layer)
|
181
179
|
_LOGGER.debug(
|
182
180
|
"%s: Frame number %s", self.file_name, str(self.frame_number)
|
183
181
|
)
|
@@ -231,7 +229,7 @@ class ReImageHandler:
|
|
231
229
|
(robot_position[1] * 10),
|
232
230
|
robot_position_angle,
|
233
231
|
)
|
234
|
-
self.img_base_layer = await self.
|
232
|
+
self.img_base_layer = await self.async_copy_array(img_np_array)
|
235
233
|
return self.img_base_layer, robot_position, robot_position_angle
|
236
234
|
|
237
235
|
async def _draw_map_elements(
|
@@ -276,59 +274,12 @@ class ReImageHandler:
|
|
276
274
|
width = self.shared.image_ref_width
|
277
275
|
height = self.shared.image_ref_height
|
278
276
|
if self.shared.image_aspect_ratio != "None":
|
279
|
-
|
280
|
-
|
281
|
-
if wsf == 0 or hsf == 0:
|
282
|
-
return pil_img
|
283
|
-
new_aspect_ratio = wsf / hsf
|
284
|
-
aspect_ratio = width / height
|
285
|
-
if aspect_ratio > new_aspect_ratio:
|
286
|
-
new_width = int(pil_img.height * new_aspect_ratio)
|
287
|
-
new_height = pil_img.height
|
288
|
-
else:
|
289
|
-
new_width = pil_img.width
|
290
|
-
new_height = int(pil_img.width / new_aspect_ratio)
|
291
|
-
|
292
|
-
resized = ImageOps.pad(pil_img, (new_width, new_height))
|
293
|
-
(
|
294
|
-
self.crop_img_size[0],
|
295
|
-
self.crop_img_size[1],
|
296
|
-
) = await self.async_map_coordinates_offset(
|
297
|
-
wsf, hsf, new_width, new_height
|
298
|
-
)
|
299
|
-
_LOGGER.debug(
|
300
|
-
"%s: Image Aspect Ratio: %s, %s",
|
301
|
-
self.file_name,
|
302
|
-
str(wsf),
|
303
|
-
str(hsf),
|
277
|
+
pil_img = await self.async_resize_image(
|
278
|
+
pil_img, width, height, self.shared.image_aspect_ratio, True
|
304
279
|
)
|
305
|
-
_LOGGER.debug("%s: Resized Frame Completed.", self.file_name)
|
306
|
-
return resized
|
307
|
-
_LOGGER.debug("%s: Padded Frame Completed.", self.file_name)
|
308
|
-
return ImageOps.pad(pil_img, (width, height))
|
309
280
|
_LOGGER.debug("%s: Frame Completed.", self.file_name)
|
310
281
|
return pil_img
|
311
282
|
|
312
|
-
def get_frame_number(self) -> int:
|
313
|
-
"""Return the frame number."""
|
314
|
-
return self.frame_number
|
315
|
-
|
316
|
-
def get_robot_position(self) -> Any:
|
317
|
-
"""Return the robot position."""
|
318
|
-
return self.robot_pos
|
319
|
-
|
320
|
-
def get_charger_position(self) -> Any:
|
321
|
-
"""Return the charger position."""
|
322
|
-
return self.charger_pos
|
323
|
-
|
324
|
-
def get_img_size(self) -> Any:
|
325
|
-
"""Return the image size."""
|
326
|
-
return self.img_size
|
327
|
-
|
328
|
-
def get_json_id(self) -> str:
|
329
|
-
"""Return the json id."""
|
330
|
-
return self.json_id
|
331
|
-
|
332
283
|
async def get_rooms_attributes(
|
333
284
|
self, destinations: JsonType = None
|
334
285
|
) -> RoomsProperties:
|
@@ -427,15 +378,7 @@ class ReImageHandler:
|
|
427
378
|
)
|
428
379
|
|
429
380
|
# Define the map points (fixed)
|
430
|
-
map_points =
|
431
|
-
{"x": 0, "y": 0}, # Top-left corner 0
|
432
|
-
{"x": self.crop_img_size[0], "y": 0}, # Top-right corner 1
|
433
|
-
{
|
434
|
-
"x": self.crop_img_size[0],
|
435
|
-
"y": self.crop_img_size[1],
|
436
|
-
}, # Bottom-right corner 2
|
437
|
-
{"x": 0, "y": self.crop_img_size[1]}, # Bottom-left corner (optional) 3
|
438
|
-
]
|
381
|
+
map_points = self.get_map_points()
|
439
382
|
|
440
383
|
# Valetudo Re version need corrections of the coordinates and are implemented with *10
|
441
384
|
vacuum_points = self.imu.re_get_vacuum_points(rotation_angle)
|
@@ -446,24 +389,3 @@ class ReImageHandler:
|
|
446
389
|
self.calibration_data.append(calibration_point)
|
447
390
|
|
448
391
|
return self.calibration_data
|
449
|
-
|
450
|
-
async def async_map_coordinates_offset(
|
451
|
-
self, wsf: int, hsf: int, width: int, height: int
|
452
|
-
) -> tuple[int, int]:
|
453
|
-
"""
|
454
|
-
Offset the coordinates to the map.
|
455
|
-
"""
|
456
|
-
|
457
|
-
if wsf == 1 and hsf == 1:
|
458
|
-
self.imu.set_image_offset_ratio_1_1(width, height, rand256=True)
|
459
|
-
elif wsf == 2 and hsf == 1:
|
460
|
-
self.imu.set_image_offset_ratio_2_1(width, height, rand256=True)
|
461
|
-
elif wsf == 3 and hsf == 2:
|
462
|
-
self.imu.set_image_offset_ratio_3_2(width, height, rand256=True)
|
463
|
-
elif wsf == 5 and hsf == 4:
|
464
|
-
self.imu.set_image_offset_ratio_5_4(width, height, rand256=True)
|
465
|
-
elif wsf == 9 and hsf == 16:
|
466
|
-
self.imu.set_image_offset_ratio_9_16(width, height, rand256=True)
|
467
|
-
elif wsf == 16 and hsf == 9:
|
468
|
-
self.imu.set_image_offset_ratio_16_9(width, height, rand256=True)
|
469
|
-
return width, height
|
{valetudo_map_parser-0.1.9b3 → valetudo_map_parser-0.1.9b5}/SCR/valetudo_map_parser/reimg_draw.py
RENAMED
@@ -10,10 +10,9 @@ import hashlib
|
|
10
10
|
import json
|
11
11
|
import logging
|
12
12
|
|
13
|
-
from .config.types import Color, JsonType, NumpyArray
|
14
13
|
from .config.drawable import Drawable
|
15
|
-
from .
|
16
|
-
from .map_data import ImageData
|
14
|
+
from .config.types import Color, JsonType, NumpyArray
|
15
|
+
from .map_data import ImageData, RandImageData
|
17
16
|
|
18
17
|
_LOGGER = logging.getLogger(__name__)
|
19
18
|
|
@@ -300,25 +299,6 @@ class ImageDraw:
|
|
300
299
|
_LOGGER.info("%s: Got the points in the json.", self.file_name)
|
301
300
|
return entity_dict
|
302
301
|
|
303
|
-
@staticmethod
|
304
|
-
async def async_copy_array(original_array: NumpyArray) -> NumpyArray:
|
305
|
-
"""Copy the array."""
|
306
|
-
return NumpyArray.copy(original_array)
|
307
|
-
|
308
|
-
async def calculate_array_hash(self, layers: dict, active: list[int] = None) -> str:
|
309
|
-
"""Calculate the hash of the image based on the layers and active segments walls."""
|
310
|
-
self.img_h.active_zones = active
|
311
|
-
if layers and active:
|
312
|
-
data_to_hash = {
|
313
|
-
"layers": len(layers["wall"][0]),
|
314
|
-
"active_segments": tuple(active),
|
315
|
-
}
|
316
|
-
data_json = json.dumps(data_to_hash, sort_keys=True)
|
317
|
-
hash_value = hashlib.sha256(data_json.encode()).hexdigest()
|
318
|
-
else:
|
319
|
-
hash_value = None
|
320
|
-
return hash_value
|
321
|
-
|
322
302
|
async def async_get_robot_position(self, m_json: JsonType) -> tuple | None:
|
323
303
|
"""Get the robot position from the entity data."""
|
324
304
|
robot_pos = None
|
File without changes
|
File without changes
|
File without changes
|
{valetudo_map_parser-0.1.9b3 → valetudo_map_parser-0.1.9b5}/SCR/valetudo_map_parser/__init__.py
RENAMED
@@ -1,20 +1,20 @@
|
|
1
1
|
"""Valetudo map parser.
|
2
2
|
Version: 0.1.8"""
|
3
3
|
|
4
|
-
from .hypfer_handler import HypferMapImageHandler
|
5
|
-
from .rand25_handler import ReImageHandler
|
6
|
-
from .config.rand25_parser import RRMapParser
|
7
|
-
from .config.shared import CameraShared, CameraSharedManager
|
8
4
|
from .config.colors import ColorsManagment
|
9
5
|
from .config.drawable import Drawable
|
6
|
+
from .config.rand25_parser import RRMapParser
|
7
|
+
from .config.shared import CameraShared, CameraSharedManager
|
10
8
|
from .config.types import (
|
11
|
-
|
12
|
-
UserLanguageStore,
|
13
|
-
RoomStore,
|
9
|
+
CameraModes,
|
14
10
|
RoomsProperties,
|
11
|
+
RoomStore,
|
12
|
+
SnapshotStore,
|
15
13
|
TrimCropData,
|
16
|
-
|
14
|
+
UserLanguageStore,
|
17
15
|
)
|
16
|
+
from .hypfer_handler import HypferMapImageHandler
|
17
|
+
from .rand25_handler import ReImageHandler
|
18
18
|
|
19
19
|
__all__ = [
|
20
20
|
"HypferMapImageHandler",
|
File without changes
|
File without changes
|
{valetudo_map_parser-0.1.9b3 → valetudo_map_parser-0.1.9b5}/SCR/valetudo_map_parser/config/types.py
RENAMED
@@ -4,13 +4,13 @@ Version 0.0.1
|
|
4
4
|
"""
|
5
5
|
|
6
6
|
import asyncio
|
7
|
-
from dataclasses import dataclass
|
8
7
|
import json
|
9
8
|
import logging
|
9
|
+
from dataclasses import dataclass
|
10
10
|
from typing import Any, Dict, Tuple, Union
|
11
11
|
|
12
|
-
from PIL import Image
|
13
12
|
import numpy as np
|
13
|
+
from PIL import Image
|
14
14
|
|
15
15
|
DEFAULT_ROOMS = 1
|
16
16
|
|
{valetudo_map_parser-0.1.9b3 → valetudo_map_parser-0.1.9b5}/SCR/valetudo_map_parser/py.typed
RENAMED
File without changes
|