valetudo-map-parser 0.1.9b43__tar.gz → 0.1.9b45__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.9b43 → valetudo_map_parser-0.1.9b45}/PKG-INFO +2 -1
- {valetudo_map_parser-0.1.9b43 → valetudo_map_parser-0.1.9b45}/SCR/valetudo_map_parser/config/colors.py +88 -8
- {valetudo_map_parser-0.1.9b43 → valetudo_map_parser-0.1.9b45}/SCR/valetudo_map_parser/config/drawable.py +83 -5
- valetudo_map_parser-0.1.9b45/SCR/valetudo_map_parser/config/drawable_elements.py +887 -0
- {valetudo_map_parser-0.1.9b43 → valetudo_map_parser-0.1.9b45}/SCR/valetudo_map_parser/config/enhanced_drawable.py +4 -9
- valetudo_map_parser-0.1.9b45/SCR/valetudo_map_parser/config/optimized_element_map.py +363 -0
- valetudo_map_parser-0.1.9b45/SCR/valetudo_map_parser/config/room_outline.py +148 -0
- {valetudo_map_parser-0.1.9b43 → valetudo_map_parser-0.1.9b45}/SCR/valetudo_map_parser/config/shared.py +1 -0
- {valetudo_map_parser-0.1.9b43 → valetudo_map_parser-0.1.9b45}/SCR/valetudo_map_parser/config/utils.py +82 -65
- {valetudo_map_parser-0.1.9b43 → valetudo_map_parser-0.1.9b45}/SCR/valetudo_map_parser/hypfer_draw.py +30 -32
- {valetudo_map_parser-0.1.9b43 → valetudo_map_parser-0.1.9b45}/SCR/valetudo_map_parser/hypfer_handler.py +88 -100
- {valetudo_map_parser-0.1.9b43 → valetudo_map_parser-0.1.9b45}/SCR/valetudo_map_parser/map_data.py +0 -9
- {valetudo_map_parser-0.1.9b43 → valetudo_map_parser-0.1.9b45}/SCR/valetudo_map_parser/rand25_handler.py +173 -132
- valetudo_map_parser-0.1.9b45/SCR/valetudo_map_parser/utils/__init__.py +5 -0
- valetudo_map_parser-0.1.9b45/SCR/valetudo_map_parser/utils/color_utils.py +62 -0
- {valetudo_map_parser-0.1.9b43 → valetudo_map_parser-0.1.9b45}/pyproject.toml +2 -1
- valetudo_map_parser-0.1.9b43/SCR/valetudo_map_parser/config/drawable_elements.py +0 -312
- {valetudo_map_parser-0.1.9b43 → valetudo_map_parser-0.1.9b45}/LICENSE +0 -0
- {valetudo_map_parser-0.1.9b43 → valetudo_map_parser-0.1.9b45}/NOTICE.txt +0 -0
- {valetudo_map_parser-0.1.9b43 → valetudo_map_parser-0.1.9b45}/README.md +0 -0
- {valetudo_map_parser-0.1.9b43 → valetudo_map_parser-0.1.9b45}/SCR/valetudo_map_parser/__init__.py +0 -0
- {valetudo_map_parser-0.1.9b43 → valetudo_map_parser-0.1.9b45}/SCR/valetudo_map_parser/config/__init__.py +0 -0
- {valetudo_map_parser-0.1.9b43 → valetudo_map_parser-0.1.9b45}/SCR/valetudo_map_parser/config/auto_crop.py +0 -0
- {valetudo_map_parser-0.1.9b43 → valetudo_map_parser-0.1.9b45}/SCR/valetudo_map_parser/config/colors_man.py +0 -0
- {valetudo_map_parser-0.1.9b43 → valetudo_map_parser-0.1.9b45}/SCR/valetudo_map_parser/config/rand25_parser.py +0 -0
- {valetudo_map_parser-0.1.9b43 → valetudo_map_parser-0.1.9b45}/SCR/valetudo_map_parser/config/types.py +0 -0
- {valetudo_map_parser-0.1.9b43 → valetudo_map_parser-0.1.9b45}/SCR/valetudo_map_parser/py.typed +0 -0
- {valetudo_map_parser-0.1.9b43 → valetudo_map_parser-0.1.9b45}/SCR/valetudo_map_parser/reimg_draw.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: valetudo-map-parser
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.9b45
|
4
4
|
Summary: A Python library to parse Valetudo map data returning a PIL Image object.
|
5
5
|
License: Apache-2.0
|
6
6
|
Author: Sandro Cantarella
|
@@ -12,6 +12,7 @@ Classifier: Programming Language :: Python :: 3.12
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.13
|
13
13
|
Requires-Dist: Pillow (>=10.3.0)
|
14
14
|
Requires-Dist: numpy (>=1.26.4)
|
15
|
+
Requires-Dist: scipy (>=1.12.0)
|
15
16
|
Project-URL: Bug Tracker, https://github.com/sca075/Python-package-valetudo-map-parser/issues
|
16
17
|
Project-URL: Changelog, https://github.com/sca075/Python-package-valetudo-map-parser/releases
|
17
18
|
Project-URL: Homepage, https://github.com/sca075/Python-package-valetudo-map-parser
|
@@ -2,14 +2,10 @@
|
|
2
2
|
|
3
3
|
from __future__ import annotations
|
4
4
|
|
5
|
-
import logging
|
6
5
|
from enum import StrEnum
|
7
6
|
from typing import Dict, List, Tuple
|
8
7
|
|
9
|
-
|
10
|
-
_LOGGER = logging.getLogger(__name__)
|
11
|
-
|
12
|
-
Color = Tuple[int, int, int, int] # RGBA color definition
|
8
|
+
from .types import LOGGER, Color
|
13
9
|
|
14
10
|
|
15
11
|
class SupportedColor(StrEnum):
|
@@ -38,7 +34,7 @@ class DefaultColors:
|
|
38
34
|
|
39
35
|
COLORS_RGB: Dict[str, Tuple[int, int, int]] = {
|
40
36
|
SupportedColor.CHARGER: (255, 128, 0),
|
41
|
-
SupportedColor.PATH: (
|
37
|
+
SupportedColor.PATH: (50, 150, 255), # More vibrant blue for better visibility
|
42
38
|
SupportedColor.PREDICTED_PATH: (93, 109, 126),
|
43
39
|
SupportedColor.WALLS: (255, 255, 0),
|
44
40
|
SupportedColor.ROBOT: (255, 255, 204),
|
@@ -77,6 +73,11 @@ class DefaultColors:
|
|
77
73
|
DEFAULT_ALPHA: Dict[str, float] = {
|
78
74
|
f"alpha_{key}": 255.0 for key in COLORS_RGB.keys()
|
79
75
|
}
|
76
|
+
# Override specific alpha values
|
77
|
+
DEFAULT_ALPHA.update({
|
78
|
+
"alpha_color_path": 200.0, # Make path slightly transparent but still very visible
|
79
|
+
"alpha_color_wall": 150.0, # Keep walls semi-transparent
|
80
|
+
})
|
80
81
|
DEFAULT_ALPHA.update({f"alpha_room_{i}": 255.0 for i in range(16)})
|
81
82
|
|
82
83
|
@classmethod
|
@@ -142,6 +143,85 @@ class ColorsManagment:
|
|
142
143
|
"""
|
143
144
|
return (*rgb, int(alpha)) if rgb else (0, 0, 0, int(alpha))
|
144
145
|
|
146
|
+
@staticmethod
|
147
|
+
def blend_colors(background: Color, foreground: Color) -> Color:
|
148
|
+
"""
|
149
|
+
Blend foreground color with background color based on alpha values.
|
150
|
+
|
151
|
+
This is used when drawing elements that overlap on the map.
|
152
|
+
The alpha channel determines how much of the foreground color is visible.
|
153
|
+
|
154
|
+
:param background: Background RGBA color (r,g,b,a)
|
155
|
+
:param foreground: Foreground RGBA color (r,g,b,a) to blend on top
|
156
|
+
:return: Blended RGBA color
|
157
|
+
"""
|
158
|
+
# Extract components
|
159
|
+
bg_r, bg_g, bg_b, bg_a = background
|
160
|
+
fg_r, fg_g, fg_b, fg_a = foreground
|
161
|
+
|
162
|
+
# If foreground is fully opaque, just return it
|
163
|
+
if fg_a == 255:
|
164
|
+
return foreground
|
165
|
+
|
166
|
+
# If foreground is fully transparent, return background
|
167
|
+
if fg_a == 0:
|
168
|
+
return background
|
169
|
+
|
170
|
+
# Calculate alpha blending
|
171
|
+
# Convert alpha from [0-255] to [0-1] for calculations
|
172
|
+
fg_alpha = fg_a / 255.0
|
173
|
+
bg_alpha = bg_a / 255.0
|
174
|
+
|
175
|
+
# Calculate resulting alpha
|
176
|
+
out_alpha = fg_alpha + bg_alpha * (1 - fg_alpha)
|
177
|
+
|
178
|
+
# Avoid division by zero
|
179
|
+
if out_alpha < 0.0001:
|
180
|
+
return (0, 0, 0, 0) # Fully transparent result
|
181
|
+
|
182
|
+
# Calculate blended RGB components
|
183
|
+
out_r = int((fg_r * fg_alpha + bg_r * bg_alpha * (1 - fg_alpha)) / out_alpha)
|
184
|
+
out_g = int((fg_g * fg_alpha + bg_g * bg_alpha * (1 - fg_alpha)) / out_alpha)
|
185
|
+
out_b = int((fg_b * fg_alpha + bg_b * bg_alpha * (1 - fg_alpha)) / out_alpha)
|
186
|
+
|
187
|
+
# Convert alpha back to [0-255] range
|
188
|
+
out_a = int(out_alpha * 255)
|
189
|
+
|
190
|
+
# Ensure values are in valid range
|
191
|
+
out_r = max(0, min(255, out_r))
|
192
|
+
out_g = max(0, min(255, out_g))
|
193
|
+
out_b = max(0, min(255, out_b))
|
194
|
+
|
195
|
+
return (out_r, out_g, out_b, out_a)
|
196
|
+
|
197
|
+
@staticmethod
|
198
|
+
def sample_and_blend_color(array, x: int, y: int, foreground: Color) -> Color:
|
199
|
+
"""
|
200
|
+
Sample the background color from the array at coordinates (x,y) and blend with foreground color.
|
201
|
+
|
202
|
+
Args:
|
203
|
+
array: The RGBA numpy array representing the image
|
204
|
+
x, y: Coordinates to sample the background color from
|
205
|
+
foreground: Foreground RGBA color (r,g,b,a) to blend on top
|
206
|
+
|
207
|
+
Returns:
|
208
|
+
Blended RGBA color
|
209
|
+
"""
|
210
|
+
# Ensure coordinates are within bounds
|
211
|
+
if array is None:
|
212
|
+
return foreground
|
213
|
+
|
214
|
+
height, width = array.shape[:2]
|
215
|
+
if not (0 <= y < height and 0 <= x < width):
|
216
|
+
return foreground # Return foreground if coordinates are out of bounds
|
217
|
+
|
218
|
+
# Sample background color from the array
|
219
|
+
# The array is in RGBA format with shape (height, width, 4)
|
220
|
+
background = tuple(array[y, x])
|
221
|
+
|
222
|
+
# Blend the colors
|
223
|
+
return ColorsManagment.blend_colors(background, foreground)
|
224
|
+
|
145
225
|
def get_user_colors(self) -> List[Color]:
|
146
226
|
"""Return the list of RGBA colors for user-defined map elements."""
|
147
227
|
return self.user_colors
|
@@ -163,7 +243,7 @@ class ColorsManagment:
|
|
163
243
|
try:
|
164
244
|
return self.rooms_colors[room_index]
|
165
245
|
except (IndexError, KeyError):
|
166
|
-
|
246
|
+
LOGGER.warning("Room index %s not found, using default.", room_index)
|
167
247
|
r, g, b = DefaultColors.DEFAULT_ROOM_COLORS[f"color_room_{room_index}"]
|
168
248
|
a = DefaultColors.DEFAULT_ALPHA[f"alpha_room_{room_index}"]
|
169
249
|
return r, g, b, int(a)
|
@@ -173,7 +253,7 @@ class ColorsManagment:
|
|
173
253
|
index = list(SupportedColor).index(supported_color)
|
174
254
|
return self.user_colors[index]
|
175
255
|
except (IndexError, KeyError, ValueError):
|
176
|
-
|
256
|
+
LOGGER.warning(
|
177
257
|
"Color for %s not found. Returning default.", supported_color
|
178
258
|
)
|
179
259
|
return DefaultColors.get_rgba(supported_color, 255) # Transparent fallback
|
@@ -174,11 +174,47 @@ class Drawable:
|
|
174
174
|
y2: int,
|
175
175
|
color: Color,
|
176
176
|
width: int = 3,
|
177
|
+
blend: bool = True,
|
177
178
|
) -> NumpyArray:
|
178
179
|
"""
|
179
180
|
Draw a line on a NumPy array (layer) from point A to B using Bresenham's algorithm.
|
181
|
+
|
182
|
+
Args:
|
183
|
+
layer: The numpy array to draw on
|
184
|
+
x1, y1: Start point coordinates
|
185
|
+
x2, y2: End point coordinates
|
186
|
+
color: Color to draw with
|
187
|
+
width: Width of the line
|
188
|
+
blend: Whether to blend the color with the background
|
180
189
|
"""
|
190
|
+
from .colors import ColorsManagment
|
191
|
+
|
181
192
|
x1, y1, x2, y2 = int(x1), int(y1), int(x2), int(y2)
|
193
|
+
|
194
|
+
# Sample background color at the endpoints and blend with foreground color if requested
|
195
|
+
if blend:
|
196
|
+
# Sample at start point
|
197
|
+
if 0 <= y1 < layer.shape[0] and 0 <= x1 < layer.shape[1]:
|
198
|
+
start_blended_color = ColorsManagment.sample_and_blend_color(layer, x1, y1, color)
|
199
|
+
else:
|
200
|
+
start_blended_color = color
|
201
|
+
|
202
|
+
# Sample at end point
|
203
|
+
if 0 <= y2 < layer.shape[0] and 0 <= x2 < layer.shape[1]:
|
204
|
+
end_blended_color = ColorsManagment.sample_and_blend_color(layer, x2, y2, color)
|
205
|
+
else:
|
206
|
+
end_blended_color = color
|
207
|
+
|
208
|
+
# Use the average of the two blended colors
|
209
|
+
blended_color = (
|
210
|
+
(start_blended_color[0] + end_blended_color[0]) // 2,
|
211
|
+
(start_blended_color[1] + end_blended_color[1]) // 2,
|
212
|
+
(start_blended_color[2] + end_blended_color[2]) // 2,
|
213
|
+
(start_blended_color[3] + end_blended_color[3]) // 2
|
214
|
+
)
|
215
|
+
else:
|
216
|
+
blended_color = color
|
217
|
+
|
182
218
|
dx = abs(x2 - x1)
|
183
219
|
dy = abs(y2 - y1)
|
184
220
|
sx = 1 if x1 < x2 else -1
|
@@ -189,7 +225,7 @@ class Drawable:
|
|
189
225
|
for i in range(-width // 2, (width + 1) // 2):
|
190
226
|
for j in range(-width // 2, (width + 1) // 2):
|
191
227
|
if 0 <= x1 + i < layer.shape[1] and 0 <= y1 + j < layer.shape[0]:
|
192
|
-
layer[y1 + j, x1 + i] =
|
228
|
+
layer[y1 + j, x1 + i] = blended_color
|
193
229
|
if x1 == x2 and y1 == y2:
|
194
230
|
break
|
195
231
|
e2 = 2 * err
|
@@ -220,12 +256,39 @@ class Drawable:
|
|
220
256
|
"""
|
221
257
|
Join the coordinates creating a continuous line (path).
|
222
258
|
"""
|
259
|
+
import logging
|
260
|
+
from .colors import ColorsManagment
|
261
|
+
|
262
|
+
_LOGGER = logging.getLogger(__name__)
|
263
|
+
_LOGGER.debug("Drawing lines with %d coordinates, width %d, color %s", len(coords), width, color)
|
223
264
|
for coord in coords:
|
224
265
|
x0, y0 = coord[0]
|
225
266
|
try:
|
226
267
|
x1, y1 = coord[1]
|
227
268
|
except IndexError:
|
228
269
|
x1, y1 = x0, y0
|
270
|
+
_LOGGER.debug("Drawing line from (%d, %d) to (%d, %d)", x0, y0, x1, y1)
|
271
|
+
|
272
|
+
# Sample background color at the endpoints and blend with foreground color
|
273
|
+
# This is more efficient than sampling at every pixel
|
274
|
+
if 0 <= y0 < arr.shape[0] and 0 <= x0 < arr.shape[1]:
|
275
|
+
start_blended_color = ColorsManagment.sample_and_blend_color(arr, x0, y0, color)
|
276
|
+
else:
|
277
|
+
start_blended_color = color
|
278
|
+
|
279
|
+
if 0 <= y1 < arr.shape[0] and 0 <= x1 < arr.shape[1]:
|
280
|
+
end_blended_color = ColorsManagment.sample_and_blend_color(arr, x1, y1, color)
|
281
|
+
else:
|
282
|
+
end_blended_color = color
|
283
|
+
|
284
|
+
# Use the average of the two blended colors
|
285
|
+
blended_color = (
|
286
|
+
(start_blended_color[0] + end_blended_color[0]) // 2,
|
287
|
+
(start_blended_color[1] + end_blended_color[1]) // 2,
|
288
|
+
(start_blended_color[2] + end_blended_color[2]) // 2,
|
289
|
+
(start_blended_color[3] + end_blended_color[3]) // 2
|
290
|
+
)
|
291
|
+
|
229
292
|
dx = abs(x1 - x0)
|
230
293
|
dy = abs(y1 - y0)
|
231
294
|
sx = 1 if x0 < x1 else -1
|
@@ -248,8 +311,9 @@ class Drawable:
|
|
248
311
|
x, y = pixel
|
249
312
|
for i in range(width):
|
250
313
|
for j in range(width):
|
251
|
-
|
252
|
-
|
314
|
+
px, py = x + i, y + j
|
315
|
+
if 0 <= px < arr.shape[1] and 0 <= py < arr.shape[0]:
|
316
|
+
arr[py, px] = blended_color
|
253
317
|
return arr
|
254
318
|
|
255
319
|
@staticmethod
|
@@ -356,8 +420,10 @@ class Drawable:
|
|
356
420
|
@staticmethod
|
357
421
|
async def zones(layers: NumpyArray, coordinates, color: Color) -> NumpyArray:
|
358
422
|
"""
|
359
|
-
Draw the zones on the input layer.
|
423
|
+
Draw the zones on the input layer with color blending.
|
360
424
|
"""
|
425
|
+
from .colors import ColorsManagment
|
426
|
+
|
361
427
|
dot_radius = 1 # Number of pixels for the dot
|
362
428
|
dot_spacing = 4 # Space between dots
|
363
429
|
for zone in coordinates:
|
@@ -366,10 +432,22 @@ class Drawable:
|
|
366
432
|
max_x = max(points[::2])
|
367
433
|
min_y = min(points[1::2])
|
368
434
|
max_y = max(points[1::2])
|
435
|
+
|
436
|
+
# Sample a point from the zone to get the background color
|
437
|
+
# Use the center of the zone for sampling
|
438
|
+
sample_x = (min_x + max_x) // 2
|
439
|
+
sample_y = (min_y + max_y) // 2
|
440
|
+
|
441
|
+
# Blend the color with the background color at the sample point
|
442
|
+
if 0 <= sample_y < layers.shape[0] and 0 <= sample_x < layers.shape[1]:
|
443
|
+
blended_color = ColorsManagment.sample_and_blend_color(layers, sample_x, sample_y, color)
|
444
|
+
else:
|
445
|
+
blended_color = color
|
446
|
+
|
369
447
|
for y in range(min_y, max_y, dot_spacing):
|
370
448
|
for x in range(min_x, max_x, dot_spacing):
|
371
449
|
for _ in range(dot_radius):
|
372
|
-
layers = Drawable._ellipse(layers, (x, y), dot_radius,
|
450
|
+
layers = Drawable._ellipse(layers, (x, y), dot_radius, blended_color)
|
373
451
|
return layers
|
374
452
|
|
375
453
|
@staticmethod
|