valetudo-map-parser 0.1.9b57__tar.gz → 0.1.9b59__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.9b57 → valetudo_map_parser-0.1.9b59}/PKG-INFO +1 -1
  2. valetudo_map_parser-0.1.9b59/SCR/valetudo_map_parser/config/rand256_parser.py +395 -0
  3. {valetudo_map_parser-0.1.9b57 → valetudo_map_parser-0.1.9b59}/SCR/valetudo_map_parser/config/utils.py +20 -18
  4. {valetudo_map_parser-0.1.9b57 → valetudo_map_parser-0.1.9b59}/SCR/valetudo_map_parser/hypfer_handler.py +3 -10
  5. {valetudo_map_parser-0.1.9b57 → valetudo_map_parser-0.1.9b59}/SCR/valetudo_map_parser/rand256_handler.py +1 -5
  6. {valetudo_map_parser-0.1.9b57 → valetudo_map_parser-0.1.9b59}/pyproject.toml +1 -1
  7. {valetudo_map_parser-0.1.9b57 → valetudo_map_parser-0.1.9b59}/LICENSE +0 -0
  8. {valetudo_map_parser-0.1.9b57 → valetudo_map_parser-0.1.9b59}/NOTICE.txt +0 -0
  9. {valetudo_map_parser-0.1.9b57 → valetudo_map_parser-0.1.9b59}/README.md +0 -0
  10. {valetudo_map_parser-0.1.9b57 → valetudo_map_parser-0.1.9b59}/SCR/valetudo_map_parser/__init__.py +0 -0
  11. {valetudo_map_parser-0.1.9b57 → valetudo_map_parser-0.1.9b59}/SCR/valetudo_map_parser/config/__init__.py +0 -0
  12. {valetudo_map_parser-0.1.9b57 → valetudo_map_parser-0.1.9b59}/SCR/valetudo_map_parser/config/auto_crop.py +0 -0
  13. {valetudo_map_parser-0.1.9b57 → valetudo_map_parser-0.1.9b59}/SCR/valetudo_map_parser/config/color_utils.py +0 -0
  14. {valetudo_map_parser-0.1.9b57 → valetudo_map_parser-0.1.9b59}/SCR/valetudo_map_parser/config/colors.py +0 -0
  15. {valetudo_map_parser-0.1.9b57 → valetudo_map_parser-0.1.9b59}/SCR/valetudo_map_parser/config/drawable.py +0 -0
  16. {valetudo_map_parser-0.1.9b57 → valetudo_map_parser-0.1.9b59}/SCR/valetudo_map_parser/config/drawable_elements.py +0 -0
  17. {valetudo_map_parser-0.1.9b57 → valetudo_map_parser-0.1.9b59}/SCR/valetudo_map_parser/config/enhanced_drawable.py +0 -0
  18. {valetudo_map_parser-0.1.9b57 → valetudo_map_parser-0.1.9b59}/SCR/valetudo_map_parser/config/optimized_element_map.py +0 -0
  19. {valetudo_map_parser-0.1.9b57 → valetudo_map_parser-0.1.9b59}/SCR/valetudo_map_parser/config/rand25_parser.py +0 -0
  20. {valetudo_map_parser-0.1.9b57 → valetudo_map_parser-0.1.9b59}/SCR/valetudo_map_parser/config/shared.py +0 -0
  21. {valetudo_map_parser-0.1.9b57 → valetudo_map_parser-0.1.9b59}/SCR/valetudo_map_parser/config/types.py +0 -0
  22. {valetudo_map_parser-0.1.9b57 → valetudo_map_parser-0.1.9b59}/SCR/valetudo_map_parser/hypfer_draw.py +0 -0
  23. {valetudo_map_parser-0.1.9b57 → valetudo_map_parser-0.1.9b59}/SCR/valetudo_map_parser/hypfer_rooms_handler.py +0 -0
  24. {valetudo_map_parser-0.1.9b57 → valetudo_map_parser-0.1.9b59}/SCR/valetudo_map_parser/map_data.py +0 -0
  25. {valetudo_map_parser-0.1.9b57 → valetudo_map_parser-0.1.9b59}/SCR/valetudo_map_parser/py.typed +0 -0
  26. {valetudo_map_parser-0.1.9b57 → valetudo_map_parser-0.1.9b59}/SCR/valetudo_map_parser/reimg_draw.py +0 -0
  27. {valetudo_map_parser-0.1.9b57 → valetudo_map_parser-0.1.9b59}/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.9b57
