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.
Files changed (28) hide show
  1. {valetudo_map_parser-0.1.9b43 → valetudo_map_parser-0.1.9b45}/PKG-INFO +2 -1
  2. {valetudo_map_parser-0.1.9b43 → valetudo_map_parser-0.1.9b45}/SCR/valetudo_map_parser/config/colors.py +88 -8
  3. {valetudo_map_parser-0.1.9b43 → valetudo_map_parser-0.1.9b45}/SCR/valetudo_map_parser/config/drawable.py +83 -5
  4. valetudo_map_parser-0.1.9b45/SCR/valetudo_map_parser/config/drawable_elements.py +887 -0
  5. {valetudo_map_parser-0.1.9b43 → valetudo_map_parser-0.1.9b45}/SCR/valetudo_map_parser/config/enhanced_drawable.py +4 -9
  6. valetudo_map_parser-0.1.9b45/SCR/valetudo_map_parser/config/optimized_element_map.py +363 -0
  7. valetudo_map_parser-0.1.9b45/SCR/valetudo_map_parser/config/room_outline.py +148 -0
  8. {valetudo_map_parser-0.1.9b43 → valetudo_map_parser-0.1.9b45}/SCR/valetudo_map_parser/config/shared.py +1 -0
  9. {valetudo_map_parser-0.1.9b43 → valetudo_map_parser-0.1.9b45}/SCR/valetudo_map_parser/config/utils.py +82 -65
  10. {valetudo_map_parser-0.1.9b43 → valetudo_map_parser-0.1.9b45}/SCR/valetudo_map_parser/hypfer_draw.py +30 -32
  11. {valetudo_map_parser-0.1.9b43 → valetudo_map_parser-0.1.9b45}/SCR/valetudo_map_parser/hypfer_handler.py +88 -100
  12. {valetudo_map_parser-0.1.9b43 → valetudo_map_parser-0.1.9b45}/SCR/valetudo_map_parser/map_data.py +0 -9
  13. {valetudo_map_parser-0.1.9b43 → valetudo_map_parser-0.1.9b45}/SCR/valetudo_map_parser/rand25_handler.py +173 -132
  14. valetudo_map_parser-0.1.9b45/SCR/valetudo_map_parser/utils/__init__.py +5 -0
  15. valetudo_map_parser-0.1.9b45/SCR/valetudo_map_parser/utils/color_utils.py +62 -0
  16. {valetudo_map_parser-0.1.9b43 → valetudo_map_parser-0.1.9b45}/pyproject.toml +2 -1
  17. valetudo_map_parser-0.1.9b43/SCR/valetudo_map_parser/config/drawable_elements.py +0 -312
  18. {valetudo_map_parser-0.1.9b43 → valetudo_map_parser-0.1.9b45}/LICENSE +0 -0
  19. {valetudo_map_parser-0.1.9b43 → valetudo_map_parser-0.1.9b45}/NOTICE.txt +0 -0
  20. {valetudo_map_parser-0.1.9b43 → valetudo_map_parser-0.1.9b45}/README.md +0 -0
  21. {valetudo_map_parser-0.1.9b43 → valetudo_map_parser-0.1.9b45}/SCR/valetudo_map_parser/__init__.py +0 -0
  22. {valetudo_map_parser-0.1.9b43 → valetudo_map_parser-0.1.9b45}/SCR/valetudo_map_parser/config/__init__.py +0 -0
  23. {valetudo_map_parser-0.1.9b43 → valetudo_map_parser-0.1.9b45}/SCR/valetudo_map_parser/config/auto_crop.py +0 -0
  24. {valetudo_map_parser-0.1.9b43 → valetudo_map_parser-0.1.9b45}/SCR/valetudo_map_parser/config/colors_man.py +0 -0
  25. {valetudo_map_parser-0.1.9b43 → valetudo_map_parser-0.1.9b45}/SCR/valetudo_map_parser/config/rand25_parser.py +0 -0
  26. {valetudo_map_parser-0.1.9b43 → valetudo_map_parser-0.1.9b45}/SCR/valetudo_map_parser/config/types.py +0 -0
  27. {valetudo_map_parser-0.1.9b43 → valetudo_map_parser-0.1.9b45}/SCR/valetudo_map_parser/py.typed +0 -0
  28. {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.9b43
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: (238, 247, 255),
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
- _LOGGER.warning("Room index %s not found, using default.", room_index)
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
- _LOGGER.warning(
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] = color
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
- if 0 <= x + i < arr.shape[1] and 0 <= y + j < arr.shape[0]:
252
- arr[y + j, x + i] = color
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, color)
450
+ layers = Drawable._ellipse(layers, (x, y), dot_radius, blended_color)
373
451
  return layers
374
452
 
375
453
  @staticmethod