valetudo-map-parser 0.1.7__py3-none-any.whl → 0.1.9a1__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 +19 -12
- valetudo_map_parser/config/auto_crop.py +174 -116
- valetudo_map_parser/config/color_utils.py +105 -0
- valetudo_map_parser/config/colors.py +662 -13
- valetudo_map_parser/config/drawable.py +624 -279
- valetudo_map_parser/config/drawable_elements.py +292 -0
- valetudo_map_parser/config/enhanced_drawable.py +324 -0
- valetudo_map_parser/config/optimized_element_map.py +406 -0
- valetudo_map_parser/config/rand25_parser.py +42 -28
- valetudo_map_parser/config/room_outline.py +148 -0
- valetudo_map_parser/config/shared.py +29 -5
- valetudo_map_parser/config/types.py +102 -51
- valetudo_map_parser/config/utils.py +841 -0
- valetudo_map_parser/hypfer_draw.py +398 -132
- valetudo_map_parser/hypfer_handler.py +259 -241
- valetudo_map_parser/hypfer_rooms_handler.py +599 -0
- valetudo_map_parser/map_data.py +45 -64
- valetudo_map_parser/rand25_handler.py +429 -310
- valetudo_map_parser/reimg_draw.py +55 -74
- valetudo_map_parser/rooms_handler.py +470 -0
- valetudo_map_parser-0.1.9a1.dist-info/METADATA +93 -0
- valetudo_map_parser-0.1.9a1.dist-info/RECORD +27 -0
- {valetudo_map_parser-0.1.7.dist-info → valetudo_map_parser-0.1.9a1.dist-info}/WHEEL +1 -1
- valetudo_map_parser/images_utils.py +0 -398
- valetudo_map_parser-0.1.7.dist-info/METADATA +0 -23
- valetudo_map_parser-0.1.7.dist-info/RECORD +0 -20
- {valetudo_map_parser-0.1.7.dist-info → valetudo_map_parser-0.1.9a1.dist-info}/LICENSE +0 -0
- {valetudo_map_parser-0.1.7.dist-info → valetudo_map_parser-0.1.9a1.dist-info}/NOTICE.txt +0 -0
@@ -2,153 +2,92 @@
|
|
2
2
|
Hypfer Image Handler Class.
|
3
3
|
It returns the PIL PNG image frame relative to the Map Data extrapolated from the vacuum json.
|
4
4
|
It also returns calibration, rooms data to the card and other images information to the camera.
|
5
|
-
Version:
|
5
|
+
Version: 0.1.9
|
6
6
|
"""
|
7
7
|
|
8
8
|
from __future__ import annotations
|
9
9
|
|
10
10
|
import json
|
11
|
-
import logging
|
12
11
|
|
13
12
|
from PIL import Image
|
14
13
|
|
14
|
+
from .config.auto_crop import AutoCrop
|
15
|
+
from .config.drawable_elements import DrawableElement
|
16
|
+
from .config.shared import CameraShared
|
15
17
|
from .config.types import (
|
18
|
+
COLORS,
|
19
|
+
LOGGER,
|
16
20
|
CalibrationPoints,
|
17
|
-
|
18
|
-
ImageSize,
|
19
|
-
RobotPosition,
|
21
|
+
Colors,
|
20
22
|
RoomsProperties,
|
23
|
+
RoomStore,
|
21
24
|
)
|
22
|
-
from .config.
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
ImageUtils as ImUtils,
|
28
|
-
resize_to_aspect_ratio,
|
29
|
-
)
|
30
|
-
from .hypfer_draw import (
|
31
|
-
ImageDraw as ImDraw,
|
25
|
+
from .config.utils import (
|
26
|
+
BaseHandler,
|
27
|
+
initialize_drawing_config,
|
28
|
+
manage_drawable_elements,
|
29
|
+
prepare_resize_params,
|
32
30
|
)
|
33
|
-
from .
|
34
|
-
|
35
|
-
|
31
|
+
from .hypfer_draw import ImageDraw as ImDraw
|
32
|
+
from .map_data import ImageData
|
33
|
+
from .rooms_handler import RoomsHandler
|
36
34
|
|
37
35
|
|
38
|
-
class HypferMapImageHandler:
|
36
|
+
class HypferMapImageHandler(BaseHandler, AutoCrop):
|
39
37
|
"""Map Image Handler Class.
|
40
38
|
This class is used to handle the image data and the drawing of the map."""
|
41
39
|
|
42
40
|
def __init__(self, shared_data: CameraShared):
|
43
41
|
"""Initialize the Map Image Handler."""
|
42
|
+
BaseHandler.__init__(self)
|
44
43
|
self.shared = shared_data # camera shared data
|
45
|
-
self
|
46
|
-
self.auto_crop = None # auto crop data to be calculate once.
|
44
|
+
AutoCrop.__init__(self, self)
|
47
45
|
self.calibration_data = None # camera shared data.
|
48
|
-
self.charger_pos = None # vacuum data charger position.
|
49
|
-
self.crop_area = None # module shared for calibration data.
|
50
|
-
self.crop_img_size = None # size of the image cropped calibration data.
|
51
46
|
self.data = ImageData # imported Image Data Module.
|
52
|
-
|
47
|
+
|
48
|
+
# Initialize drawing configuration using the shared utility function
|
49
|
+
self.drawing_config, self.draw, self.enhanced_draw = initialize_drawing_config(
|
50
|
+
self
|
51
|
+
)
|
52
|
+
|
53
53
|
self.go_to = None # vacuum go to data
|
54
54
|
self.img_hash = None # hash of the image calculated to check differences.
|
55
55
|
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
56
|
self.active_zones = None # vacuum active zones.
|
65
|
-
self.frame_number = 0 # frame number of the image.
|
66
|
-
self.max_frames = 1024
|
67
|
-
self.zooming = False # zooming the image.
|
68
57
|
self.svg_wait = False # SVG image creation wait.
|
69
|
-
self.
|
70
|
-
self.trim_left = 0 # memory stored trims calculated once.
|
71
|
-
self.trim_right = 0 # memory stored trims calculated once.
|
72
|
-
self.trim_up = 0 # memory stored trims calculated once.
|
73
|
-
self.offset_top = self.shared.offset_top # offset top
|
74
|
-
self.offset_bottom = self.shared.offset_down # offset bottom
|
75
|
-
self.offset_left = self.shared.offset_left # offset left
|
76
|
-
self.offset_right = self.shared.offset_right # offset right
|
77
|
-
self.offset_x = 0 # offset x for the aspect ratio.
|
78
|
-
self.offset_y = 0 # offset y for the aspect ratio.
|
79
|
-
self.imd = ImDraw(self)
|
80
|
-
self.imu = ImUtils(self)
|
81
|
-
self.ac = AutoCrop(self)
|
82
|
-
self.colors_manager = ColorsManagment({})
|
83
|
-
self.rooms_colors = self.colors_manager.get_rooms_colors()
|
58
|
+
self.imd = ImDraw(self) # Image Draw class.
|
84
59
|
self.color_grey = (128, 128, 128, 255)
|
60
|
+
self.file_name = self.shared.file_name # file name of the vacuum.
|
61
|
+
self.rooms_handler = RoomsHandler(
|
62
|
+
self.file_name, self.drawing_config
|
63
|
+
) # Room data handler
|
64
|
+
|
65
|
+
@staticmethod
|
66
|
+
def get_corners(x_max, x_min, y_max, y_min):
|
67
|
+
"""Get the corners of the room."""
|
68
|
+
return [(x_min, y_min), (x_max, y_min), (x_max, y_max), (x_min, y_max)]
|
85
69
|
|
86
70
|
async def async_extract_room_properties(self, json_data) -> RoomsProperties:
|
87
71
|
"""Extract room properties from the JSON data."""
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
pixel_size = json_data.get("pixelSize", [])
|
92
|
-
|
93
|
-
for layer in json_data.get("layers", []):
|
94
|
-
if layer["__class"] == "MapLayer":
|
95
|
-
meta_data = layer.get("metaData", {})
|
96
|
-
segment_id = meta_data.get("segmentId")
|
97
|
-
if segment_id is not None:
|
98
|
-
name = meta_data.get("name")
|
99
|
-
compressed_pixels = layer.get("compressedPixels", [])
|
100
|
-
pixels = self.data.sublist(compressed_pixels, 3)
|
101
|
-
# Calculate x and y min/max from compressed pixels
|
102
|
-
(
|
103
|
-
x_min,
|
104
|
-
y_min,
|
105
|
-
x_max,
|
106
|
-
y_max,
|
107
|
-
) = await self.data.async_get_rooms_coordinates(pixels, pixel_size)
|
108
|
-
corners = [
|
109
|
-
(x_min, y_min),
|
110
|
-
(x_max, y_min),
|
111
|
-
(x_max, y_max),
|
112
|
-
(x_min, y_max),
|
113
|
-
]
|
114
|
-
room_id = str(segment_id)
|
115
|
-
self.rooms_pos.append(
|
116
|
-
{
|
117
|
-
"name": name,
|
118
|
-
"corners": corners,
|
119
|
-
}
|
120
|
-
)
|
121
|
-
room_properties[room_id] = {
|
122
|
-
"number": segment_id,
|
123
|
-
"outline": corners,
|
124
|
-
"name": name,
|
125
|
-
"x": ((x_min + x_max) // 2),
|
126
|
-
"y": ((y_min + y_max) // 2),
|
127
|
-
}
|
72
|
+
room_properties = await self.rooms_handler.async_extract_room_properties(
|
73
|
+
json_data
|
74
|
+
)
|
128
75
|
if room_properties:
|
129
|
-
|
76
|
+
rooms = RoomStore(self.file_name, room_properties)
|
77
|
+
LOGGER.debug(
|
78
|
+
"%s: Rooms data extracted! %s", self.file_name, rooms.get_rooms()
|
79
|
+
)
|
80
|
+
# Convert room_properties to the format expected by async_get_robot_in_room
|
81
|
+
self.rooms_pos = []
|
82
|
+
for room_id, room_data in room_properties.items():
|
83
|
+
self.rooms_pos.append(
|
84
|
+
{"name": room_data["name"], "outline": room_data["outline"]}
|
85
|
+
)
|
130
86
|
else:
|
131
|
-
|
87
|
+
LOGGER.debug("%s: Rooms data not available!", self.file_name)
|
132
88
|
self.rooms_pos = None
|
133
89
|
return room_properties
|
134
90
|
|
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
91
|
# noinspection PyUnresolvedReferences,PyUnboundLocalVariable
|
153
92
|
async def async_get_image_from_json(
|
154
93
|
self,
|
@@ -161,11 +100,13 @@ class HypferMapImageHandler:
|
|
161
100
|
@return Image.Image: The image to display.
|
162
101
|
"""
|
163
102
|
# Initialize the colors.
|
164
|
-
colors =
|
103
|
+
colors: Colors = {
|
104
|
+
name: self.shared.user_colors[idx] for idx, name in enumerate(COLORS)
|
105
|
+
}
|
165
106
|
# Check if the JSON data is not None else process the image.
|
166
107
|
try:
|
167
108
|
if m_json is not None:
|
168
|
-
|
109
|
+
LOGGER.debug("%s: Creating Image.", self.file_name)
|
169
110
|
# buffer json data
|
170
111
|
self.json_data = m_json
|
171
112
|
# Get the image size from the JSON data
|
@@ -190,35 +131,120 @@ class HypferMapImageHandler:
|
|
190
131
|
# Get the pixels size and layers from the JSON data
|
191
132
|
pixel_size = int(m_json["pixelSize"])
|
192
133
|
layers, active = self.data.find_layers(m_json["layers"], {}, [])
|
193
|
-
new_frame_hash = await self.
|
134
|
+
new_frame_hash = await self.calculate_array_hash(layers, active)
|
194
135
|
if self.frame_number == 0:
|
195
136
|
self.img_hash = new_frame_hash
|
196
|
-
# empty image
|
137
|
+
# Create empty image
|
197
138
|
img_np_array = await self.draw.create_empty_image(
|
198
|
-
size_x, size_y, colors["
|
139
|
+
size_x, size_y, colors["background"]
|
199
140
|
)
|
200
|
-
#
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
141
|
+
# Draw layers and segments if enabled
|
142
|
+
room_id = 0
|
143
|
+
# Keep track of disabled rooms to skip their walls later
|
144
|
+
disabled_rooms = set()
|
145
|
+
|
146
|
+
if self.drawing_config.is_enabled(DrawableElement.FLOOR):
|
147
|
+
# First pass: identify disabled rooms
|
148
|
+
for layer_type, compressed_pixels_list in layers.items():
|
149
|
+
# Check if this is a room layer
|
150
|
+
if layer_type == "segment":
|
151
|
+
# The room_id is the current room being processed (0-based index)
|
152
|
+
# We need to check if ROOM_{room_id+1} is enabled (1-based in DrawableElement)
|
153
|
+
current_room_id = room_id + 1
|
154
|
+
if 1 <= current_room_id <= 15:
|
155
|
+
room_element = getattr(
|
156
|
+
DrawableElement, f"ROOM_{current_room_id}", None
|
157
|
+
)
|
158
|
+
if (
|
159
|
+
room_element
|
160
|
+
and not self.drawing_config.is_enabled(
|
161
|
+
room_element
|
162
|
+
)
|
163
|
+
):
|
164
|
+
# Add this room to the disabled rooms set
|
165
|
+
disabled_rooms.add(room_id)
|
166
|
+
LOGGER.debug(
|
167
|
+
"%s: Room %d is disabled and will be skipped",
|
168
|
+
self.file_name,
|
169
|
+
current_room_id,
|
170
|
+
)
|
171
|
+
room_id = (
|
172
|
+
room_id + 1
|
173
|
+
) % 16 # Cycle room_id back to 0 after 15
|
174
|
+
|
175
|
+
# Reset room_id for the actual drawing pass
|
176
|
+
room_id = 0
|
177
|
+
|
178
|
+
# Second pass: draw enabled rooms and walls
|
179
|
+
for layer_type, compressed_pixels_list in layers.items():
|
180
|
+
# Check if this is a room layer
|
181
|
+
is_room_layer = layer_type == "segment"
|
182
|
+
|
183
|
+
# If it's a room layer, check if the specific room is enabled
|
184
|
+
if is_room_layer:
|
185
|
+
# The room_id is the current room being processed (0-based index)
|
186
|
+
# We need to check if ROOM_{room_id+1} is enabled (1-based in DrawableElement)
|
187
|
+
current_room_id = room_id + 1
|
188
|
+
if 1 <= current_room_id <= 15:
|
189
|
+
room_element = getattr(
|
190
|
+
DrawableElement, f"ROOM_{current_room_id}", None
|
191
|
+
)
|
192
|
+
|
193
|
+
# Skip this room if it's disabled
|
194
|
+
if not self.drawing_config.is_enabled(room_element):
|
195
|
+
room_id = (
|
196
|
+
room_id + 1
|
197
|
+
) % 16 # Increment room_id even if we skip
|
198
|
+
continue
|
199
|
+
|
200
|
+
# Check if this is a wall layer and if walls are enabled
|
201
|
+
is_wall_layer = layer_type == "wall"
|
202
|
+
if is_wall_layer:
|
203
|
+
if not self.drawing_config.is_enabled(
|
204
|
+
DrawableElement.WALL
|
205
|
+
):
|
206
|
+
pass
|
207
|
+
|
208
|
+
# Draw the layer
|
209
|
+
(
|
210
|
+
room_id,
|
211
|
+
img_np_array,
|
212
|
+
) = await self.imd.async_draw_base_layer(
|
213
|
+
img_np_array,
|
214
|
+
compressed_pixels_list,
|
215
|
+
layer_type,
|
216
|
+
colors["wall"],
|
217
|
+
colors["zone_clean"],
|
218
|
+
pixel_size,
|
219
|
+
disabled_rooms if layer_type == "wall" else None,
|
220
|
+
)
|
221
|
+
|
222
|
+
# Update element map for this layer
|
223
|
+
if is_room_layer and 0 < room_id <= 15:
|
224
|
+
# Mark the room in the element map
|
225
|
+
room_element = getattr(
|
226
|
+
DrawableElement, f"ROOM_{room_id}", None
|
227
|
+
)
|
228
|
+
|
229
|
+
# Draw the virtual walls if enabled
|
230
|
+
if self.drawing_config.is_enabled(DrawableElement.VIRTUAL_WALL):
|
231
|
+
img_np_array = await self.imd.async_draw_virtual_walls(
|
232
|
+
m_json, img_np_array, colors["no_go"]
|
209
233
|
)
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
234
|
+
|
235
|
+
# Draw charger if enabled
|
236
|
+
if self.drawing_config.is_enabled(DrawableElement.CHARGER):
|
237
|
+
img_np_array = await self.imd.async_draw_charger(
|
238
|
+
img_np_array, entity_dict, colors["charger"]
|
239
|
+
)
|
240
|
+
|
241
|
+
# Draw obstacles if enabled
|
242
|
+
if self.drawing_config.is_enabled(DrawableElement.OBSTACLE):
|
243
|
+
self.shared.obstacles_pos = self.data.get_obstacles(entity_dict)
|
244
|
+
if self.shared.obstacles_pos:
|
245
|
+
img_np_array = await self.imd.async_draw_obstacle(
|
246
|
+
img_np_array, self.shared.obstacles_pos, colors["no_go"]
|
247
|
+
)
|
222
248
|
# Robot and rooms position
|
223
249
|
if (room_id > 0) and not self.room_propriety:
|
224
250
|
self.room_propriety = await self.async_extract_room_properties(
|
@@ -230,95 +256,109 @@ class HypferMapImageHandler:
|
|
230
256
|
robot_y=(robot_position[1]),
|
231
257
|
angle=robot_position_angle,
|
232
258
|
)
|
233
|
-
|
259
|
+
LOGGER.info("%s: Completed base Layers", self.file_name)
|
234
260
|
# Copy the new array in base layer.
|
235
|
-
self.img_base_layer = await self.
|
261
|
+
self.img_base_layer = await self.async_copy_array(img_np_array)
|
236
262
|
self.shared.frame_number = self.frame_number
|
237
263
|
self.frame_number += 1
|
238
264
|
if (self.frame_number >= self.max_frames) or (
|
239
265
|
new_frame_hash != self.img_hash
|
240
266
|
):
|
241
267
|
self.frame_number = 0
|
242
|
-
|
268
|
+
LOGGER.debug(
|
243
269
|
"%s: %s at Frame Number: %s",
|
244
270
|
self.file_name,
|
245
271
|
str(self.json_id),
|
246
272
|
str(self.frame_number),
|
247
273
|
)
|
248
274
|
# Copy the base layer to the new image.
|
249
|
-
img_np_array = await self.
|
275
|
+
img_np_array = await self.async_copy_array(self.img_base_layer)
|
250
276
|
# All below will be drawn at each frame.
|
251
|
-
# Draw zones if any
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
)
|
262
|
-
|
263
|
-
|
264
|
-
|
277
|
+
# Draw zones if any and if enabled
|
278
|
+
if self.drawing_config.is_enabled(DrawableElement.RESTRICTED_AREA):
|
279
|
+
img_np_array = await self.imd.async_draw_zones(
|
280
|
+
m_json,
|
281
|
+
img_np_array,
|
282
|
+
colors["zone_clean"],
|
283
|
+
colors["no_go"],
|
284
|
+
)
|
285
|
+
|
286
|
+
# Draw the go_to target flag if enabled
|
287
|
+
if self.drawing_config.is_enabled(DrawableElement.GO_TO_TARGET):
|
288
|
+
img_np_array = await self.imd.draw_go_to_flag(
|
289
|
+
img_np_array, entity_dict, colors["go_to"]
|
290
|
+
)
|
291
|
+
|
292
|
+
# Draw path prediction and paths if enabled
|
293
|
+
path_enabled = self.drawing_config.is_enabled(DrawableElement.PATH)
|
294
|
+
LOGGER.info(
|
295
|
+
"%s: PATH element enabled: %s", self.file_name, path_enabled
|
265
296
|
)
|
297
|
+
if path_enabled:
|
298
|
+
LOGGER.info("%s: Drawing path", self.file_name)
|
299
|
+
img_np_array = await self.imd.async_draw_paths(
|
300
|
+
img_np_array, m_json, colors["move"], self.color_grey
|
301
|
+
)
|
302
|
+
else:
|
303
|
+
LOGGER.info("%s: Skipping path drawing", self.file_name)
|
304
|
+
|
266
305
|
# Check if the robot is docked.
|
267
306
|
if self.shared.vacuum_state == "docked":
|
268
307
|
# Adjust the robot angle.
|
269
308
|
robot_position_angle -= 180
|
270
309
|
|
271
|
-
if
|
310
|
+
# Draw the robot if enabled
|
311
|
+
if robot_pos and self.drawing_config.is_enabled(DrawableElement.ROBOT):
|
312
|
+
# Get robot color (allows for customization)
|
313
|
+
robot_color = self.drawing_config.get_property(
|
314
|
+
DrawableElement.ROBOT, "color", colors["robot"]
|
315
|
+
)
|
316
|
+
|
272
317
|
# Draw the robot
|
273
318
|
img_np_array = await self.draw.robot(
|
274
319
|
layers=img_np_array,
|
275
320
|
x=robot_position[0],
|
276
321
|
y=robot_position[1],
|
277
322
|
angle=robot_position_angle,
|
278
|
-
fill=
|
323
|
+
fill=robot_color,
|
279
324
|
robot_state=self.shared.vacuum_state,
|
280
325
|
)
|
326
|
+
|
327
|
+
# Update element map for robot position
|
328
|
+
if (
|
329
|
+
hasattr(self.shared, "element_map")
|
330
|
+
and self.shared.element_map is not None
|
331
|
+
):
|
332
|
+
update_element_map_with_robot(
|
333
|
+
self.shared.element_map,
|
334
|
+
robot_position,
|
335
|
+
DrawableElement.ROBOT,
|
336
|
+
)
|
281
337
|
# Resize the image
|
282
|
-
img_np_array = await self.
|
338
|
+
img_np_array = await self.async_auto_trim_and_zoom_image(
|
283
339
|
img_np_array,
|
284
|
-
colors["
|
340
|
+
colors["background"],
|
285
341
|
int(self.shared.margins),
|
286
342
|
int(self.shared.image_rotate),
|
287
343
|
self.zooming,
|
288
344
|
)
|
289
345
|
# If the image is None return None and log the error.
|
290
346
|
if img_np_array is None:
|
291
|
-
|
347
|
+
LOGGER.warning("%s: Image array is None.", self.file_name)
|
292
348
|
return None
|
293
349
|
|
294
350
|
# Convert the numpy array to a PIL image
|
295
351
|
pil_img = Image.fromarray(img_np_array, mode="RGBA")
|
296
352
|
del img_np_array
|
297
353
|
# reduce the image size if the zoomed image is bigger then the original.
|
298
|
-
if (
|
299
|
-
self
|
300
|
-
|
301
|
-
and self.zooming
|
302
|
-
and self.shared.image_zoom_lock_ratio
|
303
|
-
or self.shared.image_aspect_ratio != "None"
|
304
|
-
):
|
305
|
-
width = self.shared.image_ref_width
|
306
|
-
height = self.shared.image_ref_height
|
307
|
-
(
|
308
|
-
resized_image,
|
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,
|
316
|
-
)
|
354
|
+
if self.check_zoom_and_aspect_ratio():
|
355
|
+
resize_params = prepare_resize_params(self, pil_img, False)
|
356
|
+
resized_image = await self.async_resize_images(resize_params)
|
317
357
|
return resized_image
|
318
|
-
|
358
|
+
LOGGER.debug("%s: Frame Completed.", self.file_name)
|
319
359
|
return pil_img
|
320
360
|
except (RuntimeError, RuntimeWarning) as e:
|
321
|
-
|
361
|
+
LOGGER.warning(
|
322
362
|
"%s: Error %s during image creation.",
|
323
363
|
self.file_name,
|
324
364
|
str(e),
|
@@ -326,38 +366,18 @@ class HypferMapImageHandler:
|
|
326
366
|
)
|
327
367
|
return None
|
328
368
|
|
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
369
|
async def async_get_rooms_attributes(self) -> RoomsProperties:
|
350
370
|
"""Get the rooms attributes from the JSON data.
|
351
371
|
:return: The rooms attribute's."""
|
352
372
|
if self.room_propriety:
|
353
373
|
return self.room_propriety
|
354
374
|
if self.json_data:
|
355
|
-
|
375
|
+
LOGGER.debug("Checking %s Rooms data..", self.file_name)
|
356
376
|
self.room_propriety = await self.async_extract_room_properties(
|
357
377
|
self.json_data
|
358
378
|
)
|
359
379
|
if self.room_propriety:
|
360
|
-
|
380
|
+
LOGGER.debug("Got %s Rooms Attributes.", self.file_name)
|
361
381
|
return self.room_propriety
|
362
382
|
|
363
383
|
def get_calibration_data(self) -> CalibrationPoints:
|
@@ -365,20 +385,12 @@ class HypferMapImageHandler:
|
|
365
385
|
this will create the attribute calibration points."""
|
366
386
|
calibration_data = []
|
367
387
|
rotation_angle = self.shared.image_rotate
|
368
|
-
|
388
|
+
LOGGER.info("Getting %s Calibrations points.", self.file_name)
|
369
389
|
|
370
390
|
# 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
|
-
]
|
391
|
+
map_points = self.get_map_points()
|
380
392
|
# Calculate the calibration points in the vacuum coordinate system
|
381
|
-
vacuum_points = self.
|
393
|
+
vacuum_points = self.get_vacuum_points(rotation_angle)
|
382
394
|
|
383
395
|
# Create the calibration data for each point
|
384
396
|
for vacuum_point, map_point in zip(vacuum_points, map_points):
|
@@ -387,32 +399,38 @@ class HypferMapImageHandler:
|
|
387
399
|
del vacuum_points, map_points, calibration_point, rotation_angle # free memory.
|
388
400
|
return calibration_data
|
389
401
|
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
+
# Element selection methods
|
403
|
+
def enable_element(self, element_code: DrawableElement) -> None:
|
404
|
+
"""Enable drawing of a specific element."""
|
405
|
+
self.drawing_config.enable_element(element_code)
|
406
|
+
LOGGER.info(
|
407
|
+
"%s: Enabled element %s, now enabled: %s",
|
408
|
+
self.file_name,
|
409
|
+
element_code.name,
|
410
|
+
self.drawing_config.is_enabled(element_code),
|
411
|
+
)
|
412
|
+
|
413
|
+
def disable_element(self, element_code: DrawableElement) -> None:
|
414
|
+
"""Disable drawing of a specific element."""
|
415
|
+
manage_drawable_elements(self, "disable", element_code=element_code)
|
416
|
+
|
417
|
+
def set_elements(self, element_codes: list[DrawableElement]) -> None:
|
418
|
+
"""Enable only the specified elements, disable all others."""
|
419
|
+
manage_drawable_elements(self, "set_elements", element_codes=element_codes)
|
420
|
+
|
421
|
+
def set_element_property(
|
422
|
+
self, element_code: DrawableElement, property_name: str, value
|
423
|
+
) -> None:
|
424
|
+
"""Set a drawing property for an element."""
|
425
|
+
manage_drawable_elements(
|
426
|
+
self,
|
427
|
+
"set_property",
|
428
|
+
element_code=element_code,
|
429
|
+
property_name=property_name,
|
430
|
+
value=value,
|
431
|
+
)
|
402
432
|
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
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
|
433
|
+
@staticmethod
|
434
|
+
async def async_copy_array(original_array):
|
435
|
+
"""Copy the array."""
|
436
|
+
return original_array.copy()
|