valetudo-map-parser 0.1.9b9__py3-none-any.whl → 0.1.9b11__py3-none-any.whl

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.
@@ -1,16 +1,43 @@
1
1
  """Utility code for the valetudo map parser."""
2
2
 
3
+ from dataclasses import dataclass
4
+ from functools import partial
3
5
  import hashlib
4
6
  import json
5
7
  from logging import getLogger
6
8
 
7
- from PIL import ImageOps
9
+ from PIL import ImageOps, Image
8
10
 
9
11
  from .types import ChargerPosition, ImageSize, NumpyArray, RobotPosition
12
+ from .shared import CameraShared
10
13
 
11
14
  _LOGGER = getLogger(__name__)
12
15
 
13
16
 
17
+ @dataclass
18
+ class ResizeParams:
19
+ """Resize the image to the given dimensions and aspect ratio."""
20
+
21
+ pil_img: Image # PIL image object
22
+ width: int
23
+ height: int
24
+ aspect_ratio: str = None
25
+ crop_size: list = None
26
+ is_rand: bool = False
27
+ offset_func: callable = None # Function reference for offset calculation
28
+
29
+
30
+ @dataclass
31
+ class OffsetParams:
32
+ """Map parameters."""
33
+
34
+ wsf: int
35
+ hsf: int
36
+ width: int
37
+ height: int
38
+ rand256: bool = False
39
+
40
+
14
41
  class BaseHandler:
15
42
  """Avoid Code duplication"""
16
43
 
@@ -30,9 +57,16 @@ class BaseHandler:
30
57
  self.crop_img_size = [0, 0]
31
58
  self.offset_x = 0
32
59
  self.offset_y = 0
33
- self.shared = None
60
+ self.shared:CameraShared = None
34
61
  self.crop_area = [0, 0, 0, 0]
35
62
  self.zooming = False
63
+ self.async_resize_image = partial(
64
+ async_resize_image,
65
+ width=self.shared.image_ref_width,
66
+ height=self.shared.image_ref_height,
67
+ crop_size=self.crop_img_size,
68
+ offset_func=self.async_map_coordinates_offset,
69
+ )
36
70
 
37
71
  def get_frame_number(self) -> int:
38
72
  """Return the frame number of the image."""
@@ -57,97 +91,15 @@ class BaseHandler:
57
91
  def check_zoom_and_aspect_ratio(self) -> bool:
58
92
  """Check if the image is zoomed and has an aspect ratio."""
59
93
  return (
60
- self.shared.image_auto_zoom
61
- and self.shared.vacuum_state == "cleaning"
62
- and self.zooming
63
- and self.shared.image_zoom_lock_ratio
64
- or self.shared.image_aspect_ratio != "None"
94
+ self.shared.image_auto_zoom
95
+ and self.shared.vacuum_state == "cleaning"
96
+ and self.zooming
97
+ and self.shared.image_zoom_lock_ratio
98
+ or self.shared.image_aspect_ratio != "None"
65
99
  )
66
100
 
67
- async def async_resize_image(
68
- self, pil_img, aspect_ratio=None, is_rand=False
69
- ):
70
- """Resize the image to the given dimensions and aspect ratio."""
71
- width = self.shared.image_ref_width
72
- height = self.shared.image_ref_height
73
- if aspect_ratio:
74
- wsf, hsf = [int(x) for x in aspect_ratio.split(",")]
75
- if wsf == 0 or hsf == 0:
76
- return pil_img
77
- new_aspect_ratio = wsf / hsf
78
- if width / height > new_aspect_ratio:
79
- new_width = int(pil_img.height * new_aspect_ratio)
80
- new_height = pil_img.height
81
- else:
82
- new_width = pil_img.width
83
- new_height = int(pil_img.width / new_aspect_ratio)
84
- _LOGGER.debug(
85
- "%s: Image Aspect Ratio: %s, %s",
86
- self.file_name,
87
- str(wsf),
88
- str(hsf),
89
- )
90
- (
91
- self.crop_img_size[0],
92
- self.crop_img_size[1],
93
- ) = await self.async_map_coordinates_offset(
94
- wsf, hsf, new_width, new_height, is_rand
95
- )
96
- return ImageOps.pad(pil_img, (new_width, new_height))
97
- return ImageOps.pad(pil_img, (width, height))
98
-
99
- async def async_map_coordinates_offset(
100
- self, wsf: int, hsf: int, width: int, height: int, rand256: bool = False
101
- ) -> tuple[int, int]:
102
- """
103
- Offset the coordinates to the map.
104
- """
105
-
106
- if wsf == 1 and hsf == 1:
107
- self.set_image_offset_ratio_1_1(width, height, rand256)
108
- elif wsf == 2 and hsf == 1:
109
- self.set_image_offset_ratio_2_1(width, height, rand256)
110
- elif wsf == 3 and hsf == 2:
111
- self.set_image_offset_ratio_3_2(width, height, rand256)
112
- elif wsf == 5 and hsf == 4:
113
- self.set_image_offset_ratio_5_4(width, height, rand256)
114
- elif wsf == 9 and hsf == 16:
115
- self.set_image_offset_ratio_9_16(width, height, rand256)
116
- elif wsf == 16 and hsf == 9:
117
- self.set_image_offset_ratio_16_9(width, height, rand256)
118
- return width, height
119
-
120
- @staticmethod
121
- async def calculate_array_hash(layers: dict, active: list[int] = None) -> str or None:
122
- """Calculate the hash of the image based on layers and active zones."""
123
- if layers and active:
124
- data_to_hash = {
125
- "layers": len(layers["wall"][0]),
126
- "active_segments": tuple(active),
127
- }
128
- data_json = json.dumps(data_to_hash, sort_keys=True)
129
- return hashlib.sha256(data_json.encode()).hexdigest()
130
- return None
131
-
132
- @staticmethod
133
- async def async_copy_array(original_array: NumpyArray) -> NumpyArray:
134
- """Copy the array."""
135
- return NumpyArray.copy(original_array)
136
-
137
- def get_map_points(self) -> list[dict[str, int] | dict[str, int] | dict[str, int] | dict[str, int]]:
138
- """Return the map points."""
139
- return [
140
- {"x": 0, "y": 0}, # Top-left corner 0
141
- {"x": self.crop_img_size[0], "y": 0}, # Top-right corner 1
142
- {
143
- "x": self.crop_img_size[0],
144
- "y": self.crop_img_size[1],
145
- }, # Bottom-right corner 2
146
- {"x": 0, "y": self.crop_img_size[1]}, # Bottom-left corner (optional) 3
147
- ]
148
-
149
- def set_image_offset_ratio_1_1(
150
- self, width: int, height: int, rand256: bool = False
101
+ def _set_image_offset_ratio_1_1(
102
+ self, width: int, height: int, rand256: bool = False
151
103
  ) -> None:
152
104
  """Set the image offset ratio to 1:1."""
153
105
 
@@ -173,8 +125,8 @@ class BaseHandler:
173
125
  self.offset_y,
174
126
  )
175
127
 
176
- def set_image_offset_ratio_2_1(
177
- self, width: int, height: int, rand256: bool = False
128
+ def _set_image_offset_ratio_2_1(
129
+ self, width: int, height: int, rand256: bool = False
178
130
  ) -> None:
179
131
  """Set the image offset ratio to 2:1."""
180
132
 
@@ -201,8 +153,8 @@ class BaseHandler:
201
153
  self.offset_y,
202
154
  )
203
155
 
