valetudo-map-parser 0.1.9b53__py3-none-any.whl → 0.1.9b55__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.
@@ -15,13 +15,14 @@ from .config.types import (
15
15
  TrimCropData,
16
16
  UserLanguageStore,
17
17
  )
18
- from .hypfer_rooms_handler import HypferRoomsHandler
19
18
  from .hypfer_handler import HypferMapImageHandler
20
19
  from .rand25_handler import ReImageHandler
20
+ from .rooms_handler import RoomsHandler, RandRoomsHandler
21
21
 
22
22
 
23
23
  __all__ = [
24
- "HypferRoomsHandler",
24
+ "RoomsHandler",
25
+ "RandRoomsHandler",
25
26
  "HypferMapImageHandler",
26
27
  "ReImageHandler",
27
28
  "RRMapParser",
@@ -125,7 +125,7 @@ class AutoCrop:
125
125
  if self.auto_crop:
126
126
  self.auto_crop_offset()
127
127
  else:
128
- self.handler.max_frames = 5
128
+ self.handler.max_frames = 1205
129
129
 
130
130
  # Fallback: Ensure auto_crop is valid
131
131
  if not self.auto_crop or any(v < 0 for v in self.auto_crop):
@@ -137,12 +137,32 @@ class AutoCrop:
137
137
  async def async_image_margins(
138
138
  self, image_array: NumpyArray, detect_colour: Color
139
139
  ) -> tuple[int, int, int, int]:
140
- """Crop the image based on the auto crop area."""
141
- nonzero_coords = np.column_stack(np.where(image_array != list(detect_colour)))
142
- # Calculate the trim box based on the first and last occurrences
143
- min_y, min_x, _ = NumpyArray.min(nonzero_coords, axis=0)
144
- max_y, max_x, _ = NumpyArray.max(nonzero_coords, axis=0)
145
- del nonzero_coords
140
+ """Crop the image based on the auto crop area using scipy.ndimage for better performance."""
141
+ # Import scipy.ndimage here to avoid import at module level
142
+ from scipy import ndimage
143
+
144
+ # Create a binary mask where True = non-background pixels
145
+ # This is much more memory efficient than storing coordinates
146
+ mask = ~np.all(image_array == list(detect_colour), axis=2)
147
+
148
+ # Use scipy.ndimage.find_objects to efficiently find the bounding box
149
+ # This returns a list of slice objects that define the bounding box
150
+ # Label the mask with a single label (1) and find its bounding box
151
+ labeled_mask = mask.astype(np.int8) # Convert to int8 (smallest integer type)
152
+ objects = ndimage.find_objects(labeled_mask)
153
+
154
+ if not objects: # No objects found
155
+ _LOGGER.warning(
156
+ "%s: No non-background pixels found in image", self.handler.file_name
157
+ )
158
+ # Return full image dimensions as fallback
159
+ return 0, 0, image_array.shape[1], image_array.shape[0]
160
+
161
+ # Extract the bounding box coordinates from the slice objects
162
+ y_slice, x_slice = objects[0]
163
+ min_y, max_y = y_slice.start, y_slice.stop - 1
164
+ min_x, max_x = x_slice.start, x_slice.stop - 1
165
+
146
166
  _LOGGER.debug(
147
167
  "%s: Found trims max and min values (y,x) (%s, %s) (%s, %s)...",
148
168
  self.handler.file_name,
@@ -1,9 +1,9 @@
1
1
  """Utility functions for color operations in the map parser."""
2
2
 
3
- from typing import Tuple, Optional
3
+ from typing import Optional, Tuple
4
4
 
5
5
  from .colors import ColorsManagement
6
- from .types import NumpyArray, Color
6
+ from .types import Color, NumpyArray
7
7
 
8
8
 
9
9
  def get_blended_color(
@@ -63,6 +63,7 @@ from .types import (
63
63
  Color,
64
64
  )
65
65
 
66
+
66
67
  color_transparent = (0, 0, 0, 0)
67
68
  color_charger = (0, 128, 0, 255)
68
69
  color_move = (238, 247, 255, 255)
@@ -10,15 +10,14 @@ Optimized with NumPy and SciPy for better performance.
10
10
 
11
11
  from __future__ import annotations
12
12
 
13
-
14
13
  import logging
15
14
  import math
16
15
 
17
16
  import numpy as np
18
17
  from PIL import ImageDraw, ImageFont
19
18
 
20
- from .colors import ColorsManagement
21
19
  from .color_utils import get_blended_color
20
+ from .colors import ColorsManagement
22
21
  from .types import Color, NumpyArray, PilPNG, Point, Tuple, Union
23
22
 
24
23
 
@@ -8,10 +8,12 @@ from __future__ import annotations
8
8
 
9
9
  from enum import IntEnum
10
10
  from typing import Dict, List, Tuple, Union
11
+
11
12
  import numpy as np
12
- from .types import LOGGER
13
13
 
14
14
  from .colors import DefaultColors, SupportedColor
15
+ from .types import LOGGER
16
+
15
17
 
16
18
  # Type aliases
17
19
  Color = Tuple[int, int, int, int] # RGBA color
@@ -170,13 +172,7 @@ class DrawingConfig:
170
172
 
171
173
  def is_enabled(self, element_code: DrawableElement) -> bool:
172
174
  """Check if an element is enabled for drawing."""
173
- enabled = self._enabled_elements.get(element_code, False)
174
- LOGGER.debug(
175
- "Checking if element %s is enabled: %s",
176
- element_code.name if hasattr(element_code, "name") else element_code,
177
- enabled,
178
- )
179
- return enabled
175
+ return self._enabled_elements.get(element_code, False)
180
176
 
181
177
  def set_property(
182
178
  self, element_code: DrawableElement, property_name: str, value
@@ -238,10 +234,6 @@ class DrawingConfig:
238
234
  self.set_property(room_element, "color", rgba)
239
235
  self.set_property(room_element, "opacity", alpha / 255.0)
240
236
 
241
- LOGGER.debug(
242
- "Updated room %d color to %s with alpha %s", room_id, rgb, alpha
243
- )
244
-
245
237
  # Update other element colors
246
238
  for element, color_key in element_color_mapping.items():
247
239
  if color_key in device_info:
@@ -265,13 +257,6 @@ class DrawingConfig:
265
257
  self.set_property(element, "color", rgba)
266
258
  self.set_property(element, "opacity", alpha / 255.0)
267
259
 
268
- LOGGER.debug(
269
- "Updated element %s color to %s with alpha %s",
270
- element.name,
271
- rgb,
272
- alpha,
273
- )
274
-
275
260
  # Check for disabled elements using specific boolean flags
276
261
  # Map element disable flags to DrawableElement enum values
277
262
  element_disable_mapping = {
@@ -13,12 +13,12 @@ from typing import Optional, Tuple
13
13
 
14
14
  import numpy as np
15
15
 
16
+ from .colors import ColorsManagement
16
17
  from .drawable import Drawable
17
18
  from .drawable_elements import (
18
19
  DrawableElement,
19
20
  DrawingConfig,
20
21
  )
21
- from .colors import ColorsManagement
22
22
 
23
23
 
24
24
  # Type aliases
@@ -88,6 +88,7 @@ class CameraShared:
88
88
  self.vac_json_id = None # Vacuum json id
89
89
  self.margins = "100" # Image margins
90
90
  self.obstacles_data = None # Obstacles data
91
+ self.obstacles_pos = None # Obstacles position
91
92
  self.offset_top = 0 # Image offset top
92
93
  self.offset_down = 0 # Image offset down
93
94
  self.offset_left = 0 # Image offset left
@@ -247,26 +248,6 @@ class CameraSharedManager:
247
248
  )
248
249
  instance.trims = TrimsData.from_dict(trim_data)
249
250
 
250
- # Log disable_obstacles and disable_path settings
251
- if "disable_obstacles" in device_info:
252
- _LOGGER.info(
253
- "%s: device_info contains disable_obstacles: %s",
254
- instance.file_name,
255
- device_info["disable_obstacles"],
256
- )
257
- if "disable_path" in device_info:
258
- _LOGGER.info(
259
- "%s: device_info contains disable_path: %s",
260
- instance.file_name,
261
- device_info["disable_path"],
262
- )
263
- if "disable_elements" in device_info:
264
- _LOGGER.info(
265
- "%s: device_info contains disable_elements: %s",
266
- instance.file_name,
267
- device_info["disable_elements"],
268
- )
269
-
270
251
  except TypeError as ex:
271
252
  _LOGGER.error("Shared data can't be initialized due to a TypeError! %s", ex)
272
253
  except AttributeError as ex:
@@ -8,7 +8,7 @@ import json
8
8
  import logging
9
9
  import threading
10
10
  from dataclasses import asdict, dataclass
11
- from typing import Any, Dict, Optional, Tuple, Union, TypedDict
11
+ from typing import Any, Dict, Optional, Tuple, TypedDict, Union
12
12
 
13
13
  import numpy as np
14
14
  from PIL import Image
@@ -639,7 +639,3 @@ class TrimsData:
639
639
  self.trim_down = 0
640
640
  self.trim_right = 0
641
641
  return asdict(self)
642
-
643
- def self_instance(self):
644
- """Return self instance."""
645
- return self.self_instance()
@@ -550,58 +550,8 @@ def initialize_drawing_config(handler):
550
550
  hasattr(handler.shared, "device_info")
551
551
  and handler.shared.device_info is not None
552
552
  ):
553
- LOGGER.info(
554
- "%s: Initializing drawing config from device_info", handler.file_name
555
- )
556
- LOGGER.info(
557
- "%s: device_info contains disable_obstacles: %s",
558
- handler.file_name,
559
- "disable_obstacles" in handler.shared.device_info,
560
- )
561
- LOGGER.info(
562
- "%s: device_info contains disable_path: %s",
563
- handler.file_name,
564
- "disable_path" in handler.shared.device_info,
565
- )
566
- LOGGER.info(
567
- "%s: device_info contains disable_elements: %s",
568
- handler.file_name,
569
- "disable_elements" in handler.shared.device_info,
570
- )
571
-
572
- if "disable_obstacles" in handler.shared.device_info:
573
- LOGGER.info(
574
- "%s: disable_obstacles value: %s",
575
- handler.file_name,
576
- handler.shared.device_info["disable_obstacles"],
577
- )
578
- if "disable_path" in handler.shared.device_info:
579
- LOGGER.info(
580
- "%s: disable_path value: %s",
581
- handler.file_name,
582
- handler.shared.device_info["disable_path"],
583
- )
584
- if "disable_elements" in handler.shared.device_info:
585
- LOGGER.info(
586
- "%s: disable_elements value: %s",
587
- handler.file_name,
588
- handler.shared.device_info["disable_elements"],
589
- )
590
-
591
553
  drawing_config.update_from_device_info(handler.shared.device_info)
592
554
 
593
- # Verify elements are disabled
594
- LOGGER.info(
595
- "%s: After initialization, PATH enabled: %s",
596
- handler.file_name,
597
- drawing_config.is_enabled(DrawableElement.PATH),
598
- )
599
- LOGGER.info(
600
- "%s: After initialization, OBSTACLE enabled: %s",
601
- handler.file_name,
602
- drawing_config.is_enabled(DrawableElement.OBSTACLE),
603
- )
604
-
605
555
  # Initialize both drawable systems for backward compatibility
606
556
  draw = Drawable() # Legacy drawing utilities
607
557
  enhanced_draw = EnhancedDrawable(drawing_config) # New enhanced drawing system
@@ -632,7 +582,7 @@ def blend_colors(base_color, overlay_color):
632
582
 
633
583
  # Avoid division by zero
634
584
  if a_out < 0.0001:
635
- return (0, 0, 0, 0)
585
+ return [0, 0, 0, 0]
636
586
 
637
587
  # Calculate blended RGB components
638
588
  r_out = (r1 * a1 + r2 * a2 * (1 - a1)) / a_out
@@ -142,12 +142,6 @@ class ImageDraw:
142
142
  room_element = getattr(DrawableElement, f"ROOM_{current_room_id}", None)
143
143
  if room_element and hasattr(self.img_h.drawing_config, "is_enabled"):
144
144
  draw_room = self.img_h.drawing_config.is_enabled(room_element)
145
- _LOGGER.debug(
146
- "%s: Room %d is %s",
147
- self.file_name,
148
- current_room_id,
149
- "enabled" if draw_room else "disabled",
150
- )
151
145
 
152
146
  # Get the room color
153
147
  room_color = self.img_h.shared.rooms_colors[room_id]
@@ -170,13 +164,6 @@ class ImageDraw:
170
164
  except IndexError as e:
171
165
  _LOGGER.warning("%s: Image Draw Error: %s", self.file_name, str(e))
172
166
 
173
- _LOGGER.debug(
174
- "%s Active Zones: %s and Room ID: %s",
175
- self.file_name,
176
- str(self.img_h.active_zones),
177
- str(room_id),
178
- )
179
-
180
167
  return img_np_array, room_id
181
168
 
182
169
  def _get_active_room_color(self, room_id, room_color, color_zone_clean):
@@ -316,6 +303,13 @@ class ImageDraw:
316
303
  await self.img_h.draw.async_draw_obstacles(
317
304
  np_array, obstacle_positions, color_no_go
318
305
  )
306
+
307
+ # Update both obstacles_pos and obstacles_data
308
+ self.img_h.shared.obstacles_pos = obstacle_positions
309
+ # Only update obstacles_data if it's None or if the number of obstacles has changed
310
+ if (self.img_h.shared.obstacles_data is None or
311
+ len(self.img_h.shared.obstacles_data) != len(obstacle_positions)):
312
+ self.img_h.shared.obstacles_data = obstacle_positions
319
313
  return np_array
320
314
 
321
315
  async def async_draw_charger(
@@ -20,6 +20,7 @@ from .config.types import (
20
20
  CalibrationPoints,
21
21
  Colors,
22
22
  RoomsProperties,
23
+ RoomStore,
23
24
  )
24
25
  from .config.utils import (
25
26
  BaseHandler,
@@ -28,8 +29,8 @@ from .config.utils import (
28
29
  prepare_resize_params,
29
30
  )
30
31
  from .hypfer_draw import ImageDraw as ImDraw
31
- from .hypfer_rooms_handler import HypferRoomsHandler
32
32
  from .map_data import ImageData
33
+ from .rooms_handler import RoomsHandler
33
34
 
34
35
 
35
36
  class HypferMapImageHandler(BaseHandler, AutoCrop):
@@ -57,7 +58,7 @@ class HypferMapImageHandler(BaseHandler, AutoCrop):
57
58
  self.imd = ImDraw(self) # Image Draw class.
58
59
  self.color_grey = (128, 128, 128, 255)
59
60
  self.file_name = self.shared.file_name # file name of the vacuum.
60
- self.rooms_handler = HypferRoomsHandler(
61
+ self.rooms_handler = RoomsHandler(
61
62
  self.file_name, self.drawing_config
62
63
  ) # Room data handler
63
64
 
@@ -68,51 +69,24 @@ class HypferMapImageHandler(BaseHandler, AutoCrop):
68
69
 
69
70
  async def async_extract_room_properties(self, json_data) -> RoomsProperties:
70
71
  """Extract room properties from the JSON data."""
71
-
72
- return await self.rooms_handler.async_extract_room_properties(json_data)
73
- # room_properties = {}
74
- # self.rooms_pos = []
75
- # pixel_size = json_data.get("pixelSize", [])
76
- #
77
- # for layer in json_data.get("layers", []):
78
- # if layer["__class"] == "MapLayer":
79
- # meta_data = layer.get("metaData", {})
80
- # segment_id = meta_data.get("segmentId")
81
- # if segment_id is not None:
82
- # name = meta_data.get("name")
83
- # compressed_pixels = layer.get("compressedPixels", [])
84
- # pixels = self.data.sublist(compressed_pixels, 3)
85
- # # Calculate x and y min/max from compressed pixels
86
- # (
87
- # x_min,
88
- # y_min,
89
- # x_max,
90
- # y_max,
91
- # ) = await self.data.async_get_rooms_coordinates(pixels, pixel_size)
92
- # corners = self.get_corners(x_max, x_min, y_max, y_min)
93
- # room_id = str(segment_id)
94
- # self.rooms_pos.append(
95
- # {
96
- # "name": name,
97
- # "corners": corners,
98
- # }
99
- # )
100
- # room_properties[room_id] = {
101
- # "number": segment_id,
102
- # "outline": corners,
103
- # "name": name,
104
- # "x": ((x_min + x_max) // 2),
105
- # "y": ((y_min + y_max) // 2),
106
- # }
107
- # if room_properties:
108
- # rooms = RoomStore(self.file_name, room_properties)
109
- # LOGGER.debug(
110
- # "%s: Rooms data extracted! %s", self.file_name, rooms.get_rooms()
111
- # )
112
- # else:
113
- # LOGGER.debug("%s: Rooms data not available!", self.file_name)
114
- # self.rooms_pos = None
115
- # return room_properties
72
+ room_properties = await self.rooms_handler.async_extract_room_properties(
73
+ json_data
74
+ )
75
+ if room_properties:
76
+ rooms = RoomStore(self.file_name, room_properties)
77
+ LOGGER.debug(
78
+ "%s: Rooms data extracted! %s", self.file_name, rooms.get_rooms()
79
+ )
80
+ # Convert room_properties to the format expected by async_get_robot_in_room
81
+ self.rooms_pos = []
82
+ for room_id, room_data in room_properties.items():
83
+ self.rooms_pos.append(
84
+ {"name": room_data["name"], "outline": room_data["outline"]}
85
+ )
86
+ else:
87
+ LOGGER.debug("%s: Rooms data not available!", self.file_name)
88
+ self.rooms_pos = None
89
+ return room_properties
116
90
 
117
91
  # noinspection PyUnresolvedReferences,PyUnboundLocalVariable
118
92
  async def async_get_image_from_json(
@@ -164,9 +138,6 @@ class HypferMapImageHandler(BaseHandler, AutoCrop):
164
138
  img_np_array = await self.draw.create_empty_image(
165
139
  size_x, size_y, colors["background"]
166
140
  )
167
-
168
- LOGGER.info("%s: Drawing map with color blending", self.file_name)
169
-
170
141
  # Draw layers and segments if enabled
171
142
  room_id = 0
172
143
  # Keep track of disabled rooms to skip their walls later
@@ -218,25 +189,13 @@ class HypferMapImageHandler(BaseHandler, AutoCrop):
218
189
  room_element = getattr(
219
190
  DrawableElement, f"ROOM_{current_room_id}", None
220
191
  )
221
- if room_element:
222
- # Log the room check for debugging
223
- LOGGER.debug(
224
- "%s: Checking if room %d is enabled: %s",
225
- self.file_name,
226
- current_room_id,
227
- self.drawing_config.is_enabled(
228
- room_element
229
- ),
230
- )
231
192
 
232
- # Skip this room if it's disabled
233
- if not self.drawing_config.is_enabled(
234
- room_element
235
- ):
236
- room_id = (
237
- room_id + 1
238
- ) % 16 # Increment room_id even if we skip
239
- continue
193
+ # Skip this room if it's disabled
194
+ if not self.drawing_config.is_enabled(room_element):
195
+ room_id = (
196
+ room_id + 1
197
+ ) % 16 # Increment room_id even if we skip
198
+ continue
240
199
 
241
200
  # Check if this is a wall layer and if walls are enabled
242
201
  is_wall_layer = layer_type == "wall"
@@ -244,22 +203,7 @@ class HypferMapImageHandler(BaseHandler, AutoCrop):
244
203
  if not self.drawing_config.is_enabled(
245
204
  DrawableElement.WALL
246
205
  ):
247
- LOGGER.info(
248
- "%s: Skipping wall layer because WALL element is disabled",
249
- self.file_name,
250
- )
251
- continue
252
-
253
- # Filter out walls for disabled rooms
254
- if disabled_rooms:
255
- # Need to modify compressed_pixels_list to exclude walls of disabled rooms
256
- # This requires knowledge of which walls belong to which rooms
257
- # For now, we'll just log that we're drawing walls for all rooms
258
- LOGGER.debug(
259
- "%s: Drawing walls for all rooms (including disabled ones)",
260
- self.file_name,
261
- )
262
- # In a real implementation, we would filter the walls here
206
+ pass
263
207
 
264
208
  # Draw the layer
265
209
  (
@@ -281,10 +225,6 @@ class HypferMapImageHandler(BaseHandler, AutoCrop):
281
225
  room_element = getattr(
282
226
  DrawableElement, f"ROOM_{room_id}", None
283
227
  )
284
- if room_element:
285
- # This is a simplification - in a real implementation we would
286
- # need to identify the exact pixels that belong to this room
287
- pass
288
228
 
289
229
  # Draw the virtual walls if enabled
290
230
  if self.drawing_config.is_enabled(DrawableElement.VIRTUAL_WALL):
@@ -29,13 +29,14 @@ from .config.types import (
29
29
  )
30
30
  from .config.utils import (
31
31
  BaseHandler,
32
+ # async_extract_room_outline,
32
33
  initialize_drawing_config,
33
34
  manage_drawable_elements,
34
35
  prepare_resize_params,
35
- async_extract_room_outline,
36
36
  )
37
37
  from .map_data import RandImageData
38
38
  from .reimg_draw import ImageDraw
39
+ from .rooms_handler import RandRoomsHandler
39
40
 
40
41
 
41
42
  _LOGGER = logging.getLogger(__name__)
@@ -68,6 +69,7 @@ class ReImageHandler(BaseHandler, AutoCrop):
68
69
  self.active_zones = None # Active zones
69
70
  self.file_name = self.shared.file_name # File name
70
71
  self.imd = ImageDraw(self) # Image Draw
72
+ self.rooms_handler = RandRoomsHandler(self.file_name, self.drawing_config) # Room data handler
71
73
 
72
74
  async def extract_room_outline_from_map(self, room_id_int, pixels):
73
75
  """Extract the outline of a room using the pixel data and element map.
@@ -110,62 +112,45 @@ class ReImageHandler(BaseHandler, AutoCrop):
110
112
  ) = await RandImageData.async_get_rrm_segments(
111
113
  json_data, size_x, size_y, top, left, True
112
114
  )
115
+
113
116
  dest_json = destinations
114
- room_data = dict(dest_json).get("rooms", [])
115
117
  zones_data = dict(dest_json).get("zones", [])
116
118
  points_data = dict(dest_json).get("spots", [])
117
- room_id_to_data = {room["id"]: room for room in room_data}
119
+
120
+ # Use the RandRoomsHandler to extract room properties
121
+ room_properties = await self.rooms_handler.async_extract_room_properties(
122
+ json_data, dest_json
123
+ )
124
+
125
+ # Update self.rooms_pos from room_properties for compatibility with other methods
118
126
  self.rooms_pos = []
119
- room_properties = {}
120
- if self.outlines:
121
- for id_x, room_id in enumerate(unsorted_id):
122
- if room_id in room_id_to_data:
123
- room_info = room_id_to_data[room_id]
124
- name = room_info.get("name")
125
- # Calculate x and y min/max from outlines
126
- x_min = self.outlines[id_x][0][0]
127
- x_max = self.outlines[id_x][1][0]
128
- y_min = self.outlines[id_x][0][1]
129
- y_max = self.outlines[id_x][1][1]
130
- corners = self.get_corners(x_max, x_min, y_max, y_min)
131
- # rand256 vacuums accept int(room_id) or str(name)
132
- # the card will soon support int(room_id) but the camera will send name
133
- # this avoids the manual change of the values in the card.
134
- self.rooms_pos.append(
135
- {
136
- "name": name,
137
- "corners": corners,
138
- }
139
- )
140
- room_properties[int(room_id)] = {
141
- "number": int(room_id),
142
- "outline": corners,
143
- "name": name,
144
- "x": (x_min + x_max) // 2,
145
- "y": (y_min + y_max) // 2,
146
- }
147
- # get the zones and points data
148
- zone_properties = await self.async_zone_propriety(zones_data)
149
- # get the points data
150
- point_properties = await self.async_points_propriety(points_data)
151
- if room_properties or zone_properties:
152
- extracted_data = [
153
- f"{len(room_properties)} Rooms" if room_properties else None,
154
- f"{len(zone_properties)} Zones" if zone_properties else None,
155
- ]
156
- extracted_data = ", ".join(filter(None, extracted_data))
157
- _LOGGER.debug("Extracted data: %s", extracted_data)
158
- else:
159
- self.rooms_pos = None
160
- _LOGGER.debug(
161
- "%s: Rooms and Zones data not available!", self.file_name
162
- )
163
- rooms = RoomStore(self.file_name, room_properties)
164
- _LOGGER.debug("Rooms Data: %s", rooms.get_rooms())
165
- return room_properties, zone_properties, point_properties
127
+ for room_id, props in room_properties.items():
128
+ self.rooms_pos.append({
129
+ "name": props["name"],
130
+ "corners": props["outline"], # Use the enhanced outline
131
+ })
132
+
133
+ # get the zones and points data
134
+ zone_properties = await self.async_zone_propriety(zones_data)
135
+ # get the points data
136
+ point_properties = await self.async_points_propriety(points_data)
137
+
138
+ if room_properties or zone_properties:
139
+ extracted_data = [
140
+ f"{len(room_properties)} Rooms" if room_properties else None,
141
+ f"{len(zone_properties)} Zones" if zone_properties else None,
142
+ ]
143
+ extracted_data = ", ".join(filter(None, extracted_data))
144
+ _LOGGER.debug("Extracted data: %s", extracted_data)
166
145
  else:
167
- _LOGGER.debug("%s: No outlines available", self.file_name)
168
- return None, None, None
146
+ self.rooms_pos = None
147
+ _LOGGER.debug(
148
+ "%s: Rooms and Zones data not available!", self.file_name
149
+ )
150
+
151
+ rooms = RoomStore(self.file_name, room_properties)
152
+ _LOGGER.debug("Rooms Data: %s", rooms.get_rooms())
153
+ return room_properties, zone_properties, point_properties
169
154
  except (RuntimeError, ValueError) as e:
170
155
  _LOGGER.debug(
171
156
  "No rooms Data or Error in extract_room_properties: %s",
@@ -0,0 +1,470 @@
1
+ """
2
+ Hipfer Rooms Handler Module.
3
+ Handles room data extraction and processing for Valetudo Hipfer vacuum maps.
4
+ Provides async methods for room outline extraction and properties management.
5
+ Version: 0.1.9
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import time
11
+ from typing import Any, Dict, List, Optional, Tuple
12
+
13
+ import numpy as np
14
+ from scipy.ndimage import binary_dilation, binary_erosion
15
+ from scipy.spatial import ConvexHull
16
+
17
+ from .config.drawable_elements import DrawableElement, DrawingConfig
18
+ from .config.types import LOGGER, RoomsProperties
19
+
20
+ from .map_data import RandImageData, ImageData
21
+
22
+ class RoomsHandler:
23
+ """
24
+ Handler for extracting and managing room data from Hipfer vacuum maps.
25
+
26
+ This class provides methods to:
27
+ - Extract room outlines using the Ramer-Douglas-Peucker algorithm
28
+ - Process room properties from JSON data
29
+ - Generate room masks and extract contours
30
+
31
+ All methods are async for better integration with the rest of the codebase.
32
+ """
33
+
34
+ def __init__(self, vacuum_id: str, drawing_config: Optional[DrawingConfig] = None):
35
+ """
36
+ Initialize the HipferRoomsHandler.
37
+
38
+ Args:
39
+ vacuum_id: Identifier for the vacuum
40
+ drawing_config: Configuration for which elements to draw (optional)
41
+ """
42
+ self.vacuum_id = vacuum_id
43
+ self.drawing_config = drawing_config
44
+ self.current_json_data = (
45
+ None # Will store the current JSON data being processed
46
+ )
47
+
48
+ @staticmethod
49
+ def sublist(data: list, chunk_size: int) -> list:
50
+ return [data[i : i + chunk_size] for i in range(0, len(data), chunk_size)]
51
+
52
+ @staticmethod
53
+ def convex_hull_outline(mask: np.ndarray) -> list[tuple[int, int]]:
54
+ y_indices, x_indices = np.where(mask > 0)
55
+ if len(x_indices) == 0 or len(y_indices) == 0:
56
+ return []
57
+
58
+ points = np.column_stack((x_indices, y_indices))
59
+ if len(points) < 3:
60
+ return [(int(x), int(y)) for x, y in points]
61
+
62
+ hull = ConvexHull(points)
63
+ # Convert numpy.int64 values to regular Python integers
64
+ hull_points = [
65
+ (int(points[vertex][0]), int(points[vertex][1])) for vertex in hull.vertices
66
+ ]
67
+ if hull_points[0] != hull_points[-1]:
68
+ hull_points.append(hull_points[0])
69
+ return hull_points
70
+
71
+ async def _process_room_layer(
72
+ self, layer: Dict[str, Any], width: int, height: int, pixel_size: int
73
+ ) -> Tuple[Optional[str], Optional[Dict[str, Any]]]:
74
+ """Process a single room layer and extract its outline.
75
+
76
+ Args:
77
+ layer: The layer data from the JSON
78
+ width: The width of the map
79
+ height: The height of the map
80
+ pixel_size: The size of each pixel
81
+
82
+ Returns:
83
+ Tuple of (room_id, room_data) or (None, None) if processing failed
84
+ """
85
+ meta_data = layer.get("metaData", {})
86
+ segment_id = meta_data.get("segmentId")
87
+ name = meta_data.get("name", "Room {}".format(segment_id))
88
+ compressed_pixels = layer.get("compressedPixels", [])
89
+ pixels = self.sublist(compressed_pixels, 3)
90
+
91
+ # Check if this room is enabled in the drawing configuration
92
+ if self.drawing_config is not None:
93
+ # Convert segment_id to room element (ROOM_1 to ROOM_15)
94
+ try:
95
+ # Segment IDs might not be sequential, so we need to map them to room elements
96
+ # We'll use a simple approach: if segment_id is an integer, use it directly
97
+ room_element_id = int(segment_id)
98
+ if 1 <= room_element_id <= 15:
99
+ room_element = getattr(
100
+ DrawableElement, f"ROOM_{room_element_id}", None
101
+ )
102
+ if room_element:
103
+ is_enabled = self.drawing_config.is_enabled(room_element)
104
+ if not is_enabled:
105
+ # Skip this room if it's disabled
106
+ LOGGER.debug("Skipping disabled room %s", segment_id)
107
+ return None, None
108
+ except (ValueError, TypeError):
109
+ # If segment_id is not a valid integer, we can't map it to a room element
110
+ # In this case, we'll include the room (fail open)
111
+ LOGGER.debug(
112
+ "Could not convert segment_id %s to room element", segment_id
113
+ )
114
+
115
+ # Optimization: Create a smaller mask for just the room area
116
+ if not pixels:
117
+ # Skip if no pixels
118
+ return None, None
119
+
120
+ # Convert to numpy arrays for vectorized operations
121
+ pixel_data = np.array(pixels)
122
+
123
+ if pixel_data.size == 0:
124
+ return None, None
125
+
126
+ # Find the actual bounds of the room to create a smaller mask
127
+ # Add padding to ensure we don't lose edge details
128
+ padding = 10 # Add padding pixels around the room
129
+ min_x = max(0, int(np.min(pixel_data[:, 0])) - padding)
130
+ max_x = min(
131
+ width, int(np.max(pixel_data[:, 0]) + np.max(pixel_data[:, 2])) + padding
132
+ )
133
+ min_y = max(0, int(np.min(pixel_data[:, 1])) - padding)
134
+ max_y = min(height, int(np.max(pixel_data[:, 1]) + 1) + padding)
135
+
136
+ # Create a smaller mask for just the room area (much faster)
137
+ local_width = max_x - min_x
138
+ local_height = max_y - min_y
139
+
140
+ # Skip if dimensions are invalid
141
+ if local_width <= 0 or local_height <= 0:
142
+ return None, None
143
+
144
+ # Create a smaller mask
145
+ local_mask = np.zeros((local_height, local_width), dtype=np.uint8)
146
+
147
+ # Fill the mask efficiently
148
+ for x, y, length in pixel_data:
149
+ x, y, length = int(x), int(y), int(length)
150
+ # Adjust coordinates to local mask
151
+ local_x = x - min_x
152
+ local_y = y - min_y
153
+
154
+ # Ensure we're within bounds
155
+ if 0 <= local_y < local_height and 0 <= local_x < local_width:
156
+ # Calculate the end point, clamping to mask width
157
+ end_x = min(local_x + length, local_width)
158
+ if end_x > local_x: # Only process if there's a valid segment
159
+ local_mask[local_y, local_x:end_x] = 1
160
+
161
+ # Apply morphological operations
162
+ struct_elem = np.ones((3, 3), dtype=np.uint8)
163
+ eroded = binary_erosion(local_mask, structure=struct_elem, iterations=1)
164
+ mask = binary_dilation(eroded, structure=struct_elem, iterations=1).astype(
165
+ np.uint8
166
+ )
167
+
168
+ # Extract contour from the mask
169
+ outline = self.convex_hull_outline(mask)
170
+ if not outline:
171
+ return None, None
172
+
173
+ # Adjust coordinates back to global space
174
+ outline = [(x + min_x, y + min_y) for (x, y) in outline]
175
+
176
+ # Use coordinates as-is without flipping Y coordinates
177
+ xs, ys = zip(*outline)
178
+ x_min, x_max = min(xs), max(xs)
179
+ y_min, y_max = min(ys), max(ys)
180
+
181
+ room_id = str(segment_id)
182
+
183
+ # Scale coordinates by pixel_size and convert to regular Python integers
184
+ scaled_outline = [
185
+ (int(x * pixel_size), int(y * pixel_size)) for x, y in outline
186
+ ]
187
+ room_data = {
188
+ "number": segment_id,
189
+ "outline": scaled_outline,
190
+ "name": name,
191
+ "x": int(((x_min + x_max) * pixel_size) // 2),
192
+ "y": int(((y_min + y_max) * pixel_size) // 2),
193
+ }
194
+
195
+ return room_id, room_data
196
+
197
+ async def async_extract_room_properties(self, json_data) -> RoomsProperties:
198
+ """Extract room properties from the JSON data.
199
+
200
+ This method processes all room layers in the JSON data and extracts their outlines.
201
+ It respects the drawing configuration, skipping rooms that are disabled.
202
+
203
+ Args:
204
+ json_data: The JSON data from the vacuum
205
+
206
+ Returns:
207
+ Dictionary of room properties
208
+ """
209
+ start_total = time.time()
210
+ room_properties = {}
211
+ pixel_size = json_data.get("pixelSize", 5)
212
+ height = json_data["size"]["y"]
213
+ width = json_data["size"]["x"]
214
+
215
+ for layer in json_data.get("layers", []):
216
+ if layer.get("__class") == "MapLayer" and layer.get("type") == "segment":
217
+ room_id, room_data = await self._process_room_layer(
218
+ layer, width, height, pixel_size
219
+ )
220
+ if room_id is not None and room_data is not None:
221
+ room_properties[room_id] = room_data
222
+
223
+ # Log timing information
224
+ total_time = time.time() - start_total
225
+ LOGGER.debug("Room extraction Total time: %.3fs", total_time)
226
+ return room_properties
227
+
228
+ class RandRoomsHandler:
229
+ """
230
+ Handler for extracting and managing room data from Rand25 vacuum maps.
231
+
232
+ This class provides methods to:
233
+ - Extract room outlines using the Convex Hull algorithm
234
+ - Process room properties from JSON data and destinations JSON
235
+ - Generate room masks and extract contours
236
+
237
+ All methods are async for better integration with the rest of the codebase.
238
+ """
239
+
240
+ def __init__(self, vacuum_id: str, drawing_config: Optional[DrawingConfig] = None):
241
+ """
242
+ Initialize the RandRoomsHandler.
243
+
244
+ Args:
245
+ vacuum_id: Identifier for the vacuum
246
+ drawing_config: Configuration for which elements to draw (optional)
247
+ """
248
+ self.vacuum_id = vacuum_id
249
+ self.drawing_config = drawing_config
250
+ self.current_json_data = None # Will store the current JSON data being processed
251
+ self.segment_data = None # Segment data
252
+ self.outlines = None # Outlines data
253
+
254
+ @staticmethod
255
+ def sublist(data: list, chunk_size: int) -> list:
256
+ """Split a list into chunks of specified size."""
257
+ return [data[i : i + chunk_size] for i in range(0, len(data), chunk_size)]
258
+
259
+ @staticmethod
260
+ def convex_hull_outline(points: List[Tuple[int, int]]) -> List[Tuple[int, int]]:
261
+ """
262
+ Generate a convex hull outline from a set of points.
263
+
264
+ Args:
265
+ points: List of (x, y) coordinate tuples
266
+
267
+ Returns:
268
+ List of (x, y) tuples forming the convex hull outline
269
+ """
270
+ if len(points) == 0:
271
+ return []
272
+
273
+ # Convert to numpy array for processing
274
+ points_array = np.array(points)
275
+
276
+ if len(points) < 3:
277
+ # Not enough points for a convex hull, return the points as is
278
+ return [(int(x), int(y)) for x, y in points_array]
279
+
280
+ try:
281
+ # Calculate the convex hull
282
+ hull = ConvexHull(points_array)
283
+
284
+ # Extract the vertices in order
285
+ hull_points = [
286
+ (int(points_array[vertex][0]), int(points_array[vertex][1]))
287
+ for vertex in hull.vertices
288
+ ]
289
+
290
+ # Close the polygon by adding the first point at the end
291
+ if hull_points[0] != hull_points[-1]:
292
+ hull_points.append(hull_points[0])
293
+
294
+ return hull_points
295
+
296
+ except Exception as e:
297
+ LOGGER.warning(f"Error calculating convex hull: {e}")
298
+
299
+ # Fallback to bounding box if convex hull fails
300
+ x_min, y_min = np.min(points_array, axis=0)
301
+ x_max, y_max = np.max(points_array, axis=0)
302
+
303
+ return [
304
+ (int(x_min), int(y_min)), # Top-left
305
+ (int(x_max), int(y_min)), # Top-right
306
+ (int(x_max), int(y_max)), # Bottom-right
307
+ (int(x_min), int(y_max)), # Bottom-left
308
+ (int(x_min), int(y_min)), # Back to top-left to close the polygon
309
+ ]
310
+
311
+ async def _process_segment_data(
312
+ self, segment_data: List, segment_id: int, pixel_size: int
313
+ ) -> Tuple[Optional[str], Optional[Dict[str, Any]]]:
314
+ """
315
+ Process a single segment and extract its outline.
316
+
317
+ Args:
318
+ segment_data: The segment pixel data
319
+ segment_id: The ID of the segment
320
+ pixel_size: The size of each pixel
321
+
322
+ Returns:
323
+ Tuple of (room_id, room_data) or (None, None) if processing failed
324
+ """
325
+ # Check if this room is enabled in the drawing configuration
326
+ if self.drawing_config is not None:
327
+ try:
328
+ # Convert segment_id to room element (ROOM_1 to ROOM_15)
329
+ room_element_id = int(segment_id)
330
+ if 1 <= room_element_id <= 15:
331
+ room_element = getattr(
332
+ DrawableElement, f"ROOM_{room_element_id}", None
333
+ )
334
+ if room_element:
335
+ is_enabled = self.drawing_config.is_enabled(room_element)
336
+ if not is_enabled:
337
+ # Skip this room if it's disabled
338
+ LOGGER.debug("Skipping disabled room %s", segment_id)
339
+ return None, None
340
+ except (ValueError, TypeError):
341
+ # If segment_id is not a valid integer, we can't map it to a room element
342
+ # In this case, we'll include the room (fail open)
343
+ LOGGER.debug(
344
+ "Could not convert segment_id %s to room element", segment_id
345
+ )
346
+
347
+ # Skip if no pixels
348
+ if not segment_data:
349
+ return None, None
350
+
351
+ # Extract points from segment data
352
+ points = []
353
+ for x, y, _ in segment_data:
354
+ points.append((int(x), int(y)))
355
+
356
+ if not points:
357
+ return None, None
358
+
359
+ # Use convex hull to get the outline
360
+ outline = self.convex_hull_outline(points)
361
+ if not outline:
362
+ return None, None
363
+
364
+ # Calculate bounding box for the room
365
+ xs, ys = zip(*outline)
366
+ x_min, x_max = min(xs), max(xs)
367
+ y_min, y_max = min(ys), max(ys)
368
+
369
+ # Scale coordinates by pixel_size
370
+ scaled_outline = [
371
+ (int(x * pixel_size), int(y * pixel_size)) for x, y in outline
372
+ ]
373
+
374
+ room_id = str(segment_id)
375
+ room_data = {
376
+ "number": segment_id,
377
+ "outline": scaled_outline,
378
+ "name": f"Room {segment_id}", # Default name, will be updated from destinations
379
+ "x": int(((x_min + x_max) * pixel_size) // 2),
380
+ "y": int(((y_min + y_max) * pixel_size) // 2),
381
+ }
382
+
383
+ return room_id, room_data
384
+
385
+ async def async_extract_room_properties(
386
+ self, json_data: Dict[str, Any], destinations: Dict[str, Any]
387
+ ) -> RoomsProperties:
388
+ """
389
+ Extract room properties from the JSON data and destinations.
390
+
391
+ Args:
392
+ json_data: The JSON data from the vacuum
393
+ destinations: The destinations JSON containing room names and IDs
394
+
395
+ Returns:
396
+ Dictionary of room properties
397
+ """
398
+ start_total = time.time()
399
+ room_properties = {}
400
+
401
+ # Get basic map information
402
+ unsorted_id = RandImageData.get_rrm_segments_ids(json_data)
403
+ size_x, size_y = RandImageData.get_rrm_image_size(json_data)
404
+ top, left = RandImageData.get_rrm_image_position(json_data)
405
+ pixel_size = 50 # Rand25 vacuums use a larger pixel size to match the original implementation
406
+
407
+ # Get segment data and outlines if not already available
408
+ if not self.segment_data or not self.outlines:
409
+ (
410
+ self.segment_data,
411
+ self.outlines,
412
+ ) = await RandImageData.async_get_rrm_segments(
413
+ json_data, size_x, size_y, top, left, True
414
+ )
415
+
416
+ # Process destinations JSON to get room names
417
+ dest_json = destinations
418
+ room_data = dest_json.get("rooms", [])
419
+ room_id_to_data = {room["id"]: room for room in room_data}
420
+
421
+ # Process each segment
422
+ if unsorted_id and self.segment_data and self.outlines:
423
+ for idx, segment_id in enumerate(unsorted_id):
424
+ # Extract points from segment data
425
+ points = []
426
+ for x, y, _ in self.segment_data[idx]:
427
+ points.append((int(x), int(y)))
428
+
429
+ if not points:
430
+ continue
431
+
432
+ # Use convex hull to get the outline
433
+ outline = self.convex_hull_outline(points)
434
+ if not outline:
435
+ continue
436
+
437
+ # Scale coordinates by pixel_size
438
+ scaled_outline = [
439
+ (int(x * pixel_size), int(y * pixel_size)) for x, y in outline
440
+ ]
441
+
442
+ # Calculate center point
443
+ xs, ys = zip(*outline)
444
+ x_min, x_max = min(xs), max(xs)
445
+ y_min, y_max = min(ys), max(ys)
446
+ center_x = int(((x_min + x_max) * pixel_size) // 2)
447
+ center_y = int(((y_min + y_max) * pixel_size) // 2)
448
+
449
+ # Create room data
450
+ room_id = str(segment_id)
451
+ room_data = {
452
+ "number": segment_id,
453
+ "outline": scaled_outline,
454
+ "name": f"Room {segment_id}", # Default name, will be updated from destinations
455
+ "x": center_x,
456
+ "y": center_y,
457
+ }
458
+
459
+ # Update room name from destinations if available
460
+ if segment_id in room_id_to_data:
461
+ room_info = room_id_to_data[segment_id]
462
+ room_data["name"] = room_info.get("name", room_data["name"])
463
+
464
+ room_properties[room_id] = room_data
465
+
466
+ # Log timing information
467
+ total_time = time.time() - start_total
468
+ LOGGER.debug("Room extraction Total time: %.3fs", total_time)
469
+
470
+ return room_properties
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: valetudo-map-parser
3
- Version: 0.1.9b53
3
+ Version: 0.1.9b55
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,27 @@
1
+ valetudo_map_parser/__init__.py,sha256=Fz-gtKf_OlZcDQqVfGlBwIWi5DJAiRucMbBMdQ2tX_U,1060
2
+ valetudo_map_parser/config/__init__.py,sha256=DQ9plV3ZF_K25Dp5ZQHPDoG-40dQoJNdNi-dfNeR3Zc,48
3
+ valetudo_map_parser/config/auto_crop.py,sha256=6xt_wJQqphddWhlrr7MNUkodCi8ZYdRk42qvAaxlYCM,13546
4
+ valetudo_map_parser/config/color_utils.py,sha256=nXD6WeNmdFdoMxPDW-JFpjnxJSaZR1jX-ouNfrx6zvE,4502
5
+ valetudo_map_parser/config/colors.py,sha256=DG-oPQoN5gsnwDbEsuFr8a0hRCxmbFHObWa4_5pr-70,29910
6
+ valetudo_map_parser/config/drawable.py,sha256=2MeVHXqZuVuJk3eerMJYGwo25rVetHx3xB_vxecEFOQ,34168
7
+ valetudo_map_parser/config/drawable_elements.py,sha256=o-5oiXmfqPwNQLzKIhkEcZD_A47rIU9E0CqKgWipxgc,11516
8
+ valetudo_map_parser/config/enhanced_drawable.py,sha256=QlGxlUMVgECUXPtFwIslyjubWxQuhIixsRymWV3lEvk,12586
9
+ valetudo_map_parser/config/optimized_element_map.py,sha256=52BCnkvVv9bre52LeVIfT8nhnEIpc0TuWTv1xcNu0Rk,15744
10
+ valetudo_map_parser/config/rand25_parser.py,sha256=kIayyqVZBfQfAMkiArzqrrj9vqZB3pkgT0Y5ufrQmGA,16448
11
+ valetudo_map_parser/config/room_outline.py,sha256=D20D-yeyKnlmVbW9lI7bsPtQGn2XkcWow6YNOEPnWVg,4800
12
+ valetudo_map_parser/config/shared.py,sha256=Vr4bicL7aJoRQbwbXyjEpiWhfzZ-cakLlfRqL3LBhpM,10475
13
+ valetudo_map_parser/config/types.py,sha256=TaRKoo7G7WIUw7ljOz2Vn5oYzKaLyQH-7Eb8ZYql8Ls,17464
14
+ valetudo_map_parser/config/utils.py,sha256=CFuuiS5IufEu9aeaZwi7xa1jEF1z6yDZB0mcyVX79Xo,29261
15
+ valetudo_map_parser/hypfer_draw.py,sha256=bwNTYopTJFY0nElrHquQrSfGHgN_-6t5E-8xBxDsRXA,26660
16
+ valetudo_map_parser/hypfer_handler.py,sha256=wvkZt6MsUF0gkHZDAwiUgOOawkdvakRzYBV3NtjxuJQ,19938
17
+ valetudo_map_parser/hypfer_rooms_handler.py,sha256=NkpOA6Gdq-2D3lLAxvtNuuWMvPXHxeMY2TO5RZLSHlU,22652
18
+ valetudo_map_parser/map_data.py,sha256=zQKE8EzWxR0r0qyfD1QQq51T1wFrpcIeXtnpm92-LXQ,17743
19
+ valetudo_map_parser/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
+ valetudo_map_parser/rand25_handler.py,sha256=VOBzx0AY61AggOkiX-P4c73MrPrQBGfsDQTEbVL1wCQ,18855
21
+ valetudo_map_parser/reimg_draw.py,sha256=1q8LkNTPHEA9Tsapc_JnVw51kpPYNhaBU-KmHkefCQY,12507
22
+ valetudo_map_parser/rooms_handler.py,sha256=YP8OLotBH-RXluv398l7TTT2zIBHJp91b8THWxl3NdI,17794
23
+ valetudo_map_parser-0.1.9b55.dist-info/LICENSE,sha256=Lh-qBbuRV0-jiCIBhfV7NgdwFxQFOXH3BKOzK865hRs,10480
24
+ valetudo_map_parser-0.1.9b55.dist-info/METADATA,sha256=Af2KutWU3EHPE8F1HNCWWKlIGKqr9bz1nTbC1-d3ZCo,3321
25
+ valetudo_map_parser-0.1.9b55.dist-info/NOTICE.txt,sha256=5lTOuWiU9aiEnJ2go8sc7lTJ7ntMBx0g0GFnNrswCY4,2533
26
+ valetudo_map_parser-0.1.9b55.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
27
+ valetudo_map_parser-0.1.9b55.dist-info/RECORD,,
@@ -1,26 +0,0 @@
1
- valetudo_map_parser/__init__.py,sha256=cewtLadNSOg3X2Ts2SuG8mTJqo0ncsFRg_sQ4VkM4ow,1037
2
- valetudo_map_parser/config/__init__.py,sha256=DQ9plV3ZF_K25Dp5ZQHPDoG-40dQoJNdNi-dfNeR3Zc,48
3
- valetudo_map_parser/config/auto_crop.py,sha256=6OvRsWzXMXBaSEvgwpaaisNdozDKiDyTmPjknFxoUMc,12624
4
- valetudo_map_parser/config/color_utils.py,sha256=D4NXRhuPdQ7UDKM3vLNYR0HnACl9AB75EnfCp5tGliI,4502
5
- valetudo_map_parser/config/colors.py,sha256=LE7sl4Qy0TkxbkjgB3dotYIfXqhc-fllkFQxexVvUvg,29909
6
- valetudo_map_parser/config/drawable.py,sha256=qenuxD1-Vvyus9o8alJFYRqL54aO3pakMqPSYNGvpe8,34169
7
- valetudo_map_parser/config/drawable_elements.py,sha256=bkEwdbx1upt9vaPaqE_VR1rtwRsaiH-IVKc3mHNa8IY,12065
8
- valetudo_map_parser/config/enhanced_drawable.py,sha256=6yGoOq_dLf2VCghO_URSyGfLAshFyzS3iEPeHw1PeDo,12586
9
- valetudo_map_parser/config/optimized_element_map.py,sha256=52BCnkvVv9bre52LeVIfT8nhnEIpc0TuWTv1xcNu0Rk,15744
10
- valetudo_map_parser/config/rand25_parser.py,sha256=kIayyqVZBfQfAMkiArzqrrj9vqZB3pkgT0Y5ufrQmGA,16448
11
- valetudo_map_parser/config/room_outline.py,sha256=D20D-yeyKnlmVbW9lI7bsPtQGn2XkcWow6YNOEPnWVg,4800
12
- valetudo_map_parser/config/shared.py,sha256=GIEMF-M6BVA6SFBrql7chV7TciWNMLJ8geqwHB0NrW8,11253
13
- valetudo_map_parser/config/types.py,sha256=e-eZSwbPm3m5JfCDaKhnUFspmcRFSv74huxegkSBDXM,17566
14
- valetudo_map_parser/config/utils.py,sha256=RsMjpjVqNbkI502yhLiRaB0GjCADqmRRcz-TkC6zklQ,31073
15
- valetudo_map_parser/hypfer_draw.py,sha256=P8CrKysLaBb63ZArfqxN2Og6JCU6sPHPFHOte5noCGg,26654
16
- valetudo_map_parser/hypfer_handler.py,sha256=WYFrp-q5wBsy0cTcVQUCXXVGTtW30z2W2dYvjKz2el8,23292
17
- valetudo_map_parser/hypfer_rooms_handler.py,sha256=NkpOA6Gdq-2D3lLAxvtNuuWMvPXHxeMY2TO5RZLSHlU,22652
18
- valetudo_map_parser/map_data.py,sha256=zQKE8EzWxR0r0qyfD1QQq51T1wFrpcIeXtnpm92-LXQ,17743
19
- valetudo_map_parser/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
- valetudo_map_parser/rand25_handler.py,sha256=eLFX_gmGLaWQwvp8hVj8CgcNOfLsYNIdE1OLRcQy_yM,19988
21
- valetudo_map_parser/reimg_draw.py,sha256=1q8LkNTPHEA9Tsapc_JnVw51kpPYNhaBU-KmHkefCQY,12507
22
- valetudo_map_parser-0.1.9b53.dist-info/LICENSE,sha256=Lh-qBbuRV0-jiCIBhfV7NgdwFxQFOXH3BKOzK865hRs,10480
23
- valetudo_map_parser-0.1.9b53.dist-info/METADATA,sha256=i8nDujD2s2Qs62WwEv-3AIO23XJTvxvSZXY5QcswEtc,3321
24
- valetudo_map_parser-0.1.9b53.dist-info/NOTICE.txt,sha256=5lTOuWiU9aiEnJ2go8sc7lTJ7ntMBx0g0GFnNrswCY4,2533
25
- valetudo_map_parser-0.1.9b53.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
26
- valetudo_map_parser-0.1.9b53.dist-info/RECORD,,