import numpy as np from scipy import ndimage as ndi from leonida.classify import CLASSES from leonida.inpaint import fill_nearest, is_thin, rebuild STADT, PARK, WATER = CLASSES["stadt"], CLASSES["gruen"], CLASSES["wasser"] ROAD, PATH, BUILDING = CLASSES["strasse"], CLASSES["weg"], CLASSES["gebaeude"] def city(h=200, w=300): return np.full((h, w), STADT, dtype=np.uint8) def box_mask(shape, rows, cols): mask = np.zeros(shape, dtype=bool) mask[rows, cols] = True return mask def scribble(labels, mask): """Schrift simulieren: unter der Maske steht Unsinn, der nichts mit der Karte zu tun hat.""" labels[mask] = BUILDING labels[mask & (np.indices(labels.shape).sum(axis=0) % 3 == 0)] = ROAD def test_fill_nearest_keeps_a_road_running_along_a_grid_line(): labels = city(60, 100) labels[:, 40:60] = ROAD band = box_mask(labels.shape, slice(None), slice(47, 53)) labels[band] = BUILDING fill_nearest(labels, band, band) assert (labels[:, 40:60] == ROAD).all() def test_fill_nearest_closes_a_grid_slit_in_a_park(): labels = np.full((60, 100), PARK, dtype=np.uint8) band = box_mask(labels.shape, slice(28, 34), slice(None)) labels[band] = ROAD fill_nearest(labels, band, band) assert (labels == PARK).all() def test_thin_means_at_most_four_metres_thick(): assert is_thin(box_mask((40, 40), slice(10, 14), slice(5, 35))) assert not is_thin(box_mask((40, 40), slice(10, 15), slice(5, 35))) def test_straight_road_runs_through_a_text_band_in_the_same_width(): labels = city() labels[:, 140:160] = ROAD mask = box_mask(labels.shape, slice(80, 120), slice(60, 240)) scribble(labels, mask) result = rebuild(labels, mask) assert result.stats.connected == 1 assert (labels[80:120, 141:159] == ROAD).all() assert not (labels[80:120, 60:138] == ROAD).any() assert not (labels[80:120, 162:240] == ROAD).any() def test_a_slight_offset_is_bridged_smoothly(): labels = city() labels[:100, 130:150] = ROAD labels[100:, 138:158] = ROAD mask = box_mask(labels.shape, slice(80, 120), slice(60, 240)) scribble(labels, mask) result = rebuild(labels, mask) assert result.stats.connected == 1 roads, count = ndi.label(labels == ROAD) assert count == 1 # oben und unten hängen jetzt zusammen assert 16 <= (labels[100] == ROAD).sum() <= 24 # in der Mitte etwa straßenbreit def test_roads_across_visible_ground_are_not_joined(): labels = city() labels[90:110, :60] = ROAD labels[90:110, 240:] = ROAD mask = box_mask(labels.shape, slice(70, 130), slice(60, 140)) | box_mask(labels.shape, slice(70, 130), slice(180, 240)) mask |= box_mask(labels.shape, slice(70, 75), slice(140, 180)) # schmale Brücke: eine Maske scribble(labels, mask) result = rebuild(labels, mask) assert result.stats.connected == 0 assert result.stats.dead_ends == 2 assert (labels[90:110, 141:179] == STADT).all() # sichtbarer Boden dazwischen bleibt assert not (labels[mask] == ROAD).any() # Sackgassen werden nicht gemalt def test_a_corner_under_the_mask_has_no_overhang(): labels = city() labels[:80, 100:120] = ROAD labels[130:150, 181:] = ROAD mask = box_mask(labels.shape, slice(80, 180), slice(60, 181)) scribble(labels, mask) result = rebuild(labels, mask) assert result.stats.extended == 2 assert (labels[80:130, 102:118] == ROAD).all() assert (labels[132:148, 122:181] == ROAD).all() assert not (labels[153:180, 100:120] == ROAD).any() assert not (labels[130:150, 60:97] == ROAD).any() def test_a_side_street_joins_a_hidden_through_road(): labels = city() labels[:80, 140:160] = ROAD labels[130:150, :] = ROAD mask = box_mask(labels.shape, slice(80, 180), slice(60, 240)) scribble(labels, mask) result = rebuild(labels, mask) assert result.stats.connected == 1 and result.stats.extended == 1 assert (labels[80:131, 142:158] == ROAD).all() assert not (labels[153:180, 140:160] == ROAD).any() def test_a_road_clipped_on_one_side_keeps_its_full_width(): labels = city() labels[:, 140:160] = ROAD mask = box_mask(labels.shape, slice(80, 120), slice(150, 240)) scribble(labels, mask) rebuild(labels, mask) assert (labels[80:120, 140:159] == ROAD).all() assert not (labels[80:120, 162:240] == ROAD).any() def test_a_path_stays_a_path_and_the_street_wins_the_crossing(): labels = city() labels[:, 145:150] = PATH labels[95:115, :] = ROAD mask = box_mask(labels.shape, slice(60, 140), slice(100, 200)) scribble(labels, mask) rebuild(labels, mask) assert (labels[97:113, 145:150] == ROAD).all() assert (labels[62:90, 146:149] == PATH).all() assert (labels[120:138, 146:149] == PATH).all() def test_park_and_water_under_text_stay_what_they_are(): labels = city() labels[:, :150] = PARK labels[:, 250:] = WATER mask = box_mask(labels.shape, slice(80, 120), slice(100, 280)) scribble(labels, mask) rebuild(labels, mask) assert (labels[80:120, 100:149] == PARK).all() assert (labels[80:120, 151:249] == STADT).all() assert (labels[80:120, 251:280] == WATER).all() def test_edge_crumbs_are_no_fill_source(): labels = np.full((200, 300), PARK, dtype=np.uint8) labels[74:78, 150:154] = CLASSES["sand"] # 16 m² Kantenglättung mask = box_mask(labels.shape, slice(80, 120), slice(100, 200)) scribble(labels, mask) rebuild(labels, mask) assert (labels[mask] == PARK).all() def test_a_road_running_off_the_map_under_text_is_kept_to_the_edge(): labels = city() labels[:, 140:160] = ROAD mask = box_mask(labels.shape, slice(0, 30), slice(60, 240)) scribble(labels, mask) result = rebuild(labels, mask) assert result.stats.extended == 1 assert (labels[0:30, 142:158] == ROAD).all()