You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
85 lines
3.2 KiB
85 lines
3.2 KiB
#!/usr/bin/env python3
|
|
import argparse
|
|
import pandas as pd
|
|
import matplotlib.pyplot as plt
|
|
import numpy as np
|
|
from pathlib import Path
|
|
|
|
def load_unique_faces(wide_csv: str) -> pd.DataFrame:
|
|
df = pd.read_csv(wide_csv)
|
|
# Keep the first occurrence per face_id (faces may be listed from multiple tets)
|
|
df = df.sort_values(["face_id", "tet_id"]).drop_duplicates(subset=["face_id"], keep="first")
|
|
return df
|
|
|
|
def plot_faces(df: pd.DataFrame, out_png: str, view="xy", linewidth=1.5, annotate=True):
|
|
# Choose which plane to show
|
|
axes = {"xy": ("_x","_y"), "xz": ("_x","_z"), "yz": ("_y","_z")}
|
|
if view not in axes:
|
|
raise ValueError("view must be one of: xy, xz, yz")
|
|
axa, axb = axes[view]
|
|
|
|
fig, ax = plt.subplots(figsize=(10, 10)) # larger square figure
|
|
|
|
all_x, all_y = [], []
|
|
|
|
# Plot each face as a closed triangle
|
|
for _, r in df.iterrows():
|
|
x = [r["face0"+axa], r["face1"+axa], r["face2"+axa], r["face0"+axa]]
|
|
y = [r["face0"+axb], r["face1"+axb], r["face2"+axb], r["face0"+axb]]
|
|
ax.plot(x, y, "-k", lw=linewidth)
|
|
|
|
all_x.extend(x)
|
|
all_y.extend(y)
|
|
|
|
if annotate:
|
|
cx = (r["face0"+axa] + r["face1"+axa] + r["face2"+axa]) / 3.0
|
|
cy = (r["face0"+axb] + r["face1"+axb] + r["face2"+axb]) / 3.0
|
|
ax.annotate(f'{int(r["face_id"])}', (cx, cy),
|
|
textcoords="offset points", xytext=(0, 0),
|
|
ha="center", va="center", fontsize=8,
|
|
bbox=dict(boxstyle="round,pad=0.2", fc="yellow", ec="black", lw=0.5))
|
|
|
|
# Square bounding box
|
|
if all_x and all_y:
|
|
min_x, max_x = min(all_x), max(all_x)
|
|
min_y, max_y = min(all_y), max(all_y)
|
|
span = max(max_x - min_x, max_y - min_y)
|
|
cx = 0.5 * (max_x + min_x)
|
|
cy = 0.5 * (max_y + min_y)
|
|
ax.set_xlim(cx - span/2, cx + span/2)
|
|
ax.set_ylim(cy - span/2, cy + span/2)
|
|
|
|
ax.set_aspect("equal", adjustable="box")
|
|
ax.set_xlabel(view[0])
|
|
ax.set_ylabel(view[1])
|
|
ax.grid(True, ls="--", alpha=0.4)
|
|
ax.set_title(f"Faces with face_id annotations ({view}-view)")
|
|
|
|
# Expand to fill entire figure
|
|
plt.subplots_adjust(left=0, right=1, top=1, bottom=0)
|
|
|
|
out_png = Path(out_png)
|
|
out_png.parent.mkdir(parents=True, exist_ok=True)
|
|
fig.savefig(out_png, dpi=220, bbox_inches="tight")
|
|
plt.close(fig)
|
|
print(f"Saved {out_png}")
|
|
|
|
def main():
|
|
ap = argparse.ArgumentParser(description="Plot faces from parsed CUDA debug CSV and annotate face_id.")
|
|
ap.add_argument("--wide-csv", required=True, help="Path to *_wide.csv produced by the parser.")
|
|
ap.add_argument("--out", default="faces_annotated.png", help="Output PNG path.")
|
|
ap.add_argument("--view", choices=["xy","xz","yz"], default="xy", help="Projection plane to plot.")
|
|
ap.add_argument("--linewidth", type=float, default=1.5, help="Triangle edge width.")
|
|
ap.add_argument("--no-annotate", action="store_true", help="Disable face_id annotations.")
|
|
args = ap.parse_args()
|
|
|
|
df = load_unique_faces(args.wide_csv)
|
|
if df.empty:
|
|
print("No faces found in CSV.")
|
|
return
|
|
|
|
plot_faces(df, args.out, view=args.view, linewidth=args.linewidth, annotate=not args.no_annotate)
|
|
|
|
if __name__ == "__main__":
|
|
main()
|
|
|
|
|