204
- def set_image_offset_ratio_3_2(
205
- self, width: int, height: int, rand256: bool = False
156
+ def _set_image_offset_ratio_3_2(
157
+ self, width: int, height: int, rand256: bool = False
206
158
  ) -> None:
207
159
  """Set the image offset ratio to 3:2."""
208
160
 
@@ -212,13 +164,11 @@ class BaseHandler:
212
164
  if rotation in [0, 180]:
213
165
  self.offset_y = width - self.crop_img_size[0]
214
166
  self.offset_x = ((height - self.crop_img_size[1]) // 2) - (
215
- self.crop_img_size[1] // 10
167
+ self.crop_img_size[1] // 10
216
168
  )
217
169
  elif rotation in [90, 270]:
218
170
  self.offset_y = (self.crop_img_size[0] - width) // 2
219
- self.offset_x = (self.crop_img_size[1] - height) + (
220
- (height // 10) // 2
221
- )
171
+ self.offset_x = (self.crop_img_size[1] - height) + ((height // 10) // 2)
222
172
  else:
223
173
  if rotation in [0, 180]:
224
174
  self.offset_x = (width - self.crop_img_size[0]) // 2
@@ -234,8 +184,8 @@ class BaseHandler:
234
184
  self.offset_y,
235
185
  )
236
186
 
237
- def set_image_offset_ratio_5_4(
238
- self, width: int, height: int, rand256: bool = False
187
+ def _set_image_offset_ratio_5_4(
188
+ self, width: int, height: int, rand256: bool = False
239
189
  ) -> None:
240
190
  """Set the image offset ratio to 5:4."""
241
191
 
@@ -243,16 +193,14 @@ class BaseHandler:
243
193
  if not rand256:
244
194
  if rotation in [0, 180]:
245
195
  self.offset_x = ((width - self.crop_img_size[0]) // 2) - (
246
- self.crop_img_size[0] // 2
196
+ self.crop_img_size[0] // 2
247
197
  )
248
198
  self.offset_y = (self.crop_img_size[1] - height) - (
249
- self.crop_img_size[1] // 2
199
+ self.crop_img_size[1] // 2
250
200
  )
251
201
  elif rotation in [90, 270]:
252
202
  self.offset_y = ((self.crop_img_size[0] - width) // 2) - 10
253
- self.offset_x = (self.crop_img_size[1] - height) + (
254
- height // 10
255
- )
203
+ self.offset_x = (self.crop_img_size[1] - height) + (height // 10)
256
204
  else:
257
205
  if rotation in [0, 180]:
258
206
  self.offset_y = (width - self.crop_img_size[0]) // 2
@@ -268,8 +216,8 @@ class BaseHandler:
268
216
  self.offset_y,
269
217
  )
270
218
 
271
- def set_image_offset_ratio_9_16(
272
- self, width: int, height: int, rand256: bool = False
219
+ def _set_image_offset_ratio_9_16(
220
+ self, width: int, height: int, rand256: bool = False
273
221
  ) -> None:
274
222
  """Set the image offset ratio to 9:16."""
275
223
 
@@ -296,8 +244,8 @@ class BaseHandler:
296
244
  self.offset_y,
297
245
  )
298
246
 
299
- def set_image_offset_ratio_16_9(
300
- self, width: int, height: int, rand256: bool = False
247
+ def _set_image_offset_ratio_16_9(
248
+ self, width: int, height: int, rand256: bool = False
301
249
  ) -> None:
302
250
  """Set the image offset ratio to 16:9."""
303
251
 
@@ -324,6 +272,71 @@ class BaseHandler:
324
272
  self.offset_y,
325
273
  )
326
274
 
275
+ async def async_map_coordinates_offset(
276
+ self, params: OffsetParams
277
+ ) -> tuple[int, int]:
278
+ """
279
+ Offset the coordinates to the map.
280
+ """
281
+ if params.wsf == 1 and params.hsf == 1:
282
+ self._set_image_offset_ratio_1_1(
283
+ params.width, params.height, params.rand256
284
+ )
285
+ elif params.wsf == 2 and params.hsf == 1:
286
+ self._set_image_offset_ratio_2_1(
287
+ params.width, params.height, params.rand256
288
+ )
289
+ elif params.wsf == 3 and params.hsf == 2:
290
+ self._set_image_offset_ratio_3_2(
291
+ params.width, params.height, params.rand256
292
+ )
293
+ elif params.wsf == 5 and params.hsf == 4:
294
+ self._set_image_offset_ratio_5_4(
295
+ params.width, params.height, params.rand256
296
+ )
297
+ elif params.wsf == 9 and params.hsf == 16:
298
+ self._set_image_offset_ratio_9_16(
299
+ params.width, params.height, params.rand256
300
+ )
301
+ elif params.wsf == 16 and params.hsf == 9:
302
+ self._set_image_offset_ratio_16_9(
303
+ params.width, params.height, params.rand256
304
+ )
305
+ return params.width, params.height
306
+
307
+ @staticmethod
308
+ async def calculate_array_hash(
309
+ layers: dict, active: list[int] = None
310
+ ) -> str or None:
311
+ """Calculate the hash of the image based on layers and active zones."""
312
+ if layers and active:
313
+ data_to_hash = {
314
+ "layers": len(layers["wall"][0]),
315
+ "active_segments": tuple(active),
316
+ }
317
+ data_json = json.dumps(data_to_hash, sort_keys=True)
318
+ return hashlib.sha256(data_json.encode()).hexdigest()
319
+ return None
320
+
321
+ @staticmethod
322
+ async def async_copy_array(original_array: NumpyArray) -> NumpyArray:
323
+ """Copy the array."""
324
+ return NumpyArray.copy(original_array)
325
+
326
+ def get_map_points(
327
+ self,
328
+ ) -> list[dict[str, int] | dict[str, int] | dict[str, int] | dict[str, int]]:
329
+ """Return the map points."""
330
+ return [
331
+ {"x": 0, "y": 0}, # Top-left corner 0
332
+ {"x": self.crop_img_size[0], "y": 0}, # Top-right corner 1
333
+ {
334
+ "x": self.crop_img_size[0],
335
+ "y": self.crop_img_size[1],
336
+ }, # Bottom-right corner 2
337
+ {"x": 0, "y": self.crop_img_size[1]}, # Bottom-left corner (optional) 3
338
+ ]
339
+
327
340
  def get_vacuum_points(self, rotation_angle: int) -> list[dict[str, int]]:
328
341
  """Calculate the calibration points based on the rotation angle."""
329
342
 
@@ -462,11 +475,47 @@ class BaseHandler:
462
475
  return point_properties
463
476
 
464
477
  @staticmethod
465
- def get_corners(x_max:int, x_min:int, y_max:int, y_min:int) -> list[tuple[int, int]]:
478
+ def get_corners(
479
+ x_max: int, x_min: int, y_max: int, y_min: int
480
+ ) -> list[tuple[int, int]]:
466
481
  """Return the corners of the image."""
467
482
  return [
468
- (x_min, y_min),
469
- (x_max, y_min),
470
- (x_max, y_max),
471
- (x_min, y_max),
483
+ (x_min, y_min),
484
+ (x_max, y_min),
485
+ (x_max, y_max),
486
+ (x_min, y_max),
472
487
  ]
488
+
489
+
490
+ async def async_resize_image(params: ResizeParams):
491
+ """Resize the image to the given dimensions and aspect ratio."""
492
+ if params.aspect_ratio:
493
+ wsf, hsf = [int(x) for x in params.aspect_ratio.split(",")]
494
+ if wsf == 0 or hsf == 0:
495
+ return params.pil_img
496
+ new_aspect_ratio = wsf / hsf
497
+ if params.width / params.height > new_aspect_ratio:
498
+ new_width = int(params.pil_img.height * new_aspect_ratio)
499
+ new_height = params.pil_img.height
500
+ else:
501
+ new_width = params.pil_img.width
502
+ new_height = int(params.pil_img.width / new_aspect_ratio)
503
+
504
+ _LOGGER.debug(
505
+ "Image Aspect Ratio: %s, %s",
506
+ str(wsf),
507
+ str(hsf),
508
+ )
509
+
510
+ if params.crop_size is not None:
511
+ params.crop_size[0], params.crop_size[1] = await params.offset_func(
512
+ wsf=wsf,
513
+ hsf=hsf,
514
+ width=new_width,
515
+ height=new_height,
516
+ rand256=params.is_rand,
517
+ )
518
+
519
+ return ImageOps.pad(params.pil_img, (new_width, new_height))
520
+
521
+ return ImageOps.pad(params.pil_img, (params.width, params.height))
@@ -256,7 +256,7 @@ class ReImageHandler(BaseHandler):
256
256
  return img_np_array
257
257
 
258
258
  async def _finalize_image(self, pil_img):
259
- if self.check_zoom_and_aspect_ratio() :
259
+ if self.check_zoom_and_aspect_ratio():
260
260
  pil_img = await self.async_resize_image(
261
261
  pil_img, self.shared.image_aspect_ratio, True
262
262
  )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: valetudo-map-parser
3
- Version: 0.1.9b9
3
+ Version: 0.1.9b11
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
@@ -6,15 +6,15 @@ valetudo_map_parser/config/drawable.py,sha256=hsrEJCMVOrjs5sJfr26SeqJD0VNlYWwxcV
6
6
  valetudo_map_parser/config/rand25_parser.py,sha256=fehyF18hRWRWbXbojocQCIaIch21Lbh1wtl2XdKRSl0,16447
7
7
  valetudo_map_parser/config/shared.py,sha256=LQV5K8tbVhEKUkby9ssjEmh_T4Ai-Euzsbag_HWYVRc,9448
8
8
  valetudo_map_parser/config/types.py,sha256=sdjqhcxpRSxNFLVHIyCg-RDL9JIjtQHoR2C2WpocWBc,16107
9
- valetudo_map_parser/config/utils.py,sha256=eK7Ai5G6Za9U_kNwER2Z6ELwHmJMkflGtLGufE6FHb0,17515
9
+ valetudo_map_parser/config/utils.py,sha256=ieBsNcjzYzhXZzM6JdQQHRUGpuk3dSzBpp3NdDRywyA,18525
10
10
  valetudo_map_parser/hypfer_draw.py,sha256=s58ak9IBYLjJyoddfDC99PfQ12HTjkSfJYXqCi4vZKs,14931
11
11
  valetudo_map_parser/hypfer_handler.py,sha256=lA9rC4gO0cNas4ValRikw4X6Uc0sqo2-Roj7EQi1MBA,13471
12
12
  valetudo_map_parser/map_data.py,sha256=6FbQfgxFB6E4kcOWokReJOVSekVaE1kStyhTQhAhiOg,19469
13
13
  valetudo_map_parser/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
- valetudo_map_parser/rand25_handler.py,sha256=xvnUAd5C3byGx7j1u2QmNlSCVDcuDOHa89PUdsVRfgc,15277
14
+ valetudo_map_parser/rand25_handler.py,sha256=u3N-_qjpYQM3WO4cnhqHIPa7TMquJ9XYqhoUYFKoc8I,15276
15
15
  valetudo_map_parser/reimg_draw.py,sha256=dtdbYKKxmQnbOaHBHayWEF07OdSnTKo2CPSOW0qpgH0,12506
16
- valetudo_map_parser-0.1.9b9.dist-info/LICENSE,sha256=Lh-qBbuRV0-jiCIBhfV7NgdwFxQFOXH3BKOzK865hRs,10480
17
- valetudo_map_parser-0.1.9b9.dist-info/METADATA,sha256=uXL383aQm_sHMQKIRs0AM2iAJIeilsMy1uit9KnMqm8,1028
18
- valetudo_map_parser-0.1.9b9.dist-info/NOTICE.txt,sha256=5lTOuWiU9aiEnJ2go8sc7lTJ7ntMBx0g0GFnNrswCY4,2533
19
- valetudo_map_parser-0.1.9b9.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
20
- valetudo_map_parser-0.1.9b9.dist-info/RECORD,,
16
+ valetudo_map_parser-0.1.9b11.dist-info/LICENSE,sha256=Lh-qBbuRV0-jiCIBhfV7NgdwFxQFOXH3BKOzK865hRs,10480
17
+ valetudo_map_parser-0.1.9b11.dist-info/METADATA,sha256=kxebR1Ww4xKkgoEhMOXAgL80TfyXlb0oeL1z24QU7qQ,1029
18
+ valetudo_map_parser-0.1.9b11.dist-info/NOTICE.txt,sha256=5lTOuWiU9aiEnJ2go8sc7lTJ7ntMBx0g0GFnNrswCY4,2533
19
+ valetudo_map_parser-0.1.9b11.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
20
+ valetudo_map_parser-0.1.9b11.dist-info/RECORD,,