3
+ Version: 0.1.9b59
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
@@ -0,0 +1,395 @@
1
+ """New Rand256 Map Parser - Based on Xiaomi/Roborock implementation with precise binary parsing."""
2
+
3
+ import struct
4
+ import math
5
+ from enum import Enum
6
+ from typing import Any, Dict, List, Optional
7
+
8
+
9
+ class RRMapParser:
10
+ """New Rand256 Map Parser using Xiaomi/Roborock approach for precise data extraction."""
11
+
12
+ class Types(Enum):
13
+ """Map data block types."""
14
+
15
+ CHARGER_LOCATION = 1
16
+ IMAGE = 2
17
+ PATH = 3
18
+ GOTO_PATH = 4
19
+ GOTO_PREDICTED_PATH = 5
20
+ CURRENTLY_CLEANED_ZONES = 6
21
+ GOTO_TARGET = 7
22
+ ROBOT_POSITION = 8
23
+ FORBIDDEN_ZONES = 9
24
+ VIRTUAL_WALLS = 10
25
+ CURRENTLY_CLEANED_BLOCKS = 11
26
+ FORBIDDEN_MOP_ZONES = 12
27
+
28
+ class Tools:
29
+ """Tools for coordinate transformations."""
30
+
31
+ DIMENSION_PIXELS = 1024
32
+ DIMENSION_MM = 50 * 1024
33
+
34
+ def __init__(self):
35
+ """Initialize the parser."""
36
+ self.map_data: Dict[str, Any] = {}
37
+
38
+ # Xiaomi/Roborock style byte extraction methods
39
+ @staticmethod
40
+ def _get_bytes(data: bytes, start_index: int, size: int) -> bytes:
41
+ """Extract bytes from data."""
42
+ return data[start_index : start_index + size]
43
+
44
+ @staticmethod
45
+ def _get_int8(data: bytes, address: int) -> int:
46
+ """Get an 8-bit integer from data using Xiaomi method."""
47
+ return data[address] & 0xFF
48
+
49
+ @staticmethod
50
+ def _get_int16(data: bytes, address: int) -> int:
51
+ """Get a 16-bit little-endian integer using Xiaomi method."""
52
+ return ((data[address + 0] << 0) & 0xFF) | ((data[address + 1] << 8) & 0xFFFF)
53
+
54
+ @staticmethod
55
+ def _get_int32(data: bytes, address: int) -> int:
56
+ """Get a 32-bit little-endian integer using Xiaomi method."""
57
+ return (
58
+ ((data[address + 0] << 0) & 0xFF)
59
+ | ((data[address + 1] << 8) & 0xFFFF)
60
+ | ((data[address + 2] << 16) & 0xFFFFFF)
61
+ | ((data[address + 3] << 24) & 0xFFFFFFFF)
62
+ )
63
+
64
+ @staticmethod
65
+ def _get_int32_signed(data: bytes, address: int) -> int:
66
+ """Get a 32-bit signed integer."""
67
+ value = RRMapParser._get_int32(data, address)
68
+ return value if value < 0x80000000 else value - 0x100000000
69
+
70
+ @staticmethod
71
+ def _parse_object_position(block_data_length: int, data: bytes) -> Dict[str, Any]:
72
+ """Parse object position using Xiaomi method."""
73
+ x = RRMapParser._get_int32(data, 0x00)
74
+ y = RRMapParser._get_int32(data, 0x04)
75
+ angle = 0
76
+ if block_data_length > 8:
77
+ raw_angle = RRMapParser._get_int32(data, 0x08)
78
+ # Apply Xiaomi angle normalization
79
+ if raw_angle > 0xFF:
80
+ angle = (raw_angle & 0xFF) - 256
81
+ else:
82
+ angle = raw_angle
83
+ return {"position": [x, y], "angle": angle}
84
+
85
+ @staticmethod
86
+ def _parse_path_block(buf: bytes, offset: int, length: int) -> Dict[str, Any]:
87
+ """Parse path block using EXACT same method as working parser."""
88
+ points = [
89
+ [
90
+ struct.unpack("<H", buf[offset + 20 + i : offset + 22 + i])[0],
91
+ struct.unpack("<H", buf[offset + 22 + i : offset + 24 + i])[0],
92
+ ]
93
+ for i in range(0, length, 4)
94
+ ]
95
+ return {
96
+ "current_angle": struct.unpack("<I", buf[offset + 16 : offset + 20])[0],
97
+ "points": points,
98
+ }
99
+
100
+ @staticmethod
101
+ def _parse_goto_target(data: bytes) -> List[int]:
102
+ """Parse goto target using Xiaomi method."""
103
+ try:
104
+ x = RRMapParser._get_int16(data, 0x00)
105
+ y = RRMapParser._get_int16(data, 0x02)
106
+ return [x, y]
107
+ except (struct.error, IndexError):
108
+ return [0, 0]
109
+
110
+ def parse(self, map_buf: bytes) -> Dict[str, Any]:
111
+ """Parse the map header data using Xiaomi method."""
112
+ if len(map_buf) < 18 or map_buf[0:2] != b"rr":
113
+ return {}
114
+
115
+ try:
116
+ return {
117
+ "header_length": self._get_int16(map_buf, 0x02),
118
+ "data_length": self._get_int16(map_buf, 0x04),
119
+ "version": {
120
+ "major": self._get_int16(map_buf, 0x08),
121
+ "minor": self._get_int16(map_buf, 0x0A),
122
+ },
123
+ "map_index": self._get_int32(map_buf, 0x0C),
124
+ "map_sequence": self._get_int32(map_buf, 0x10),
125
+ }
126
+ except (struct.error, IndexError):
127
+ return {}
128
+
129
+ def parse_blocks(self, raw: bytes, pixels: bool = True) -> Dict[int, Any]:
130
+ """Parse all blocks using Xiaomi method."""
131
+ blocks = {}
132
+ map_header_length = self._get_int16(raw, 0x02)
133
+ block_start_position = map_header_length
134
+
135
+ while block_start_position < len(raw):
136
+ try:
137
+ # Parse block header using Xiaomi method
138
+ block_header_length = self._get_int16(raw, block_start_position + 0x02)
139
+ header = self._get_bytes(raw, block_start_position, block_header_length)
140
+ block_type = self._get_int16(header, 0x00)
141
+ block_data_length = self._get_int32(header, 0x04)
142
+ block_data_start = block_start_position + block_header_length
143
+ data = self._get_bytes(raw, block_data_start, block_data_length)
144
+
145
+ # Parse different block types
146
+ if block_type == self.Types.ROBOT_POSITION.value:
147
+ blocks[block_type] = self._parse_object_position(
148
+ block_data_length, data
149
+ )
150
+ elif block_type == self.Types.CHARGER_LOCATION.value:
151
+ blocks[block_type] = self._parse_object_position(
152
+ block_data_length, data
153
+ )
154
+ elif block_type == self.Types.PATH.value:
155
+ blocks[block_type] = self._parse_path_block(
156
+ raw, block_start_position, block_data_length
157
+ )
158
+ elif block_type == self.Types.GOTO_PREDICTED_PATH.value:
159
+ blocks[block_type] = self._parse_path_block(
160
+ raw, block_start_position, block_data_length
161
+ )
162
+ elif block_type == self.Types.GOTO_TARGET.value:
163
+ blocks[block_type] = {"position": self._parse_goto_target(data)}
164
+ elif block_type == self.Types.IMAGE.value:
165
+ # Get header length for Gen1/Gen3 detection
166
+ header_length = self._get_int8(header, 2)
167
+ blocks[block_type] = self._parse_image_block(
168
+ raw,
169
+ block_start_position,
170
+ block_data_length,
171
+ header_length,
172
+ pixels,
173
+ )
174
+
175
+ # Move to next block using Xiaomi method
176
+ block_start_position = (
177
+ block_start_position + block_data_length + self._get_int8(header, 2)
178
+ )
179
+
180
+ except (struct.error, IndexError):
181
+ break
182
+
183
+ return blocks
184
+
185
+ def _parse_image_block(
186
+ self, buf: bytes, offset: int, length: int, hlength: int, pixels: bool = True
187
+ ) -> Dict[str, Any]:
188
+ """Parse image block using EXACT logic from working parser."""
189
+ try:
190
+ # CRITICAL: Gen1 vs Gen3 detection like working parser
191
+ g3offset = 4 if hlength > 24 else 0
192
+
193
+ # Use EXACT same structure as working parser
194
+ parameters = {
195
+ "segments": {
196
+ "count": (
197
+ struct.unpack("<i", buf[offset + 8 : offset + 12])[0]
198
+ if g3offset
199
+ else 0
200
+ ),
201
+ "id": [],
202
+ },
203
+ "position": {
204
+ "top": struct.unpack(
205
+ "<i", buf[offset + 8 + g3offset : offset + 12 + g3offset]
206
+ )[0],
207
+ "left": struct.unpack(
208
+ "<i", buf[offset + 12 + g3offset : offset + 16 + g3offset]
209
+ )[0],
210
+ },
211
+ "dimensions": {
212
+ "height": struct.unpack(
213
+ "<i", buf[offset + 16 + g3offset : offset + 20 + g3offset]
214
+ )[0],
215
+ "width": struct.unpack(
216
+ "<i", buf[offset + 20 + g3offset : offset + 24 + g3offset]
217
+ )[0],
218
+ },
219
+ "pixels": {"floor": [], "walls": [], "segments": {}},
220
+ }
221
+
222
+ # Apply EXACT working parser coordinate transformation
223
+ parameters["position"]["top"] = (
224
+ self.Tools.DIMENSION_PIXELS
225
+ - parameters["position"]["top"]
226
+ - parameters["dimensions"]["height"]
227
+ )
228
+
229
+ # Extract pixels using optimized sequential processing
230
+ if (
231
+ parameters["dimensions"]["height"] > 0
232
+ and parameters["dimensions"]["width"] > 0
233
+ ):
234
+ # Process data sequentially - segments are organized as blocks
235
+ current_segments = {}
236
+
237
+ for i in range(length):
238
+ pixel_byte = struct.unpack(
239
+ "<B",
240
+ buf[offset + 24 + g3offset + i : offset + 25 + g3offset + i],
241
+ )[0]
242
+
243
+ segment_type = pixel_byte & 0x07
244
+ if segment_type == 0:
245
+ continue
246
+
247
+ if segment_type == 1 and pixels:
248
+ # Wall pixel
249
+ parameters["pixels"]["walls"].append(i)
250
+ else:
251
+ # Floor or room segment
252
+ segment_id = pixel_byte >> 3
253
+ if segment_id == 0 and pixels:
254
+ # Floor pixel
255
+ parameters["pixels"]["floor"].append(i)
256
+ elif segment_id != 0:
257
+ # Room segment - segments are sequential blocks
258
+ if segment_id not in current_segments:
259
+ parameters["segments"]["id"].append(segment_id)
260
+ parameters["segments"][
261
+ "pixels_seg_" + str(segment_id)
262
+ ] = []
263
+ current_segments[segment_id] = True
264
+
265
+ if pixels:
266
+ parameters["segments"][
267
+ "pixels_seg_" + str(segment_id)
268
+ ].append(i)
269
+
270
+ parameters["segments"]["count"] = len(parameters["segments"]["id"])
271
+ return parameters
272
+
273
+ except (struct.error, IndexError):
274
+ return {
275
+ "segments": {"count": 0, "id": []},
276
+ "position": {"top": 0, "left": 0},
277
+ "dimensions": {"height": 0, "width": 0},
278
+ "pixels": {"floor": [], "walls": [], "segments": {}},
279
+ }
280
+
281
+ def parse_rrm_data(
282
+ self, map_buf: bytes, pixels: bool = False
283
+ ) -> Optional[Dict[str, Any]]:
284
+ """Parse the complete map data and return in your JSON format."""
285
+ if not self.parse(map_buf).get("map_index"):
286
+ return None
287
+
288
+ try:
289
+ parsed_map_data = {}
290
+ blocks = self.parse_blocks(map_buf, pixels)
291
+
292
+ # Parse robot position
293
+ if self.Types.ROBOT_POSITION.value in blocks:
294
+ robot_data = blocks[self.Types.ROBOT_POSITION.value]
295
+ parsed_map_data["robot"] = robot_data["position"]
296
+
297
+ # Parse path data with coordinate transformation FIRST
298
+ transformed_path_points = []
299
+ if self.Types.PATH.value in blocks:
300
+ path_data = blocks[self.Types.PATH.value].copy()
301
+ # Apply coordinate transformation like current parser
302
+ transformed_path_points = [
303
+ [point[0], self.Tools.DIMENSION_MM - point[1]]
304
+ for point in path_data["points"]
305
+ ]
306
+ path_data["points"] = transformed_path_points
307
+
308
+ # Calculate current angle from transformed points
309
+ if len(transformed_path_points) >= 2:
310
+ last_point = transformed_path_points[-1]
311
+ second_last = transformed_path_points[-2]
312
+ dx = last_point[0] - second_last[0]
313
+ dy = last_point[1] - second_last[1]
314
+ if dx != 0 or dy != 0:
315
+ angle_rad = math.atan2(dy, dx)
316
+ path_data["current_angle"] = math.degrees(angle_rad)
317
+ parsed_map_data["path"] = path_data
318
+
319
+ # Get robot angle from TRANSFORMED path data (like current implementation)
320
+ robot_angle = 0
321
+ if len(transformed_path_points) >= 2:
322
+ last_point = transformed_path_points[-1]
323
+ second_last = transformed_path_points[-2]
324
+ dx = last_point[0] - second_last[0]
325
+ dy = last_point[1] - second_last[1]
326
+ if dx != 0 or dy != 0:
327
+ angle_rad = math.atan2(dy, dx)
328
+ robot_angle = int(math.degrees(angle_rad))
329
+
330
+ parsed_map_data["robot_angle"] = robot_angle
331
+
332
+ # Parse charger position
333
+ if self.Types.CHARGER_LOCATION.value in blocks:
334
+ charger_data = blocks[self.Types.CHARGER_LOCATION.value]
335
+ parsed_map_data["charger"] = charger_data["position"]
336
+
337
+ # Parse image data
338
+ if self.Types.IMAGE.value in blocks:
339
+ parsed_map_data["image"] = blocks[self.Types.IMAGE.value]
340
+
341
+ # Parse goto predicted path
342
+ if self.Types.GOTO_PREDICTED_PATH.value in blocks:
343
+ goto_path_data = blocks[self.Types.GOTO_PREDICTED_PATH.value].copy()
344
+ # Apply coordinate transformation
345
+ goto_path_data["points"] = [
346
+ [point[0], self.Tools.DIMENSION_MM - point[1]]
347
+ for point in goto_path_data["points"]
348
+ ]
349
+ # Calculate current angle from transformed points (like working parser)
350
+ if len(goto_path_data["points"]) >= 2:
351
+ points = goto_path_data["points"]
352
+ last_point = points[-1]
353
+ second_last = points[-2]
354
+ dx = last_point[0] - second_last[0]
355
+ dy = last_point[1] - second_last[1]
356
+ if dx != 0 or dy != 0:
357
+ angle_rad = math.atan2(dy, dx)
358
+ goto_path_data["current_angle"] = math.degrees(angle_rad)
359
+ parsed_map_data["goto_predicted_path"] = goto_path_data
360
+
361
+ # Parse goto target
362
+ if self.Types.GOTO_TARGET.value in blocks:
363
+ parsed_map_data["goto_target"] = blocks[self.Types.GOTO_TARGET.value][
364
+ "position"
365
+ ]
366
+
367
+ # Add missing fields to match expected JSON format
368
+ parsed_map_data["forbidden_zones"] = []
369
+ parsed_map_data["virtual_walls"] = []
370
+
371
+ return parsed_map_data
372
+
373
+ except (struct.error, IndexError, ValueError):
374
+ return None
375
+
376
+ def parse_data(
377
+ self, payload: Optional[bytes] = None, pixels: bool = False
378
+ ) -> Optional[Dict[str, Any]]:
379
+ """Get the map data from MQTT and return dictionary like old parsers."""
380
+ if payload:
381
+ try:
382
+ self.map_data = self.parse(payload)
383
+ parsed_data = self.parse_rrm_data(payload, pixels)
384
+ if parsed_data:
385
+ self.map_data.update(parsed_data)
386
+ # Return dictionary directly - faster!
387
+ return self.map_data
388
+ except (struct.error, IndexError, ValueError):
389
+ return None
390
+ return self.map_data
391
+
392
+ @staticmethod
393
+ def get_int32(data: bytes, address: int) -> int:
394
+ """Get a 32-bit integer from the data - kept for compatibility."""
395
+ return struct.unpack_from("<i", data, address)[0]
@@ -12,7 +12,15 @@ from PIL import Image, ImageOps
12
12
  from .drawable import Drawable
