pipeline/axo : extraction du Rhino, recalage sur la planche (1:640), maisons, arbres (positions réelles des blocs, couleurs de la planche), scène en mètres, sols depuis le PDF Illustrator vectoriel (calques pilotés), bulles de la page 15, planche de l'eau nettoyée. prototypes/axo-test : la page de test et son assembleur. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
78 lines
4.4 KiB
Python
78 lines
4.4 KiB
Python
import json, numpy as np, collections, math
|
|
from PIL import Image, ImageDraw
|
|
from shapely.geometry import Polygon, MultiPoint, LineString, Point
|
|
R = json.load(open("rhino.json")); T = json.load(open("transfo.json"))
|
|
s, x0, y1, dx, dy = T["s"], T["x0"], T["y1"], T["dx"], T["dy"]
|
|
f = lambda x, y: ((x - x0) * s + dx, (y1 - y) * s + dy)
|
|
W, H = 1654, 1170
|
|
inimg = lambda x, y: 0 <= f(x, y)[0] <= W and 0 <= f(x, y)[1] <= H
|
|
# 1. maisons existantes : rectangles de 3_ TRAITS VUS
|
|
cand = json.load(open("cand_maisons.json"))
|
|
exist = [Polygon(c["p"]) for c in cand if c["l"] == "3_ TRAITS VUS"]
|
|
# faîtage : ligne intérieure la plus longue de 3_ TRAITS VUS contenue dans la maison
|
|
lignes = [LineString(c["p"]) for c in R["courbes"] if c["l"] in ("3_ TRAITS VUS", "4_ TRAITS VUS FIINS") and not c["f"] and len(c["p"]) >= 2]
|
|
maisons = []
|
|
for P in exist:
|
|
inner = [l for l in lignes if P.buffer(0.3).contains(l) and l.length > 3]
|
|
mrr = list(P.minimum_rotated_rectangle.exterior.coords)[:4]
|
|
e1 = np.subtract(mrr[1], mrr[0]); e2 = np.subtract(mrr[2], mrr[1])
|
|
if inner:
|
|
l = max(inner, key=lambda l: l.length); a, b = l.coords[0], l.coords[-1]
|
|
ang = math.atan2(b[1] - a[1], b[0] - a[0])
|
|
else:
|
|
e = e1 if np.linalg.norm(e1) >= np.linalg.norm(e2) else e2; ang = math.atan2(e[1], e[0])
|
|
maisons.append({"type": "existante", "p": [list(map(lambda v: round(v, 2), q)) for q in mrr], "faitage": round(ang, 4)})
|
|
# 2. maisons transformées et grand bâtiment : grappes du calque A_ISOL _ COUPE
|
|
iso = [c for c in R["courbes"] if c["l"] == "A_ISOL _ COUPE"]
|
|
pts = [(p, i) for i, c in enumerate(iso) for p in c["p"]]
|
|
centres = [np.mean(np.array(c["p"]), axis=0) for c in iso]
|
|
# regroupement simple par distance (8 m)
|
|
groupes = []
|
|
for i, cc in enumerate(centres):
|
|
for g in groupes:
|
|
if min(np.linalg.norm(cc - centres[j]) for j in g) < 8: g.append(i); break
|
|
else: groupes.append([i])
|
|
# fusion itérative des groupes proches
|
|
chg = True
|
|
while chg:
|
|
chg = False
|
|
for a in range(len(groupes)):
|
|
for b in range(a + 1, len(groupes)):
|
|
if min(np.linalg.norm(centres[i] - centres[j]) for i in groupes[a] for j in groupes[b]) < 8:
|
|
groupes[a] += groupes[b]; del groupes[b]; chg = True; break
|
|
if chg: break
|
|
transfo = []
|
|
for g in groupes:
|
|
P = MultiPoint([tuple(p) for i in g for p in iso[i]["p"]]).minimum_rotated_rectangle
|
|
if P.area < 60: continue
|
|
cx, cy = P.centroid.x, P.centroid.y
|
|
if not inimg(cx, cy): continue
|
|
mrr = list(P.exterior.coords)[:4]
|
|
e1 = np.subtract(mrr[1], mrr[0]); e2 = np.subtract(mrr[2], mrr[1])
|
|
e = e1 if np.linalg.norm(e1) >= np.linalg.norm(e2) else e2
|
|
transfo.append({"type": "grand" if P.area > 300 else "transformee", "p": [[round(v, 2) for v in q] for q in mrr], "faitage": round(math.atan2(e[1], e[0]), 4), "aire": round(P.area, 1)})
|
|
print("existantes", len(maisons), "| transformées/grand", [(t["type"], t["aire"]) for t in transfo])
|
|
# 3. arbres : instances de blocs, couleur échantillonnée sur le dessin TOTAL
|
|
tot = np.asarray(Image.open("total100-1.png").convert("RGB")).astype(int)
|
|
arbres = []
|
|
rayons = {"Ishigami's fruit tree's 1": 1.6, "ishigami foret": 2.6, "B_Existantbuisson": 1.2, "A_Existant arbre": 2.4, "0": 2.0, "Ishigami's tree's 2": 2.4}
|
|
INST = json.load(open("instances_monde.json"))
|
|
for it in INST:
|
|
if it["l"] not in rayons or not inimg(it["x"], it["y"]): continue
|
|
rayons[it["l"]] = rayons[it["l"]]
|
|
px, py = f(it["x"], it["y"]); r = max(2, int(max(it["r"], 0.8) * s * 0.7))
|
|
x_, y_ = int(px), int(py)
|
|
zone = tot[max(0, y_ - r):y_ + r, max(0, x_ - r):x_ + r].reshape(-1, 3)
|
|
col = zone[(zone.sum(1) < 690) & (zone.max(1) - zone.min(1) > 18)]
|
|
c = col.mean(0) if len(col) > 3 else np.array([150, 185, 120])
|
|
arbres.append({"x": it["x"], "y": it["y"], "k": it["l"], "r": round(max(it["r"], 0.8), 2), "c": "#%02x%02x%02x" % tuple(int(v) for v in c)})
|
|
print("arbres", len(arbres), collections.Counter(a["k"] for a in arbres))
|
|
json.dump({"maisons": maisons, "speciaux": transfo, "arbres": arbres}, open("batiments.json", "w"))
|
|
# contrôle sur TOTAL
|
|
ov = Image.open("total100-1.png").convert("RGB"); d = ImageDraw.Draw(ov)
|
|
for m_ in maisons: d.polygon([f(*q) for q in m_["p"]], outline=(0, 60, 230), width=3)
|
|
for t in transfo: d.polygon([f(*q) for q in t["p"]], outline=(230, 0, 0), width=4)
|
|
for a in arbres[::3]:
|
|
px, py = f(a["x"], a["y"]); rr = a["r"] * s * 0.5; d.ellipse([px - rr, py - rr, px + rr, py + rr], outline=(0, 0, 0))
|
|
ov.save("controle-batiments.png")
|