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
@@ -2,19 +2,18 @@
|
|
2
2
|
Image Handler Module for Valetudo Re Vacuums.
|
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
|
-
import logging
|
11
10
|
import uuid
|
12
11
|
from typing import Any
|
13
12
|
|
14
13
|
import numpy as np
|
14
|
+
from mvcrender.autocrop import AutoCrop
|
15
15
|
|
16
|
-
from .config.async_utils import
|
17
|
-
from .config.auto_crop import AutoCrop
|
16
|
+
from .config.async_utils import AsyncPIL
|
18
17
|
from .config.drawable_elements import DrawableElement
|
19
18
|
from .config.types import (
|
20
19
|
COLORS,
|
@@ -26,23 +25,18 @@ from .config.types import (
|
|
26
25
|
RobotPosition,
|
27
26
|
RoomsProperties,
|
28
27
|
RoomStore,
|
29
|
-
|
28
|
+
LOGGER,
|
30
29
|
)
|
31
30
|
from .config.utils import (
|
32
31
|
BaseHandler,
|
33
32
|
initialize_drawing_config,
|
34
|
-
|
35
|
-
numpy_to_webp_bytes,
|
36
|
-
prepare_resize_params,
|
33
|
+
point_in_polygon,
|
37
34
|
)
|
38
35
|
from .map_data import RandImageData
|
39
36
|
from .reimg_draw import ImageDraw
|
40
37
|
from .rooms_handler import RandRoomsHandler
|
41
38
|
|
42
39
|
|
43
|
-
_LOGGER = logging.getLogger(__name__)
|
44
|
-
|
45
|
-
|
46
40
|
# noinspection PyTypeChecker
|
47
41
|
class ReImageHandler(BaseHandler, AutoCrop):
|
48
42
|
"""
|
@@ -60,9 +54,7 @@ class ReImageHandler(BaseHandler, AutoCrop):
|
|
60
54
|
self.data = RandImageData # Image Data
|
61
55
|
|
62
56
|
# Initialize drawing configuration using the shared utility function
|
63
|
-
self.drawing_config, self.draw
|
64
|
-
self
|
65
|
-
)
|
57
|
+
self.drawing_config, self.draw = initialize_drawing_config(self)
|
66
58
|
self.go_to = None # Go to position data
|
67
59
|
self.img_base_layer = None # Base image layer
|
68
60
|
self.img_rotate = shared_data.image_rotate # Image rotation
|
@@ -101,56 +93,40 @@ class ReImageHandler(BaseHandler, AutoCrop):
|
|
101
93
|
|
102
94
|
# Update self.rooms_pos from room_properties for compatibility with other methods
|
103
95
|
self.rooms_pos = []
|
104
|
-
room_ids = [] # Collect room IDs for shared.map_rooms
|
105
96
|
for room_id, room_data in room_properties.items():
|
106
97
|
self.rooms_pos.append(
|
107
98
|
{"name": room_data["name"], "outline": room_data["outline"]}
|
108
99
|
)
|
109
|
-
# Store the room number (segment ID) for MQTT active zone mapping
|
110
|
-
room_ids.append(room_data["number"])
|
111
|
-
|
112
|
-
# Update shared.map_rooms with the room IDs for MQTT active zone mapping
|
113
|
-
self.shared.map_rooms = room_ids
|
114
|
-
_LOGGER.debug("Updated shared.map_rooms with room IDs: %s", room_ids)
|
115
100
|
|
101
|
+
# Update shared.map_rooms with the full room properties (consistent with Hypfer)
|
102
|
+
self.shared.map_rooms = room_properties
|
116
103
|
# get the zones and points data
|
117
|
-
|
104
|
+
self.shared.map_pred_zones = await self.async_zone_propriety(zones_data)
|
118
105
|
# get the points data
|
119
|
-
|
120
|
-
|
121
|
-
if room_properties or
|
122
|
-
extracted_data = [
|
123
|
-
f"{len(room_properties)} Rooms" if room_properties else None,
|
124
|
-
f"{len(zone_properties)} Zones" if zone_properties else None,
|
125
|
-
]
|
126
|
-
extracted_data = ", ".join(filter(None, extracted_data))
|
127
|
-
_LOGGER.debug("Extracted data: %s", extracted_data)
|
128
|
-
else:
|
106
|
+
self.shared.map_pred_points = await self.async_points_propriety(points_data)
|
107
|
+
|
108
|
+
if not (room_properties or self.shared.map_pred_zones):
|
129
109
|
self.rooms_pos = None
|
130
|
-
_LOGGER.debug("%s: Rooms and Zones data not available!", self.file_name)
|
131
110
|
|
132
|
-
|
133
|
-
|
134
|
-
return room_properties, zone_properties, point_properties
|
111
|
+
_ = RoomStore(self.file_name, room_properties)
|
112
|
+
return room_properties
|
135
113
|
except (RuntimeError, ValueError) as e:
|
136
|
-
|
114
|
+
LOGGER.warning(
|
137
115
|
"No rooms Data or Error in extract_room_properties: %s",
|
138
116
|
e,
|
139
117
|
exc_info=True,
|
140
118
|
)
|
141
|
-
return None
|
119
|
+
return None
|
142
120
|
|
143
121
|
async def get_image_from_rrm(
|
144
122
|
self,
|
145
123
|
m_json: JsonType, # json data
|
146
124
|
destinations: None = None, # MQTT destinations for labels
|
147
|
-
|
148
|
-
) -> WebPBytes | PilPNG | None:
|
125
|
+
) -> PilPNG | None:
|
149
126
|
"""Generate Images from the json data.
|
150
127
|
@param m_json: The JSON data to use to draw the image.
|
151
128
|
@param destinations: MQTT destinations for labels (unused).
|
152
|
-
@
|
153
|
-
@return WebPBytes | Image.Image: WebP bytes or PIL Image depending on return_webp parameter.
|
129
|
+
@return Image.Image: PIL Image.
|
154
130
|
"""
|
155
131
|
colors: Colors = {
|
156
132
|
name: self.shared.user_colors[idx] for idx, name in enumerate(COLORS)
|
@@ -159,12 +135,12 @@ class ReImageHandler(BaseHandler, AutoCrop):
|
|
159
135
|
|
160
136
|
try:
|
161
137
|
if (m_json is not None) and (not isinstance(m_json, tuple)):
|
162
|
-
|
138
|
+
LOGGER.info("%s: Composing the image for the camera.", self.file_name)
|
163
139
|
self.json_data = m_json
|
164
140
|
size_x, size_y = self.data.get_rrm_image_size(m_json)
|
165
141
|
self.img_size = DEFAULT_IMAGE_SIZE
|
166
142
|
self.json_id = str(uuid.uuid4()) # image id
|
167
|
-
|
143
|
+
LOGGER.info("Vacuum Data ID: %s", self.json_id)
|
168
144
|
|
169
145
|
(
|
170
146
|
img_np_array,
|
@@ -177,9 +153,6 @@ class ReImageHandler(BaseHandler, AutoCrop):
|
|
177
153
|
# Increment frame number
|
178
154
|
self.frame_number += 1
|
179
155
|
img_np_array = await self.async_copy_array(self.img_base_layer)
|
180
|
-
_LOGGER.debug(
|
181
|
-
"%s: Frame number %s", self.file_name, str(self.frame_number)
|
182
|
-
)
|
183
156
|
if self.frame_number > 5:
|
184
157
|
self.frame_number = 0
|
185
158
|
|
@@ -188,20 +161,13 @@ class ReImageHandler(BaseHandler, AutoCrop):
|
|
188
161
|
img_np_array, m_json, colors, robot_position, robot_position_angle
|
189
162
|
)
|
190
163
|
|
191
|
-
# Return
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
del img_np_array # free memory
|
196
|
-
return webp_bytes
|
197
|
-
else:
|
198
|
-
# Convert to PIL Image using async utilities
|
199
|
-
pil_img = await AsyncPIL.async_fromarray(img_np_array, mode="RGBA")
|
200
|
-
del img_np_array # free memory
|
201
|
-
return await self._finalize_image(pil_img)
|
164
|
+
# Return PIL Image using async utilities
|
165
|
+
pil_img = await AsyncPIL.async_fromarray(img_np_array, mode="RGBA")
|
166
|
+
del img_np_array # free memory
|
167
|
+
return await self._finalize_image(pil_img)
|
202
168
|
|
203
169
|
except (RuntimeError, RuntimeWarning) as e:
|
204
|
-
|
170
|
+
LOGGER.warning(
|
205
171
|
"%s: Runtime Error %s during image creation.",
|
206
172
|
self.file_name,
|
207
173
|
str(e),
|
@@ -215,6 +181,7 @@ class ReImageHandler(BaseHandler, AutoCrop):
|
|
215
181
|
async def _setup_robot_and_image(
|
216
182
|
self, m_json, size_x, size_y, colors, destinations
|
217
183
|
):
|
184
|
+
"""Set up the elements of the map and the image."""
|
218
185
|
(
|
219
186
|
_,
|
220
187
|
robot_position,
|
@@ -237,13 +204,7 @@ class ReImageHandler(BaseHandler, AutoCrop):
|
|
237
204
|
colors["background"],
|
238
205
|
DEFAULT_PIXEL_SIZE,
|
239
206
|
)
|
240
|
-
|
241
|
-
|
242
|
-
# Update element map for rooms
|
243
|
-
if 0 < room_id <= 15:
|
244
|
-
# This is a simplification - in a real implementation we would
|
245
|
-
# need to identify the exact pixels that belong to each room
|
246
|
-
pass
|
207
|
+
LOGGER.info("%s: Completed base Layers", self.file_name)
|
247
208
|
|
248
209
|
if room_id > 0 and not self.room_propriety:
|
249
210
|
self.room_propriety = await self.get_rooms_attributes(destinations)
|
@@ -252,8 +213,10 @@ class ReImageHandler(BaseHandler, AutoCrop):
|
|
252
213
|
if not self.rooms_pos and not self.room_propriety:
|
253
214
|
self.room_propriety = await self.get_rooms_attributes(destinations)
|
254
215
|
|
255
|
-
# Always check robot position for zooming (
|
256
|
-
if self.rooms_pos and robot_position and
|
216
|
+
# Always check robot position for zooming (update if room info is missing)
|
217
|
+
if self.rooms_pos and robot_position and (
|
218
|
+
self.robot_pos is None or "in_room" not in self.robot_pos
|
219
|
+
):
|
257
220
|
self.robot_pos = await self.async_get_robot_in_room(
|
258
221
|
(robot_position[0] * 10),
|
259
222
|
(robot_position[1] * 10),
|
@@ -300,12 +263,7 @@ class ReImageHandler(BaseHandler, AutoCrop):
|
|
300
263
|
# Restore original rooms_pos
|
301
264
|
self.rooms_pos = original_rooms_pos
|
302
265
|
|
303
|
-
except
|
304
|
-
_LOGGER.debug(
|
305
|
-
"%s: Early room extraction failed: %s, falling back to robot-position zoom",
|
306
|
-
self.file_name,
|
307
|
-
e,
|
308
|
-
)
|
266
|
+
except (ValueError, KeyError, TypeError):
|
309
267
|
# Fallback to robot-position-based zoom if room extraction fails
|
310
268
|
if (
|
311
269
|
self.shared.image_auto_zoom
|
@@ -313,16 +271,13 @@ class ReImageHandler(BaseHandler, AutoCrop):
|
|
313
271
|
and robot_position
|
314
272
|
):
|
315
273
|
self.zooming = True
|
316
|
-
_LOGGER.debug(
|
317
|
-
"%s: Enabling fallback robot-position-based zoom",
|
318
|
-
self.file_name,
|
319
|
-
)
|
320
274
|
|
321
275
|
return self.img_base_layer, robot_position, robot_position_angle
|
322
276
|
|
323
277
|
async def _draw_map_elements(
|
324
278
|
self, img_np_array, m_json, colors, robot_position, robot_position_angle
|
325
279
|
):
|
280
|
+
"""Draw map elements on the image."""
|
326
281
|
# Draw charger if enabled
|
327
282
|
if self.drawing_config.is_enabled(DrawableElement.CHARGER):
|
328
283
|
img_np_array, self.charger_pos = await self.imd.async_draw_charger(
|
@@ -379,19 +334,10 @@ class ReImageHandler(BaseHandler, AutoCrop):
|
|
379
334
|
active_zones = self.shared.rand256_active_zone
|
380
335
|
if active_zones and any(zone for zone in active_zones):
|
381
336
|
self.zooming = True
|
382
|
-
_LOGGER.debug(
|
383
|
-
"%s: Enabling zoom for Rand256 - active zones detected: %s",
|
384
|
-
self.file_name,
|
385
|
-
active_zones,
|
386
|
-
)
|
387
337
|
else:
|
388
338
|
self.zooming = False
|
389
|
-
_LOGGER.debug(
|
390
|
-
"%s: Zoom disabled for Rand256 - no active zones set",
|
391
|
-
self.file_name,
|
392
|
-
)
|
393
339
|
|
394
|
-
img_np_array =
|
340
|
+
img_np_array = self.auto_trim_and_zoom_image(
|
395
341
|
img_np_array,
|
396
342
|
detect_colour=colors["background"],
|
397
343
|
margin_size=int(self.shared.margins),
|
@@ -402,77 +348,30 @@ class ReImageHandler(BaseHandler, AutoCrop):
|
|
402
348
|
return img_np_array
|
403
349
|
|
404
350
|
async def _finalize_image(self, pil_img):
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
return pil_img
|
351
|
+
"""Finalize the image by resizing if needed."""
|
352
|
+
if pil_img is None:
|
353
|
+
LOGGER.warning("%s: Image is None. Returning None.", self.file_name)
|
354
|
+
return None
|
410
355
|
if self.check_zoom_and_aspect_ratio():
|
411
|
-
resize_params = prepare_resize_params(
|
356
|
+
resize_params = self.prepare_resize_params(pil_img, True)
|
412
357
|
pil_img = await self.async_resize_images(resize_params)
|
413
|
-
|
358
|
+
else:
|
359
|
+
LOGGER.warning(
|
360
|
+
"%s: Invalid image dimensions. Returning original image.",
|
361
|
+
self.file_name,
|
362
|
+
)
|
414
363
|
return pil_img
|
415
364
|
|
416
365
|
async def get_rooms_attributes(
|
417
366
|
self, destinations: JsonType = None
|
418
367
|
) -> tuple[RoomsProperties, Any, Any]:
|
419
368
|
"""Return the rooms attributes."""
|
420
|
-
if self.room_propriety:
|
421
|
-
return self.room_propriety
|
422
369
|
if self.json_data and destinations:
|
423
|
-
_LOGGER.debug("Checking for rooms data..")
|
424
370
|
self.room_propriety = await self.extract_room_properties(
|
425
371
|
self.json_data, destinations
|
426
372
|
)
|
427
|
-
if self.room_propriety:
|
428
|
-
_LOGGER.debug("Got Rooms Attributes.")
|
429
373
|
return self.room_propriety
|
430
374
|
|
431
|
-
@staticmethod
|
432
|
-
def point_in_polygon(x: int, y: int, polygon: list) -> bool:
|
433
|
-
"""
|
434
|
-
Check if a point is inside a polygon using ray casting algorithm.
|
435
|
-
Enhanced version with better handling of edge cases.
|
436
|
-
|
437
|
-
Args:
|
438
|
-
x: X coordinate of the point
|
439
|
-
y: Y coordinate of the point
|
440
|
-
polygon: List of (x, y) tuples forming the polygon
|
441
|
-
|
442
|
-
Returns:
|
443
|
-
True if the point is inside the polygon, False otherwise
|
444
|
-
"""
|
445
|
-
# Ensure we have a valid polygon with at least 3 points
|
446
|
-
if len(polygon) < 3:
|
447
|
-
return False
|
448
|
-
|
449
|
-
# Make sure the polygon is closed (last point equals first point)
|
450
|
-
if polygon[0] != polygon[-1]:
|
451
|
-
polygon = polygon + [polygon[0]]
|
452
|
-
|
453
|
-
# Use winding number algorithm for better accuracy
|
454
|
-
wn = 0 # Winding number counter
|
455
|
-
|
456
|
-
# Loop through all edges of the polygon
|
457
|
-
for i in range(len(polygon) - 1): # Last vertex is first vertex
|
458
|
-
p1x, p1y = polygon[i]
|
459
|
-
p2x, p2y = polygon[i + 1]
|
460
|
-
|
461
|
-
# Test if a point is left/right/on the edge defined by two vertices
|
462
|
-
if p1y <= y: # Start y <= P.y
|
463
|
-
if p2y > y: # End y > P.y (upward crossing)
|
464
|
-
# Point left of edge
|
465
|
-
if ((p2x - p1x) * (y - p1y) - (x - p1x) * (p2y - p1y)) > 0:
|
466
|
-
wn += 1 # Valid up intersect
|
467
|
-
else: # Start y > P.y
|
468
|
-
if p2y <= y: # End y <= P.y (downward crossing)
|
469
|
-
# Point right of edge
|
470
|
-
if ((p2x - p1x) * (y - p1y) - (x - p1x) * (p2y - p1y)) < 0:
|
471
|
-
wn -= 1 # Valid down intersect
|
472
|
-
|
473
|
-
# If winding number is not 0, the point is inside the polygon
|
474
|
-
return wn != 0
|
475
|
-
|
476
375
|
async def async_get_robot_in_room(
|
477
376
|
self, robot_x: int, robot_y: int, angle: float
|
478
377
|
) -> RobotPosition:
|
@@ -482,7 +381,7 @@ class ReImageHandler(BaseHandler, AutoCrop):
|
|
482
381
|
# If we have outline data, use point_in_polygon for accurate detection
|
483
382
|
if "outline" in self.robot_in_room:
|
484
383
|
outline = self.robot_in_room["outline"]
|
485
|
-
if
|
384
|
+
if point_in_polygon(int(robot_x), int(robot_y), outline):
|
486
385
|
temp = {
|
487
386
|
"x": robot_x,
|
488
387
|
"y": robot_y,
|
@@ -491,6 +390,12 @@ class ReImageHandler(BaseHandler, AutoCrop):
|
|
491
390
|
}
|
492
391
|
# Handle active zones
|
493
392
|
self.active_zones = self.shared.rand256_active_zone
|
393
|
+
LOGGER.debug(
|
394
|
+
"%s: Robot is in %s room (polygon detection). %s",
|
395
|
+
self.file_name,
|
396
|
+
self.robot_in_room["room"],
|
397
|
+
self.active_zones,
|
398
|
+
)
|
494
399
|
self.zooming = False
|
495
400
|
if self.active_zones and (
|
496
401
|
self.robot_in_room["id"] in range(len(self.active_zones))
|
@@ -537,12 +442,6 @@ class ReImageHandler(BaseHandler, AutoCrop):
|
|
537
442
|
# This helps prevent false positives for points very far from any room
|
538
443
|
map_boundary = 50000 # Typical map size is around 25000-30000 units for Rand25
|
539
444
|
if abs(robot_x) > map_boundary or abs(robot_y) > map_boundary:
|
540
|
-
_LOGGER.debug(
|
541
|
-
"%s robot position (%s, %s) is far outside map boundaries.",
|
542
|
-
self.file_name,
|
543
|
-
robot_x,
|
544
|
-
robot_y,
|
545
|
-
)
|
546
445
|
self.robot_in_room = last_room
|
547
446
|
self.zooming = False
|
548
447
|
temp = {
|
@@ -555,10 +454,6 @@ class ReImageHandler(BaseHandler, AutoCrop):
|
|
555
454
|
|
556
455
|
# Search through all rooms to find which one contains the robot
|
557
456
|
if not self.rooms_pos:
|
558
|
-
_LOGGER.debug(
|
559
|
-
"%s: No rooms data available for robot position detection.",
|
560
|
-
self.file_name,
|
561
|
-
)
|
562
457
|
self.robot_in_room = last_room
|
563
458
|
self.zooming = False
|
564
459
|
temp = {
|
@@ -569,13 +464,12 @@ class ReImageHandler(BaseHandler, AutoCrop):
|
|
569
464
|
}
|
570
465
|
return temp
|
571
466
|
|
572
|
-
_LOGGER.debug("%s: Searching for robot in rooms...", self.file_name)
|
573
467
|
for room in self.rooms_pos:
|
574
468
|
# Check if the room has an outline (polygon points)
|
575
469
|
if "outline" in room:
|
576
470
|
outline = room["outline"]
|
577
471
|
# Use point_in_polygon for accurate detection with complex shapes
|
578
|
-
if
|
472
|
+
if point_in_polygon(int(robot_x), int(robot_y), outline):
|
579
473
|
# Robot is in this room
|
580
474
|
self.robot_in_room = {
|
581
475
|
"id": room_count,
|
@@ -598,19 +492,10 @@ class ReImageHandler(BaseHandler, AutoCrop):
|
|
598
492
|
else:
|
599
493
|
self.zooming = False
|
600
494
|
|
601
|
-
_LOGGER.debug(
|
602
|
-
"%s is in %s room (polygon detection).",
|
603
|
-
self.file_name,
|
604
|
-
self.robot_in_room["room"],
|
605
|
-
)
|
606
495
|
return temp
|
607
496
|
room_count += 1
|
608
497
|
|
609
498
|
# Robot not found in any room
|
610
|
-
_LOGGER.debug(
|
611
|
-
"%s not located within any room coordinates.",
|
612
|
-
self.file_name,
|
613
|
-
)
|
614
499
|
self.robot_in_room = last_room
|
615
500
|
self.zooming = False
|
616
501
|
temp = {
|
@@ -625,7 +510,7 @@ class ReImageHandler(BaseHandler, AutoCrop):
|
|
625
510
|
"""Return the map calibration data."""
|
626
511
|
if not self.calibration_data and self.crop_img_size:
|
627
512
|
self.calibration_data = []
|
628
|
-
|
513
|
+
LOGGER.info(
|
629
514
|
"%s: Getting Calibrations points %s",
|
630
515
|
self.file_name,
|
631
516
|
str(self.crop_area),
|
@@ -643,32 +528,3 @@ class ReImageHandler(BaseHandler, AutoCrop):
|
|
643
528
|
self.calibration_data.append(calibration_point)
|
644
529
|
|
645
530
|
return self.calibration_data
|
646
|
-
|
647
|
-
# Element selection methods
|
648
|
-
def enable_element(self, element_code: DrawableElement) -> None:
|
649
|
-
"""Enable drawing of a specific element."""
|
650
|
-
self.drawing_config.enable_element(element_code)
|
651
|
-
|
652
|
-
def disable_element(self, element_code: DrawableElement) -> None:
|
653
|
-
"""Disable drawing of a specific element."""
|
654
|
-
manage_drawable_elements(self, "disable", element_code=element_code)
|
655
|
-
|
656
|
-
def set_elements(self, element_codes: list[DrawableElement]) -> None:
|
657
|
-
"""Enable only the specified elements, disable all others."""
|
658
|
-
manage_drawable_elements(self, "set_elements", element_codes=element_codes)
|
659
|
-
|
660
|
-
def set_element_property(
|
661
|
-
self, element_code: DrawableElement, property_name: str, value
|
662
|
-
) -> None:
|
663
|
-
"""Set a drawing property for an element."""
|
664
|
-
manage_drawable_elements(
|
665
|
-
self,
|
666
|
-
"set_property",
|
667
|
-
element_code=element_code,
|
668
|
-
property_name=property_name,
|
669
|
-
value=value,
|
670
|
-
)
|
671
|
-
|
672
|
-
async def async_copy_array(self, original_array):
|
673
|
-
"""Copy the array using async utilities."""
|
674
|
-
return await AsyncNumPy.async_copy(original_array)
|
@@ -6,17 +6,12 @@ Version: 0.1.9.b42
|
|
6
6
|
|
7
7
|
from __future__ import annotations
|
8
8
|
|
9
|
-
import logging
|
10
|
-
|
11
9
|
from .config.drawable import Drawable
|
12
10
|
from .config.drawable_elements import DrawableElement
|
13
|
-
from .config.types import Color, JsonType, NumpyArray
|
11
|
+
from .config.types import LOGGER, Color, JsonType, NumpyArray
|
14
12
|
from .map_data import ImageData, RandImageData
|
15
13
|
|
16
14
|
|
17
|
-
_LOGGER = logging.getLogger(__name__)
|
18
|
-
|
19
|
-
|
20
15
|
class ImageDraw:
|
21
16
|
"""Class to handle the image creation."""
|
22
17
|
|
@@ -48,7 +43,7 @@ class ImageDraw:
|
|
48
43
|
)
|
49
44
|
return np_array
|
50
45
|
except KeyError as e:
|
51
|
-
|
46
|
+
LOGGER.warning(
|
52
47
|
"%s: Error in extraction of go-to target: %s",
|
53
48
|
self.file_name,
|
54
49
|
e,
|
@@ -70,7 +65,7 @@ class ImageDraw:
|
|
70
65
|
)
|
71
66
|
except ValueError as e:
|
72
67
|
self.img_h.segment_data = None
|
73
|
-
|
68
|
+
LOGGER.info("%s: No segments data found: %s", self.file_name, e)
|
74
69
|
|
75
70
|
async def async_draw_base_layer(
|
76
71
|
self,
|
@@ -87,13 +82,13 @@ class ImageDraw:
|
|
87
82
|
walls_data = self.data.get_rrm_walls(m_json)
|
88
83
|
floor_data = self.data.get_rrm_floor(m_json)
|
89
84
|
|
90
|
-
|
85
|
+
LOGGER.info("%s: Empty image with background color", self.file_name)
|
91
86
|
img_np_array = await self.draw.create_empty_image(
|
92
87
|
self.img_h.img_size["x"], self.img_h.img_size["y"], color_background
|
93
88
|
)
|
94
89
|
room_id = 0
|
95
90
|
if self.img_h.frame_number == 0:
|
96
|
-
|
91
|
+
LOGGER.info("%s: Overlapping Layers", self.file_name)
|
97
92
|
|
98
93
|
# checking if there are segments too (sorted pixels in the raw data).
|
99
94
|
await self.async_segment_data(m_json, size_x, size_y, pos_top, pos_left)
|
@@ -148,10 +143,10 @@ class ImageDraw:
|
|
148
143
|
room_id = 0
|
149
144
|
rooms_list = [color_wall]
|
150
145
|
if not segment_data:
|
151
|
-
|
146
|
+
LOGGER.info("%s: No segments data found.", self.file_name)
|
152
147
|
return room_id, img_np_array
|
153
148
|
|
154
|
-
|
149
|
+
LOGGER.info("%s: Drawing segments.", self.file_name)
|
155
150
|
for pixels in segment_data:
|
156
151
|
room_color = self.img_h.shared.rooms_colors[room_id]
|
157
152
|
rooms_list.append(room_color)
|
@@ -211,9 +206,8 @@ class ImageDraw:
|
|
211
206
|
self.data.get_rrm_charger_position(m_json)
|
212
207
|
)
|
213
208
|
except KeyError as e:
|
214
|
-
|
209
|
+
LOGGER.warning("%s: No charger position found: %s", self.file_name, e)
|
215
210
|
else:
|
216
|
-
_LOGGER.debug("Charger position: %s", charger_pos)
|
217
211
|
if charger_pos:
|
218
212
|
charger_pos_dictionary = {
|
219
213
|
"x": (charger_pos[0] * 10),
|
@@ -239,7 +233,7 @@ class ImageDraw:
|
|
239
233
|
zone_clean = None
|
240
234
|
|
241
235
|
if zone_clean:
|
242
|
-
|
236
|
+
LOGGER.info("%s: Got zones.", self.file_name)
|
243
237
|
return await self.draw.zones(np_array, zone_clean, color_zone_clean)
|
244
238
|
return np_array
|
245
239
|
|
@@ -253,7 +247,7 @@ class ImageDraw:
|
|
253
247
|
virtual_walls = None
|
254
248
|
|
255
249
|
if virtual_walls:
|
256
|
-
|
250
|
+
LOGGER.info("%s: Got virtual walls.", self.file_name)
|
257
251
|
np_array = await self.draw.draw_virtual_walls(
|
258
252
|
np_array, virtual_walls, color_no_go
|
259
253
|
)
|
@@ -281,7 +275,7 @@ class ImageDraw:
|
|
281
275
|
self.data.rrm_valetudo_path_array(path_pixel["points"]), 2
|
282
276
|
)
|
283
277
|
except KeyError as e:
|
284
|
-
|
278
|
+
LOGGER.warning(
|
285
279
|
"%s: Error extracting paths data: %s", self.file_name, str(e)
|
286
280
|
)
|
287
281
|
finally:
|
@@ -298,7 +292,7 @@ class ImageDraw:
|
|
298
292
|
except (ValueError, KeyError):
|
299
293
|
entity_dict = None
|
300
294
|
else:
|
301
|
-
|
295
|
+
LOGGER.info("%s: Got the points in the json.", self.file_name)
|
302
296
|
return entity_dict
|
303
297
|
|
304
298
|
async def async_get_robot_position(self, m_json: JsonType) -> tuple | None:
|
@@ -311,17 +305,12 @@ class ImageDraw:
|
|
311
305
|
robot_pos = self.data.rrm_coordinates_to_valetudo(robot_pos_data)
|
312
306
|
angle = self.data.get_rrm_robot_angle(m_json)
|
313
307
|
except (ValueError, KeyError):
|
314
|
-
|
308
|
+
LOGGER.warning("%s No robot position found.", self.file_name)
|
315
309
|
return None, None, None
|
316
310
|
finally:
|
317
311
|
robot_position_angle = round(angle[0], 0)
|
318
312
|
if robot_pos and robot_position_angle:
|
319
313
|
robot_position = robot_pos
|
320
|
-
_LOGGER.debug(
|
321
|
-
"robot position: %s, robot angle: %s",
|
322
|
-
str(robot_pos),
|
323
|
-
str(robot_position_angle),
|
324
|
-
)
|
325
314
|
if self.img_h.rooms_pos is None:
|
326
315
|
self.img_h.robot_pos = {
|
327
316
|
"x": robot_position[0] * 10,
|
@@ -351,6 +340,7 @@ class ImageDraw:
|
|
351
340
|
y=robot_pos[1],
|
352
341
|
angle=robot_angle,
|
353
342
|
fill=color_robot,
|
343
|
+
radius=self.img_h.shared.robot_size,
|
354
344
|
robot_state=self.img_h.shared.vacuum_state,
|
355
345
|
)
|
356
346
|
return np_array
|