13
13
  from .drawable_elements import DrawableElement, DrawingConfig
14
14
  from .enhanced_drawable import EnhancedDrawable
15
- from .types import LOGGER, ChargerPosition, ImageSize, NumpyArray, PilPNG, RobotPosition, WebPBytes
15
+ from .types import (
16
+ LOGGER,
17
+ ChargerPosition,
18
+ ImageSize,
19
+ NumpyArray,
20
+ PilPNG,
21
+ RobotPosition,
22
+ WebPBytes,
23
+ )
16
24
 
17
25
 
18
26
  @dataclass
@@ -843,10 +851,8 @@ async def async_extract_room_outline(
843
851
 
844
852
 
845
853
  async def numpy_to_webp_bytes(
846
- img_np_array: np.ndarray,
847
- quality: int = 85,
848
- lossless: bool = False
849
- ) -> bytes:
854
+ img_np_array: np.ndarray, quality: int = 85, lossless: bool = False
855
+ ) -> WebPBytes:
850
856
  """
851
857
  Convert NumPy array directly to WebP bytes.
852
858
 
@@ -864,13 +870,12 @@ async def numpy_to_webp_bytes(
864
870
  # Create bytes buffer
865
871
  webp_buffer = io.BytesIO()
866
872
 
867
- # Save as WebP
873
+ # Save as WebP - PIL images should use lossless=True for best results
868
874
  pil_img.save(
869
875
  webp_buffer,
870
- format='WEBP',
871
- quality=quality,
872
- lossless=lossless,
873
- method=6 # Best compression method
876
+ format="WEBP",
877
+ lossless=True, # Always lossless for PIL images
878
+ method=1, # Fastest method for lossless
874
879
  )
875
880
 
876
881
  # Get bytes and cleanup
@@ -881,9 +886,7 @@ async def numpy_to_webp_bytes(
881
886
 
882
887
 
883
888
  async def pil_to_webp_bytes(
884
- pil_img: Image.Image,
885
- quality: int = 85,
886
- lossless: bool = False
889
+ pil_img: Image.Image, quality: int = 85, lossless: bool = False
887
890
  ) -> bytes:
888
891
  """
889
892
  Convert PIL Image to WebP bytes.
@@ -899,13 +902,12 @@ async def pil_to_webp_bytes(
899
902
  # Create bytes buffer
900
903
  webp_buffer = io.BytesIO()
901
904
 
902
- # Save as WebP
905
+ # Save as WebP - PIL images should use lossless=True for best results
903
906
  pil_img.save(
904
907
  webp_buffer,
905
- format='WEBP',
906
- quality=quality,
907
- lossless=lossless,
908
- method=6 # Best compression method
908
+ format="WEBP",
909
+ lossless=True, # Always lossless for PIL images
910
+ method=1, # Fastest method for lossless
909
911
  )
910
912
 
911
913
  # Get bytes and cleanup
@@ -379,11 +379,8 @@ class HypferMapImageHandler(BaseHandler, AutoCrop):
379
379
  # Return WebP bytes or PIL Image based on parameter
380
380
  if return_webp:
381
381
  from .config.utils import pil_to_webp_bytes
382
- webp_bytes = await pil_to_webp_bytes(
383
- resized_image,
384
- quality=90,
385
- lossless=False
386
- )
382
+
383
+ webp_bytes = await pil_to_webp_bytes(resized_image)
387
384
  return webp_bytes
388
385
  else:
389
386
  return resized_image
@@ -391,11 +388,7 @@ class HypferMapImageHandler(BaseHandler, AutoCrop):
391
388
  # Return WebP bytes or PIL Image based on parameter
392
389
  if return_webp:
393
390
  # Convert directly from NumPy to WebP for better performance
394
- webp_bytes = await numpy_to_webp_bytes(
395
- img_np_array,
396
- quality=90,
397
- lossless=False
398
- )
391
+ webp_bytes = await numpy_to_webp_bytes(img_np_array)
399
392
  del img_np_array
400
393
  LOGGER.debug("%s: Frame Completed.", self.file_name)
401
394
  return webp_bytes
@@ -191,11 +191,7 @@ class ReImageHandler(BaseHandler, AutoCrop):
191
191
  # Return WebP bytes or PIL Image based on parameter
192
192
  if return_webp:
193
193
  # Convert directly to WebP bytes for better performance
194
- webp_bytes = await numpy_to_webp_bytes(
195
- img_np_array,
196
- quality=90, # High quality for vacuum maps
197
- lossless=False # Use lossy compression for smaller size
198
- )
194
+ webp_bytes = await numpy_to_webp_bytes(img_np_array)
199
195
  del img_np_array # free memory
200
196
  return webp_bytes
201
197
  else:
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "valetudo-map-parser"
3
- version = "0.1.9b57"
3
+ version = "0.1.9b59"
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"