valetudo-map-parser 0.1.7__tar.gz → 0.1.9b0__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 (20) hide show
  1. {valetudo_map_parser-0.1.7 → valetudo_map_parser-0.1.9b0}/PKG-INFO +1 -1
  2. {valetudo_map_parser-0.1.7 → valetudo_map_parser-0.1.9b0}/SCR/valetudo_map_parser/__init__.py +1 -1
  3. {valetudo_map_parser-0.1.7 → valetudo_map_parser-0.1.9b0}/SCR/valetudo_map_parser/config/auto_crop.py +1 -1
  4. {valetudo_map_parser-0.1.7 → valetudo_map_parser-0.1.9b0}/SCR/valetudo_map_parser/config/rand25_parser.py +36 -28
  5. {valetudo_map_parser-0.1.7 → valetudo_map_parser-0.1.9b0}/SCR/valetudo_map_parser/hypfer_handler.py +1 -1
  6. {valetudo_map_parser-0.1.7 → valetudo_map_parser-0.1.9b0}/SCR/valetudo_map_parser/map_data.py +17 -16
  7. {valetudo_map_parser-0.1.7 → valetudo_map_parser-0.1.9b0}/SCR/valetudo_map_parser/rand25_handler.py +162 -152
  8. {valetudo_map_parser-0.1.7 → valetudo_map_parser-0.1.9b0}/SCR/valetudo_map_parser/reimg_draw.py +53 -51
  9. {valetudo_map_parser-0.1.7 → valetudo_map_parser-0.1.9b0}/pyproject.toml +1 -1
  10. {valetudo_map_parser-0.1.7 → valetudo_map_parser-0.1.9b0}/LICENSE +0 -0
  11. {valetudo_map_parser-0.1.7 → valetudo_map_parser-0.1.9b0}/NOTICE.txt +0 -0
  12. {valetudo_map_parser-0.1.7 → valetudo_map_parser-0.1.9b0}/README.md +0 -0
  13. {valetudo_map_parser-0.1.7 → valetudo_map_parser-0.1.9b0}/SCR/valetudo_map_parser/config/__init__.py +0 -0
  14. {valetudo_map_parser-0.1.7 → valetudo_map_parser-0.1.9b0}/SCR/valetudo_map_parser/config/colors.py +0 -0
  15. {valetudo_map_parser-0.1.7 → valetudo_map_parser-0.1.9b0}/SCR/valetudo_map_parser/config/drawable.py +0 -0
  16. {valetudo_map_parser-0.1.7 → valetudo_map_parser-0.1.9b0}/SCR/valetudo_map_parser/config/shared.py +0 -0
  17. {valetudo_map_parser-0.1.7 → valetudo_map_parser-0.1.9b0}/SCR/valetudo_map_parser/config/types.py +0 -0
  18. {valetudo_map_parser-0.1.7 → valetudo_map_parser-0.1.9b0}/SCR/valetudo_map_parser/hypfer_draw.py +0 -0
  19. {valetudo_map_parser-0.1.7 → valetudo_map_parser-0.1.9b0}/SCR/valetudo_map_parser/images_utils.py +0 -0
  20. {valetudo_map_parser-0.1.7 → valetudo_map_parser-0.1.9b0}/SCR/valetudo_map_parser/py.typed +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: valetudo-map-parser
