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
@@ -13,11 +13,11 @@ import asyncio
|
|
13
13
|
import logging
|
14
14
|
import math
|
15
15
|
|
16
|
-
# cv2 is imported but not used directly in this file
|
17
|
-
# It's needed for other modules that import from here
|
18
16
|
import numpy as np
|
19
17
|
from PIL import ImageDraw, ImageFont
|
18
|
+
from scipy import ndimage
|
20
19
|
|
20
|
+
from .colors import ColorsManagement
|
21
21
|
from .types import Color, NumpyArray, PilPNG, Point, Tuple, Union
|
22
22
|
|
23
23
|
|
@@ -187,7 +187,6 @@ class Drawable:
|
|
187
187
|
width: Width of the line
|
188
188
|
blend: Whether to blend the color with the background
|
189
189
|
"""
|
190
|
-
from .colors import ColorsManagment
|
191
190
|
|
192
191
|
x1, y1, x2, y2 = int(x1), int(y1), int(x2), int(y2)
|
193
192
|
|
@@ -195,13 +194,17 @@ class Drawable:
|
|
195
194
|
if blend:
|
196
195
|
# Sample at start point
|
197
196
|
if 0 <= y1 < layer.shape[0] and 0 <= x1 < layer.shape[1]:
|
198
|
-
start_blended_color =
|
197
|
+
start_blended_color = ColorsManagement.sample_and_blend_color(
|
198
|
+
layer, x1, y1, color
|
199
|
+
)
|
199
200
|
else:
|
200
201
|
start_blended_color = color
|
201
202
|
|
202
203
|
# Sample at end point
|
203
204
|
if 0 <= y2 < layer.shape[0] and 0 <= x2 < layer.shape[1]:
|
204
|
-
end_blended_color =
|
205
|
+
end_blended_color = ColorsManagement.sample_and_blend_color(
|
206
|
+
layer, x2, y2, color
|
207
|
+
)
|
205
208
|
else:
|
206
209
|
end_blended_color = color
|
207
210
|
|
@@ -210,7 +213,7 @@ class Drawable:
|
|
210
213
|
(start_blended_color[0] + end_blended_color[0]) // 2,
|
211
214
|
(start_blended_color[1] + end_blended_color[1]) // 2,
|
212
215
|
(start_blended_color[2] + end_blended_color[2]) // 2,
|
213
|
-
(start_blended_color[3] + end_blended_color[3]) // 2
|
216
|
+
(start_blended_color[3] + end_blended_color[3]) // 2,
|
214
217
|
)
|
215
218
|
else:
|
216
219
|
blended_color = color
|
@@ -256,28 +259,27 @@ class Drawable:
|
|
256
259
|
"""
|
257
260
|
Join the coordinates creating a continuous line (path).
|
258
261
|
"""
|
259
|
-
import logging
|
260
|
-
from .colors import ColorsManagment
|
261
262
|
|
262
|
-
_LOGGER = logging.getLogger(__name__)
|
263
|
-
_LOGGER.debug("Drawing lines with %d coordinates, width %d, color %s", len(coords), width, color)
|
264
263
|
for coord in coords:
|
265
264
|
x0, y0 = coord[0]
|
266
265
|
try:
|
267
266
|
x1, y1 = coord[1]
|
268
267
|
except IndexError:
|
269
268
|
x1, y1 = x0, y0
|
270
|
-
_LOGGER.debug("Drawing line from (%d, %d) to (%d, %d)", x0, y0, x1, y1)
|
271
269
|
|
272
270
|
# Sample background color at the endpoints and blend with foreground color
|
273
271
|
# This is more efficient than sampling at every pixel
|
274
272
|
if 0 <= y0 < arr.shape[0] and 0 <= x0 < arr.shape[1]:
|
275
|
-
start_blended_color =
|
273
|
+
start_blended_color = ColorsManagement.sample_and_blend_color(
|
274
|
+
arr, x0, y0, color
|
275
|
+
)
|
276
276
|
else:
|
277
277
|
start_blended_color = color
|
278
278
|
|
279
279
|
if 0 <= y1 < arr.shape[0] and 0 <= x1 < arr.shape[1]:
|
280
|
-
end_blended_color =
|
280
|
+
end_blended_color = ColorsManagement.sample_and_blend_color(
|
281
|
+
arr, x1, y1, color
|
282
|
+
)
|
281
283
|
else:
|
282
284
|
end_blended_color = color
|
283
285
|
|
@@ -286,7 +288,7 @@ class Drawable:
|
|
286
288
|
(start_blended_color[0] + end_blended_color[0]) // 2,
|
287
289
|
(start_blended_color[1] + end_blended_color[1]) // 2,
|
288
290
|
(start_blended_color[2] + end_blended_color[2]) // 2,
|
289
|
-
(start_blended_color[3] + end_blended_color[3]) // 2
|
291
|
+
(start_blended_color[3] + end_blended_color[3]) // 2,
|
290
292
|
)
|
291
293
|
|
292
294
|
dx = abs(x1 - x0)
|
@@ -422,7 +424,6 @@ class Drawable:
|
|
422
424
|
"""
|
423
425
|
Draw the zones on the input layer with color blending.
|
424
426
|
"""
|
425
|
-
from .colors import ColorsManagment
|
426
427
|
|
427
428
|
dot_radius = 1 # Number of pixels for the dot
|
428
429
|
dot_spacing = 4 # Space between dots
|
@@ -440,14 +441,18 @@ class Drawable:
|
|
440
441
|
|
441
442
|
# Blend the color with the background color at the sample point
|
442
443
|
if 0 <= sample_y < layers.shape[0] and 0 <= sample_x < layers.shape[1]:
|
443
|
-
blended_color =
|
444
|
+
blended_color = ColorsManagement.sample_and_blend_color(
|
445
|
+
layers, sample_x, sample_y, color
|
446
|
+
)
|
444
447
|
else:
|
445
448
|
blended_color = color
|
446
449
|
|
447
450
|
for y in range(min_y, max_y, dot_spacing):
|
448
451
|
for x in range(min_x, max_x, dot_spacing):
|
449
452
|
for _ in range(dot_radius):
|
450
|
-
layers = Drawable._ellipse(
|
453
|
+
layers = Drawable._ellipse(
|
454
|
+
layers, (x, y), dot_radius, blended_color
|
455
|
+
)
|
451
456
|
return layers
|
452
457
|
|
453
458
|
@staticmethod
|
@@ -542,7 +547,7 @@ class Drawable:
|
|
542
547
|
|
543
548
|
@staticmethod
|
544
549
|
async def async_draw_obstacles(
|
545
|
-
image: np.ndarray, obstacle_info_list, color:
|
550
|
+
image: np.ndarray, obstacle_info_list, color: Color
|
546
551
|
) -> np.ndarray:
|
547
552
|
"""
|
548
553
|
Optimized async version of draw_obstacles using asyncio.gather().
|
@@ -592,3 +597,60 @@ class Drawable:
|
|
592
597
|
else:
|
593
598
|
draw.text((x, y), text, font=font, fill=color)
|
594
599
|
x += draw.textlength(text, font=default_font)
|
600
|
+
|
601
|
+
@staticmethod
|
602
|
+
def draw_filled_polygon(image_array, vertices, color):
|
603
|
+
"""
|
604
|
+
Draw a filled polygon on the image array using scipy.ndimage.
|
605
|
+
|
606
|
+
Args:
|
607
|
+
image_array: NumPy array of shape (height, width, 4) containing RGBA image data
|
608
|
+
vertices: List of (x,y) tuples defining the polygon vertices
|
609
|
+
color: RGBA color tuple to fill the polygon with
|
610
|
+
|
611
|
+
Returns:
|
612
|
+
Modified image array with the filled polygon
|
613
|
+
"""
|
614
|
+
if len(vertices) < 3:
|
615
|
+
return image_array # Need at least 3 points for a polygon
|
616
|
+
|
617
|
+
height, width = image_array.shape[:2]
|
618
|
+
|
619
|
+
# Create a mask for the polygon
|
620
|
+
polygon_mask = np.zeros((height, width), dtype=bool)
|
621
|
+
|
622
|
+
# Convert vertices to numpy arrays for processing
|
623
|
+
vertices_array = np.array(vertices)
|
624
|
+
x_coords = vertices_array[:, 0]
|
625
|
+
y_coords = vertices_array[:, 1]
|
626
|
+
|
627
|
+
# Clip coordinates to image boundaries
|
628
|
+
x_coords = np.clip(x_coords, 0, width - 1)
|
629
|
+
y_coords = np.clip(y_coords, 0, height - 1)
|
630
|
+
|
631
|
+
# Create a polygon using scipy.ndimage
|
632
|
+
# First create the boundary
|
633
|
+
for i in range(len(vertices)):
|
634
|
+
x1, y1 = int(x_coords[i]), int(y_coords[i])
|
635
|
+
x2, y2 = (
|
636
|
+
int(x_coords[(i + 1) % len(vertices)]),
|
637
|
+
int(y_coords[(i + 1) % len(vertices)]),
|
638
|
+
)
|
639
|
+
|
640
|
+
# Draw line between consecutive vertices
|
641
|
+
length = max(abs(x2 - x1), abs(y2 - y1)) * 2
|
642
|
+
if length == 0:
|
643
|
+
continue
|
644
|
+
|
645
|
+
t = np.linspace(0, 1, length)
|
646
|
+
x = np.round(x1 * (1 - t) + x2 * t).astype(int)
|
647
|
+
y = np.round(y1 * (1 - t) + y2 * t).astype(int)
|
648
|
+
|
649
|
+
# Add boundary points to mask
|
650
|
+
polygon_mask[y, x] = True
|
651
|
+
|
652
|
+
# Fill the polygon using scipy.ndimage.binary_fill_holes
|
653
|
+
filled_polygon = ndimage.binary_fill_holes(polygon_mask)
|
654
|
+
|
655
|
+
# Apply color to the filled polygon
|
656
|
+
return ColorsManagement.batch_blend_colors(image_array, filled_polygon, color)
|
@@ -344,7 +344,9 @@ class ElementMapGenerator:
|
|
344
344
|
self.element_map = existing_element_map
|
345
345
|
|
346
346
|
# Check if this is a Valetudo map or a Rand256 map
|
347
|
-
is_valetudo =
|
347
|
+
is_valetudo = (
|
348
|
+
"size" in json_data and "pixelSize" in json_data and "layers" in json_data
|
349
|
+
)
|
348
350
|
is_rand256 = "image" in json_data and "map_data" in json_data
|
349
351
|
|
350
352
|
# Debug logging
|
@@ -362,7 +364,9 @@ class ElementMapGenerator:
|
|
362
364
|
size_y = map_size.get("height", 0)
|
363
365
|
else:
|
364
366
|
# If map_size is a number, use it for both dimensions
|
365
|
-
pixel_size = json_data.get(
|
367
|
+
pixel_size = json_data.get(
|
368
|
+
"pixelSize", 5
|
369
|
+
) # Default to 5mm per pixel
|
366
370
|
size_x = int(map_size // pixel_size)
|
367
371
|
size_y = int(map_size // pixel_size)
|
368
372
|
self.element_map = np.zeros((size_y, size_x), dtype=np.int32)
|
@@ -387,16 +391,24 @@ class ElementMapGenerator:
|
|
387
391
|
# Calculate scale factor based on pixel size (normalize to 5mm standard)
|
388
392
|
# This helps handle maps with different scales
|
389
393
|
scale_factor = pixel_size if pixel_size != 0 else 1.0
|
390
|
-
LOGGER.info(
|
394
|
+
LOGGER.info(
|
395
|
+
f"Map dimensions: {size_x}x{size_y}, pixel size: {pixel_size}mm, scale factor: {scale_factor:.2f}"
|
396
|
+
)
|
391
397
|
|
392
398
|
# Ensure element_map is properly initialized with the correct dimensions
|
393
|
-
if
|
399
|
+
if (
|
400
|
+
self.element_map is None
|
401
|
+
or self.element_map.shape[0] == 0
|
402
|
+
or self.element_map.shape[1] == 0
|
403
|
+
):
|
394
404
|
# For now, create a full-sized element map to ensure coordinates match
|
395
405
|
# We'll resize it at the end for efficiency
|
396
406
|
map_width = int(size_x // pixel_size) if pixel_size != 0 else size_x
|
397
407
|
map_height = int(size_y // pixel_size) if pixel_size != 0 else size_y
|
398
408
|
|
399
|
-
LOGGER.info(
|
409
|
+
LOGGER.info(
|
410
|
+
f"Creating element map with dimensions: {map_width}x{map_height}"
|
411
|
+
)
|
400
412
|
self.element_map = np.zeros((map_height, map_width), dtype=np.int32)
|
401
413
|
self.element_map[:] = DrawableElement.FLOOR
|
402
414
|
|
@@ -410,14 +422,18 @@ class ElementMapGenerator:
|
|
410
422
|
if "segments" in layer:
|
411
423
|
segments = layer["segments"]
|
412
424
|
else:
|
413
|
-
segments = [
|
425
|
+
segments = [
|
426
|
+
layer
|
427
|
+
] # Some formats have segment data directly in the layer
|
414
428
|
|
415
429
|
for segment in segments:
|
416
430
|
# Get room ID and check if it's enabled
|
417
431
|
if "id" in segment:
|
418
432
|
room_id = segment["id"]
|
419
433
|
room_id_int = int(room_id)
|
420
|
-
elif
|
434
|
+
elif (
|
435
|
+
"metaData" in segment and "segmentId" in segment["metaData"]
|
436
|
+
):
|
421
437
|
# Handle Hypfer format
|
422
438
|
room_id = segment["metaData"]["segmentId"]
|
423
439
|
room_id_int = int(room_id)
|
@@ -426,8 +442,12 @@ class ElementMapGenerator:
|
|
426
442
|
continue
|
427
443
|
|
428
444
|
# Skip if room is disabled
|
429
|
-
room_element = getattr(
|
430
|
-
|
445
|
+
room_element = getattr(
|
446
|
+
DrawableElement, f"ROOM_{room_id_int}", None
|
447
|
+
)
|
448
|
+
if room_element is None or not self.drawing_config.is_enabled(
|
449
|
+
room_element
|
450
|
+
):
|
431
451
|
continue
|
432
452
|
|
433
453
|
# Room element code was already retrieved above
|
@@ -449,18 +469,27 @@ class ElementMapGenerator:
|
|
449
469
|
region_row_end = row + pixel_size
|
450
470
|
|
451
471
|
# Update element map for this region
|
452
|
-
if
|
472
|
+
if (
|
473
|
+
region_row_start < size_y
|
474
|
+
and region_col_start < size_x
|
475
|
+
):
|
453
476
|
# Ensure we stay within bounds
|
454
477
|
end_row = min(region_row_end, size_y)
|
455
478
|
end_col = min(region_col_end, size_x)
|
456
479
|
|
457
480
|
# Set element code for this region
|
458
481
|
# Only set pixels that are not already set (floor is 1)
|
459
|
-
region = self.element_map[
|
482
|
+
region = self.element_map[
|
483
|
+
region_row_start:end_row,
|
484
|
+
region_col_start:end_col,
|
485
|
+
]
|
460
486
|
mask = region == DrawableElement.FLOOR
|
461
487
|
region[mask] = room_element
|
462
488
|
|
463
|
-
elif
|
489
|
+
elif (
|
490
|
+
"compressedPixels" in segment
|
491
|
+
and segment["compressedPixels"]
|
492
|
+
):
|
464
493
|
# Compressed pixel format (used in Valetudo)
|
465
494
|
compressed_pixels = segment["compressedPixels"]
|
466
495
|
i = 0
|
@@ -468,8 +497,8 @@ class ElementMapGenerator:
|
|
468
497
|
|
469
498
|
while i < len(compressed_pixels):
|
470
499
|
x = compressed_pixels[i]
|
471
|
-
y = compressed_pixels[i+1]
|
472
|
-
count = compressed_pixels[i+2]
|
500
|
+
y = compressed_pixels[i + 1]
|
501
|
+
count = compressed_pixels[i + 2]
|
473
502
|
pixel_count += count
|
474
503
|
|
475
504
|
# Set element code for this run of pixels
|
@@ -481,8 +510,12 @@ class ElementMapGenerator:
|
|
481
510
|
i += 3
|
482
511
|
|
483
512
|
# Debug: Log that we're adding room pixels
|
484
|
-
LOGGER.info(
|
485
|
-
|
513
|
+
LOGGER.info(
|
514
|
+
f"Adding room {room_id_int} pixels to element map with code {room_element}"
|
515
|
+
)
|
516
|
+
LOGGER.info(
|
517
|
+
f"Room {room_id_int} has {len(compressed_pixels) // 3} compressed runs with {pixel_count} total pixels"
|
518
|
+
)
|
486
519
|
|
487
520
|
# Process walls
|
488
521
|
elif layer_type == "wall":
|
@@ -507,14 +540,20 @@ class ElementMapGenerator:
|
|
507
540
|
region_row_end = row + pixel_size
|
508
541
|
|
509
542
|
# Update element map for this region
|
510
|
-
if
|
543
|
+
if (
|
544
|
+
region_row_start < size_y
|
545
|
+
and region_col_start < size_x
|
546
|
+
):
|
511
547
|
# Ensure we stay within bounds
|
512
548
|
end_row = min(region_row_end, size_y)
|
513
549
|
end_col = min(region_col_end, size_x)
|
514
550
|
|
515
551
|
# Set element code for this region
|
516
552
|
# Only set pixels that are not already set (floor is 1)
|
517
|
-
region = self.element_map[
|
553
|
+
region = self.element_map[
|
554
|
+
region_row_start:end_row,
|
555
|
+
region_col_start:end_col,
|
556
|
+
]
|
518
557
|
mask = region == DrawableElement.FLOOR
|
519
558
|
region[mask] = DrawableElement.WALL
|
520
559
|
|
@@ -526,8 +565,8 @@ class ElementMapGenerator:
|
|
526
565
|
|
527
566
|
while i < len(compressed_pixels):
|
528
567
|
x = compressed_pixels[i]
|
529
|
-
y = compressed_pixels[i+1]
|
530
|
-
count = compressed_pixels[i+2]
|
568
|
+
y = compressed_pixels[i + 1]
|
569
|
+
count = compressed_pixels[i + 2]
|
531
570
|
pixel_count += count
|
532
571
|
|
533
572
|
# Set element code for this run of pixels
|
@@ -539,8 +578,12 @@ class ElementMapGenerator:
|
|
539
578
|
i += 3
|
540
579
|
|
541
580
|
# Debug: Log that we're adding wall pixels
|
542
|
-
LOGGER.info(
|
543
|
-
|
581
|
+
LOGGER.info(
|
582
|
+
f"Adding wall pixels to element map with code {DrawableElement.WALL}"
|
583
|
+
)
|
584
|
+
LOGGER.info(
|
585
|
+
f"Wall layer has {len(compressed_pixels) // 3} compressed runs with {pixel_count} total pixels"
|
586
|
+
)
|
544
587
|
|
545
588
|
elif is_rand256:
|
546
589
|
# Get map dimensions from the Rand256 JSON data
|
@@ -565,7 +608,9 @@ class ElementMapGenerator:
|
|
565
608
|
# Get room element code (ROOM_1, ROOM_2, etc.)
|
566
609
|
room_element = None
|
567
610
|
if 0 < room_id_int <= 15:
|
568
|
-
room_element = getattr(
|
611
|
+
room_element = getattr(
|
612
|
+
DrawableElement, f"ROOM_{room_id_int}", None
|
613
|
+
)
|
569
614
|
|
570
615
|
if room_element is not None and "coordinates" in room:
|
571
616
|
# Process coordinates for this room
|
@@ -588,7 +633,9 @@ class ElementMapGenerator:
|
|
588
633
|
# Get room element code (ROOM_1, ROOM_2, etc.)
|
589
634
|
room_element = None
|
590
635
|
if 0 < room_id_int <= 15:
|
591
|
-
room_element = getattr(
|
636
|
+
room_element = getattr(
|
637
|
+
DrawableElement, f"ROOM_{room_id_int}", None
|
638
|
+
)
|
592
639
|
|
593
640
|
if room_element is not None and coordinates:
|
594
641
|
# Process individual coordinates
|
@@ -625,21 +672,27 @@ class ElementMapGenerator:
|
|
625
672
|
crop_max_x = min(self.element_map.shape[1] - 1, max_x + margin)
|
626
673
|
|
627
674
|
# Log the cropping information
|
628
|
-
LOGGER.info(
|
629
|
-
|
630
|
-
|
675
|
+
LOGGER.info(
|
676
|
+
f"Cropping element map from {self.element_map.shape} to bounding box: "
|
677
|
+
f"({crop_min_x}, {crop_min_y}) to ({crop_max_x}, {crop_max_y})"
|
678
|
+
)
|
679
|
+
LOGGER.info(
|
680
|
+
f"Cropped dimensions: {crop_max_x - crop_min_x + 1}x{crop_max_y - crop_min_y + 1}"
|
681
|
+
)
|
631
682
|
|
632
683
|
# Create a new, smaller array with just the non-zero region
|
633
|
-
cropped_map = self.element_map[
|
684
|
+
cropped_map = self.element_map[
|
685
|
+
crop_min_y : crop_max_y + 1, crop_min_x : crop_max_x + 1
|
686
|
+
].copy()
|
634
687
|
|
635
688
|
# Store the cropping coordinates in the shared data for later reference
|
636
689
|
if self.shared:
|
637
690
|
self.shared.element_map_crop = {
|
638
|
-
|
639
|
-
|
640
|
-
|
641
|
-
|
642
|
-
|
691
|
+
"min_x": crop_min_x,
|
692
|
+
"min_y": crop_min_y,
|
693
|
+
"max_x": crop_max_x,
|
694
|
+
"max_y": crop_max_y,
|
695
|
+
"original_shape": self.element_map.shape,
|
643
696
|
}
|
644
697
|
|
645
698
|
# Replace the element map with the cropped version
|
@@ -648,8 +701,12 @@ class ElementMapGenerator:
|
|
648
701
|
# Now resize the element map to reduce its dimensions
|
649
702
|
# Calculate the resize factor based on the current size
|
650
703
|
resize_factor = 5 # Reduce to 1/5 of the current size
|
651
|
-
new_height = max(
|
652
|
-
|
704
|
+
new_height = max(
|
705
|
+
self.element_map.shape[0] // resize_factor, 50
|
706
|
+
) # Ensure minimum size
|
707
|
+
new_width = max(
|
708
|
+
self.element_map.shape[1] // resize_factor, 50
|
709
|
+
) # Ensure minimum size
|
653
710
|
|
654
711
|
# Create a resized element map
|
655
712
|
resized_map = np.zeros((new_height, new_width), dtype=np.int32)
|
@@ -670,13 +727,17 @@ class ElementMapGenerator:
|
|
670
727
|
|
671
728
|
# Store the resize information in shared data
|
672
729
|
if self.shared:
|
673
|
-
if hasattr(self.shared,
|
674
|
-
self.shared.element_map_crop[
|
675
|
-
self.shared.element_map_crop[
|
676
|
-
self.shared.element_map_crop[
|
730
|
+
if hasattr(self.shared, "element_map_crop"):
|
731
|
+
self.shared.element_map_crop["resize_factor"] = resize_factor
|
732
|
+
self.shared.element_map_crop["resized_shape"] = resized_map.shape
|
733
|
+
self.shared.element_map_crop["original_cropped_shape"] = (
|
734
|
+
self.element_map.shape
|
735
|
+
)
|
677
736
|
|
678
737
|
# Log the resizing information
|
679
|
-
LOGGER.info(
|
738
|
+
LOGGER.info(
|
739
|
+
f"Resized element map from {self.element_map.shape} to {resized_map.shape} (1/{resize_factor} of cropped size)"
|
740
|
+
)
|
680
741
|
|
681
742
|
# Replace the element map with the resized version
|
682
743
|
self.element_map = resized_map
|
@@ -709,7 +770,7 @@ class ElementMapGenerator:
|
|
709
770
|
|
710
771
|
# Get calibration points if available
|
711
772
|
calibration_points = None
|
712
|
-
if hasattr(self.shared,
|
773
|
+
if hasattr(self.shared, "attr_calibration_points"):
|
713
774
|
calibration_points = self.shared.attr_calibration_points
|
714
775
|
|
715
776
|
if calibration_points and len(calibration_points) >= 4:
|
@@ -717,9 +778,9 @@ class ElementMapGenerator:
|
|
717
778
|
image_points = []
|
718
779
|
vacuum_points = []
|
719
780
|
for point in calibration_points:
|
720
|
-
if
|
721
|
-
image_points.append((point[
|
722
|
-
vacuum_points.append((point[
|
781
|
+
if "map" in point and "vacuum" in point:
|
782
|
+
image_points.append((point["map"]["x"], point["map"]["y"]))
|
783
|
+
vacuum_points.append((point["vacuum"]["x"], point["vacuum"]["y"]))
|
723
784
|
|
724
785
|
if len(image_points) >= 2:
|
725
786
|
# Calculate scaling factors
|
@@ -734,35 +795,54 @@ class ElementMapGenerator:
|
|
734
795
|
vac_y_max = max(p[1] for p in vacuum_points)
|
735
796
|
|
736
797
|
# Normalize the input coordinates to 0-1 range in image space
|
737
|
-
norm_x = (
|
738
|
-
|
798
|
+
norm_x = (
|
799
|
+
(x - img_x_min) / (img_x_max - img_x_min)
|
800
|
+
if img_x_max > img_x_min
|
801
|
+
else 0
|
802
|
+
)
|
803
|
+
norm_y = (
|
804
|
+
(y - img_y_min) / (img_y_max - img_y_min)
|
805
|
+
if img_y_max > img_y_min
|
806
|
+
else 0
|
807
|
+
)
|
739
808
|
|
740
809
|
# Map to vacuum coordinates
|
741
810
|
vac_x = vac_x_min + norm_x * (vac_x_max - vac_x_min)
|
742
811
|
vac_y = vac_y_min + norm_y * (vac_y_max - vac_y_min)
|
743
812
|
|
744
|
-
LOGGER.debug(
|
813
|
+
LOGGER.debug(
|
814
|
+
f"Mapped image ({x}, {y}) to vacuum ({vac_x:.1f}, {vac_y:.1f})"
|
815
|
+
)
|
745
816
|
|
746
817
|
# Now map from vacuum coordinates to element map coordinates
|
747
818
|
# This depends on how the element map was created
|
748
|
-
if
|
819
|
+
if (
|
820
|
+
hasattr(self.shared, "element_map_crop")
|
821
|
+
and self.shared.element_map_crop
|
822
|
+
):
|
749
823
|
crop_info = self.shared.element_map_crop
|
750
824
|
|
751
825
|
# Adjust for cropping
|
752
|
-
if
|
753
|
-
elem_x = int(vac_x - crop_info[
|
754
|
-
elem_y = int(vac_y - crop_info[
|
826
|
+
if "min_x" in crop_info and "min_y" in crop_info:
|
827
|
+
elem_x = int(vac_x - crop_info["min_x"])
|
828
|
+
elem_y = int(vac_y - crop_info["min_y"])
|
755
829
|
|
756
830
|
# Adjust for resizing
|
757
|
-
if
|
758
|
-
|
759
|
-
|
831
|
+
if (
|
832
|
+
"resize_factor" in crop_info
|
833
|
+
and "original_cropped_shape" in crop_info
|
834
|
+
and "resized_shape" in crop_info
|
835
|
+
):
|
836
|
+
orig_h, orig_w = crop_info["original_cropped_shape"]
|
837
|
+
resized_h, resized_w = crop_info["resized_shape"]
|
760
838
|
|
761
839
|
# Scale to resized coordinates
|
762
840
|
elem_x = int(elem_x * resized_w / orig_w)
|
763
841
|
elem_y = int(elem_y * resized_h / orig_h)
|
764
842
|
|
765
|
-
LOGGER.debug(
|
843
|
+
LOGGER.debug(
|
844
|
+
f"Mapped vacuum ({vac_x:.1f}, {vac_y:.1f}) to element map ({elem_x}, {elem_y})"
|
845
|
+
)
|
766
846
|
|
767
847
|
# Check bounds and return element
|
768
848
|
height, width = self.element_map.shape
|
@@ -782,19 +862,23 @@ class ElementMapGenerator:
|
|
782
862
|
The name of the element (e.g., 'FLOOR', 'WALL', 'ROOM_1', etc.)
|
783
863
|
"""
|
784
864
|
if element_code is None:
|
785
|
-
return
|
865
|
+
return "NONE"
|
786
866
|
|
787
867
|
# Check if it's a room
|
788
868
|
if element_code >= 100:
|
789
869
|
room_number = element_code - 100
|
790
|
-
return f
|
870
|
+
return f"ROOM_{room_number}"
|
791
871
|
|
792
872
|
# Check standard elements
|
793
873
|
for name, code in vars(DrawableElement).items():
|
794
|
-
if
|
874
|
+
if (
|
875
|
+
not name.startswith("_")
|
876
|
+
and isinstance(code, int)
|
877
|
+
and code == element_code
|
878
|
+
):
|
795
879
|
return name
|
796
880
|
|
797
|
-
return f
|
881
|
+
return f"UNKNOWN_{element_code}"
|
798
882
|
|
799
883
|
def get_element_at_position(self, x: int, y: int, is_image_coords: bool = False):
|
800
884
|
"""Get the element code at the specified position in the element map.
|
@@ -814,7 +898,11 @@ class ElementMapGenerator:
|
|
814
898
|
# If coordinates are from the image, convert them to element map coordinates first
|
815
899
|
if is_image_coords and self.shared:
|
816
900
|
# Get image dimensions
|
817
|
-
if
|
901
|
+
if (
|
902
|
+
hasattr(self.shared, "image_size")
|
903
|
+
and self.shared.image_size is not None
|
904
|
+
and len(self.shared.image_size) >= 2
|
905
|
+
):
|
818
906
|
image_width = self.shared.image_size[0]
|
819
907
|
image_height = self.shared.image_size[1]
|
820
908
|
else:
|
@@ -823,8 +911,14 @@ class ElementMapGenerator:
|
|
823
911
|
image_height = 1824
|
824
912
|
|
825
913
|
# Get original element map dimensions (before resizing)
|
826
|
-
if
|
827
|
-
|
914
|
+
if (
|
915
|
+
hasattr(self.shared, "element_map_crop")
|
916
|
+
and self.shared.element_map_crop is not None
|
917
|
+
and "original_cropped_shape" in self.shared.element_map_crop
|
918
|
+
):
|
919
|
+
original_map_height, original_map_width = self.shared.element_map_crop[
|
920
|
+
"original_cropped_shape"
|
921
|
+
]
|
828
922
|
else:
|
829
923
|
# Estimate based on typical values
|
830
924
|
original_map_width = 1310
|
@@ -842,22 +936,24 @@ class ElementMapGenerator:
|
|
842
936
|
x = int((x + x_offset) * x_scale_to_map)
|
843
937
|
y = int((y + y_offset) * y_scale_to_map)
|
844
938
|
|
845
|
-
LOGGER.debug(
|
939
|
+
LOGGER.debug(
|
940
|
+
f"Converted image coordinates ({x}, {y}) to element map coordinates"
|
941
|
+
)
|
846
942
|
|
847
943
|
# Adjust coordinates if the element map has been cropped and resized
|
848
|
-
if self.shared and hasattr(self.shared,
|
944
|
+
if self.shared and hasattr(self.shared, "element_map_crop"):
|
849
945
|
# Get the crop information
|
850
946
|
crop_info = self.shared.element_map_crop
|
851
947
|
|
852
948
|
# Adjust coordinates to the cropped map
|
853
|
-
x_cropped = x - crop_info[
|
854
|
-
y_cropped = y - crop_info[
|
949
|
+
x_cropped = x - crop_info["min_x"]
|
950
|
+
y_cropped = y - crop_info["min_y"]
|
855
951
|
|
856
952
|
# If the map has been resized, adjust coordinates further
|
857
|
-
if
|
858
|
-
resize_factor = crop_info[
|
859
|
-
original_cropped_shape = crop_info.get(
|
860
|
-
resized_shape = crop_info.get(
|
953
|
+
if "resize_factor" in crop_info:
|
954
|
+
resize_factor = crop_info["resize_factor"]
|
955
|
+
original_cropped_shape = crop_info.get("original_cropped_shape")
|
956
|
+
resized_shape = crop_info.get("resized_shape")
|
861
957
|
|
862
958
|
if original_cropped_shape and resized_shape:
|
863
959
|
# Calculate scaling factors
|