valetudo-map-parser 0.1.9b69__tar.gz → 0.1.9b70__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 (27) hide show
  1. {valetudo_map_parser-0.1.9b69 → valetudo_map_parser-0.1.9b70}/PKG-INFO +1 -1
  2. {valetudo_map_parser-0.1.9b69 → valetudo_map_parser-0.1.9b70}/SCR/valetudo_map_parser/config/drawable.py +43 -210
  3. {valetudo_map_parser-0.1.9b69 → valetudo_map_parser-0.1.9b70}/SCR/valetudo_map_parser/hypfer_draw.py +1 -2
  4. {valetudo_map_parser-0.1.9b69 → valetudo_map_parser-0.1.9b70}/SCR/valetudo_map_parser/hypfer_handler.py +19 -8
  5. {valetudo_map_parser-0.1.9b69 → valetudo_map_parser-0.1.9b70}/pyproject.toml +1 -1
  6. {valetudo_map_parser-0.1.9b69 → valetudo_map_parser-0.1.9b70}/LICENSE +0 -0
  7. {valetudo_map_parser-0.1.9b69 → valetudo_map_parser-0.1.9b70}/NOTICE.txt +0 -0
  8. {valetudo_map_parser-0.1.9b69 → valetudo_map_parser-0.1.9b70}/README.md +0 -0
  9. {valetudo_map_parser-0.1.9b69 → valetudo_map_parser-0.1.9b70}/SCR/valetudo_map_parser/__init__.py +0 -0
  10. {valetudo_map_parser-0.1.9b69 → valetudo_map_parser-0.1.9b70}/SCR/valetudo_map_parser/config/__init__.py +0 -0
  11. {valetudo_map_parser-0.1.9b69 → valetudo_map_parser-0.1.9b70}/SCR/valetudo_map_parser/config/async_utils.py +0 -0
  12. {valetudo_map_parser-0.1.9b69 → valetudo_map_parser-0.1.9b70}/SCR/valetudo_map_parser/config/auto_crop.py +0 -0
  13. {valetudo_map_parser-0.1.9b69 → valetudo_map_parser-0.1.9b70}/SCR/valetudo_map_parser/config/color_utils.py +0 -0
  14. {valetudo_map_parser-0.1.9b69 → valetudo_map_parser-0.1.9b70}/SCR/valetudo_map_parser/config/colors.py +0 -0
  15. {valetudo_map_parser-0.1.9b69 → valetudo_map_parser-0.1.9b70}/SCR/valetudo_map_parser/config/drawable_elements.py +0 -0
  16. {valetudo_map_parser-0.1.9b69 → valetudo_map_parser-0.1.9b70}/SCR/valetudo_map_parser/config/enhanced_drawable.py +0 -0
  17. {valetudo_map_parser-0.1.9b69 → valetudo_map_parser-0.1.9b70}/SCR/valetudo_map_parser/config/optimized_element_map.py +0 -0
  18. {valetudo_map_parser-0.1.9b69 → valetudo_map_parser-0.1.9b70}/SCR/valetudo_map_parser/config/rand256_parser.py +0 -0
  19. {valetudo_map_parser-0.1.9b69 → valetudo_map_parser-0.1.9b70}/SCR/valetudo_map_parser/config/shared.py +0 -0
  20. {valetudo_map_parser-0.1.9b69 → valetudo_map_parser-0.1.9b70}/SCR/valetudo_map_parser/config/types.py +0 -0
  21. {valetudo_map_parser-0.1.9b69 → valetudo_map_parser-0.1.9b70}/SCR/valetudo_map_parser/config/utils.py +0 -0
  22. {valetudo_map_parser-0.1.9b69 → valetudo_map_parser-0.1.9b70}/SCR/valetudo_map_parser/hypfer_rooms_handler.py +0 -0
  23. {valetudo_map_parser-0.1.9b69 → valetudo_map_parser-0.1.9b70}/SCR/valetudo_map_parser/map_data.py +0 -0
  24. {valetudo_map_parser-0.1.9b69 → valetudo_map_parser-0.1.9b70}/SCR/valetudo_map_parser/py.typed +0 -0
  25. {valetudo_map_parser-0.1.9b69 → valetudo_map_parser-0.1.9b70}/SCR/valetudo_map_parser/rand256_handler.py +0 -0
  26. {valetudo_map_parser-0.1.9b69 → valetudo_map_parser-0.1.9b70}/SCR/valetudo_map_parser/reimg_draw.py +0 -0
  27. {valetudo_map_parser-0.1.9b69 → valetudo_map_parser-0.1.9b70}/SCR/valetudo_map_parser/rooms_handler.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: valetudo-map-parser