3
- Version: 0.1.7
3
+ Version: 0.1.9b0
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
@@ -1,5 +1,5 @@
1
1
  """Valetudo map parser.
2
- Version: 0.1.7"""
2
+ Version: 0.1.8"""
3
3
 
4
4
  from .hypfer_handler import HypferMapImageHandler
5
5
  from .rand25_handler import ReImageHandler
@@ -70,7 +70,7 @@ class AutoCrop:
70
70
  )
71
71
  return trimmed_width, trimmed_height
72
72
 
73
- async def _async_auto_crop_data(self, tdata=None):
73
+ async def _async_auto_crop_data(self): #, tdata=None
74
74
  """Load the auto crop data from the Camera config."""
75
75
  # todo: implement this method but from config data
76
76
  # if not self.imh.auto_crop:
@@ -162,7 +162,7 @@ class RRMapParser:
162
162
  )
163
163
  if segment_type == 0:
164
164
  continue
165
- elif segment_type == 1 and pixels:
165
+ if segment_type == 1 and pixels:
166
166
  parameters["pixels"]["walls"].append(i)
167
167
  else:
168
168
  s = (
@@ -257,7 +257,7 @@ class RRMapParser:
257
257
 
258
258
  @callback
259
259
  def parse_rrm_data(
260
- self, map_buf: bytes, pixels: bool = False
260
+ self, map_buf: bytes, pixels: bool = False
261
261
  ) -> Optional[Dict[str, Any]]:
262
262
  """Parse the complete map data."""
263
263
  if not self.parse(map_buf).get("map_index"):
@@ -266,6 +266,18 @@ class RRMapParser:
266
266
  parsed_map_data = {}
267
267
  blocks = self.parse_block(map_buf, 0x14, None, pixels)
268
268
 
269
+ self._parse_image_data(parsed_map_data, blocks)
270
+ self._parse_charger_data(parsed_map_data, blocks)
271
+ self._parse_robot_data(parsed_map_data, blocks)
272
+ self._parse_zones_data(parsed_map_data, blocks)
273
+ self._parse_virtual_walls_data(parsed_map_data, blocks)
274
+ self._parse_misc_data(parsed_map_data, blocks)
275
+
276
+ return parsed_map_data
277
+
278
+ @staticmethod
279
+ def _parse_image_data(parsed_map_data: Dict[str, Any], blocks: Dict[int, Any]):
280
+ """Parse image-related data."""
269
281
  if RRMapParser.Types.IMAGE.value in blocks:
270
282
  parsed_map_data["image"] = blocks[RRMapParser.Types.IMAGE.value]
271
283
  for item in [
@@ -290,28 +302,27 @@ class RRMapParser:
290
302
  - parsed_map_data[item["path"]]["points"][-2][0],
291
303
  )
292
304
  )
305
+
306
+ @staticmethod
307
+ def _parse_charger_data(parsed_map_data: Dict[str, Any], blocks: Dict[int, Any]):
308
+ """Parse charger location data."""
293
309
  if RRMapParser.Types.CHARGER_LOCATION.value in blocks:
294
310
  charger = blocks[RRMapParser.Types.CHARGER_LOCATION.value]["position"]
295
- # Assume no transformation needed here
296
311
  parsed_map_data["charger"] = charger
297
312
 
313
+ @staticmethod
314
+ def _parse_robot_data(parsed_map_data: Dict[str, Any], blocks: Dict[int, Any]):
315
+ """Parse robot position data."""
298
316
  if RRMapParser.Types.ROBOT_POSITION.value in blocks:
299
317
  robot = blocks[RRMapParser.Types.ROBOT_POSITION.value]["position"]
300
318
  rob_angle = blocks[RRMapParser.Types.ROBOT_POSITION.value]["angle"]
301
- # Assume no transformation needed here
302
319
  parsed_map_data["robot"] = robot
303
320
  parsed_map_data["robot_angle"] = rob_angle
304
321
 
305
- if RRMapParser.Types.GOTO_TARGET.value in blocks:
306
- parsed_map_data["goto_target"] = blocks[
307
- RRMapParser.Types.GOTO_TARGET.value
308
- ]["position"]
309
- # Assume no transformation needed here
310
-
322
+ @staticmethod
323
+ def _parse_zones_data(parsed_map_data: Dict[str, Any], blocks: Dict[int, Any]):
324
+ """Parse zones and forbidden zones data."""
311
325
  if RRMapParser.Types.CURRENTLY_CLEANED_ZONES.value in blocks:
312
- parsed_map_data["currently_cleaned_zones"] = blocks[
313
- RRMapParser.Types.CURRENTLY_CLEANED_ZONES.value
314
- ]
315
326
  parsed_map_data["currently_cleaned_zones"] = [
316
327
  [
317
328
  zone[0],
@@ -319,13 +330,10 @@ class RRMapParser:
319
330
  zone[2],
320
331
  RRMapParser.Tools.DIMENSION_MM - zone[3],
321
332
  ]
322
- for zone in parsed_map_data["currently_cleaned_zones"]
333
+ for zone in blocks[RRMapParser.Types.CURRENTLY_CLEANED_ZONES.value]
323
334
  ]
324
335
 
325
336
  if RRMapParser.Types.FORBIDDEN_ZONES.value in blocks:
326
- parsed_map_data["forbidden_zones"] = blocks[
327
- RRMapParser.Types.FORBIDDEN_ZONES.value
328
- ]
329
337
  parsed_map_data["forbidden_zones"] = [
330
338
  [
331
339
  zone[0],
@@ -337,13 +345,15 @@ class RRMapParser:
337
345
  zone[6],
338
346
  RRMapParser.Tools.DIMENSION_MM - zone[7],
339
347
  ]
340
- for zone in parsed_map_data["forbidden_zones"]
348
+ for zone in blocks[RRMapParser.Types.FORBIDDEN_ZONES.value]
341
349
  ]
342
350
 
351
+ @staticmethod
352
+ def _parse_virtual_walls_data(
353
+ parsed_map_data: Dict[str, Any], blocks: Dict[int, Any]
354
+ ):
355
+ """Parse virtual walls data."""
343
356
  if RRMapParser.Types.VIRTUAL_WALLS.value in blocks:
344
- parsed_map_data["virtual_walls"] = blocks[
345
- RRMapParser.Types.VIRTUAL_WALLS.value
346
- ]
347
357
  parsed_map_data["virtual_walls"] = [
348
358
  [
349
359
  wall[0],
@@ -351,18 +361,18 @@ class RRMapParser:
351
361
  wall[2],
352
362
  RRMapParser.Tools.DIMENSION_MM - wall[3],
353
363
  ]
354
- for wall in parsed_map_data["virtual_walls"]
364
+ for wall in blocks[RRMapParser.Types.VIRTUAL_WALLS.value]
355
365
  ]
356
366
 
367
+ @staticmethod
368
+ def _parse_misc_data(parsed_map_data: Dict[str, Any], blocks: Dict[int, Any]):
369
+ """Parse miscellaneous data like cleaned blocks and mop zones."""
357
370
  if RRMapParser.Types.CURRENTLY_CLEANED_BLOCKS.value in blocks:
358
371
  parsed_map_data["currently_cleaned_blocks"] = blocks[
359
372
  RRMapParser.Types.CURRENTLY_CLEANED_BLOCKS.value
360
373
  ]
361
374
 
362
375
  if RRMapParser.Types.FORBIDDEN_MOP_ZONES.value in blocks:
363
- parsed_map_data["forbidden_mop_zones"] = blocks[
364
- RRMapParser.Types.FORBIDDEN_MOP_ZONES.value
365
- ]
366
376
  parsed_map_data["forbidden_mop_zones"] = [
367
377
  [
368
378
  zone[0],
@@ -374,11 +384,9 @@ class RRMapParser:
374
384
  zone[6],
375
385
  RRMapParser.Tools.DIMENSION_MM - zone[7],
376
386
  ]
377
- for zone in parsed_map_data["forbidden_mop_zones"]
387
+ for zone in blocks[RRMapParser.Types.FORBIDDEN_MOP_ZONES.value]
378
388
  ]
379
389
 
380
- return parsed_map_data
381
-
382
390
  def parse_data(
383
391
  self, payload: Optional[bytes] = None, pixels: bool = False
384
392
  ) -> Optional[Dict[str, Any]]:
@@ -2,7 +2,7 @@
2
2
  Hypfer Image Handler Class.
3
3
  It returns the PIL PNG image frame relative to the Map Data extrapolated from the vacuum json.
4
4
  It also returns calibration, rooms data to the card and other images information to the camera.
5
- Version: 2024.08.0
5
+ Version: 0.1.9
6
6
  """
7
7
 
8
8
  from __future__ import annotations
@@ -209,10 +209,11 @@ class ImageData:
209
209
  max_y * pixel_size,
210
210
  )
211
211
 
212
- # Added below in order to support Valetudo Re.
213
- # This functions read directly the data from the json created
214
- # from the parser for Valetudo Re. They allow to use the
215
- # functions to draw the image without changes on the drawing class.
212
+
213
+ class RandImageData:
214
+ """This functions read directly the data from the json created
215
+ from the parser for Valetudo Re. They allow to use the
216
+ functions to draw the image without changes on the drawing class."""
216
217
 
217
218
  @staticmethod
218
219
  def from_rrm_to_compressed_pixels(
@@ -297,7 +298,7 @@ class ImageData:
297
298
  except KeyError:
298
299
  return None
299
300
  predicted_path = ImageData.sublist_join(
300
- ImageData.rrm_valetudo_path_array(points), 2
301
+ RandImageData.rrm_valetudo_path_array(points), 2
301
302
  )
302
303
  return predicted_path
303
304
 
@@ -330,7 +331,7 @@ class ImageData:
330
331
  return None
331
332
 
332
333
  if path_data and path_data != []:
333
- path_data = ImageData.rrm_coordinates_to_valetudo(path_data)
334
+ path_data = RandImageData.rrm_coordinates_to_valetudo(path_data)
334
335
  return path_data
335
336
  return None
336
337
 
@@ -338,14 +339,14 @@ class ImageData:
338
339
  def get_rrm_currently_cleaned_zones(json_data: JsonType) -> dict:
339
340
  """Get the currently cleaned zones from the json."""
340
341
  re_zones = json_data.get("currently_cleaned_zones", [])
341
- formatted_zones = ImageData.rrm_valetudo_format_zone(re_zones)
342
+ formatted_zones = RandImageData.rrm_valetudo_format_zone(re_zones)
342
343
  return formatted_zones
343
344
 
344
345
  @staticmethod
345
346
  def get_rrm_forbidden_zones(json_data: JsonType) -> dict:
346
347
  """Get the forbidden zones from the json."""
347
348
  re_zones = json_data.get("forbidden_zones", [])
348
- formatted_zones = ImageData.rrm_valetudo_format_zone(re_zones)
349
+ formatted_zones = RandImageData.rrm_valetudo_format_zone(re_zones)
349
350
  return formatted_zones
350
351
 
351
352
  @staticmethod
@@ -406,7 +407,7 @@ class ImageData:
406
407
  tmp_data = json_data.get("virtual_walls", [])
407
408
  except KeyError:
408
409
  return None
409
- virtual_walls = ImageData.rrm_valetudo_lines(tmp_data)
410
+ virtual_walls = RandImageData.rrm_valetudo_lines(tmp_data)
410
411
  return virtual_walls
411
412
 
412
413
  @staticmethod
@@ -424,7 +425,7 @@ class ImageData:
424
425
  """Get the image size from the json."""
425
426
  if isinstance(json_data, tuple):
426
427
  return 0, 0
427
- image = ImageData.get_rrm_image(json_data)
428
+ image = RandImageData.get_rrm_image(json_data)
428
429
  if image == {}:
429
430
  return 0, 0
430
431
  dimensions = image.get("dimensions", {})
@@ -433,20 +434,20 @@ class ImageData:
433
434
  @staticmethod
434
435
  def get_rrm_image_position(json_data: JsonType) -> tuple:
435
436
  """Get the image position from the json."""
436
- image = ImageData.get_rrm_image(json_data)
437
+ image = RandImageData.get_rrm_image(json_data)
437
438
  position = image.get("position", {})
438
439
  return position.get("top", 0), position.get("left", 0)
439
440
 
440
441
  @staticmethod
441
442
  def get_rrm_floor(json_data: JsonType) -> list:
442
443
  """Get the floor data from the json."""
443
- img = ImageData.get_rrm_image(json_data)
444
+ img = RandImageData.get_rrm_image(json_data)
444
445
  return img.get("pixels", {}).get("floor", [])
445
446
 
446
447
  @staticmethod
447
448
  def get_rrm_walls(json_data: JsonType) -> list:
448
449
  """Get the walls data from the json."""
449
- img = ImageData.get_rrm_image(json_data)
450
+ img = RandImageData.get_rrm_image(json_data)
450
451
  return img.get("pixels", {}).get("walls", [])
451
452
 
452
453
  @staticmethod
@@ -460,7 +461,7 @@ class ImageData:
460
461
  ) -> tuple or list:
461
462
  """Get the segments data from the json."""
462
463
 
463
- img = ImageData.get_rrm_image(json_data)
464
+ img = RandImageData.get_rrm_image(json_data)
464
465
  seg_data = img.get("segments", {})
465
466
  seg_ids = seg_data.get("id")
466
467
  segments = []
@@ -469,7 +470,7 @@ class ImageData:
469
470
  for id_seg in seg_ids:
470
471
  tmp_data = seg_data.get("pixels_seg_" + str(id_seg))
