valetudo-map-parser 0.1.9b44__py3-none-any.whl → 0.1.9b46__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.
@@ -34,7 +34,11 @@ class OptimizedElementMapGenerator:
34
34
  self.element_map = None
35
35
  self.element_map_shape = None
36
36
  self.scale_info = None
37
- self.file_name = getattr(shared_data, 'file_name', 'ElementMap') if shared_data else 'ElementMap'
37
+ self.file_name = (
38
+ getattr(shared_data, "file_name", "ElementMap")
39
+ if shared_data
40
+ else "ElementMap"
41
+ )
38
42
 
39
43
  async def async_generate_from_json(self, json_data, existing_element_map=None):
40
44
  """Generate a 2D element map from JSON data with optimized performance.
@@ -74,56 +78,66 @@ class OptimizedElementMapGenerator:
74
78
  size_x = json_data["size"]["x"]
75
79
  size_y = json_data["size"]["y"]
76
80
  pixel_size = json_data["pixelSize"]
77
-
81
+
78
82
  # Calculate downscale factor based on pixel size
79
83
  # Standard pixel size is 5mm, so adjust accordingly
80
84
  downscale_factor = max(1, pixel_size // 5 * 2) # More aggressive downscaling
81
-
85
+
82
86
  # Calculate dimensions for the downscaled map
83
87
  map_width = max(100, size_x // (pixel_size * downscale_factor))
84
88
  map_height = max(100, size_y // (pixel_size * downscale_factor))
85
-
89
+
86
90
  LOGGER.info(
87
91
  "%s: Creating optimized element map with dimensions: %dx%d (downscale factor: %d)",
88
92
  self.file_name,
89
- map_width, map_height, downscale_factor
93
+ map_width,
94
+ map_height,
95
+ downscale_factor,
90
96
  )
91
-
97
+
92
98
  # Create the element map at the reduced size
93
99
  element_map = np.zeros((map_height, map_width), dtype=np.int32)
94
100
  element_map[:] = DrawableElement.FLOOR
95
-
101
+
96
102
  # Store scaling information for coordinate conversion
97
103
  self.scale_info = {
98
104
  "original_size": (size_x, size_y),
99
105
  "map_size": (map_width, map_height),
100
106
  "scale_factor": downscale_factor * pixel_size,
101
- "pixel_size": pixel_size
107
+ "pixel_size": pixel_size,
102
108
  }
103
-
109
+
104
110
  # Process layers at the reduced resolution
105
111
  for layer in json_data.get("layers", []):
106
112
  layer_type = layer.get("type")
107
-
113
+
108
114
  # Process rooms (segments)
109
115
  if layer_type == "segment":
110
116
  # Get room ID
111
117
  meta_data = layer.get("metaData", {})
112
118
  segment_id = meta_data.get("segmentId")
113
-
119
+
114
120
  if segment_id is not None:
115
121
  # Convert segment_id to int if it's a string
116
- segment_id_int = int(segment_id) if isinstance(segment_id, str) else segment_id
122
+ segment_id_int = (
123
+ int(segment_id) if isinstance(segment_id, str) else segment_id
124
+ )
117
125
  if 1 <= segment_id_int <= 15:
118
- room_element = getattr(DrawableElement, f"ROOM_{segment_id_int}", None)
119
-
126
+ room_element = getattr(
127
+ DrawableElement, f"ROOM_{segment_id_int}", None
128
+ )
129
+
120
130
  # Skip if room is disabled
121
- if room_element is None or not self.drawing_config.is_enabled(room_element):
131
+ if room_element is None or not self.drawing_config.is_enabled(
132
+ room_element
133
+ ):
122
134
  continue
123
-
135
+
124
136
  # Create a temporary high-resolution mask for this room
125
- temp_mask = np.zeros((size_y // pixel_size, size_x // pixel_size), dtype=np.uint8)
126
-
137
+ temp_mask = np.zeros(
138
+ (size_y // pixel_size, size_x // pixel_size), dtype=np.uint8
139
+ )
140
+
127
141
  # Process pixels for this room
128
142
  compressed_pixels = layer.get("compressedPixels", [])
129
143
  if compressed_pixels:
@@ -131,34 +145,44 @@ class OptimizedElementMapGenerator:
131
145
  for i in range(0, len(compressed_pixels), 3):
132
146
  if i + 2 < len(compressed_pixels):
133
147
  x = compressed_pixels[i]
134
- y = compressed_pixels[i+1]
135
- count = compressed_pixels[i+2]
136
-
148
+ y = compressed_pixels[i + 1]
149
+ count = compressed_pixels[i + 2]
150
+
137
151
  # Set pixels in the high-resolution mask
138
152
  for j in range(count):
139
153
  px = x + j
140
- if 0 <= y < temp_mask.shape[0] and 0 <= px < temp_mask.shape[1]:
154
+ if (
155
+ 0 <= y < temp_mask.shape[0]
156
+ and 0 <= px < temp_mask.shape[1]
157
+ ):
141
158
  temp_mask[y, px] = 1
142
-
159
+
143
160
  # Use scipy to downsample the mask efficiently
144
161
  # This preserves the room shape better than simple decimation
145
162
  downsampled_mask = ndimage.zoom(
146
- temp_mask,
147
- (map_height / temp_mask.shape[0], map_width / temp_mask.shape[1]),
148
- order=0 # Nearest neighbor interpolation
163
+ temp_mask,
164
+ (
165
+ map_height / temp_mask.shape[0],
166
+ map_width / temp_mask.shape[1],
167
+ ),
168
+ order=0, # Nearest neighbor interpolation
149
169
  )
150
-
170
+
151
171
  # Apply the downsampled mask to the element map
152
172
  element_map[downsampled_mask > 0] = room_element
153
-
173
+
154
174
  # Clean up
155
175
  del temp_mask, downsampled_mask
156
-
176
+
157
177
  # Process walls similarly
158
- elif layer_type == "wall" and self.drawing_config.is_enabled(DrawableElement.WALL):
178
+ elif layer_type == "wall" and self.drawing_config.is_enabled(
179
+ DrawableElement.WALL
180
+ ):
159
181
  # Create a temporary high-resolution mask for walls
160
- temp_mask = np.zeros((size_y // pixel_size, size_x // pixel_size), dtype=np.uint8)
161
-
182
+ temp_mask = np.zeros(
183
+ (size_y // pixel_size, size_x // pixel_size), dtype=np.uint8
184
+ )
185
+
162
186
  # Process compressed pixels for walls
163
187
  compressed_pixels = layer.get("compressedPixels", [])
164
188
  if compressed_pixels:
@@ -166,38 +190,43 @@ class OptimizedElementMapGenerator:
166
190
  for i in range(0, len(compressed_pixels), 3):
167
191
  if i + 2 < len(compressed_pixels):
168
192
  x = compressed_pixels[i]
169
- y = compressed_pixels[i+1]
170
- count = compressed_pixels[i+2]
171
-
193
+ y = compressed_pixels[i + 1]
194
+ count = compressed_pixels[i + 2]
195
+
172
196
  # Set pixels in the high-resolution mask
173
197
  for j in range(count):
174
198
  px = x + j
175
- if 0 <= y < temp_mask.shape[0] and 0 <= px < temp_mask.shape[1]:
199
+ if (
200
+ 0 <= y < temp_mask.shape[0]
201
+ and 0 <= px < temp_mask.shape[1]
202
+ ):
176
203
  temp_mask[y, px] = 1
177
-
204
+
178
205
  # Use scipy to downsample the mask efficiently
179
206
  downsampled_mask = ndimage.zoom(
180
- temp_mask,
207
+ temp_mask,
181
208
  (map_height / temp_mask.shape[0], map_width / temp_mask.shape[1]),
182
- order=0
209
+ order=0,
183
210
  )
184
-
211
+
185
212
  # Apply the downsampled mask to the element map
186
213
  # Only overwrite floor pixels, not room pixels
187
- wall_mask = (downsampled_mask > 0) & (element_map == DrawableElement.FLOOR)
214
+ wall_mask = (downsampled_mask > 0) & (
215
+ element_map == DrawableElement.FLOOR
216
+ )
188
217
  element_map[wall_mask] = DrawableElement.WALL
189
-
218
+
190
219
  # Clean up
191
220
  del temp_mask, downsampled_mask
192
-
221
+
193
222
  # Store the element map
194
223
  self.element_map = element_map
195
224
  self.element_map_shape = element_map.shape
196
-
225
+
197
226
  LOGGER.info(
198
227
  "%s: Element map generation complete with shape: %s",
199
228
  self.file_name,
200
- element_map.shape
229
+ element_map.shape,
201
230
  )
202
231
  return element_map
203
232
 
@@ -207,131 +236,141 @@ class OptimizedElementMapGenerator:
207
236
  map_data = json_data["map_data"]
208
237
  size_x = map_data["dimensions"]["width"]
209
238
  size_y = map_data["dimensions"]["height"]
210
-
239
+
211
240
  # Calculate downscale factor
212
- downscale_factor = max(1, min(size_x, size_y) // 500) # Target ~500px in smallest dimension
213
-
241
+ downscale_factor = max(
242
+ 1, min(size_x, size_y) // 500
243
+ ) # Target ~500px in smallest dimension
244
+
214
245
  # Calculate dimensions for the downscaled map
215
246
  map_width = max(100, size_x // downscale_factor)
216
247
  map_height = max(100, size_y // downscale_factor)
217
-
248
+
218
249
  LOGGER.info(
219
250
  "%s: Creating optimized Rand256 element map with dimensions: %dx%d (downscale factor: %d)",
220
251
  self.file_name,
221
- map_width, map_height, downscale_factor
252
+ map_width,
253
+ map_height,
254
+ downscale_factor,
222
255
  )
223
-
256
+
224
257
  # Create the element map at the reduced size
225
258
  element_map = np.zeros((map_height, map_width), dtype=np.int32)
226
259
  element_map[:] = DrawableElement.FLOOR
227
-
260
+
228
261
  # Store scaling information for coordinate conversion
229
262
  self.scale_info = {
230
263
  "original_size": (size_x, size_y),
231
264
  "map_size": (map_width, map_height),
232
265
  "scale_factor": downscale_factor,
233
- "pixel_size": 1 # Rand256 uses 1:1 pixel mapping
266
+ "pixel_size": 1, # Rand256 uses 1:1 pixel mapping
234
267
  }
235
-
268
+
236
269
  # Process rooms
237
270
  if "rooms" in map_data and map_data["rooms"]:
238
271
  for room in map_data["rooms"]:
239
272
  # Get room ID and check if it's enabled
240
273
  room_id_int = room["id"]
241
-
274
+
242
275
  # Get room element code (ROOM_1, ROOM_2, etc.)
243
276
  room_element = None
244
277
  if 0 < room_id_int <= 15:
245
278
  room_element = getattr(DrawableElement, f"ROOM_{room_id_int}", None)
246
-
279
+
247
280
  # Skip if room is disabled
248
- if room_element is None or not self.drawing_config.is_enabled(room_element):
281
+ if room_element is None or not self.drawing_config.is_enabled(
282
+ room_element
283
+ ):
249
284
  continue
250
-
285
+
251
286
  if "coordinates" in room:
252
287
  # Create a high-resolution mask for this room
253
288
  temp_mask = np.zeros((size_y, size_x), dtype=np.uint8)
254
-
289
+
255
290
  # Fill the mask with room coordinates
256
291
  for coord in room["coordinates"]:
257
292
  x, y = coord
258
293
  if 0 <= y < size_y and 0 <= x < size_x:
259
294
  temp_mask[y, x] = 1
260
-
295
+
261
296
  # Use scipy to downsample the mask efficiently
262
297
  downsampled_mask = ndimage.zoom(
263
- temp_mask,
298
+ temp_mask,
264
299
  (map_height / size_y, map_width / size_x),
265
- order=0 # Nearest neighbor interpolation
300
+ order=0, # Nearest neighbor interpolation
266
301
  )
267
-
302
+
268
303
  # Apply the downsampled mask to the element map
269
304
  element_map[downsampled_mask > 0] = room_element
270
-
305
+
271
306
  # Clean up
272
307
  del temp_mask, downsampled_mask
273
-
308
+
274
309
  # Process walls
275
- if "walls" in map_data and map_data["walls"] and self.drawing_config.is_enabled(DrawableElement.WALL):
310
+ if (
311
+ "walls" in map_data
312
+ and map_data["walls"]
313
+ and self.drawing_config.is_enabled(DrawableElement.WALL)
314
+ ):
276
315
  # Create a high-resolution mask for walls
277
316
  temp_mask = np.zeros((size_y, size_x), dtype=np.uint8)
278
-
317
+
279
318
  # Fill the mask with wall coordinates
280
319
  for coord in map_data["walls"]:
281
320
  x, y = coord
282
321
  if 0 <= y < size_y and 0 <= x < size_x:
283
322
  temp_mask[y, x] = 1
284
-
323
+
285
324
  # Use scipy to downsample the mask efficiently
286
325
  downsampled_mask = ndimage.zoom(
287
- temp_mask,
288
- (map_height / size_y, map_width / size_x),
289
- order=0
326
+ temp_mask, (map_height / size_y, map_width / size_x), order=0
290
327
  )
291
-
328
+
292
329
  # Apply the downsampled mask to the element map
293
330
  # Only overwrite floor pixels, not room pixels
294
331
  wall_mask = (downsampled_mask > 0) & (element_map == DrawableElement.FLOOR)
295
332
  element_map[wall_mask] = DrawableElement.WALL
296
-
333
+
297
334
  # Clean up
298
335
  del temp_mask, downsampled_mask
299
-
336
+
300
337
  # Store the element map
301
338
  self.element_map = element_map
302
339
  self.element_map_shape = element_map.shape
303
-
340
+
304
341
  LOGGER.info(
305
342
  "%s: Rand256 element map generation complete with shape: %s",
306
343
  self.file_name,
307
- element_map.shape
344
+ element_map.shape,
308
345
  )
309
346
  return element_map
310
347
 
311
348
  def map_to_element_coordinates(self, x, y):
312
349
  """Convert map coordinates to element map coordinates."""
313
- if not hasattr(self, 'scale_info'):
350
+ if not hasattr(self, "scale_info"):
314
351
  return x, y
315
-
352
+
316
353
  scale = self.scale_info["scale_factor"]
317
354
  return int(x / scale), int(y / scale)
318
-
355
+
319
356
  def element_to_map_coordinates(self, x, y):
320
357
  """Convert element map coordinates to map coordinates."""
321
- if not hasattr(self, 'scale_info'):
358
+ if not hasattr(self, "scale_info"):
322
359
  return x, y
323
-
360
+
324
361
  scale = self.scale_info["scale_factor"]
325
362
  return int(x * scale), int(y * scale)
326
363
 
327
364
  def get_element_at_position(self, x, y):
328
365
  """Get the element at the specified position."""
329
- if not hasattr(self, 'element_map') or self.element_map is None:
366
+ if not hasattr(self, "element_map") or self.element_map is None:
330
367
  return None
331
-
332
- if not (0 <= y < self.element_map.shape[0] and 0 <= x < self.element_map.shape[1]):
368
+
369
+ if not (
370
+ 0 <= y < self.element_map.shape[0] and 0 <= x < self.element_map.shape[1]
371
+ ):
333
372
  return None
334
-
373
+
335
374
  return self.element_map[y, x]
336
375
 
337
376
  def get_room_at_position(self, x, y):
@@ -339,7 +378,7 @@ class OptimizedElementMapGenerator:
339
378
  element_code = self.get_element_at_position(x, y)
340
379
  if element_code is None:
341
380
  return None
342
-
381
+
343
382
  # Check if it's a room (codes 101-115)
344
383
  if 101 <= element_code <= 115:
345
384
  return element_code
@@ -348,16 +387,20 @@ class OptimizedElementMapGenerator:
348
387
  def get_element_name(self, element_code):
349
388
  """Get the name of the element from its code."""
350
389
  if element_code is None:
351
- return 'NONE'
390
+ return "NONE"
352
391
 
353
392
  # Check if it's a room
354
393
  if element_code >= 100:
355
394
  room_number = element_code - 100
356
- return f'ROOM_{room_number}'
395
+ return f"ROOM_{room_number}"
357
396
 
358
397
  # Check standard elements
359
398
  for name, code in vars(DrawableElement).items():
360
- if not name.startswith('_') and isinstance(code, int) and code == element_code:
399
+ if (
400
+ not name.startswith("_")
401
+ and isinstance(code, int)
402
+ and code == element_code
403
+ ):
361
404
  return name
362
405
 
363
- return f'UNKNOWN_{element_code}'
406
+ return f"UNKNOWN_{element_code}"
@@ -16,13 +16,13 @@ async def extract_room_outline_with_scipy(
16
16
  room_mask, min_x, min_y, max_x, max_y, file_name=None, room_id=None
17
17
  ):
18
18
  """Extract a room outline using scipy for contour finding.
