valetudo-map-parser 0.1.9b60__tar.gz → 0.1.9b61__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.9b60 → valetudo_map_parser-0.1.9b61}/PKG-INFO +2 -1
  2. valetudo_map_parser-0.1.9b61/SCR/valetudo_map_parser/config/async_utils.py +55 -0
  3. {valetudo_map_parser-0.1.9b60 → valetudo_map_parser-0.1.9b61}/SCR/valetudo_map_parser/config/drawable.py +2 -1
  4. {valetudo_map_parser-0.1.9b60 → valetudo_map_parser-0.1.9b61}/SCR/valetudo_map_parser/hypfer_handler.py +4 -3
  5. {valetudo_map_parser-0.1.9b60 → valetudo_map_parser-0.1.9b61}/SCR/valetudo_map_parser/map_data.py +51 -90
  6. {valetudo_map_parser-0.1.9b60 → valetudo_map_parser-0.1.9b61}/pyproject.toml +2 -1
  7. {valetudo_map_parser-0.1.9b60 → valetudo_map_parser-0.1.9b61}/LICENSE +0 -0
  8. {valetudo_map_parser-0.1.9b60 → valetudo_map_parser-0.1.9b61}/NOTICE.txt +0 -0
  9. {valetudo_map_parser-0.1.9b60 → valetudo_map_parser-0.1.9b61}/README.md +0 -0
  10. {valetudo_map_parser-0.1.9b60 → valetudo_map_parser-0.1.9b61}/SCR/valetudo_map_parser/__init__.py +0 -0
  11. {valetudo_map_parser-0.1.9b60 → valetudo_map_parser-0.1.9b61}/SCR/valetudo_map_parser/config/__init__.py +0 -0
  12. {valetudo_map_parser-0.1.9b60 → valetudo_map_parser-0.1.9b61}/SCR/valetudo_map_parser/config/auto_crop.py +0 -0
  13. {valetudo_map_parser-0.1.9b60 → valetudo_map_parser-0.1.9b61}/SCR/valetudo_map_parser/config/color_utils.py +0 -0
  14. {valetudo_map_parser-0.1.9b60 → valetudo_map_parser-0.1.9b61}/SCR/valetudo_map_parser/config/colors.py +0 -0
  15. {valetudo_map_parser-0.1.9b60 → valetudo_map_parser-0.1.9b61}/SCR/valetudo_map_parser/config/drawable_elements.py +0 -0
  16. {valetudo_map_parser-0.1.9b60 → valetudo_map_parser-0.1.9b61}/SCR/valetudo_map_parser/config/enhanced_drawable.py +0 -0
  17. {valetudo_map_parser-0.1.9b60 → valetudo_map_parser-0.1.9b61}/SCR/valetudo_map_parser/config/optimized_element_map.py +0 -0
  18. {valetudo_map_parser-0.1.9b60 → valetudo_map_parser-0.1.9b61}/SCR/valetudo_map_parser/config/rand256_parser.py +0 -0
  19. {valetudo_map_parser-0.1.9b60 → valetudo_map_parser-0.1.9b61}/SCR/valetudo_map_parser/config/rand25_parser.py +0 -0
  20. {valetudo_map_parser-0.1.9b60 → valetudo_map_parser-0.1.9b61}/SCR/valetudo_map_parser/config/shared.py +0 -0
  21. {valetudo_map_parser-0.1.9b60 → valetudo_map_parser-0.1.9b61}/SCR/valetudo_map_parser/config/types.py +0 -0
  22. {valetudo_map_parser-0.1.9b60 → valetudo_map_parser-0.1.9b61}/SCR/valetudo_map_parser/config/utils.py +0 -0
  23. {valetudo_map_parser-0.1.9b60 → valetudo_map_parser-0.1.9b61}/SCR/valetudo_map_parser/hypfer_draw.py +0 -0
  24. {valetudo_map_parser-0.1.9b60 → valetudo_map_parser-0.1.9b61}/SCR/valetudo_map_parser/hypfer_rooms_handler.py +0 -0
  25. {valetudo_map_parser-0.1.9b60 → valetudo_map_parser-0.1.9b61}/SCR/valetudo_map_parser/py.typed +0 -0
  26. {valetudo_map_parser-0.1.9b60 → valetudo_map_parser-0.1.9b61}/SCR/valetudo_map_parser/rand256_handler.py +0 -0
  27. {valetudo_map_parser-0.1.9b60 → valetudo_map_parser-0.1.9b61}/SCR/valetudo_map_parser/reimg_draw.py +0 -0
  28. {valetudo_map_parser-0.1.9b60 → valetudo_map_parser-0.1.9b61}/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.9b60