471
472
  segments.append(
472
- ImageData.from_rrm_to_compressed_pixels(
473
+ RandImageData.from_rrm_to_compressed_pixels(
473
474
  tmp_data,
474
475
  image_width=size_x,
475
476
  image_height=size_y,
@@ -493,7 +494,7 @@ class ImageData:
493
494
  def get_rrm_segments_ids(json_data: JsonType) -> list or None:
494
495
  """Get the segments ids from the json."""
495
496
  try:
496
- img = ImageData.get_rrm_image(json_data)
497
+ img = RandImageData.get_rrm_image(json_data)
497
498
  seg_ids = img.get("segments", {}).get("id", [])
498
499
  except KeyError:
499
500
  return None
@@ -2,7 +2,7 @@
2
2
  Image Handler Module for Valetudo Re Vacuums.
3
3
  It returns the PIL PNG image frame relative to the Map Data extrapolated from the vacuum json.
4
4
  It also returns calibration, rooms data to the card and other images information to the camera.
5
- Version: v2024.12.0
5
+ Version: 0.1.9
6
6
  """
7
7
 
8
8
  from __future__ import annotations
@@ -11,19 +11,19 @@ import logging
11
11
  import uuid
12
12
 
13
13
  from PIL import Image, ImageOps
14
-
14
+ from typing import Any
15
15
  from .config.types import COLORS, DEFAULT_IMAGE_SIZE, DEFAULT_PIXEL_SIZE
16
16
  from .config.types import Colors, JsonType, PilPNG, RobotPosition, RoomsProperties
17
17
  from .config.auto_crop import AutoCrop
18
18
  from .images_utils import ImageUtils as ImUtils
19
- from .map_data import ImageData
19
+ from .map_data import RandImageData
20
20
  from .reimg_draw import ImageDraw
21
21
 
22
22
  _LOGGER = logging.getLogger(__name__)
23
23
 
24
24
 
25
25
  # noinspection PyTypeChecker
26
- class ReImageHandler(object):
26
+ class ReImageHandler:
27
27
  """
28
28
  Image Handler for Valetudo Re Vacuums.
29
29
  """
@@ -35,8 +35,8 @@ class ReImageHandler(object):
35
35
  self.calibration_data = None # Calibration data
36
36
  self.charger_pos = None # Charger position
37
37
  self.crop_area = None # Crop area
38
- self.crop_img_size = None # Crop image size
39
- self.data = ImageData # Image Data
38
+ self.crop_img_size = [] # Crop image size
39
+ self.data = RandImageData # Image Data
40
40
  self.frame_number = 0 # Image Frame number
41
41
  self.max_frames = 1024
42
42
  self.go_to = None # Go to position data
@@ -72,15 +72,15 @@ class ReImageHandler(object):
72
72
  self, json_data: JsonType, destinations: JsonType
73
73
  ) -> RoomsProperties:
74
74
  """Extract the room properties."""
75
- unsorted_id = ImageData.get_rrm_segments_ids(json_data)
76
- size_x, size_y = ImageData.get_rrm_image_size(json_data)
77
- top, left = ImageData.get_rrm_image_position(json_data)
75
+ unsorted_id = RandImageData.get_rrm_segments_ids(json_data)
76
+ size_x, size_y = RandImageData.get_rrm_image_size(json_data)
77
+ top, left = RandImageData.get_rrm_image_position(json_data)
78
78
  try:
79
79
  if not self.segment_data or not self.outlines:
80
80
  (
81
81
  self.segment_data,
82
82
  self.outlines,
83
- ) = await ImageData.async_get_rrm_segments(
83
+ ) = await RandImageData.async_get_rrm_segments(
84
84
  json_data, size_x, size_y, top, left, True
85
85
  )
86
86
  dest_json = destinations
@@ -126,23 +126,23 @@ class ReImageHandler(object):
126
126
  zone_properties = await self.imu.async_zone_propriety(zones_data)
127
127
  # get the points data
128
128
  point_properties = await self.imu.async_points_propriety(points_data)
129
-
130
- if room_properties != {}:
131
- if zone_properties != {}:
132
- _LOGGER.debug("Rooms and Zones, data extracted!")
133
- else:
134
- _LOGGER.debug("Rooms, data extracted!")
135
- elif zone_properties != {}:
136
- _LOGGER.debug("Zones, data extracted!")
129
+ if room_properties or zone_properties:
130
+ extracted_data = [
131
+ f"{len(room_properties)} Rooms" if room_properties else None,
132
+ f"{len(zone_properties)} Zones" if zone_properties else None,
133
+ ]
134
+ extracted_data = ", ".join(filter(None, extracted_data))
135
+ _LOGGER.debug("Extracted data: %s", extracted_data)
137
136
  else:
138
137
  self.rooms_pos = None
139
138
  _LOGGER.debug(
140
- f"{self.file_name}: Rooms and Zones data not available!"
139
+ "%s: Rooms and Zones data not available!", self.file_name
141
140
  )
142
141
  return room_properties, zone_properties, point_properties
143
- except Exception as e:
142
+ except RuntimeError as e:
144
143
  _LOGGER.debug(
145
- f"No rooms Data or Error in extract_room_properties: {e}",
144
+ "No rooms Data or Error in extract_room_properties: %s",
145
+ e,
146
146
  exc_info=True,
147
147
  )
148
148
  return None, None, None
@@ -160,154 +160,164 @@ class ReImageHandler(object):
160
160
 
161
161
  try:
162
162
  if (m_json is not None) and (not isinstance(m_json, tuple)):
163
- _LOGGER.info(f"{self.file_name}: Composing the image for the camera.")
164
- # buffer json data
163
+ _LOGGER.info("%s: Composing the image for the camera.", self.file_name)
165
164
  self.json_data = m_json
166
- # get the image size
167
165
  size_x, size_y = self.data.get_rrm_image_size(m_json)
168
- ##########################
169
166
  self.img_size = DEFAULT_IMAGE_SIZE
170
- ###########################
171
167
  self.json_id = str(uuid.uuid4()) # image id
172
- _LOGGER.info(f"Vacuum Data ID: {self.json_id}")
173
- # get the robot position
168
+ _LOGGER.info("Vacuum Data ID: %s", self.json_id)
169
+
174
170
  (
175
- robot_pos,
171
+ img_np_array,
176
172
  robot_position,
177
173
  robot_position_angle,
178
- ) = await self.imd.async_get_robot_position(m_json)
179
- if self.frame_number == 0:
180
- room_id, img_np_array = await self.imd.async_draw_base_layer(
181
- m_json,
182
- size_x,
183
- size_y,
184
- colors["wall"],
185
- colors["zone_clean"],
186
- colors["background"],
187
- DEFAULT_PIXEL_SIZE,
188
- )
189
- _LOGGER.info(f"{self.file_name}: Completed base Layers")
190
- if (room_id > 0) and not self.room_propriety:
191
- self.room_propriety = await self.get_rooms_attributes(
192
- destinations
193
- )
194
- if self.rooms_pos:
195
- self.robot_pos = await self.async_get_robot_in_room(
196
- (robot_position[0] * 10),
197
- (robot_position[1] * 10),
198
- robot_position_angle,
199
- )
200
- self.img_base_layer = await self.imd.async_copy_array(img_np_array)
201
-
202
- # If there is a zone clean we draw it now.
174
+ ) = await self._setup_robot_and_image(
175
+ m_json, size_x, size_y, colors, destinations
176
+ )
177
+
178
+ # Increment frame number
203
179
  self.frame_number += 1
204
180
  img_np_array = await self.imd.async_copy_array(self.img_base_layer)
205
- _LOGGER.debug(f"{self.file_name}: Frame number {self.frame_number}")
206
- if self.frame_number > 5:
207
- self.frame_number = 0
208
- # All below will be drawn each time
209
- # charger
210
- img_np_array, self.charger_pos = await self.imd.async_draw_charger(
211
- img_np_array, m_json, colors["charger"]
212
- )
213
- # zone clean
214
- img_np_array = await self.imd.async_draw_zones(
215
- m_json, img_np_array, colors["zone_clean"]
216
- )
217
- # virtual walls
218
- img_np_array = await self.imd.async_draw_virtual_restrictions(
219
- m_json, img_np_array, colors["no_go"]
220
- )
221
- # draw path
222
- img_np_array = await self.imd.async_draw_path(
223
- img_np_array, m_json, colors["move"]
224
- )
225
- # go to flag and predicted path
226
- await self.imd.async_draw_go_to_flag(
227
- img_np_array, m_json, colors["go_to"]
228
- )
229
- # draw the robot
230
- img_np_array = await self.imd.async_draw_robot_on_map(
231
- img_np_array, robot_position, robot_position_angle, colors["robot"]
232
- )
233
181
  _LOGGER.debug(
234
- f"{self.file_name}:"
235
- f" Auto cropping the image with rotation {int(self.shared.image_rotate)}"
182
+ "%s: Frame number %s", self.file_name, str(self.frame_number)
236
183
  )
237
- img_np_array = await self.ac.async_auto_trim_and_zoom_image(
238
- img_np_array,
239
- colors["background"],
240
- int(self.shared.margins),
241
- int(self.shared.image_rotate),
242
- self.zooming,
243
- rand256=True,
184
+ if self.frame_number > 5:
185
+ self.frame_number = 0
186
+
187
+ # Draw map elements
188
+ img_np_array = await self._draw_map_elements(
189
+ img_np_array, m_json, colors, robot_position, robot_position_angle
244
190
  )
191
+
192
+ # Final adjustments
245
193
  pil_img = Image.fromarray(img_np_array, mode="RGBA")
246
194
  del img_np_array # free memory
247
- # reduce the image size if the zoomed image is bigger then the original.
248
- if (
249
- self.shared.image_auto_zoom
250
- and self.shared.vacuum_state == "cleaning"
251
- and self.zooming
252
- and self.shared.image_zoom_lock_ratio
253
- or self.shared.image_aspect_ratio != "None"
254
- ):
255
- width = self.shared.image_ref_width
256
- height = self.shared.image_ref_height
257
- if self.shared.image_aspect_ratio != "None":
258
- wsf, hsf = [
259
- int(x) for x in self.shared.image_aspect_ratio.split(",")
260
- ]
261
- _LOGGER.debug(f"Aspect Ratio: {wsf}, {hsf}")
262
- if wsf == 0 or hsf == 0:
263
- return pil_img
264
- new_aspect_ratio = wsf / hsf
265
- aspect_ratio = width / height
266
- if aspect_ratio > new_aspect_ratio:
267
- new_width = int(pil_img.height * new_aspect_ratio)
268
- new_height = pil_img.height
269
- else:
270
- new_width = pil_img.width
271
- new_height = int(pil_img.width / new_aspect_ratio)
272
-
273
- resized = ImageOps.pad(pil_img, (new_width, new_height))
274
- (
275
- self.crop_img_size[0],
276
- self.crop_img_size[1],
277
- ) = await self.async_map_coordinates_offset(
278
- wsf, hsf, new_width, new_height
279
- )
280
- _LOGGER.debug(
281
- f"{self.file_name}: Image Aspect Ratio ({wsf}, {hsf}): {new_width}x{new_height}"
282
- )
283
- _LOGGER.debug(f"{self.file_name}: Frame Completed.")
284
- return resized
285
- else:
286
- _LOGGER.debug(f"{self.file_name}: Frame Completed.")
287
- return ImageOps.pad(pil_img, (width, height))
288
- else:
289
- _LOGGER.debug(f"{self.file_name}: Frame Completed.")
290
- return pil_img
195
+
196
+ return await self._finalize_image(pil_img)
197
+
291
198
  except (RuntimeError, RuntimeWarning) as e:
292
199
  _LOGGER.warning(
293
- f"{self.file_name}: Error {e} during image creation.",
200
+ "%s: Runtime Error %s during image creation.",
201
+ self.file_name,
202
+ str(e),
294
203
  exc_info=True,
295
204
  )
296
205
  return None
297
206
 
207
+ async def _setup_robot_and_image(
208
+ self, m_json, size_x, size_y, colors, destinations
209
+ ):
210
+ robot_position, robot_position_angle = await self.imd.async_get_robot_position(
211
+ m_json
212
+ )
213
+ if self.frame_number == 0:
214
+ room_id, img_np_array = await self.imd.async_draw_base_layer(
215
+ m_json,
216
+ size_x,
217
+ size_y,
218
+ colors["wall"],
219
+ colors["zone_clean"],
220
+ colors["background"],
221
+ DEFAULT_PIXEL_SIZE,
222
+ )
223
+ _LOGGER.info("%s: Completed base Layers", self.file_name)
224
+ if (room_id > 0) and not self.room_propriety:
225
+ self.room_propriety = await self.get_rooms_attributes(destinations)
226
+ if self.rooms_pos:
227
+ self.robot_pos = await self.async_get_robot_in_room(
228
+ (robot_position[0] * 10),
229
+ (robot_position[1] * 10),
230
+ robot_position_angle,
231
+ )
232
+ self.img_base_layer = await self.imd.async_copy_array(img_np_array)
233
+ return self.img_base_layer, robot_position, robot_position_angle
234
+
235
+ async def _draw_map_elements(
236
+ self, img_np_array, m_json, colors, robot_position, robot_position_angle
237
+ ):
238
+ img_np_array, self.charger_pos = await self.imd.async_draw_charger(
239
+ img_np_array, m_json, colors["charger"]
240
+ )
241
+ img_np_array = await self.imd.async_draw_zones(
242
+ m_json, img_np_array, colors["zone_clean"]
243
+ )
244
+ img_np_array = await self.imd.async_draw_virtual_restrictions(
245
+ m_json, img_np_array, colors["no_go"]
246
+ )
247
+ img_np_array = await self.imd.async_draw_path(
248
+ img_np_array, m_json, colors["move"]
249
+ )
250
+ await self.imd.async_draw_go_to_flag(img_np_array, m_json, colors["go_to"])
251
+ img_np_array = await self.imd.async_draw_robot_on_map(
252
+ img_np_array, robot_position, robot_position_angle, colors["robot"]
253
+ )
254
+ img_np_array = await self.ac.async_auto_trim_and_zoom_image(
255
+ img_np_array,
256
+ colors["background"],
257
+ int(self.shared.margins),
258
+ int(self.shared.image_rotate),
259
+ self.zooming,
260
+ rand256=True,
261
+ )
262
+ return img_np_array
263
+
264
+ async def _finalize_image(self, pil_img):
265
+ if (
266
+ self.shared.image_auto_zoom
267
+ and self.shared.vacuum_state == "cleaning"
268
+ and self.zooming
269
+ and self.shared.image_zoom_lock_ratio
270
+ or self.shared.image_aspect_ratio != "None"
271
+ ):
272
+ width = self.shared.image_ref_width
273
+ height = self.shared.image_ref_height
274
+ if self.shared.image_aspect_ratio != "None":
275
+ wsf, hsf = [int(x) for x in self.shared.image_aspect_ratio.split(",")]
276
+ _LOGGER.debug("Aspect Ratio: %s, %s", str(wsf), str(hsf))
277
+ if wsf == 0 or hsf == 0:
278
+ return pil_img
279
+ new_aspect_ratio = wsf / hsf
280
+ aspect_ratio = width / height
281
+ if aspect_ratio > new_aspect_ratio:
282
+ new_width = int(pil_img.height * new_aspect_ratio)
283
+ new_height = pil_img.height
284
+ else:
285
+ new_width = pil_img.width
286
+ new_height = int(pil_img.width / new_aspect_ratio)
287
+
288
+ resized = ImageOps.pad(pil_img, (new_width, new_height))
289
+ (
290
+ self.crop_img_size[0],
291
+ self.crop_img_size[1],
292
+ ) = await self.async_map_coordinates_offset(
293
+ wsf, hsf, new_width, new_height
294
+ )
295
+ _LOGGER.debug(
296
+ "%s: Image Aspect Ratio: %s, %s",
297
+ self.file_name,
298
+ str(wsf),
299
+ str(hsf),
300
+ )
301
+ _LOGGER.debug("%s: Resized Frame Completed.", self.file_name)
302
+ return resized
303
+ _LOGGER.debug("%s: Padded Frame Completed.", self.file_name)
304
+ return ImageOps.pad(pil_img, (width, height))
305
+ _LOGGER.debug("%s: Frame Completed.", self.file_name)
306
+ return pil_img
307
+
298
308
  def get_frame_number(self) -> int:
299
309
  """Return the frame number."""
300
310
  return self.frame_number
301
311
 
302
- def get_robot_position(self) -> any:
312
+ def get_robot_position(self) -> Any:
303
313
  """Return the robot position."""
304
314
  return self.robot_pos
305
315
 
306
- def get_charger_position(self) -> any:
316
+ def get_charger_position(self) -> Any:
307
317
  """Return the charger position."""
308
318
  return self.charger_pos
309
319
 
310
- def get_img_size(self) -> any:
320
+ def get_img_size(self) -> Any:
311
321
  """Return the image size."""
312
322
  return self.img_size
313
323
 
@@ -336,15 +346,13 @@ class ReImageHandler(object):
336
346
  """Get the robot position and return in what room is."""
337
347
 
338
348
  def _check_robot_position(x: int, y: int) -> bool:
339
- x_in_room = (self.robot_in_room["left"] >= x) and (
340
- self.robot_in_room["right"] <= x
341
- )
342
- y_in_room = (self.robot_in_room["up"] >= y) and (
343
- self.robot_in_room["down"] <= y
349
+ # Check if the robot coordinates are inside the room's corners
350
+ return (
351
+ self.robot_in_room["left"] >= x >= self.robot_in_room["right"]
352
+ and self.robot_in_room["up"] >= y >= self.robot_in_room["down"]
344
353
  )
345
- return x_in_room and y_in_room
346
354
 
347
- # Check if the robot coordinates are inside the room's
355
+ # If the robot coordinates are inside the room's
348
356
  if self.robot_in_room and _check_robot_position(robot_x, robot_y):
349
357
  temp = {
350
358
  "x": robot_x,
@@ -360,7 +368,7 @@ class ReImageHandler(object):
360
368
  self.zooming = bool(self.active_zones[self.robot_in_room["id"]])
361
369
  return temp
362
370
  # else we need to search and use the async method
363
- _LOGGER.debug(f"{self.file_name} changed room.. searching..")
371
+ _LOGGER.debug("%s Changed room.. searching..", self.file_name)
364
372
  room_count = -1
365
373
  last_room = None
366
374
  if self.rooms_pos:
@@ -386,13 +394,13 @@ class ReImageHandler(object):
386
394
  "in_room": self.robot_in_room["room"],
387
395
  }
388
396
  _LOGGER.debug(
389
- f"{self.file_name} is in {self.robot_in_room['room']}"
397
+ "%s is in %s", self.file_name, self.robot_in_room["room"]
390
398
  )
391
399
  del room, corners, robot_x, robot_y # free memory.
392
400
  return temp
393
401
  del room, corners # free memory.
394
402
  _LOGGER.debug(
395
- f"{self.file_name}: Not located within Camera Rooms coordinates."
403
+ "%s: Not located within Camera Rooms coordinates.", self.file_name
396
404
  )
397
405
  self.zooming = False
398
406
  self.robot_in_room = last_room
@@ -404,12 +412,14 @@ class ReImageHandler(object):
404
412
  }
405
413
  return temp
406
414
 
407
- def get_calibration_data(self, rotation_angle: int = 0) -> any:
415
+ def get_calibration_data(self, rotation_angle: int = 0) -> Any:
408
416
  """Return the map calibration data."""
409
- if not self.calibration_data:
417
+ if not self.calibration_data and self.crop_img_size:
410
418
  self.calibration_data = []
411
419
  _LOGGER.info(
412
- f"{self.file_name}: Getting Calibrations points {self.crop_area}"
420
+ "%s: Getting Calibrations points %s",
421
+ self.file_name,
422
+ str(self.crop_area),
413
423
  )
414
424
 
415
425
  # Define the map points (fixed)
@@ -12,7 +12,8 @@ import logging
12
12
 
13
13
  from .config.types import Color, JsonType, NumpyArray
14
14
  from .config.drawable import Drawable
15
- from map_data import ImageData
15
+ from .map_data import RandImageData
16
+ from .map_data import ImageData
16
17
 
17
18
  _LOGGER = logging.getLogger(__name__)
18
19
 
@@ -20,12 +21,11 @@ _LOGGER = logging.getLogger(__name__)
20
21
  class ImageDraw:
21
22
  """Class to handle the image creation."""
22
23
 
23
- """It Draws each elements of the images, like the walls, zones, paths, etc."""
24
-
25
24
  def __init__(self, image_handler):
26
25
  self.img_h = image_handler
27
26
  self.file_name = self.img_h.shared.file_name
28
- self.data = ImageData
27
+ self.data = RandImageData
28
+ self.data_sup = ImageData
29
29
  self.draw = Drawable
30
30
  self.color_grey = (128, 128, 128, 255)
31
31
 
@@ -47,12 +47,13 @@ class ImageDraw:
47
47
  np_array = await self.draw.lines(
48
48
  np_array, predicted_path, 3, self.color_grey
49
49
  )
50
- return np_array
51
- else:
52
- return np_array
53
- except Exception as e:
50
+ return np_array
51
+ except KeyError as e:
54
52
  _LOGGER.warning(
55
- f"{self.file_name}: Error in extraction of go to. {e}", exc_info=True
53
+ "%s: Error in extraction of go-to target: %s",
54
+ self.file_name,
55
+ e,
56
+ exc_info=True,
56
57
  )
57
58
  return np_array
58
59
 
@@ -70,7 +71,7 @@ class ImageDraw:
70
71
  )
71
72
  except ValueError as e:
72
73
  self.img_h.segment_data = None
73
- _LOGGER.info(f"{self.file_name}: No segments data found. {e}")
74
+ _LOGGER.info("%s: No segments data found: %s", self.file_name, e)
74
75
 
75
76
  async def async_draw_base_layer(
76
77
  self,
@@ -87,13 +88,13 @@ class ImageDraw:
87
88
  walls_data = self.data.get_rrm_walls(m_json)
88
89
  floor_data = self.data.get_rrm_floor(m_json)
89
90
 
90
- _LOGGER.info(self.file_name + ": Empty image with background color")
91
+ _LOGGER.info("%s: Empty image with background color", self.file_name)
91
92
  img_np_array = await self.draw.create_empty_image(
92
93
  self.img_h.img_size["x"], self.img_h.img_size["y"], color_background
93
94
  )
94
95
  room_id = 0
95
96
  if self.img_h.frame_number == 0:
96
- _LOGGER.info(self.file_name + ": Overlapping Layers")
97
+ _LOGGER.info("%s: Overlapping Layers", self.file_name)
97
98
 
98
99
  # checking if there are segments too (sorted pixels in the raw data).
99
100
  await self.async_segment_data(m_json, size_x, size_y, pos_top, pos_left)
@@ -146,31 +147,30 @@ class ImageDraw:
146
147
  room_id = 0
147
148
  rooms_list = [color_wall]
148
149
  if not segment_data:
149
- _LOGGER.info(f"{self.file_name}: No segments data found.")
150
+ _LOGGER.info("%s: No segments data found.", self.file_name)
150
151
  return room_id, img_np_array
151
152
 
152
- if segment_data:
153
- _LOGGER.info(f"{self.file_name}: Drawing segments.")
154
- for pixels in segment_data:
155
- room_color = self.img_h.shared.rooms_colors[room_id]
156
- rooms_list.append(room_color)
157
- if (
158
- self.img_h.active_zones
159
- and len(self.img_h.active_zones) > room_id
160
- and self.img_h.active_zones[room_id] == 1
161
- ):
162
- room_color = (
163
- ((2 * room_color[0]) + color_zone_clean[0]) // 3,
164
- ((2 * room_color[1]) + color_zone_clean[1]) // 3,
165
- ((2 * room_color[2]) + color_zone_clean[2]) // 3,
166
- ((2 * room_color[3]) + color_zone_clean[3]) // 3,
167
- )
168
- img_np_array = await self.draw.from_json_to_image(
169
- img_np_array, pixels, pixel_size, room_color
153
+ _LOGGER.info("%s: Drawing segments.", self.file_name)
154
+ for pixels in segment_data:
155
+ room_color = self.img_h.shared.rooms_colors[room_id]
156
+ rooms_list.append(room_color)
157
+ if (
158
+ self.img_h.active_zones
159
+ and len(self.img_h.active_zones) > room_id
160
+ and self.img_h.active_zones[room_id] == 1
161
+ ):
162
+ room_color = (
163
+ ((2 * room_color[0]) + color_zone_clean[0]) // 3,
164
+ ((2 * room_color[1]) + color_zone_clean[1]) // 3,
165
+ ((2 * room_color[2]) + color_zone_clean[2]) // 3,
166
+ ((2 * room_color[3]) + color_zone_clean[3]) // 3,
170
167
  )
171
- room_id += 1
172
- if room_id > 15:
173
- room_id = 0
168
+ img_np_array = await self.draw.from_json_to_image(
169
+ img_np_array, pixels, pixel_size, room_color
170
+ )
171
+ room_id += 1
172
+ if room_id > 15:
173
+ room_id = 0
174
174
  return room_id, img_np_array
175
175
 
176
176
  async def _draw_walls(
@@ -209,10 +209,10 @@ class ImageDraw:
209
209
  charger_pos = self.data.rrm_coordinates_to_valetudo(
210
210
  self.data.get_rrm_charger_position(m_json)
211
211
  )
212
- except Exception as e:
213
- _LOGGER.warning(f"{self.file_name}: No charger position found. {e}")
212
+ except KeyError as e:
213
+ _LOGGER.warning("%s: No charger position found: %s", self.file_name, e)
214
214
  else:
215
- _LOGGER.debug("charger position: %s", charger_pos)
215
+ _LOGGER.debug("Charger position: %s", charger_pos)
216
216
  if charger_pos:
217
217
  charger_pos_dictionary = {
218
218
  "x": (charger_pos[0] * 10),
@@ -223,8 +223,7 @@ class ImageDraw:
223
223
  np_array, charger_pos[0], charger_pos[1], color_charger
224
224
  )
225
225
  return np_array, charger_pos_dictionary
226
- else:
227
- return np_array, {}
226
+ return np_array, {}
228
227
 
229
228
  async def async_draw_zones(
230
229
  self,
@@ -237,12 +236,11 @@ class ImageDraw:
237
236
  zone_clean = self.data.get_rrm_currently_cleaned_zones(m_json)
238
237
  except (ValueError, KeyError):
239
238
  zone_clean = None
240
- else:
241
- _LOGGER.info(f"{self.file_name}: Got zones.")
239
+
242
240
  if zone_clean:
241
+ _LOGGER.info("%s: Got zones.", self.file_name)
243
242
  return await self.draw.zones(np_array, zone_clean, color_zone_clean)
244
- else:
245
- return np_array
243
+ return np_array
246
244
 
247
245
  async def async_draw_virtual_restrictions(
248
246
  self, m_json: JsonType, np_array: NumpyArray, color_no_go: Color
@@ -252,9 +250,9 @@ class ImageDraw:
252
250
  virtual_walls = self.data.get_rrm_virtual_walls(m_json)
253
251
  except (ValueError, KeyError):
254
252
  virtual_walls = None
255
- else:
256
- _LOGGER.info(f"{self.file_name}: Got virtual walls.")
253
+
257
254
  if virtual_walls:
255
+ _LOGGER.info("%s: Got virtual walls.", self.file_name)
258
256
  np_array = await self.draw.draw_virtual_walls(
259
257
  np_array, virtual_walls, color_no_go
260
258
  )
@@ -278,11 +276,13 @@ class ImageDraw:
278
276
  # Extract the paths data from the JSON data.
279
277
  try:
280
278
  path_pixel = self.data.get_rrm_path(m_json)
281
- path_pixel_formatted = self.data.sublist_join(
279
+ path_pixel_formatted = self.data_sup.sublist_join(
282
280
  self.data.rrm_valetudo_path_array(path_pixel["points"]), 2
283
281
  )
284
282
  except KeyError as e:
285
- _LOGGER.warning(f"{self.file_name}: Error extracting paths data: {str(e)}")
283
+ _LOGGER.warning(
284
+ "%s: Error extracting paths data: %s", self.file_name, str(e)
285
+ )
286
286
  finally:
287
287
  if path_pixel_formatted:
288
288
  np_array = await self.draw.lines(
@@ -293,11 +293,11 @@ class ImageDraw:
293
293
  async def async_get_entity_data(self, m_json: JsonType) -> dict or None:
294
294
  """Get the entity data from the JSON data."""
295
295
  try:
296
- entity_dict = self.data.find_points_entities(m_json)
296
+ entity_dict = self.data_sup.find_points_entities(m_json)
297
297
  except (ValueError, KeyError):
298
298
  entity_dict = None
299
299
  else:
300
- _LOGGER.info(f"{self.file_name}: Got the points in the json.")
300
+ _LOGGER.info("%s: Got the points in the json.", self.file_name)
301
301
  return entity_dict
302
302
 
303
303
  @staticmethod
@@ -329,14 +329,16 @@ class ImageDraw:
329
329
  robot_pos = self.data.rrm_coordinates_to_valetudo(robot_pos_data)
330
330
  angle = self.data.get_rrm_robot_angle(m_json)
331
331
  except (ValueError, KeyError):
332
- _LOGGER.warning(f"{self.file_name} No robot position found.")
332
+ _LOGGER.warning("%s No robot position found.", self.file_name)
333
333
  return None, None, None
334
334
  finally:
335
335
  robot_position_angle = round(angle[0], 0)
336
336
  if robot_pos and robot_position_angle:
337
337
  robot_position = robot_pos
338
338
  _LOGGER.debug(
339
- f"robot position: {robot_pos}, robot angle: {robot_position_angle}"
339
+ "robot position: %s, robot angle: %s",
340
+ str(robot_pos),
341
+ str(robot_position_angle),
340
342
  )
341
343
  if self.img_h.rooms_pos is None:
342
344
  self.img_h.robot_pos = {
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "valetudo-map-parser"
3
- version = "0.1.7"
3
+ version = "0.1.9.b0"
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"