19
-
19
+
20
20
  Args:
21
21
  room_mask: Binary mask of the room (1 for room, 0 for non-room)
22
22
  min_x, min_y, max_x, max_y: Bounding box coordinates
23
23
  file_name: Optional file name for logging
24
24
  room_id: Optional room ID for logging
25
-
25
+
26
26
  Returns:
27
27
  List of points forming the outline of the room
28
28
  """
@@ -34,11 +34,11 @@ async def extract_room_outline_with_scipy(
34
34
  str(room_id) if room_id is not None else "unknown",
35
35
  )
36
36
  return [(min_x, min_y), (max_x, min_y), (max_x, max_y), (min_x, max_y)]
37
-
37
+
38
38
  # Use scipy to clean up the mask (remove noise, fill holes)
39
39
  # Fill small holes
40
40
  room_mask = ndimage.binary_fill_holes(room_mask).astype(np.uint8)
41
-
41
+
42
42
  # Remove small objects
43
43
  labeled_array, num_features = ndimage.label(room_mask)
44
44
  if num_features > 1:
@@ -46,11 +46,11 @@ async def extract_room_outline_with_scipy(
46
46
  component_sizes = np.bincount(labeled_array.ravel())[1:]
47
47
  largest_component = np.argmax(component_sizes) + 1
48
48
  room_mask = (labeled_array == largest_component).astype(np.uint8)
49
-
49
+
50
50
  # Find the boundary points by tracing the perimeter
51
51
  boundary_points = []
52
52
  height, width = room_mask.shape
53
-
53
+
54
54
  # Scan horizontally (top and bottom edges)
55
55
  for x in range(width):
56
56
  # Top edge
@@ -58,13 +58,13 @@ async def extract_room_outline_with_scipy(
58
58
  if room_mask[y, x] == 1:
59
59
  boundary_points.append((x + min_x, y + min_y))
60
60
  break
61
-
61
+
62
62
  # Bottom edge
63
- for y in range(height-1, -1, -1):
63
+ for y in range(height - 1, -1, -1):
64
64
  if room_mask[y, x] == 1:
65
65
  boundary_points.append((x + min_x, y + min_y))
66
66
  break
67
-
67
+
68
68
  # Scan vertically (left and right edges)
69
69
  for y in range(height):
70
70
  # Left edge
@@ -72,19 +72,19 @@ async def extract_room_outline_with_scipy(
72
72
  if room_mask[y, x] == 1:
73
73
  boundary_points.append((x + min_x, y + min_y))
74
74
  break
75
-
75
+
76
76
  # Right edge
77
- for x in range(width-1, -1, -1):
77
+ for x in range(width - 1, -1, -1):
78
78
  if room_mask[y, x] == 1:
79
79
  boundary_points.append((x + min_x, y + min_y))
80
80
  break
81
-
81
+
82
82
  # Remove duplicates while preserving order
83
83
  unique_points = []
84
84
  for point in boundary_points:
85
85
  if point not in unique_points:
86
86
  unique_points.append(point)
87
-
87
+
88
88
  # If we have too few points, return a simple rectangle
89
89
  if len(unique_points) < 4:
90
90
  LOGGER.warning(
@@ -93,17 +93,17 @@ async def extract_room_outline_with_scipy(
93
93
  str(room_id) if room_id is not None else "unknown",
94
94
  )
95
95
  return [(min_x, min_y), (max_x, min_y), (max_x, max_y), (min_x, max_y)]
96
-
96
+
97
97
  # Simplify the outline by keeping only significant points
98
98
  simplified = simplify_outline(unique_points, tolerance=5)
99
-
99
+
100
100
  LOGGER.debug(
101
101
  "%s: Extracted outline for room %s with %d points",
102
102
  file_name or "RoomOutline",
103
103
  str(room_id) if room_id is not None else "unknown",
104
104
  len(simplified),
105
105
  )
106
-
106
+
107
107
  return simplified
108
108
 
109
109
 
@@ -111,38 +111,38 @@ def simplify_outline(points, tolerance=5):
111
111
  """Simplify an outline by removing points that don't contribute much to the shape."""
112
112
  if len(points) <= 4:
113
113
  return points
114
-
114
+
115
115
  # Start with the first point
116
116
  simplified = [points[0]]
117
-
117
+
118
118
  # Process remaining points
119
119
  for i in range(1, len(points) - 1):
120
120
  # Get previous and next points
121
121
  prev = simplified[-1]
122
122
  current = points[i]
123
123
  next_point = points[i + 1]
124
-
124
+
125
125
  # Calculate vectors
126
126
  v1 = (current[0] - prev[0], current[1] - prev[1])
127
127
  v2 = (next_point[0] - current[0], next_point[1] - current[1])
128
-
128
+
129
129
  # Calculate change in direction
130
130
  dot_product = v1[0] * v2[0] + v1[1] * v2[1]
131
- len_v1 = (v1[0]**2 + v1[1]**2)**0.5
132
- len_v2 = (v2[0]**2 + v2[1]**2)**0.5
133
-
131
+ len_v1 = (v1[0] ** 2 + v1[1] ** 2) ** 0.5
132
+ len_v2 = (v2[0] ** 2 + v2[1] ** 2) ** 0.5
133
+
134
134
  # Avoid division by zero
135
135
  if len_v1 == 0 or len_v2 == 0:
136
136
  continue
137
-
137
+
138
138
  # Calculate cosine of angle between vectors
139
139
  cos_angle = dot_product / (len_v1 * len_v2)
140
-
140
+
141
141
  # If angle is significant or distance is large, keep the point
142
142
  if abs(cos_angle) < 0.95 or len_v1 > tolerance or len_v2 > tolerance:
143
143
  simplified.append(current)
144
-
144
+
145
145
  # Add the last point
146
146
  simplified.append(points[-1])
147
-
147
+
148
148
  return simplified