3
+ Version: 0.1.9b61
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: pandas (>=2.3.0)
15
16
  Requires-Dist: scipy (>=1.12.0)
16
17
  Project-URL: Bug Tracker, https://github.com/sca075/Python-package-valetudo-map-parser/issues
17
18
  Project-URL: Changelog, https://github.com/sca075/Python-package-valetudo-map-parser/releases
@@ -0,0 +1,55 @@
1
+ """Async utility functions for making NumPy and PIL operations truly async."""
2
+
3
+ import asyncio
4
+ import io
5
+ from typing import Any, Callable
6
+
7
+ import numpy as np
8
+ from PIL import Image
9
+
10
+
11
+ async def make_async(func: Callable, *args, **kwargs) -> Any:
12
+ """Convert a synchronous function to async by yielding control to the event loop."""
13
+ result = func(*args, **kwargs)
14
+ await asyncio.sleep(0)
15
+ return result
16
+
17
+
18
+ class AsyncNumPy:
19
+ """Async wrappers for NumPy operations that yield control to the event loop."""
20
+
21
+ @staticmethod
22
+ async def async_copy(array: np.ndarray) -> np.ndarray:
23
+ """Async array copying."""
24
+ return await make_async(np.copy, array)
25
+
26
+ @staticmethod
27
+ async def async_full(shape: tuple, fill_value: Any, dtype: np.dtype = None) -> np.ndarray:
28
+ """Async array creation with fill value."""
29
+ return await make_async(np.full, shape, fill_value, dtype=dtype)
30
+
31
+
32
+ class AsyncPIL:
33
+ """Async wrappers for PIL operations that yield control to the event loop."""
34
+
35
+ @staticmethod
36
+ async def async_fromarray(array: np.ndarray, mode: str = "RGBA") -> Image.Image:
37
+ """Async PIL Image creation from NumPy array."""
38
+ return await make_async(Image.fromarray, array, mode)
39
+
40
+ @staticmethod
41
+ async def async_resize(image: Image.Image, size: tuple, resample: int = None) -> Image.Image:
42
+ """Async image resizing."""
43
+ if resample is None:
44
+ resample = Image.LANCZOS
45
+ return await make_async(image.resize, size, resample)
46
+
47
+ @staticmethod
48
+ async def async_save_to_bytes(image: Image.Image, format_type: str = "WEBP", **kwargs) -> bytes:
49
+ """Async image saving to bytes."""
50
+ def save_to_bytes():
51
+ buffer = io.BytesIO()
52
+ image.save(buffer, format=format_type, **kwargs)
53
+ return buffer.getvalue()
54
+
55
+ return await make_async(save_to_bytes)
@@ -16,6 +16,7 @@ import math
16
16
  import numpy as np
17
17
  from PIL import ImageDraw, ImageFont
18
18
 
19
+ from .async_utils import AsyncNumPy
19
20
  from .color_utils import get_blended_color
20
21
  from .colors import ColorsManagement
21
22
  from .types import Color, NumpyArray, PilPNG, Point, Tuple, Union
@@ -45,7 +46,7 @@ class Drawable:
45
46
  ) -> NumpyArray:
46
47
  """Create the empty background image NumPy array.
47
48
  Background color is specified as an RGBA tuple."""
48
- return np.full((height, width, 4), background_color, dtype=np.uint8)
49
+ return await AsyncNumPy.async_full((height, width, 4), background_color, dtype=np.uint8)
49
50
 
50
51
  @staticmethod
