From a86a4c960181bf1ec033e9b0281103b6425b999a Mon Sep 17 00:00:00 2001 From: Kenso Grimm Date: Wed, 16 Sep 2026 23:48:15 +0200 Subject: [PATCH] =?UTF-8?q?docs:=20f=C3=BCnf=20Befunde=20des=20Vorabscans?= =?UTF-8?q?=20im=20Plan=20berichtigt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signatur von road_graph, eine wirkungslose Hilfsfunktion, eine Zusicherung die der Vereinfachung widerspricht, ein doppelter Aufruf und ein sorted() über Wörterbücher ohne Schlüssel. Co-Authored-By: Claude Opus 5 --- docs/superpowers/plans/2026-09-16-strassen.md | 27 +++++++++---------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/docs/superpowers/plans/2026-09-16-strassen.md b/docs/superpowers/plans/2026-09-16-strassen.md index f2ea638..d06f895 100644 --- a/docs/superpowers/plans/2026-09-16-strassen.md +++ b/docs/superpowers/plans/2026-09-16-strassen.md @@ -388,12 +388,9 @@ def _junction_clusters(pixels: nx.Graph, shape) -> tuple[np.ndarray, list[tuple[ for k in range(1, count + 1): rows, cols = np.nonzero(junction & (labelled == k)) points.append(to_world([(rows.mean(), cols.mean())])[0]) - return labelled * junction_reach(labelled, junction), points - - -def junction_reach(labelled: np.ndarray, junction: np.ndarray) -> np.ndarray: - """Alle gewachsenen Pixel gehören zum Knoten, nicht nur die ursprünglichen.""" - return labelled > 0 + # labelled traegt die Knotennummer auf jedem *gewachsenen* Pixel. Genau das braucht _attach(): + # Das Ende einer Kette liegt neben einem gewachsenen Pixel, nicht neben einem Kreuzungspixel. + return labelled, points def _chain(sub: nx.Graph) -> list[tuple[int, int]]: @@ -420,7 +417,7 @@ def _attach(graph: Graph, pixels: nx.Graph, clusters: np.ndarray, pixel) -> int: Ausführen: `cd tools/extract && PYTHONUTF8=1 uv run pytest tests/test_roads_graph.py -q` Erwartet: 5 bestanden. -Bleibt etwas rot, ist der wahrscheinlichste Grund `_junction_clusters`: `clusters` muss für **jedes** gewachsene Pixel die Knotennummer tragen, damit `_attach` das Ende einer Kette einem Knoten zuordnen kann. Die Rückgabe `labelled * junction_reach(...)` ist genau dafür da — `labelled` allein ist außerhalb der gewachsenen Bereiche 0, und das ist gewollt. +Bleibt etwas rot, ist der wahrscheinlichste Grund `_junction_clusters`: `clusters` muss für **jedes** gewachsene Pixel die Knotennummer tragen, damit `_attach` das Ende einer Kette einem Knoten zuordnen kann. `ndi.label` auf der gewachsenen Maske leistet genau das; außerhalb der gewachsenen Bereiche steht 0, und das ist gewollt. - [ ] **Schritt 6: Volle Suite und Commit** @@ -457,7 +454,9 @@ dort ein. - `CLASS_WIDTH = {"gasse": 6.0, "wohnstrasse": 12.0, "hauptstrasse": 18.0, "boulevard": 24.0}` - `clean(graph: Graph) -> Graph` - `measure_widths(graph: Graph, distance: np.ndarray) -> Graph` (setzt `width` und `klass` je Kante) - - `road_graph(labels: np.ndarray) -> Graph` — der Aufruf für die Pipeline: Skelett, Graph, säubern, messen + - `road_graph(labels: np.ndarray, distance: np.ndarray) -> Graph` — der Aufruf für die Pipeline: + Skelett, Graph, säubern, Breite und Klasse. Die Distanztransformation kommt von außen, weil + `run.py` sie auch für die Palmen braucht. - [ ] **Schritt 1: Den fehlschlagenden Test schreiben** @@ -508,7 +507,9 @@ def test_knoten_mit_nur_zwei_kanten_verschwindet(): Edge(1, 2, gerade(punkt(50, 0), punkt(100, 0)))]) out = clean(graph) assert len(out.edges) == 1 - assert out.edges[0].points == [punkt(0, 0), punkt(50, 0), punkt(100, 0)] + # clean() vereinfacht mit 1,0 m, und der Zwischenpunkt liegt genau auf der Verbindungslinie - + # Douglas-Peucker wirft ihn weg. Geprüft wird das Verketten, nicht die Zahl der Stützpunkte. + assert out.edges[0].points == [punkt(0, 0), punkt(100, 0)] def test_breite_ignoriert_die_kreuzungsenden(): @@ -1193,8 +1194,8 @@ def _sidewalk(carriage, corridor): def _kerb_lines(graph: Graph, carriage, bridges) -> list[LineString]: """Die Fahrbahnkante, ohne die Stücke an Gassen und auf Brücken.""" - ohne = [ribbon(graph, k).buffer(0.2) for k, edge in enumerate(graph.edges) - if not SECTION[edge.klass].kerb and ribbon(graph, k) is not None] + ohne = [band.buffer(0.2) for k, edge in enumerate(graph.edges) + if not SECTION[edge.klass].kerb and (band := ribbon(graph, k)) is not None] ohne += [b.buffer(0.2) for b in bridges] rand = carriage.boundary if ohne: @@ -1690,7 +1691,7 @@ def chunk_payload(i, j, *, land, surfaces, carriageway, curb, sidewalk, markings "carriageway": sorted(({"poly": _rings(p, i, j), "klasse": k} for p, k in carriageway), key=lambda c: (c["poly"], c["klasse"])), "curb": sorted(_line(l, i, j) for l in curb), - "sidewalk": sorted({"poly": _rings(p, i, j)} for p in sidewalk), + "sidewalk": sorted(({"poly": _rings(p, i, j)} for p in sidewalk), key=lambda s: s["poly"]), "markings": sorted(({"poly": _rings(p, i, j), "art": a} for p, a in markings), key=lambda m: (m["poly"], m["art"])), "lots": sorted(({"poly": _rings(p, i, j)} for p in lots), key=lambda l: l["poly"]), @@ -1706,8 +1707,6 @@ def chunk_payload(i, j, *, land, surfaces, carriageway, curb, sidewalk, markings } ``` -`sorted({"poly": ...} for ...)` ohne `key` schlägt bei Wörterbüchern fehl — bei `sidewalk` deshalb `key=lambda s: s["poly"]` ergänzen, wie bei `lots`. - `write_debug_png` liest `payload["roads"]`; dort auf `payload["carriageway"]` umstellen, Farbe unverändert. - [ ] **Schritt 4: Test laufen lassen**