valetudo-map-parser 0.1.9b45__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.
- valetudo_map_parser/config/colors.py +597 -24
- valetudo_map_parser/config/drawable.py +80 -18
- valetudo_map_parser/config/drawable_elements.py +166 -70
- valetudo_map_parser/config/optimized_element_map.py +133 -90
- valetudo_map_parser/config/room_outline.py +27 -27
- valetudo_map_parser/hypfer_handler.py +34 -14
- valetudo_map_parser/map_data.py +1 -1
- valetudo_map_parser/rand25_handler.py +61 -85
- valetudo_map_parser/utils/color_utils.py +8 -8
- {valetudo_map_parser-0.1.9b45.dist-info → valetudo_map_parser-0.1.9b46.dist-info}/METADATA +1 -1
- valetudo_map_parser-0.1.9b46.dist-info/RECORD +26 -0
- valetudo_map_parser/config/colors_man.py +0 -249
- valetudo_map_parser-0.1.9b45.dist-info/RECORD +0 -27
- {valetudo_map_parser-0.1.9b45.dist-info → valetudo_map_parser-0.1.9b46.dist-info}/LICENSE +0 -0
- {valetudo_map_parser-0.1.9b45.dist-info → valetudo_map_parser-0.1.9b46.dist-info}/NOTICE.txt +0 -0
- {valetudo_map_parser-0.1.9b45.dist-info → valetudo_map_parser-0.1.9b46.dist-info}/WHEEL +0 -0
@@ -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 =
|
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,
|
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 =
|
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(
|
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(
|
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(
|
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
|
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
|
-
(
|
148
|
-
|
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(
|
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(
|
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
|
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) & (
|
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(
|
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,
|
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(
|
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
|
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,
|
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,
|
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,
|
366
|
+
if not hasattr(self, "element_map") or self.element_map is None:
|
330
367
|
return None
|
331
|
-
|
332
|
-
if not (
|
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
|
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
|
395
|
+
return f"ROOM_{room_number}"
|
357
396
|
|
358
397
|
# Check standard elements
|
359
398
|
for name, code in vars(DrawableElement).items():
|
360
|
-
if
|
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
|
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
|