51
52
  async def from_json_to_image(
@@ -11,6 +11,7 @@ import json
11
11
 
12
12
  from PIL import Image
13
13
 
14
+ from .config.async_utils import AsyncNumPy, AsyncPIL
14
15
  from .config.auto_crop import AutoCrop
15
16
  from .config.drawable_elements import DrawableElement
16
17
  from .config.shared import CameraShared
@@ -371,7 +372,7 @@ class HypferMapImageHandler(BaseHandler, AutoCrop):
371
372
  # Handle resizing if needed, then return based on format preference
372
373
  if self.check_zoom_and_aspect_ratio():
373
374
  # Convert to PIL for resizing
374
- pil_img = Image.fromarray(img_np_array, mode="RGBA")
375
+ pil_img = await AsyncPIL.async_fromarray(img_np_array, mode="RGBA")
375
376
  del img_np_array
376
377
  resize_params = prepare_resize_params(self, pil_img, False)
377
378
  resized_image = await self.async_resize_images(resize_params)
@@ -394,7 +395,7 @@ class HypferMapImageHandler(BaseHandler, AutoCrop):
394
395
  return webp_bytes
395
396
  else:
396
397
  # Convert to PIL Image (original behavior)
397
- pil_img = Image.fromarray(img_np_array, mode="RGBA")
398
+ pil_img = await AsyncPIL.async_fromarray(img_np_array, mode="RGBA")
398
399
  del img_np_array
399
400
  LOGGER.debug("%s: Frame Completed.", self.file_name)
400
401
  return pil_img
@@ -474,4 +475,4 @@ class HypferMapImageHandler(BaseHandler, AutoCrop):
474
475
  @staticmethod
475
476
  async def async_copy_array(original_array):
476
477
  """Copy the array."""
477
- return original_array.copy()
478
+ return await AsyncNumPy.async_copy(original_array)
@@ -9,7 +9,7 @@ Version: v0.1.6
9
9
  from __future__ import annotations
10
10
 
11
11
  import numpy as np
12
-
12
+ import pandas as pd
13
13
  from .config.types import ImageSize, JsonType
14
14
 
15
15
 
@@ -18,65 +18,48 @@ class ImageData:
18
18
 
19
19
  @staticmethod
20
20
  def sublist(lst, n):
21
- """Sub lists of specific n number of elements"""
22
- return [lst[i : i + n] for i in range(0, len(lst), n)]
21
+ """Split a list into n chunks of specified size."""
22
+ return [lst[i: i + n] for i in range(0, len(lst), n)]
23
23
 
24
24
  @staticmethod
25
25
  def sublist_join(lst, n):
26
- """Join the lists in a unique list of n elements"""
26
+ """Join the lists in a unique list of n elements."""
27
27
  arr = np.array(lst)
28
28
  num_windows = len(lst) - n + 1
29
- result = [arr[i : i + n].tolist() for i in range(num_windows)]
29
+ result = [arr[i: i + n].tolist() for i in range(num_windows)]
30
30
  return result
31
31
 
32
- # The below functions are basically the same ech one
33
- # of them is allowing filtering and putting together in a
34
- # list the specific Layers, Paths, Zones and Pints in the
35
- # Vacuums Json in parallel.
36
-
37
32
  @staticmethod
38
33
  def get_obstacles(entity_dict: dict) -> list:
39
34
  """Get the obstacles positions from the entity data."""
40
- try:
41
- obstacle_data = entity_dict.get("obstacle")
42
- except KeyError:
43
- return []
35
+ obstacles = entity_dict.get("obstacle", [])
44
36
  obstacle_positions = []
45
- if obstacle_data:
46
- for obstacle in obstacle_data:
47
- label = obstacle.get("metaData", {}).get("label")
48
- points = obstacle.get("points", [])
49
- image_id = obstacle.get("metaData", {}).get("id")
50
-
51
- if label and points:
52
- obstacle_pos = {
53
- "label": label,
54
- "points": {"x": points[0], "y": points[1]},
55
- "id": image_id,
56
- }
57
- obstacle_positions.append(obstacle_pos)
58
- return obstacle_positions
59
- return []
60
-
61
- @staticmethod
62
- def find_layers(
63
- json_obj: JsonType, layer_dict: dict, active_list: list
64
- ) -> tuple[dict, list]:
37
+ for obstacle in obstacles:
38
+ label = obstacle.get("metaData", {}).get("label")
39
+ points = obstacle.get("points", [])
40
+ image_id = obstacle.get("metaData", {}).get("id")
41
+ if label and points:
42
+ obstacle_positions.append({
43
+ "label": label,
44
+ "points": {"x": points[0], "y": points[1]},
45
+ "id": image_id,
46
+ })
47
+ return obstacle_positions
48
+
49
+ @staticmethod
50
+ def find_layers(json_obj: JsonType, layer_dict: dict, active_list: list) -> tuple[dict, list]:
65
51
  """Find the layers in the json object."""
66
52
  layer_dict = {} if layer_dict is None else layer_dict
67
53
  active_list = [] if active_list is None else active_list
68
54
  if isinstance(json_obj, dict):
69
- if "__class" in json_obj and json_obj["__class"] == "MapLayer":
55
+ if json_obj.get("__class") == "MapLayer":
70
56
  layer_type = json_obj.get("type")
71
57
  active_type = json_obj.get("metaData")
72
58
  if layer_type:
73
- if layer_type not in layer_dict:
74
- layer_dict[layer_type] = []
75
- layer_dict[layer_type].append(json_obj.get("compressedPixels", []))
59
+ layer_dict.setdefault(layer_type, []).append(json_obj.get("compressedPixels", []))
76
60
  if layer_type == "segment":
77
- active_list.append(int(active_type["active"]))
78
-
79
- for value in json_obj.items():
61
+ active_list.append(int(active_type.get("active", 0)))
62
+ for value in json_obj.values():
80
63
  ImageData.find_layers(value, layer_dict, active_list)
81
64
  elif isinstance(json_obj, list):
82
65
  for item in json_obj:
@@ -86,8 +69,7 @@ class ImageData:
86
69
  @staticmethod
87
70
  def find_points_entities(json_obj: JsonType, entity_dict: dict = None) -> dict:
88
71
  """Find the points entities in the json object."""
89
- if entity_dict is None:
90
- entity_dict = {}
72
+ entity_dict = {} if entity_dict is None else entity_dict
91
73
  if isinstance(json_obj, dict):
92
74
  if json_obj.get("__class") == "PointMapEntity":
93
75
  entity_type = json_obj.get("type")
@@ -103,9 +85,7 @@ class ImageData:
103
85
  @staticmethod
104
86
  def find_paths_entities(json_obj: JsonType, entity_dict: dict = None) -> dict:
105
87
  """Find the paths entities in the json object."""
106
-
107
- if entity_dict is None:
108
- entity_dict = {}
88
+ entity_dict = {} if entity_dict is None else entity_dict
109
89
  if isinstance(json_obj, dict):
110
90
  if json_obj.get("__class") == "PathMapEntity":
111
91
  entity_type = json_obj.get("type")
@@ -121,8 +101,7 @@ class ImageData:
121
101
  @staticmethod
122
102
  def find_zone_entities(json_obj: JsonType, entity_dict: dict = None) -> dict:
123
103
  """Find the zone entities in the json object."""
124
- if entity_dict is None:
125
- entity_dict = {}
104
+ entity_dict = {} if entity_dict is None else entity_dict
126
105
  if isinstance(json_obj, dict):
127
106
  if json_obj.get("__class") == "PolygonMapEntity":
128
107
  entity_type = json_obj.get("type")
@@ -138,59 +117,41 @@ class ImageData:
138
117
  @staticmethod
139
118
  def find_virtual_walls(json_obj: JsonType) -> list:
140
119
  """Find the virtual walls in the json object."""
141
- virtual_walls = []
120
+ walls = []
142
121
 
143
- def find_virtual_walls_recursive(obj):
144
- """Find the virtual walls in the json object recursively."""
122
+ def _recursive(obj):
145
123
  if isinstance(obj, dict):
146
- if obj.get("__class") == "LineMapEntity":
147
- entity_type = obj.get("type")
148
- if entity_type == "virtual_wall":
149
- virtual_walls.append(obj["points"])
124
+ if obj.get("__class") == "LineMapEntity" and obj.get("type") == "virtual_wall":
125
+ walls.append(obj["points"])
150
126
  for value in obj.values():
151
- find_virtual_walls_recursive(value)
127
+ _recursive(value)
152
128
  elif isinstance(obj, list):
153
129
  for item in obj:
154
- find_virtual_walls_recursive(item)
130
+ _recursive(item)
155
131
 
156
- find_virtual_walls_recursive(json_obj)
157
- return virtual_walls
132
+ _recursive(json_obj)
133
+ return walls
158
134
 
159
135
  @staticmethod
160
- async def async_get_rooms_coordinates(
161
- pixels: list, pixel_size: int = 5, rand: bool = False
162
- ) -> tuple:
163
- """
164
- Extract the room coordinates from the vacuum pixels data.
165
- piexels: dict: The pixels data format [[x,y,z], [x1,y1,z1], [xn,yn,zn]].
166
- pixel_size: int: The size of the pixel in mm (optional).
167
- rand: bool: Return the coordinates in a rand256 format (optional).
168
- """
169
- # Initialize variables to store max and min coordinates
170
- max_x, max_y = pixels[0][0], pixels[0][1]
171
- min_x, min_y = pixels[0][0], pixels[0][1]
172
- # Iterate through the data list to find max and min coordinates
173
- for entry in pixels:
174
- if rand:
175
- x, y, _ = entry # Extract x and y coordinates
176
- max_x = max(max_x, x) # Update max x coordinate
177
- max_y = max(max_y, y + pixel_size) # Update max y coordinate
178
- min_x = min(min_x, x) # Update min x coordinate
179
- min_y = min(min_y, y) # Update min y coordinate
180
- else:
181
- x, y, z = entry # Extract x and y coordinates
182
- max_x = max(max_x, x + z) # Update max x coordinate
183
- max_y = max(max_y, y + pixel_size) # Update max y coordinate
184
- min_x = min(min_x, x) # Update min x coordinate
185
- min_y = min(min_y, y) # Update min y coordinate
136
+ async def async_get_rooms_coordinates(pixels: list, pixel_size: int = 5, rand: bool = False) -> tuple:
137
+ """Extract the room coordinates from the vacuum pixels data."""
138
+ df = pd.DataFrame(pixels, columns=["x", "y", "length"])
139
+ if rand:
140
+ df["x_end"] = df["x"]
141
+ df["y_end"] = df["y"] + pixel_size
142
+ else:
143
+ df["x_end"] = df["x"] + df["length"]
144
+ df["y_end"] = df["y"] + pixel_size
145
+
146
+ min_x, max_x = df["x"].min(), df["x_end"].max()
147
+ min_y, max_y = df["y"].min(), df["y_end"].max()
148
+
186
149
  if rand:
187
150
  return (
188
- (((max_x * pixel_size) * 10), ((max_y * pixel_size) * 10)),
189
- (
190
- ((min_x * pixel_size) * 10),
191
- ((min_y * pixel_size) * 10),
192
- ),
151
+ ((max_x * pixel_size) * 10, (max_y * pixel_size) * 10),
152
+ ((min_x * pixel_size) * 10, (min_y * pixel_size) * 10),
193
153
  )
154
+
194
155
  return (
195
156
  min_x * pixel_size,
196
157
  min_y * pixel_size,
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "valetudo-map-parser"
3
- version = "0.1.9b60"
3
+ version = "0.1.9b61"
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"
@@ -18,6 +18,7 @@ python = ">=3.12"
18
18
  numpy = ">=1.26.4"
19
19
  Pillow = ">=10.3.0"
20
20
  scipy = ">=1.12.0"
21
+ pandas = ">=2.3.0"
21
22
 
22
23
  [tool.poetry.group.dev.dependencies]
23
24
  ruff = "*"