This commit is contained in:
knso committed 2026-09-15 21:05:18 +02:00
1 parent 6a056262a8
commit a55dca5fb6
11 files changed
+132 -25

No files matched your search

+11 -3
View File
@@ -24,7 +24,7 @@ from skimage.morphology import disk
from .classify import PALETTE
from .coords import SLICE, chunk_for_world, chunk_name
from .inpaint import RebuildStats, fill_nearest, is_thin, rebuild
from .inpaint import RebuildStats, add_buildings, fill_nearest, is_thin, rebuild
KINDS = ("text_white", "text_red", "marker", "symbol", "street_name", "region")
MARGIN = {"text_white": 7, "text_red": 7, "marker": 3, "symbol": 4, "street_name": 1, "region": 0}
@@ -406,7 +406,7 @@ def remove_annotations(mosaic: np.ndarray, labels: np.ndarray, annotations: Anno
owners.setdefault(int(cid), entry)
touched.append(ids)
generated = []
generated, rebuilt = [], []
h, w = labels.shape
for cid, sl in enumerate(ndi.find_objects(components), start=1):
if sl is None:
@@ -419,9 +419,17 @@ def remove_annotations(mosaic: np.ndarray, labels: np.ndarray, annotations: Anno
continue
mask = np.zeros(labels.shape, dtype=bool)
mask[sl] = piece
result = rebuild(labels, mask, union)
result = rebuild(labels, mask, union, add=False)
owner = owners[cid]
owner.stats = result.stats if owner.stats is None else _merged(owner.stats, result.stats)
rebuilt.append((cid, sl))
# Ergänzt wird erst, wenn alle Masken aufgebaut sind: sonst zählten Buchstabenkonturen noch nicht
# aufgebauter Masken als Straße und teilten Blöcke, und das Ergebnis hinge von der Reihenfolge ab.
for cid, sl in rebuilt:
mask = np.zeros(labels.shape, dtype=bool)
mask[sl] = components[sl] == cid
result = add_buildings(labels, mask, union)
owners[cid].stats = _merged(owners[cid].stats, result.stats)
generated += [Polygon([(SLICE["x0"] + c, SLICE["y1"] - r) for r, c in corners])
for corners in result.buildings]
for entry, ids in zip(entries, touched):
+18 -4
View File
@@ -90,12 +90,14 @@ def is_thin(mask: np.ndarray) -> bool:
return float(ndi.distance_transform_edt(np.pad(mask, 1)).max()) <= THIN_DEPTH
def rebuild(labels: np.ndarray, mask: np.ndarray, blocked: np.ndarray | None = None) -> RebuildResult:
def rebuild(labels: np.ndarray, mask: np.ndarray, blocked: np.ndarray | None = None,
add: bool = True) -> RebuildResult:
"""Baut das Klassenbild unter einer zusammenhängenden Maske wieder auf.
blocked sind alle Pixel, die nicht als Quelle taugen — alle Beschriftungsmasken; ohne
Angabe nur diese Maske. Reihenfolge wie in der Spec: Straßen und Wege, Flächen,
Gebäude schließen, Gebäude ergänzen.
Gebäude schließen, Gebäude ergänzen. Mit add=False endet der Aufbau nach dem Schließen;
ergänzt wird dann über add_buildings, sobald alle Masken aufgebaut sind.
"""
blocked = mask if blocked is None else blocked
stats = RebuildStats()
@@ -107,6 +109,14 @@ def rebuild(labels: np.ndarray, mask: np.ndarray, blocked: np.ndarray | None = N
_stamp(L, M, painted, road, points, halves)
_fill_areas(L, M & ~painted, B)
_close_buildings(L, M, B, painted, stats)
corners = _add_buildings(labels, mask, blocked, stats) if add else []
return RebuildResult(stats, corners)
def add_buildings(labels: np.ndarray, mask: np.ndarray, blocked: np.ndarray | None = None) -> RebuildResult:
"""Ergänzt Gebäude unter einer schon aufgebauten Maske; die Statistik trägt nur added und note."""
blocked = mask if blocked is None else blocked
stats = RebuildStats()
corners = _add_buildings(labels, mask, blocked, stats)
return RebuildResult(stats, corners)
@@ -535,13 +545,17 @@ def _window(mask, margin):
def _rectangle(rows, cols) -> Polygon:
"""Gedrehtes Hüllrechteck um Pixel (Zeilen, Spalten) als Polygon in (Spalte, Zeile), mit Pixelkante."""
"""Gedrehtes Hüllrechteck um Pixel (Zeilen, Spalten) als Polygon in (Spalte, Zeile), mit Pixelkante.
Liegen die Pixel auf einer Linie, ist das Hüllrechteck eine Linie; eckige Enden machen daraus
wieder ein Rechteck mit vier Ecken statt einer Kapsel.
"""
points = MultiPoint(np.column_stack([cols, rows]).astype(float))
with warnings.catch_warnings():
# Siehe buildings.regularize(): harmlose GEOS-Warnung bei achsenparallelen Formen.
warnings.filterwarnings("ignore", message=r".*oriented_envelope.*", category=RuntimeWarning)
envelope = points.minimum_rotated_rectangle
return envelope.buffer(0.5, join_style="mitre")
return envelope.buffer(0.5, cap_style="square", join_style="mitre")
def _sides(rect: Polygon) -> tuple[float, float]: