valetudo-map-parser 0.1.10rc7__py3-none-any.whl → 0.1.11b1__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 +8 -10
- valetudo_map_parser/config/drawable.py +91 -329
- valetudo_map_parser/config/drawable_elements.py +0 -2
- valetudo_map_parser/config/rand256_parser.py +51 -15
- valetudo_map_parser/config/shared.py +6 -3
- valetudo_map_parser/config/status_text/status_text.py +1 -0
- valetudo_map_parser/config/types.py +5 -4
- valetudo_map_parser/config/utils.py +31 -127
- valetudo_map_parser/hypfer_draw.py +0 -2
- valetudo_map_parser/hypfer_handler.py +13 -19
- valetudo_map_parser/map_data.py +15 -10
- valetudo_map_parser/rand256_handler.py +68 -38
- valetudo_map_parser/reimg_draw.py +1 -1
- valetudo_map_parser/rooms_handler.py +10 -10
- {valetudo_map_parser-0.1.10rc7.dist-info → valetudo_map_parser-0.1.11b1.dist-info}/METADATA +2 -2
- valetudo_map_parser-0.1.11b1.dist-info/RECORD +32 -0
- valetudo_map_parser/config/auto_crop.py +0 -452
- valetudo_map_parser/config/color_utils.py +0 -105
- valetudo_map_parser/config/enhanced_drawable.py +0 -324
- valetudo_map_parser-0.1.10rc7.dist-info/RECORD +0 -35
- {valetudo_map_parser-0.1.10rc7.dist-info → valetudo_map_parser-0.1.11b1.dist-info}/WHEEL +0 -0
- {valetudo_map_parser-0.1.10rc7.dist-info → valetudo_map_parser-0.1.11b1.dist-info}/licenses/LICENSE +0 -0
- {valetudo_map_parser-0.1.10rc7.dist-info → valetudo_map_parser-0.1.11b1.dist-info}/licenses/NOTICE.txt +0 -0
@@ -1,7 +1,8 @@
|
|
1
|
-
"""New Rand256 Map Parser -
|
1
|
+
"""New Rand256 Map Parser -
|
2
|
+
Based on Xiaomi/Roborock implementation with precise binary parsing."""
|
2
3
|
|
3
|
-
import struct
|
4
4
|
import math
|
5
|
+
import struct
|
5
6
|
from enum import Enum
|
6
7
|
from typing import Any, Dict, List, Optional
|
7
8
|
|
@@ -78,6 +79,7 @@ class RRMapParser:
|
|
78
79
|
|
79
80
|
@staticmethod
|
80
81
|
def _parse_carpet_map(data: bytes) -> set[int]:
|
82
|
+
"""Parse carpet map using Xiaomi method."""
|
81
83
|
carpet_map = set()
|
82
84
|
|
83
85
|
for i, v in enumerate(data):
|
@@ -87,6 +89,7 @@ class RRMapParser:
|
|
87
89
|
|
88
90
|
@staticmethod
|
89
91
|
def _parse_area(header: bytes, data: bytes) -> list:
|
92
|
+
"""Parse area using Xiaomi method."""
|
90
93
|
area_pairs = RRMapParser._get_int16(header, 0x08)
|
91
94
|
areas = []
|
92
95
|
for area_start in range(0, area_pairs * 16, 16):
|
@@ -114,6 +117,7 @@ class RRMapParser:
|
|
114
117
|
|
115
118
|
@staticmethod
|
116
119
|
def _parse_zones(data: bytes, header: bytes) -> list:
|
120
|
+
"""Parse zones using Xiaomi method."""
|
117
121
|
zone_pairs = RRMapParser._get_int16(header, 0x08)
|
118
122
|
zones = []
|
119
123
|
for zone_start in range(0, zone_pairs * 8, 8):
|
@@ -146,9 +150,9 @@ class RRMapParser:
|
|
146
150
|
angle = raw_angle
|
147
151
|
return {"position": [x, y], "angle": angle}
|
148
152
|
|
149
|
-
|
150
153
|
@staticmethod
|
151
154
|
def _parse_walls(data: bytes, header: bytes) -> list:
|
155
|
+
"""Parse walls using Xiaomi method."""
|
152
156
|
wall_pairs = RRMapParser._get_int16(header, 0x08)
|
153
157
|
walls = []
|
154
158
|
for wall_start in range(0, wall_pairs * 8, 8):
|
@@ -156,7 +160,14 @@ class RRMapParser:
|
|
156
160
|
y0 = RRMapParser._get_int16(data, wall_start + 2)
|
157
161
|
x1 = RRMapParser._get_int16(data, wall_start + 4)
|
158
162
|
y1 = RRMapParser._get_int16(data, wall_start + 6)
|
159
|
-
walls.append(
|
163
|
+
walls.append(
|
164
|
+
[
|
165
|
+
x0,
|
166
|
+
RRMapParser.Tools.DIMENSION_MM - y0,
|
167
|
+
x1,
|
168
|
+
RRMapParser.Tools.DIMENSION_MM - y1,
|
169
|
+
]
|
170
|
+
)
|
160
171
|
return walls
|
161
172
|
|
162
173
|
@staticmethod
|
@@ -204,6 +215,7 @@ class RRMapParser:
|
|
204
215
|
return {}
|
205
216
|
|
206
217
|
def parse_blocks(self, raw: bytes, pixels: bool = True) -> Dict[int, Any]:
|
218
|
+
"""Parse all blocks using Xiaomi method."""
|
207
219
|
blocks = {}
|
208
220
|
map_header_length = self._get_int16(raw, 0x02)
|
209
221
|
block_start_position = map_header_length
|
@@ -218,29 +230,53 @@ class RRMapParser:
|
|
218
230
|
match block_type:
|
219
231
|
case self.Types.DIGEST.value:
|
220
232
|
self.is_valid = True
|
221
|
-
case
|
222
|
-
|
233
|
+
case (
|
234
|
+
self.Types.ROBOT_POSITION.value
|
235
|
+
| self.Types.CHARGER_LOCATION.value
|
236
|
+
):
|
237
|
+
blocks[block_type] = self._parse_object_position(
|
238
|
+
block_data_length, data
|
239
|
+
)
|
223
240
|
case self.Types.PATH.value | self.Types.GOTO_PREDICTED_PATH.value:
|
224
|
-
blocks[block_type] = self._parse_path_block(
|
241
|
+
blocks[block_type] = self._parse_path_block(
|
242
|
+
raw, block_start_position, block_data_length
|
243
|
+
)
|
225
244
|
case self.Types.CURRENTLY_CLEANED_ZONES.value:
|
226
245
|
blocks[block_type] = {"zones": self._parse_zones(data, header)}
|
227
246
|
case self.Types.FORBIDDEN_ZONES.value:
|
228
|
-
blocks[block_type] = {
|
247
|
+
blocks[block_type] = {
|
248
|
+
"forbidden_zones": self._parse_area(header, data)
|
249
|
+
}
|
229
250
|
case self.Types.FORBIDDEN_MOP_ZONES.value:
|
230
|
-
blocks[block_type] = {
|
251
|
+
blocks[block_type] = {
|
252
|
+
"forbidden_mop_zones": self._parse_area(header, data)
|
253
|
+
}
|
231
254
|
case self.Types.GOTO_TARGET.value:
|
232
255
|
blocks[block_type] = {"position": self._parse_goto_target(data)}
|
233
256
|
case self.Types.VIRTUAL_WALLS.value:
|
234
|
-
blocks[block_type] = {
|
257
|
+
blocks[block_type] = {
|
258
|
+
"virtual_walls": self._parse_walls(data, header)
|
259
|
+
}
|
235
260
|
case self.Types.CARPET_MAP.value:
|
236
|
-
data = RRMapParser._get_bytes(
|
237
|
-
|
261
|
+
data = RRMapParser._get_bytes(
|
262
|
+
raw, block_data_start, block_data_length
|
263
|
+
)
|
264
|
+
blocks[block_type] = {
|
265
|
+
"carpet_map": self._parse_carpet_map(data)
|
266
|
+
}
|
238
267
|
case self.Types.IMAGE.value:
|
239
268
|
header_length = self._get_int8(header, 2)
|
240
269
|
blocks[block_type] = self._parse_image_block(
|
241
|
-
raw,
|
242
|
-
|
243
|
-
|
270
|
+
raw,
|
271
|
+
block_start_position,
|
272
|
+
block_data_length,
|
273
|
+
header_length,
|
274
|
+
pixels,
|
275
|
+
)
|
276
|
+
|
277
|
+
block_start_position = (
|
278
|
+
block_start_position + block_data_length + self._get_int8(header, 2)
|
279
|
+
)
|
244
280
|
except (struct.error, IndexError):
|
245
281
|
break
|
246
282
|
return blocks
|
@@ -7,18 +7,20 @@ Version: v0.1.12
|
|
7
7
|
import asyncio
|
8
8
|
import logging
|
9
9
|
from typing import List
|
10
|
+
|
10
11
|
from PIL import Image
|
11
12
|
|
13
|
+
from .utils import pil_size_rotation
|
12
14
|
from .types import (
|
13
15
|
ATTR_CALIBRATION_POINTS,
|
14
16
|
ATTR_CAMERA_MODE,
|
15
17
|
ATTR_CONTENT_TYPE,
|
18
|
+
ATTR_IMAGE_LAST_UPDATED,
|
16
19
|
ATTR_MARGINS,
|
17
20
|
ATTR_OBSTACLES,
|
18
21
|
ATTR_POINTS,
|
19
22
|
ATTR_ROOMS,
|
20
23
|
ATTR_ROTATE,
|
21
|
-
ATTR_IMAGE_LAST_UPDATED,
|
22
24
|
ATTR_VACUUM_BATTERY,
|
23
25
|
ATTR_VACUUM_CHARGING,
|
24
26
|
ATTR_VACUUM_JSON_ID,
|
@@ -40,8 +42,8 @@ from .types import (
|
|
40
42
|
DEFAULT_VALUES,
|
41
43
|
CameraModes,
|
42
44
|
Colors,
|
43
|
-
TrimsData,
|
44
45
|
PilPNG,
|
46
|
+
TrimsData,
|
45
47
|
)
|
46
48
|
|
47
49
|
|
@@ -209,11 +211,12 @@ class CameraShared:
|
|
209
211
|
|
210
212
|
def to_dict(self) -> dict:
|
211
213
|
"""Return a dictionary with image and attributes data."""
|
214
|
+
|
212
215
|
return {
|
213
216
|
"image": {
|
214
217
|
"binary": self.binary_image,
|
215
218
|
"pil_image": self.new_image,
|
216
|
-
"size": self.
|
219
|
+
"size": pil_size_rotation(self.image_rotate, self.new_image),
|
217
220
|
},
|
218
221
|
"attributes": self.generate_attributes(),
|
219
222
|
}
|
@@ -8,7 +8,7 @@ import json
|
|
8
8
|
import logging
|
9
9
|
import threading
|
10
10
|
from dataclasses import asdict, dataclass
|
11
|
-
from typing import Any, Dict, Optional, Tuple, TypedDict, Union
|
11
|
+
from typing import Any, Dict, List, NotRequired, Optional, Tuple, TypedDict, Union
|
12
12
|
|
13
13
|
import numpy as np
|
14
14
|
from PIL import Image
|
@@ -34,12 +34,11 @@ class Room(TypedDict):
|
|
34
34
|
id: int
|
35
35
|
|
36
36
|
|
37
|
-
# list[dict[str, str | list[int]]] | list[dict[str, str | list[list[int]]]] | list[dict[str, str | int]] | int]'
|
38
37
|
class Destinations(TypedDict, total=False):
|
39
38
|
spots: NotRequired[Optional[List[Spot]]]
|
40
39
|
zones: NotRequired[Optional[List[Zone]]]
|
41
40
|
rooms: NotRequired[Optional[List[Room]]]
|
42
|
-
updated: NotRequired[Optional[float]]
|
41
|
+
updated: NotRequired[Optional[float | int]]
|
43
42
|
|
44
43
|
|
45
44
|
class RoomProperty(TypedDict):
|
@@ -222,7 +221,9 @@ class SnapshotStore:
|
|
222
221
|
Color = Union[Tuple[int, int, int], Tuple[int, int, int, int]]
|
223
222
|
Colors = Dict[str, Color]
|
224
223
|
CalibrationPoints = list[dict[str, Any]]
|
225
|
-
RobotPosition
|
224
|
+
RobotPosition: type[tuple[Any, Any, dict[str, int | float] | None]] = tuple[
|
225
|
+
Any, Any, dict[str, int | float] | None
|
226
|
+
]
|
226
227
|
ChargerPosition = dict[str, Any]
|
227
228
|
RoomsProperties = dict[str, RoomProperty]
|
228
229
|
ImageSize = dict[str, int | list[int]]
|
@@ -1,32 +1,30 @@
|
|
1
1
|
"""Utility code for the valetudo map parser."""
|
2
2
|
|
3
3
|
import datetime
|
4
|
-
from time import time
|
5
4
|
import hashlib
|
5
|
+
import io
|
6
6
|
import json
|
7
7
|
from dataclasses import dataclass
|
8
|
+
from time import time
|
8
9
|
from typing import Callable, List, Optional, Tuple
|
9
|
-
import io
|
10
10
|
|
11
11
|
import numpy as np
|
12
12
|
from PIL import Image, ImageOps
|
13
13
|
|
14
|
+
from ..map_data import HyperMapData
|
15
|
+
from .async_utils import AsyncNumPy
|
14
16
|
from .drawable import Drawable
|
15
17
|
from .drawable_elements import DrawingConfig
|
16
|
-
from .enhanced_drawable import EnhancedDrawable
|
17
18
|
from .status_text.status_text import StatusText
|
18
|
-
|
19
19
|
from .types import (
|
20
20
|
LOGGER,
|
21
21
|
ChargerPosition,
|
22
|
-
|
22
|
+
Destinations,
|
23
23
|
NumpyArray,
|
24
24
|
PilPNG,
|
25
25
|
RobotPosition,
|
26
|
-
|
26
|
+
Size,
|
27
27
|
)
|
28
|
-
from ..map_data import HyperMapData
|
29
|
-
from .async_utils import AsyncNumPy
|
30
28
|
|
31
29
|
|
32
30
|
@dataclass
|
@@ -79,7 +77,6 @@ class BaseHandler:
|
|
79
77
|
# Drawing components are initialized by initialize_drawing_config in handlers
|
80
78
|
self.drawing_config: Optional[DrawingConfig] = None
|
81
79
|
self.draw: Optional[Drawable] = None
|
82
|
-
self.enhanced_draw: Optional[EnhancedDrawable] = None
|
83
80
|
|
84
81
|
def get_frame_number(self) -> int:
|
85
82
|
"""Return the frame number of the image."""
|
@@ -114,6 +111,12 @@ class BaseHandler:
|
|
114
111
|
try:
|
115
112
|
# Backup current image to last_image before processing new one
|
116
113
|
if hasattr(self.shared, "new_image") and self.shared.new_image is not None:
|
114
|
+
# Close old last_image to free memory before replacing it
|
115
|
+
if hasattr(self.shared, "last_image") and self.shared.last_image is not None:
|
116
|
+
try:
|
117
|
+
self.shared.last_image.close()
|
118
|
+
except Exception:
|
119
|
+
pass # Ignore errors if image is already closed
|
117
120
|
self.shared.last_image = self.shared.new_image
|
118
121
|
|
119
122
|
# Call the appropriate handler method based on handler type
|
@@ -199,10 +202,11 @@ class BaseHandler:
|
|
199
202
|
if hasattr(self, "get_rooms_attributes") and (
|
200
203
|
self.shared.map_rooms is None and destinations is not None
|
201
204
|
):
|
202
|
-
|
205
|
+
self.shared.map_rooms = await self.get_rooms_attributes(destinations)
|
203
206
|
if self.shared.map_rooms:
|
204
207
|
LOGGER.debug("%s: Rand256 attributes rooms updated", self.file_name)
|
205
208
|
|
209
|
+
|
206
210
|
if hasattr(self, "async_get_rooms_attributes") and (
|
207
211
|
self.shared.map_rooms is None
|
208
212
|
):
|
@@ -233,15 +237,8 @@ class BaseHandler:
|
|
233
237
|
self, pil_img: PilPNG, rand: bool = False
|
234
238
|
) -> ResizeParams:
|
235
239
|
"""Prepare resize parameters for image resizing."""
|
236
|
-
|
237
|
-
|
238
|
-
else:
|
239
|
-
height, width = pil_img.size
|
240
|
-
LOGGER.debug(
|
241
|
-
"Shared PIL image size: %s x %s",
|
242
|
-
self.shared.image_ref_width,
|
243
|
-
self.shared.image_ref_height,
|
244
|
-
)
|
240
|
+
width, height = pil_size_rotation(self.shared.image_rotate, pil_img)
|
241
|
+
|
245
242
|
return ResizeParams(
|
246
243
|
pil_img=pil_img,
|
247
244
|
width=width,
|
@@ -662,9 +659,6 @@ class BaseHandler:
|
|
662
659
|
|
663
660
|
async def async_resize_image(params: ResizeParams):
|
664
661
|
"""Resize the image to the given dimensions and aspect ratio."""
|
665
|
-
LOGGER.debug("Resizing image to aspect ratio: %s", params.aspect_ratio)
|
666
|
-
LOGGER.debug("Original image size: %s x %s", params.width, params.height)
|
667
|
-
LOGGER.debug("Image crop size: %s", params.crop_size)
|
668
662
|
if params.aspect_ratio == "None":
|
669
663
|
return params.pil_img
|
670
664
|
if params.aspect_ratio != "None":
|
@@ -701,6 +695,17 @@ async def async_resize_image(params: ResizeParams):
|
|
701
695
|
return params.pil_img
|
702
696
|
|
703
697
|
|
698
|
+
def pil_size_rotation(image_rotate, pil_img):
|
699
|
+
"""Return the size of the image."""
|
700
|
+
if not pil_img:
|
701
|
+
return 0, 0
|
702
|
+
if image_rotate in [0, 180]:
|
703
|
+
width, height = pil_img.size
|
704
|
+
else:
|
705
|
+
height, width = pil_img.size
|
706
|
+
return width, height
|
707
|
+
|
708
|
+
|
704
709
|
def initialize_drawing_config(handler):
|
705
710
|
"""
|
706
711
|
Initialize drawing configuration from device_info.
|
@@ -709,7 +714,7 @@ def initialize_drawing_config(handler):
|
|
709
714
|
handler: The handler instance with shared data and file_name attributes
|
710
715
|
|
711
716
|
Returns:
|
712
|
-
Tuple of (DrawingConfig, Drawable
|
717
|
+
Tuple of (DrawingConfig, Drawable)
|
713
718
|
"""
|
714
719
|
|
715
720
|
# Initialize drawing configuration
|
@@ -721,98 +726,10 @@ def initialize_drawing_config(handler):
|
|
721
726
|
):
|
722
727
|
drawing_config.update_from_device_info(handler.shared.device_info)
|
723
728
|
|
724
|
-
# Initialize
|
725
|
-
draw = Drawable()
|
726
|
-
enhanced_draw = EnhancedDrawable(drawing_config) # New enhanced drawing system
|
729
|
+
# Initialize drawing utilities
|
730
|
+
draw = Drawable()
|
727
731
|
|
728
|
-
return drawing_config, draw
|
729
|
-
|
730
|
-
|
731
|
-
def blend_colors(base_color, overlay_color):
|
732
|
-
"""
|
733
|
-
Blend two RGBA colors using alpha compositing.
|
734
|
-
|
735
|
-
Args:
|
736
|
-
base_color: Base RGBA color tuple (r, g, b, a)
|
737
|
-
overlay_color: Overlay RGBA color tuple (r, g, b, a)
|
738
|
-
|
739
|
-
Returns:
|
740
|
-
Blended RGBA color tuple (r, g, b, a)
|
741
|
-
"""
|
742
|
-
r1, g1, b1, a1 = base_color
|
743
|
-
r2, g2, b2, a2 = overlay_color
|
744
|
-
|
745
|
-
# Convert alpha to 0-1 range
|
746
|
-
a1 = a1 / 255.0
|
747
|
-
a2 = a2 / 255.0
|
748
|
-
|
749
|
-
# Calculate resulting alpha
|
750
|
-
a_out = a1 + a2 * (1 - a1)
|
751
|
-
|
752
|
-
# Avoid division by zero
|
753
|
-
if a_out < 0.0001:
|
754
|
-
return [0, 0, 0, 0]
|
755
|
-
|
756
|
-
# Calculate blended RGB components
|
757
|
-
r_out = (r1 * a1 + r2 * a2 * (1 - a1)) / a_out
|
758
|
-
g_out = (g1 * a1 + g2 * a2 * (1 - a1)) / a_out
|
759
|
-
b_out = (b1 * a1 + b2 * a2 * (1 - a1)) / a_out
|
760
|
-
|
761
|
-
# Convert back to 0-255 range and return as tuple
|
762
|
-
return (
|
763
|
-
int(max(0, min(255, r_out))),
|
764
|
-
int(max(0, min(255, g_out))),
|
765
|
-
int(max(0, min(255, b_out))),
|
766
|
-
int(max(0, min(255, a_out * 255))),
|
767
|
-
)
|
768
|
-
|
769
|
-
|
770
|
-
def blend_pixel(array, x, y, color, element, element_map=None, drawing_config=None):
|
771
|
-
"""
|
772
|
-
Blend a pixel color with the existing color at the specified position.
|
773
|
-
Also updates the element map if the new element has higher z-index.
|
774
|
-
|
775
|
-
Args:
|
776
|
-
array: The image array to modify
|
777
|
-
x: X coordinate
|
778
|
-
y: Y coordinate
|
779
|
-
color: RGBA color tuple to blend
|
780
|
-
element: Element code for the pixel
|
781
|
-
element_map: Optional element map to update
|
782
|
-
drawing_config: Optional drawing configuration for z-index lookup
|
783
|
-
|
784
|
-
Returns:
|
785
|
-
None
|
786
|
-
"""
|
787
|
-
# Check bounds
|
788
|
-
if not (0 <= y < array.shape[0] and 0 <= x < array.shape[1]):
|
789
|
-
return
|
790
|
-
|
791
|
-
# Get current element at this position
|
792
|
-
current_element = None
|
793
|
-
if element_map is not None:
|
794
|
-
current_element = element_map[y, x]
|
795
|
-
|
796
|
-
# Get z-index values for comparison
|
797
|
-
current_z = 0
|
798
|
-
new_z = 0
|
799
|
-
|
800
|
-
if drawing_config is not None:
|
801
|
-
current_z = (
|
802
|
-
drawing_config.get_property(current_element, "z_index", 0)
|
803
|
-
if current_element
|
804
|
-
else 0
|
805
|
-
)
|
806
|
-
new_z = drawing_config.get_property(element, "z_index", 0)
|
807
|
-
|
808
|
-
# Update element map if new element has higher z-index
|
809
|
-
if element_map is not None and new_z >= current_z:
|
810
|
-
element_map[y, x] = element
|
811
|
-
|
812
|
-
# Blend colors
|
813
|
-
base_color = array[y, x]
|
814
|
-
blended_color = blend_colors(base_color, color)
|
815
|
-
array[y, x] = blended_color
|
732
|
+
return drawing_config, draw
|
816
733
|
|
817
734
|
|
818
735
|
def manage_drawable_elements(
|
@@ -996,12 +913,6 @@ async def async_extract_room_outline(
|
|
996
913
|
|
997
914
|
# If we found too few boundary points, use the rectangular outline
|
998
915
|
if len(boundary_points) < 8: # Need at least 8 points for a meaningful shape
|
999
|
-
LOGGER.debug(
|
1000
|
-
"%s: Room %s has too few boundary points (%d), using rectangular outline",
|
1001
|
-
file_name,
|
1002
|
-
str(room_id_int),
|
1003
|
-
len(boundary_points),
|
1004
|
-
)
|
1005
916
|
return rect_outline
|
1006
917
|
|
1007
918
|
# Use a more sophisticated algorithm to create a coherent outline
|
@@ -1037,13 +948,6 @@ async def async_extract_room_outline(
|
|
1037
948
|
# Convert NumPy int64 values to regular Python integers
|
1038
949
|
simplified_outline = [(int(x), int(y)) for x, y in simplified_outline]
|
1039
950
|
|
1040
|
-
LOGGER.debug(
|
1041
|
-
"%s: Room %s outline has %d points",
|
1042
|
-
file_name,
|
1043
|
-
str(room_id_int),
|
1044
|
-
len(simplified_outline),
|
1045
|
-
)
|
1046
|
-
|
1047
951
|
return simplified_outline
|
1048
952
|
|
1049
953
|
except (ValueError, IndexError, TypeError, ArithmeticError) as e:
|
@@ -269,8 +269,6 @@ class ImageDraw:
|
|
269
269
|
zone_clean = self.img_h.data.find_zone_entities(m_json)
|
270
270
|
except (ValueError, KeyError):
|
271
271
|
zone_clean = None
|
272
|
-
else:
|
273
|
-
_LOGGER.info("%s: Got zones.", self.file_name)
|
274
272
|
|
275
273
|
if zone_clean:
|
276
274
|
# Process zones sequentially to avoid memory-intensive array copies
|
@@ -8,24 +8,22 @@ Version: 0.1.10
|
|
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
16
|
from .config.async_utils import AsyncPIL
|
16
|
-
|
17
|
-
from mvcrender.autocrop import AutoCrop
|
18
17
|
from .config.drawable_elements import DrawableElement
|
19
18
|
from .config.shared import CameraShared
|
20
|
-
|
21
19
|
from .config.types import (
|
22
20
|
COLORS,
|
23
21
|
LOGGER,
|
24
22
|
CalibrationPoints,
|
25
23
|
Colors,
|
24
|
+
JsonType,
|
26
25
|
RoomsProperties,
|
27
26
|
RoomStore,
|
28
|
-
JsonType,
|
29
27
|
)
|
30
28
|
from .config.utils import (
|
31
29
|
BaseHandler,
|
@@ -48,9 +46,7 @@ class HypferMapImageHandler(BaseHandler, AutoCrop):
|
|
48
46
|
self.calibration_data = None # camera shared data.
|
49
47
|
self.data = ImageData # imported Image Data Module.
|
50
48
|
# Initialize drawing configuration using the shared utility function
|
51
|
-
self.drawing_config, self.draw
|
52
|
-
self
|
53
|
-
)
|
49
|
+
self.drawing_config, self.draw = initialize_drawing_config(self)
|
54
50
|
|
55
51
|
self.go_to = None # vacuum go to data
|
56
52
|
self.img_hash = None # hash of the image calculated to check differences.
|
@@ -77,7 +73,7 @@ class HypferMapImageHandler(BaseHandler, AutoCrop):
|
|
77
73
|
json_data
|
78
74
|
)
|
79
75
|
if room_properties:
|
80
|
-
|
76
|
+
_ = RoomStore(self.file_name, room_properties)
|
81
77
|
# Convert room_properties to the format expected by async_get_robot_in_room
|
82
78
|
self.rooms_pos = []
|
83
79
|
for room_id, room_data in room_properties.items():
|
@@ -258,7 +254,12 @@ class HypferMapImageHandler(BaseHandler, AutoCrop):
|
|
258
254
|
)
|
259
255
|
LOGGER.info("%s: Completed base Layers", self.file_name)
|
260
256
|
# Copy the new array in base layer.
|
257
|
+
# Delete old base layer before creating new one to free memory
|
258
|
+
if self.img_base_layer is not None:
|
259
|
+
del self.img_base_layer
|
261
260
|
self.img_base_layer = await self.async_copy_array(img_np_array)
|
261
|
+
# Delete source array after copying to free memory
|
262
|
+
del img_np_array
|
262
263
|
|
263
264
|
self.shared.frame_number = self.frame_number
|
264
265
|
self.frame_number += 1
|
@@ -272,6 +273,9 @@ class HypferMapImageHandler(BaseHandler, AutoCrop):
|
|
272
273
|
or self.img_work_layer.shape != self.img_base_layer.shape
|
273
274
|
or self.img_work_layer.dtype != self.img_base_layer.dtype
|
274
275
|
):
|
276
|
+
# Delete old buffer before creating new one to free memory
|
277
|
+
if self.img_work_layer is not None:
|
278
|
+
del self.img_work_layer
|
275
279
|
self.img_work_layer = np.empty_like(self.img_base_layer)
|
276
280
|
|
277
281
|
# Copy the base layer into the persistent working buffer (no new allocation per frame)
|
@@ -346,16 +350,6 @@ class HypferMapImageHandler(BaseHandler, AutoCrop):
|
|
346
350
|
robot_state=self.shared.vacuum_state,
|
347
351
|
)
|
348
352
|
|
349
|
-
# Update element map for robot position
|
350
|
-
if (
|
351
|
-
hasattr(self.shared, "element_map")
|
352
|
-
and self.shared.element_map is not None
|
353
|
-
):
|
354
|
-
update_element_map_with_robot(
|
355
|
-
self.shared.element_map,
|
356
|
-
robot_position,
|
357
|
-
DrawableElement.ROBOT,
|
358
|
-
)
|
359
353
|
# Synchronize zooming state from ImageDraw to handler before auto-crop
|
360
354
|
self.zooming = self.imd.img_h.zooming
|
361
355
|
|
valetudo_map_parser/map_data.py
CHANGED
@@ -8,22 +8,22 @@ Version: v0.1.10
|
|
8
8
|
|
9
9
|
from __future__ import annotations
|
10
10
|
|
11
|
-
import
|
11
|
+
from dataclasses import asdict, dataclass, field
|
12
12
|
from typing import (
|
13
|
-
List,
|
14
|
-
Sequence,
|
15
|
-
TypeVar,
|
16
13
|
Any,
|
17
|
-
TypedDict,
|
18
|
-
NotRequired,
|
19
14
|
Literal,
|
15
|
+
NotRequired,
|
20
16
|
Optional,
|
17
|
+
Sequence,
|
18
|
+
TypedDict,
|
19
|
+
TypeVar,
|
21
20
|
)
|
22
21
|
|
23
|
-
|
22
|
+
import numpy as np
|
24
23
|
|
25
24
|
from .config.types import ImageSize, JsonType
|
26
25
|
|
26
|
+
|
27
27
|
T = TypeVar("T")
|
28
28
|
|
29
29
|
# --- Common Nested Structures ---
|
@@ -373,6 +373,11 @@ class ImageData:
|
|
373
373
|
Else:
|
374
374
|
(min_x_mm, min_y_mm, max_x_mm, max_y_mm)
|
375
375
|
"""
|
376
|
+
|
377
|
+
def to_mm(coord):
|
378
|
+
"""Convert pixel coordinates to millimeters."""
|
379
|
+
return round(coord * pixel_size * 10)
|
380
|
+
|
376
381
|
if not pixels:
|
377
382
|
raise ValueError("Pixels list cannot be empty.")
|
378
383
|
|
@@ -393,7 +398,6 @@ class ImageData:
|
|
393
398
|
min_y = min(min_y, y)
|
394
399
|
|
395
400
|
if rand:
|
396
|
-
to_mm = lambda v: v * pixel_size * 10
|
397
401
|
return (to_mm(max_x), to_mm(max_y)), (to_mm(min_x), to_mm(min_y))
|
398
402
|
|
399
403
|
return (
|
@@ -548,8 +552,9 @@ class RandImageData:
|
|
548
552
|
@staticmethod
|
549
553
|
def get_rrm_forbidden_zones(json_data: JsonType) -> list[dict[str, Any]]:
|
550
554
|
"""Get the forbidden zones from the json."""
|
551
|
-
re_zones = json_data.get("forbidden_zones", [])
|
552
|
-
|
555
|
+
re_zones = json_data.get("forbidden_zones", []) + json_data.get(
|
556
|
+
"forbidden_mop_zones", []
|
557
|
+
)
|
553
558
|
formatted_zones = RandImageData._rrm_valetudo_format_zone(re_zones)
|
554
559
|
return formatted_zones
|
555
560
|
|