3
- Version: 0.1.9b69
3
+ Version: 0.1.9b70
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,8 +12,6 @@ from __future__ import annotations
12
12
 
13
13
  import logging
14
14
  import math
15
- import asyncio
16
- import inspect
17
15
 
18
16
  import numpy as np
19
17
  from PIL import ImageDraw, ImageFont
@@ -46,12 +44,8 @@ class Drawable:
46
44
  width: int, height: int, background_color: Color
47
45
  ) -> NumpyArray:
48
46
  """Create the empty background image NumPy array.
49
- Background color is specified as an RGBA tuple.
50
- Optimized: Uses np.empty + broadcast instead of np.full for better performance."""
51
- # Use np.empty + broadcast instead of np.full (avoids double initialization)
52
- img_array = np.empty((height, width, 4), dtype=np.uint8)
53
- img_array[:] = background_color # Broadcast color to all pixels efficiently
54
- return img_array
47
+ Background color is specified as an RGBA tuple."""
48
+ return np.full((height, width, 4), background_color, dtype=np.uint8)
55
49
 
56
50
  @staticmethod
57
51
  async def from_json_to_image(
@@ -158,8 +152,6 @@ class Drawable:
158
152
  It uses the rotation angle of the image to orient the flag.
159
153
  Includes color blending for better visual integration.
160
154
  """
161
- await asyncio.sleep(0) # Yield control
162
-
163
155
  # Check if coordinates are within bounds
164
156
  height, width = layer.shape[:2]
165
157
  x, y = center
@@ -311,79 +303,6 @@ class Drawable:
311
303
 
312
304
  return layer
313
305
 
