valetudo-map-parser 0.1.9b42__py3-none-any.whl → 0.1.9b44__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.
@@ -8,25 +8,39 @@ Version: 0.1.9
8
8
  from __future__ import annotations
9
9
 
10
10
  import json
11
- import logging
12
11
 
13
12
  import numpy as np
14
13
  from PIL import Image
15
14
 
16
15
  from .config.auto_crop import AutoCrop
17
- from .config.drawable import Drawable
18
- from .config.drawable_elements import DrawableElement, DrawingConfig
19
- from .config.enhanced_drawable import EnhancedDrawable
16
+ from .config.drawable_elements import DrawableElement
17
+ from .config.optimized_element_map import OptimizedElementMapGenerator
20
18
  from .config.shared import CameraShared
21
- from .config.types import COLORS, CalibrationPoints, Colors, RoomsProperties, RoomStore
22
- from .config.utils import BaseHandler, prepare_resize_params
19
+ from .config.types import (
20
+ COLORS,
21
+ LOGGER,
22
+ CalibrationPoints,
23
+ Colors,
24
+ RoomsProperties,
25
+ RoomStore,
26
+ )
27
+ from .config.utils import (
28
+ BaseHandler,
29
+ blend_colors,
30
+ blend_pixel,
31
+ get_element_at_position,
32
+ get_room_at_position,
33
+ handle_room_outline_error,
34
+ initialize_drawing_config,
35
+ manage_drawable_elements,
36
+ prepare_resize_params,
37
+ update_element_map_with_robot,
38
+ )
39
+ from .config.room_outline import extract_room_outline_with_scipy
23
40
  from .hypfer_draw import ImageDraw as ImDraw
24
41
  from .map_data import ImageData
25
42
 
26
43
 
27
- _LOGGER = logging.getLogger(__name__)
28
-
29
-
30
44
  class HypferMapImageHandler(BaseHandler, AutoCrop):
31
45
  """Map Image Handler Class.
32
46
  This class is used to handle the image data and the drawing of the map."""
@@ -40,8 +54,9 @@ class HypferMapImageHandler(BaseHandler, AutoCrop):
40
54
  self.data = ImageData # imported Image Data Module.
41
55
 
42
56
  # Initialize drawing configuration using the shared utility function
43
- from .config.utils import initialize_drawing_config
44
- self.drawing_config, self.draw, self.enhanced_draw = initialize_drawing_config(self)
57
+ self.drawing_config, self.draw, self.enhanced_draw = initialize_drawing_config(
58
+ self
59
+ )
45
60
 
46
61
  self.go_to = None # vacuum go to data
47
62
  self.img_hash = None # hash of the image calculated to check differences.
@@ -51,13 +66,15 @@ class HypferMapImageHandler(BaseHandler, AutoCrop):
51
66
  self.imd = ImDraw(self) # Image Draw class.
52
67
  self.color_grey = (128, 128, 128, 255)
53
68
  self.file_name = self.shared.file_name # file name of the vacuum.
54
- self.element_map = None # Map of element codes
69
+ self.element_map_manager = OptimizedElementMapGenerator(self.drawing_config,
70
+ self.shared) # Map of element codes
55
71
 
56
72
  @staticmethod
57
73
  def get_corners(x_max, x_min, y_max, y_min):
58
74
  """Get the corners of the room."""
59
75
  return [(x_min, y_min), (x_max, y_min), (x_max, y_max), (x_min, y_max)]
60
76
 
77
+
61
78
  async def extract_room_outline_from_map(self, room_id_int, pixels, pixel_size):
62
79
  """Extract the outline of a room using the pixel data and element map.
63
80
 
@@ -84,13 +101,13 @@ class HypferMapImageHandler(BaseHandler, AutoCrop):
84
101
  min_y, max_y = min(y_values), max(y_values)
85
102
 
86
103
  # If we don't have an element map, return a rectangular outline
87
- if not hasattr(self, "element_map") or self.element_map is None:
104
+ if not hasattr(self, "element_map") or self.shared.element_map is None:
88
105
  # Return rectangular outline
89
106
  return [(min_x, min_y), (max_x, min_y), (max_x, max_y), (min_x, max_y)]
90
107
 
91
108
  # Create a binary mask for this room using the pixel data
92
109
  # This is more reliable than using the element_map since we're directly using the pixel data
93
- height, width = self.element_map.shape
110
+ height, width = self.shared.element_map.shape
94
111
  room_mask = np.zeros((height, width), dtype=np.uint8)
95
112
 
96
113
  # Fill the mask with room pixels using the pixel data
@@ -108,15 +125,16 @@ class HypferMapImageHandler(BaseHandler, AutoCrop):
108
125
 
109
126
  # Debug log to check if we have any room pixels
110
127
  num_room_pixels = np.sum(room_mask)
111
- _LOGGER.debug(
128
+ LOGGER.debug(
112
129
  "%s: Room %s mask has %d pixels",
113
- self.file_name, str(room_id_int), int(num_room_pixels)
130
+ self.file_name,
131
+ str(room_id_int),
132
+ int(num_room_pixels),
114
133
  )
115
134
 
116
- # Use the shared utility function to extract the room outline
117
- from .config.utils import async_extract_room_outline
118
- return await async_extract_room_outline(
119
- room_mask, min_x, min_y, max_x, max_y, self.file_name, room_id_int, _LOGGER
135
+ # Use the scipy-based room outline extraction
136
+ return await extract_room_outline_with_scipy(
137
+ room_mask, min_x, min_y, max_x, max_y, self.file_name, room_id_int
120
138
  )
121
139
 
122
140
  async def async_extract_room_properties(self, json_data) -> RoomsProperties:
@@ -151,15 +169,14 @@ class HypferMapImageHandler(BaseHandler, AutoCrop):
151
169
  outline = await self.extract_room_outline_from_map(
152
170
  segment_id, pixels, pixel_size
153
171
  )
154
- _LOGGER.debug(
172
+ LOGGER.debug(
155
173
  "%s: Traced outline for room %s with %d points",
156
174
  self.file_name,
157
175
  segment_id,
158
176
  len(outline),
159
177
  )
160
178
  except (ValueError, IndexError, TypeError, ArithmeticError) as e:
161
- from .config.utils import handle_room_outline_error
162
- handle_room_outline_error(self.file_name, segment_id, e, _LOGGER)
179
+ handle_room_outline_error(self.file_name, segment_id, e)
163
180
  outline = corners
164
181
 
165
182
  room_id = str(segment_id)
@@ -178,11 +195,11 @@ class HypferMapImageHandler(BaseHandler, AutoCrop):
178
195
  }
179
196
  if room_properties:
180
197
  rooms = RoomStore(self.file_name, room_properties)
181
- _LOGGER.debug(
198
+ LOGGER.debug(
182
199
  "%s: Rooms data extracted! %s", self.file_name, rooms.get_rooms()
183
200
  )
184
201
  else:
185
- _LOGGER.debug("%s: Rooms data not available!", self.file_name)
202
+ LOGGER.debug("%s: Rooms data not available!", self.file_name)
186
203
  self.rooms_pos = None
187
204
  return room_properties
188
205
 
@@ -204,7 +221,7 @@ class HypferMapImageHandler(BaseHandler, AutoCrop):
204
221
  # Check if the JSON data is not None else process the image.
205
222
  try:
206
223
  if m_json is not None:
207
- _LOGGER.debug("%s: Creating Image.", self.file_name)
224
+ LOGGER.debug("%s: Creating Image.", self.file_name)
208
225
  # buffer json data
209
226
  self.json_data = m_json
210
227
  # Get the image size from the JSON data
@@ -237,11 +254,8 @@ class HypferMapImageHandler(BaseHandler, AutoCrop):
237
254
  size_x, size_y, colors["background"]
238
255
  )
239
256
 
240
- # Create element map for tracking what's drawn where
241
- self.element_map = np.zeros((size_y, size_x), dtype=np.int32)
242
- self.element_map[:] = DrawableElement.FLOOR
243
257
 
244
- _LOGGER.info("%s: Drawing map with color blending", self.file_name)
258
+ LOGGER.info("%s: Drawing map with color blending", self.file_name)
245
259
 
246
260
  # Draw layers and segments if enabled
247
261
  room_id = 0
@@ -268,7 +282,7 @@ class HypferMapImageHandler(BaseHandler, AutoCrop):
268
282
  ):
269
283
  # Add this room to the disabled rooms set
270
284
  disabled_rooms.add(room_id)
271
- _LOGGER.debug(
285
+ LOGGER.debug(
272
286
  "%s: Room %d is disabled and will be skipped",
273
287
  self.file_name,
274
288
  current_room_id,
@@ -296,7 +310,7 @@ class HypferMapImageHandler(BaseHandler, AutoCrop):
296
310
  )
297
311
  if room_element:
298
312
  # Log the room check for debugging
299
- _LOGGER.debug(
313
+ LOGGER.debug(
300
314
  "%s: Checking if room %d is enabled: %s",
301
315
  self.file_name,
302
316
  current_room_id,
@@ -320,7 +334,7 @@ class HypferMapImageHandler(BaseHandler, AutoCrop):
320
334
  if not self.drawing_config.is_enabled(
321
335
  DrawableElement.WALL
322
336
  ):
323
- _LOGGER.info(
337
+ LOGGER.info(
324
338
  "%s: Skipping wall layer because WALL element is disabled",
325
339
  self.file_name,
326
340
  )
@@ -331,7 +345,7 @@ class HypferMapImageHandler(BaseHandler, AutoCrop):
331
345
  # Need to modify compressed_pixels_list to exclude walls of disabled rooms
332
346
  # This requires knowledge of which walls belong to which rooms
333
347
  # For now, we'll just log that we're drawing walls for all rooms
334
- _LOGGER.debug(
348
+ LOGGER.debug(
335
349
  "%s: Drawing walls for all rooms (including disabled ones)",
336
350
  self.file_name,
337
351
  )
@@ -390,7 +404,7 @@ class HypferMapImageHandler(BaseHandler, AutoCrop):
390
404
  robot_y=(robot_position[1]),
391
405
  angle=robot_position_angle,
392
406
  )
393
- _LOGGER.info("%s: Completed base Layers", self.file_name)
407
+ LOGGER.info("%s: Completed base Layers", self.file_name)
394
408
  # Copy the new array in base layer.
395
409
  self.img_base_layer = await self.async_copy_array(img_np_array)
396
410
  self.shared.frame_number = self.frame_number
@@ -399,7 +413,7 @@ class HypferMapImageHandler(BaseHandler, AutoCrop):
399
413
  new_frame_hash != self.img_hash
400
414
  ):
401
415
  self.frame_number = 0
402
- _LOGGER.debug(
416
+ LOGGER.debug(
403
417
  "%s: %s at Frame Number: %s",
404
418
  self.file_name,
405
419
  str(self.json_id),
@@ -425,16 +439,16 @@ class HypferMapImageHandler(BaseHandler, AutoCrop):
425
439
 
426
440
  # Draw path prediction and paths if enabled
427
441
  path_enabled = self.drawing_config.is_enabled(DrawableElement.PATH)
428
- _LOGGER.info(
442
+ LOGGER.info(
429
443
  "%s: PATH element enabled: %s", self.file_name, path_enabled
430
444
  )
431
445
  if path_enabled:
432
- _LOGGER.info("%s: Drawing path", self.file_name)
446
+ LOGGER.info("%s: Drawing path", self.file_name)
433
447
  img_np_array = await self.imd.async_draw_paths(
434
448
  img_np_array, m_json, colors["move"], self.color_grey
435
449
  )
436
450
  else:
437
- _LOGGER.info("%s: Skipping path drawing", self.file_name)
451
+ LOGGER.info("%s: Skipping path drawing", self.file_name)
438
452
 
439
453
  # Check if the robot is docked.
440
454
  if self.shared.vacuum_state == "docked":
@@ -459,8 +473,10 @@ class HypferMapImageHandler(BaseHandler, AutoCrop):
459
473
  )
460
474
 
461
475
  # Update element map for robot position
462
- from .config.utils import update_element_map_with_robot
463
- update_element_map_with_robot(self.element_map, robot_position, DrawableElement.ROBOT)
476
+ if hasattr(self.shared, 'element_map') and self.shared.element_map is not None:
477
+ update_element_map_with_robot(
478
+ self.shared.element_map, robot_position, DrawableElement.ROBOT
479
+ )
464
480
  # Resize the image
465
481
  img_np_array = await self.async_auto_trim_and_zoom_image(
466
482
  img_np_array,
@@ -471,9 +487,24 @@ class HypferMapImageHandler(BaseHandler, AutoCrop):
471
487
  )
472
488
  # If the image is None return None and log the error.
473
489
  if img_np_array is None:
474
- _LOGGER.warning("%s: Image array is None.", self.file_name)
490
+ LOGGER.warning("%s: Image array is None.", self.file_name)
475
491
  return None
476
-
492
+ # Debug logging for element map creation
493
+ LOGGER.info("%s: Frame number: %d, has element_map: %s",
494
+ self.file_name, self.frame_number, hasattr(self.shared, 'element_map'))
495
+
496
+ if (self.shared.element_map is None) and (self.frame_number == 1):
497
+ # Create element map for tracking what's drawn where
498
+ LOGGER.info("%s: Creating element map with shape: %s",
499
+ self.file_name, img_np_array.shape)
500
+
501
+ # Generate the element map directly from JSON data
502
+ # This will create a cropped element map containing only the non-zero elements
503
+ LOGGER.info("%s: Generating element map from JSON data", self.file_name)
504
+ self.shared.element_map = await self.element_map_manager.async_generate_from_json(m_json)
505
+
506
+ LOGGER.info("%s: Element map created with shape: %s",
507
+ self.file_name, self.shared.element_map.shape if self.shared.element_map is not None else None)
477
508
  # Convert the numpy array to a PIL image
478
509
  pil_img = Image.fromarray(img_np_array, mode="RGBA")
479
510
  del img_np_array
@@ -482,10 +513,10 @@ class HypferMapImageHandler(BaseHandler, AutoCrop):
482
513
  resize_params = prepare_resize_params(self, pil_img, False)
483
514
  resized_image = await self.async_resize_images(resize_params)
484
515
  return resized_image
485
- _LOGGER.debug("%s: Frame Completed.", self.file_name)
516
+ LOGGER.debug("%s: Frame Completed.", self.file_name)
486
517
  return pil_img
487
518
  except (RuntimeError, RuntimeWarning) as e:
488
- _LOGGER.warning(
519
+ LOGGER.warning(
489
520
  "%s: Error %s during image creation.",
490
521
  self.file_name,
491
522
  str(e),
@@ -499,12 +530,12 @@ class HypferMapImageHandler(BaseHandler, AutoCrop):
499
530
  if self.room_propriety:
500
531
  return self.room_propriety
501
532
  if self.json_data:
502
- _LOGGER.debug("Checking %s Rooms data..", self.file_name)
533
+ LOGGER.debug("Checking %s Rooms data..", self.file_name)
503
534
  self.room_propriety = await self.async_extract_room_properties(
504
535
  self.json_data
505
536
  )
506
537
  if self.room_propriety:
507
- _LOGGER.debug("Got %s Rooms Attributes.", self.file_name)
538
+ LOGGER.debug("Got %s Rooms Attributes.", self.file_name)
508
539
  return self.room_propriety
509
540
 
510
541
  def get_calibration_data(self) -> CalibrationPoints:
@@ -512,7 +543,7 @@ class HypferMapImageHandler(BaseHandler, AutoCrop):
512
543
  this will create the attribute calibration points."""
513
544
  calibration_data = []
514
545
  rotation_angle = self.shared.image_rotate
515
- _LOGGER.info("Getting %s Calibrations points.", self.file_name)
546
+ LOGGER.info("Getting %s Calibrations points.", self.file_name)
516
547
 
517
548
  # Define the map points (fixed)
518
549
  map_points = self.get_map_points()
@@ -530,7 +561,7 @@ class HypferMapImageHandler(BaseHandler, AutoCrop):
530
561
  def enable_element(self, element_code: DrawableElement) -> None:
531
562
  """Enable drawing of a specific element."""
532
563
  self.drawing_config.enable_element(element_code)
533
- _LOGGER.info(
564
+ LOGGER.info(
534
565
  "%s: Enabled element %s, now enabled: %s",
535
566
  self.file_name,
536
567
  element_code.name,
@@ -539,33 +570,35 @@ class HypferMapImageHandler(BaseHandler, AutoCrop):
539
570
 
540
571
  def disable_element(self, element_code: DrawableElement) -> None:
541
572
  """Disable drawing of a specific element."""
542
- from .config.utils import manage_drawable_elements
543
573
  manage_drawable_elements(self, "disable", element_code=element_code)
544
574
 
545
575
  def set_elements(self, element_codes: list[DrawableElement]) -> None:
546
576
  """Enable only the specified elements, disable all others."""
547
- from .config.utils import manage_drawable_elements
548
577
  manage_drawable_elements(self, "set_elements", element_codes=element_codes)
549
578
 
550
579
  def set_element_property(
551
580
  self, element_code: DrawableElement, property_name: str, value
552
581
  ) -> None:
553
582
  """Set a drawing property for an element."""
554
- from .config.utils import manage_drawable_elements
555
- manage_drawable_elements(self, "set_property", element_code=element_code, property_name=property_name, value=value)
583
+ manage_drawable_elements(
584
+ self,
585
+ "set_property",
586
+ element_code=element_code,
587
+ property_name=property_name,
588
+ value=value,
589
+ )
556
590
 
557
591
  def get_element_at_position(self, x: int, y: int) -> DrawableElement | None:
558
592
  """Get the element code at a specific position."""
559
- from .config.utils import get_element_at_position
560
- return get_element_at_position(self.element_map, x, y)
593
+
594
+ return get_element_at_position(self.shared.element_map, x, y)
561
595
 
562
596
  def get_room_at_position(self, x: int, y: int) -> int | None:
563
597
  """Get the room ID at a specific position, or None if not a room."""
564
- from .config.utils import get_room_at_position
565
- return get_room_at_position(self.element_map, x, y, DrawableElement.ROOM_1)
598
+ return get_room_at_position(self.shared.element_map, x, y, DrawableElement.ROOM_1)
566
599
 
567
600
  @staticmethod
568
- def blend_colors(self, base_color, overlay_color):
601
+ def blend_colors(base_color, overlay_color):
569
602
  """
570
603
  Blend two RGBA colors, considering alpha channels.
571
604
 
@@ -576,7 +609,6 @@ class HypferMapImageHandler(BaseHandler, AutoCrop):
576
609
  Returns:
577
610
  The blended RGBA color
578
611
  """
579
- from .config.utils import blend_colors
580
612
  return blend_colors(base_color, overlay_color)
581
613
 
582
614
  def blend_pixel(self, array, x, y, color, element):
@@ -584,10 +616,11 @@ class HypferMapImageHandler(BaseHandler, AutoCrop):
584
616
  Blend a pixel color with the existing color at the specified position.
585
617
  Also updates the element map if the new element has higher z-index.
586
618
  """
587
- from .config.utils import blend_pixel
588
- return blend_pixel(array, x, y, color, element, self.element_map, self.drawing_config)
619
+ return blend_pixel(
620
+ array, x, y, color, element, self.shared.element_map, self.drawing_config
621
+ )
589
622
 
590
623
  @staticmethod
591
- async def async_copy_array(array):
624
+ async def async_copy_array(original_array):
592
625
  """Copy the array."""
593
- return array.copy()
626
+ return original_array.copy()
@@ -3,7 +3,7 @@ Collections of Json and List routines
3
3
  ImageData is part of the Image_Handler
4
4
  used functions to search data in the json
5
5
  provided for the creation of the new camera frame
6
- Version: v0.1.6
6
+ Version: v0.1.9.b42
7
7
  """
8
8
 
9
9
  from __future__ import annotations
@@ -494,12 +494,3 @@ class RandImageData:
494
494
  except KeyError:
495
495
  return None
496
496
  return seg_ids
497
-
498
- # @staticmethod
499
- # def convert_negative_angle(angle: int) -> int:
500
- # """Convert negative angle to positive."""
501
- # angle_c = angle % 360 # Ensure angle is within 0-359
502
- # if angle_c < 0:
503
- # angle_c += 360 # Convert negative angle to positive
504
- # angle = angle_c + 180 # add offset
505
- # return angle