314
- @staticmethod
315
- def draw_lines_batch(
316
- layer: NumpyArray,
317
- line_segments: list,
318
- color: Color,
319
- width: int = 3,
320
- ) -> NumpyArray:
321
- """
322
- Draw multiple line segments with batch processing for better performance.
323
-
324
- Args:
325
- layer: The numpy array to draw on
326
- line_segments: List of tuples [(x1, y1, x2, y2), ...]
327
- color: Color to draw with
328
- width: Width of the lines
329
- """
330
- if not line_segments:
331
- return layer
332
-
333
- # Pre-calculate blended color once for the entire batch
334
- # Use the first line segment for color sampling
335
- x1, y1, x2, y2 = line_segments[0]
336
- blended_color = get_blended_color(x1, y1, x2, y2, layer, color)
337
-
338
- # Fast path for fully opaque colors - skip individual blending
339
- if color[3] == 255:
340
- blended_color = color
341
-
342
- # Process all line segments with the same blended color
343
- for x1, y1, x2, y2 in line_segments:
344
- # Ensure coordinates are integers
345
- x1, y1, x2, y2 = int(x1), int(y1), int(x2), int(y2)
346
-
347
- # Calculate line length
348
- length = max(abs(x2 - x1), abs(y2 - y1))
349
- if length == 0: # Handle case of a single point
350
- # Draw a dot with the specified width
351
- for i in range(-width // 2, (width + 1) // 2):
352
- for j in range(-width // 2, (width + 1) // 2):
353
- if (
354
- 0 <= x1 + i < layer.shape[1]
355
- and 0 <= y1 + j < layer.shape[0]
356
- ):
357
- layer[y1 + j, x1 + i] = blended_color
358
- continue
359
-
360
- # Create parametric points along the line
361
- t = np.linspace(0, 1, length + 1) # Reduced from length * 2 to length + 1
362
- x_coords = np.round(x1 * (1 - t) + x2 * t).astype(int)
363
- y_coords = np.round(y1 * (1 - t) + y2 * t).astype(int)
364
-
365
- # Draw the line with the specified width
366
- if width == 1:
367
- # Fast path for width=1
368
- for x, y in zip(x_coords, y_coords):
369
- if 0 <= x < layer.shape[1] and 0 <= y < layer.shape[0]:
370
- layer[y, x] = blended_color
371
- else:
372
- # For thicker lines, draw a rectangle at each point
373
- half_width = width // 2
374
- for x, y in zip(x_coords, y_coords):
375
- for i in range(-half_width, half_width + 1):
376
- for j in range(-half_width, half_width + 1):
377
- if (
378
- i * i + j * j
379
- <= half_width * half_width # Make it round
380
- and 0 <= x + i < layer.shape[1]
381
- and 0 <= y + j < layer.shape[0]
382
- ):
383
- layer[y + j, x + i] = blended_color
384
-
385
- return layer
386
-
387
306
  @staticmethod
388
307
  async def draw_virtual_walls(
389
308
  layer: NumpyArray, virtual_walls, color: Color
@@ -402,15 +321,8 @@ class Drawable:
402
321
  async def lines(arr: NumpyArray, coords, width: int, color: Color) -> NumpyArray:
403
322
  """
404
323
  Join the coordinates creating a continuous line (path).
405
- Optimized with batch processing for better performance.
324
+ Optimized with vectorized operations for better performance.
406
325
  """
407
-
408
- # Handle case where arr might be a coroutine (shouldn't happen but let's be safe)
409
- if inspect.iscoroutine(arr):
410
- arr = await arr
411
-
412
- # Collect all line segments for batch processing
413
- line_segments = []
414
326
  for coord in coords:
415
327
  x0, y0 = coord[0]
416
328
  try:
@@ -422,16 +334,11 @@ class Drawable:
422
334
  if x0 == x1 and y0 == y1:
423
335
  continue
424
336
 
425
- line_segments.append((x0, y0, x1, y1))
337
+ # Get blended color for this line segment
338
+ blended_color = get_blended_color(x0, y0, x1, y1, arr, color)
426
339
 
427
- # Process all line segments in batches
428
- batch_size = 100 # Process 100 lines at a time
429
- for i in range(0, len(line_segments), batch_size):
430
- batch = line_segments[i : i + batch_size]
431
- arr = Drawable.draw_lines_batch(arr, batch, color, width)
432
-
433
- # Yield control between batches to prevent blocking
434
- await asyncio.sleep(0)
340
+ # Use the optimized line drawing method
341
+ arr = Drawable._line(arr, x0, y0, x1, y1, blended_color, width)
435
342
 
436
343
  return arr
437
344
 
@@ -577,130 +484,56 @@ class Drawable:
577
484
  async def zones(layers: NumpyArray, coordinates, color: Color) -> NumpyArray:
578
485
  """
579
486
  Draw the zones on the input layer with color blending.
580
- Optimized with parallel processing for better performance.
487
+ Optimized with NumPy vectorized operations for better performance.
581
488
  """
582
- await asyncio.sleep(0) # Yield control
583
-
584
489
  dot_radius = 1 # Number of pixels for the dot
585
490
  dot_spacing = 4 # Space between dots
586
491
 
587
- # Process zones in parallel if there are multiple zones
588
- if len(coordinates) > 1:
589
- # Create tasks for parallel zone processing
590
- zone_tasks = []
591
- for zone in coordinates:
592
- zone_tasks.append(
593
- Drawable._process_single_zone(
594
- layers.copy(), zone, color, dot_radius, dot_spacing
595
- )
596
- )
597
-
598
- # Execute all zone processing tasks in parallel
599
- zone_results = await asyncio.gather(*zone_tasks, return_exceptions=True)
600
-
601
- # Merge results back into the main layer
602
- for result in zone_results:
603
- if not isinstance(result, Exception):
604
- # Simple overlay - pixels that are different from original get updated
605
- mask = result != layers
606
- layers[mask] = result[mask]
607
- else:
608
- # Single zone - process directly
609
- for zone in coordinates:
610
- points = zone["points"]
611
- min_x = max(0, min(points[::2]))
612
- max_x = min(layers.shape[1] - 1, max(points[::2]))
613
- min_y = max(0, min(points[1::2]))
614
- max_y = min(layers.shape[0] - 1, max(points[1::2]))
615
-
616
- # Skip if zone is outside the image
617
- if min_x >= max_x or min_y >= max_y:
618
- continue
619
-
620
- # Sample a point from the zone to get the background color
621
- # Use the center of the zone for sampling
622
- sample_x = (min_x + max_x) // 2
623
- sample_y = (min_y + max_y) // 2
624
-
625
- # Blend the color with the background color at the sample point
626
- if 0 <= sample_y < layers.shape[0] and 0 <= sample_x < layers.shape[1]:
627
- blended_color = ColorsManagement.sample_and_blend_color(
628
- layers, sample_x, sample_y, color
629
- )
630
- else:
631
- blended_color = color
632
-
633
- # Create a grid of dot centers
634
- x_centers = np.arange(min_x, max_x, dot_spacing)
635
- y_centers = np.arange(min_y, max_y, dot_spacing)
636
-
637
- # Draw dots at each grid point
638
- for y in y_centers:
639
- for x in x_centers:
640
- # Create a small mask for the dot
641
- y_min = max(0, y - dot_radius)
642
- y_max = min(layers.shape[0], y + dot_radius + 1)
643
- x_min = max(0, x - dot_radius)
644
- x_max = min(layers.shape[1], x + dot_radius + 1)
645
-
646
- # Create coordinate arrays for the dot
647
- y_indices, x_indices = np.ogrid[y_min:y_max, x_min:x_max]
648
-
649
- # Create a circular mask
650
- mask = (y_indices - y) ** 2 + (
651
- x_indices - x
652
- ) ** 2 <= dot_radius**2
653
-
654
- # Apply the color to the masked region
655
- layers[y_min:y_max, x_min:x_max][mask] = blended_color
492
+ for zone in coordinates:
493
+ points = zone["points"]
494
+ min_x = max(0, min(points[::2]))
495
+ max_x = min(layers.shape[1] - 1, max(points[::2]))
496
+ min_y = max(0, min(points[1::2]))
497
+ max_y = min(layers.shape[0] - 1, max(points[1::2]))
656
498
 
657
- return layers
658
-
659
- @staticmethod
660
- async def _process_single_zone(
661
- layers: NumpyArray, zone, color: Color, dot_radius: int, dot_spacing: int
662
- ) -> NumpyArray:
663
- """Process a single zone for parallel execution."""
664
- await asyncio.sleep(0) # Yield control
665
-
666
- points = zone["points"]
667
- min_x = max(0, min(points[::2]))
668
- max_x = min(layers.shape[1] - 1, max(points[::2]))
669
- min_y = max(0, min(points[1::2]))
670
- max_y = min(layers.shape[0] - 1, max(points[1::2]))
499
+ # Skip if zone is outside the image
500
+ if min_x >= max_x or min_y >= max_y:
501
+ continue
671
502
 
672
- # Skip if zone is outside the image
673
- if min_x >= max_x or min_y >= max_y:
674
- return layers
503
+ # Sample a point from the zone to get the background color
504
+ # Use the center of the zone for sampling
505
+ sample_x = (min_x + max_x) // 2
506
+ sample_y = (min_y + max_y) // 2
675
507
 
676
- # Sample a point from the zone to get the background color
677
- sample_x = (min_x + max_x) // 2
678
- sample_y = (min_y + max_y) // 2
508
+ # Blend the color with the background color at the sample point
509
+ if 0 <= sample_y < layers.shape[0] and 0 <= sample_x < layers.shape[1]:
510
+ blended_color = ColorsManagement.sample_and_blend_color(
511
+ layers, sample_x, sample_y, color
512
+ )
513
+ else:
514
+ blended_color = color
679
515
 
680
- # Blend the color with the background color at the sample point
681
- if 0 <= sample_y < layers.shape[0] and 0 <= sample_x < layers.shape[1]:
682
- blended_color = ColorsManagement.sample_and_blend_color(
683
- layers, sample_x, sample_y, color
684
- )
685
- else:
686
- blended_color = color
516
+ # Create a grid of dot centers
517
+ x_centers = np.arange(min_x, max_x, dot_spacing)
518
+ y_centers = np.arange(min_y, max_y, dot_spacing)
687
519
 
688
- # Create a dotted pattern within the zone
689
- for y in range(min_y, max_y + 1, dot_spacing):
690
- for x in range(min_x, max_x + 1, dot_spacing):
691
- if Drawable.point_inside(x, y, points):
692
- # Draw a small filled circle (dot) using vectorized operations
520
+ # Draw dots at each grid point
521
+ for y in y_centers:
522
+ for x in x_centers:
523
+ # Create a small mask for the dot
693
524
  y_min = max(0, y - dot_radius)
694
525
  y_max = min(layers.shape[0], y + dot_radius + 1)
695
526
  x_min = max(0, x - dot_radius)
696
527
  x_max = min(layers.shape[1], x + dot_radius + 1)
697
528
 
698
- if y_min < y_max and x_min < x_max:
699
- y_indices, x_indices = np.ogrid[y_min:y_max, x_min:x_max]
700
- mask = (y_indices - y) ** 2 + (
701
- x_indices - x
702
- ) ** 2 <= dot_radius**2
703
- layers[y_min:y_max, x_min:x_max][mask] = blended_color
529
+ # Create coordinate arrays for the dot
530
+ y_indices, x_indices = np.ogrid[y_min:y_max, x_min:x_max]
531
+
532
+ # Create a circular mask
533
+ mask = (y_indices - y) ** 2 + (x_indices - x) ** 2 <= dot_radius**2
534
+
535
+ # Apply the color to the masked region
536
+ layers[y_min:y_max, x_min:x_max][mask] = blended_color
704
537
 
705
538
  return layers
706
539
 
@@ -1,12 +1,11 @@
1
1
  """
2
2
  Image Draw Class for Valetudo Hypfer Image Handling.
3
3
  This class is used to simplify the ImageHandler class.
4
- Version: 2024.07.2
4
+ Version: 0.1.9
5
5
  """
6
6
 
7
7
  from __future__ import annotations
8
8
 
9
- import asyncio
10
9
  import logging
11
10
 
12
11
  from .config.drawable_elements import DrawableElement
@@ -8,6 +8,7 @@ Version: 0.1.9
8
8
  from __future__ import annotations
9
9
 
10
10
  import asyncio
11
+ import numpy as np
11
12
 
12
13
  from PIL import Image
13
14
 
@@ -58,6 +59,7 @@ class HypferMapImageHandler(BaseHandler, AutoCrop):
58
59
  self.go_to = None # vacuum go to data
59
60
  self.img_hash = None # hash of the image calculated to check differences.
60
61
  self.img_base_layer = None # numpy array store the map base layer.
62
+ self.img_work_layer = None # persistent working buffer to avoid per-frame allocations
61
63
  self.active_zones = None # vacuum active zones.
62
64
  self.svg_wait = False # SVG image creation wait.
63
65
  self.imd = ImDraw(self) # Image Draw class.
@@ -210,14 +212,12 @@ class HypferMapImageHandler(BaseHandler, AutoCrop):
210
212
  ) % 16 # Increment room_id even if we skip
211
213
  continue
212
214
 
213
- # Check if this is a wall layer and if walls are enabled
215
+ # Draw the layer ONLY if enabled
214
216
  is_wall_layer = layer_type == "wall"
215
217
  if is_wall_layer:
216
- if not self.drawing_config.is_enabled(
217
- DrawableElement.WALL
218
- ):
219
- pass
220
-
218
+ # Skip walls entirely if disabled
219
+ if not self.drawing_config.is_enabled(DrawableElement.WALL):
220
+ continue
221
221
  # Draw the layer
222
222
  (
223
223
  room_id,
@@ -273,6 +273,8 @@ class HypferMapImageHandler(BaseHandler, AutoCrop):
273
273
  LOGGER.info("%s: Completed base Layers", self.file_name)
274
274
  # Copy the new array in base layer.
275
275
  self.img_base_layer = await self.async_copy_array(img_np_array)
276
+
277
+
276
278
  self.shared.frame_number = self.frame_number
277
279
  self.frame_number += 1
278
280
  if (self.frame_number >= self.max_frames) or (
@@ -285,8 +287,17 @@ class HypferMapImageHandler(BaseHandler, AutoCrop):
285
287
  str(self.json_id),
286
288
  str(self.frame_number),
287
289
  )
288
- # Copy the base layer to the new image.
289
- img_np_array = await self.async_copy_array(self.img_base_layer)
290
+ # Ensure persistent working buffer exists and matches base (allocate only when needed)
291
+ if (
292
+ self.img_work_layer is None
293
+ or self.img_work_layer.shape != self.img_base_layer.shape
294
+ or self.img_work_layer.dtype != self.img_base_layer.dtype
295
+ ):
296
+ self.img_work_layer = np.empty_like(self.img_base_layer)
297
+
298
+ # Copy the base layer into the persistent working buffer (no new allocation per frame)
299
+ np.copyto(self.img_work_layer, self.img_base_layer)
300
+ img_np_array = self.img_work_layer
290
301
 
291
302
  # Prepare parallel data extraction tasks
292
303
  data_tasks = []
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "valetudo-map-parser"
3
- version = "0.1.9b69"
3
+ version = "0.1.9b70"
4
4
  description = "A Python library to parse Valetudo map data returning a PIL Image object."
5
5
  authors = ["Sandro Cantarella <gsca075@gmail.com>"]
6
6
  license = "Apache-2.0"