Download text2layout/plot_boxplot.py from diing/AURAD: direct link, hf CLI and curl.
- Browser
- Download file 7.77 kB
-
https://huggingface.co/diing/AURAD/resolve/main/text2layout/plot_boxplot.py
- Command line
-
hf download hf://diing/AURAD/text2layout/plot_boxplot.py
-
curl -L -o plot_boxplot.py https://huggingface.co/diing/AURAD/resolve/main/text2layout/plot_boxplot.py
7.77 kB
| """ | |
| 逐样本指标 CSV + Gen vs GT 带统计显著性的箱线图 | |
| ================================================ | |
| 输入: 与 eval_mask_population.py 一致 | |
| --jsonl, --gen_root, --gt_root | |
| 输出: | |
| per_sample_shape.csv 每个 mask 的形态学描述子 (gen + gt 各一行) | |
| boxplot_shape.png 2x2 箱线图: area / aspect_ratio / circularity / solidity | |
| 每个子图按疾病分面, gen vs GT 两个箱, 带 Mann-Whitney U 显著性星号 | |
| 用法: | |
| python plot_shape_boxplot.py \\ | |
| --jsonl /path/test.json \\ | |
| --gen_root /home/jovyan/AURAD_infer/mask/det-test-cp9000 \\ | |
| --gt_root /home/jovyan/AURAD_dataset \\ | |
| --out_dir ./boxplot_out | |
| """ | |
| import argparse | |
| import json | |
| from pathlib import Path | |
| import matplotlib.pyplot as plt | |
| import numpy as np | |
| import pandas as pd | |
| from PIL import Image | |
| from scipy.ndimage import binary_erosion | |
| from scipy.spatial import ConvexHull | |
| from scipy.stats import mannwhitneyu | |
| # ====================================================================== | |
| def load_mask(path): | |
| arr = np.array(Image.open(path).convert("L")) | |
| return (arr > 127).astype(np.uint8) | |
| def shape_descriptors(mask): | |
| if mask.sum() == 0: | |
| return {k: np.nan for k in | |
| ["area", "aspect_ratio", "circularity", "solidity"]} | |
| ys, xs = np.where(mask > 0) | |
| area = int(len(ys)) | |
| h = ys.max() - ys.min() + 1 | |
| w = xs.max() - xs.min() + 1 | |
| aspect = h / w if w > 0 else np.nan | |
| eroded = binary_erosion(mask) | |
| perimeter = int((mask & ~eroded).sum()) | |
| circ = 4 * np.pi * area / (perimeter ** 2) if perimeter > 0 else np.nan | |
| try: | |
| pts = np.column_stack([xs, ys]) | |
| if len(pts) >= 3: | |
| hull = ConvexHull(pts) | |
| solidity = area / hull.volume | |
| else: | |
| solidity = 1.0 | |
| except Exception: | |
| solidity = np.nan | |
| return {"area": area, "aspect_ratio": aspect, | |
| "circularity": circ, "solidity": solidity} | |
| # ====================================================================== | |
| def collect_descriptors(jsonl, gen_root, gt_root): | |
| gen_root, gt_root = Path(gen_root), Path(gt_root) | |
| items = [json.loads(l) for l in open(jsonl) if l.strip()] | |
| rows = [] | |
| n_skip = 0 | |
| for it in items: | |
| gen_p = gen_root / it["attn_list"][0][1] | |
| gt_p = gt_root / it["mask"] | |
| if not gen_p.is_file() or not gt_p.is_file(): | |
| n_skip += 1 | |
| continue | |
| img_id = Path(it["file_name"]).parent.name | |
| disease = it["attn_list"][0][0] | |
| for side, path in [("gen", gen_p), ("gt", gt_p)]: | |
| d = shape_descriptors(load_mask(path)) | |
| d.update({"id": img_id, "disease": disease, "source": side}) | |
| rows.append(d) | |
| print(f"Computed descriptors for {len(rows)//2} mask pairs (skipped {n_skip})") | |
| return pd.DataFrame(rows) | |
| # ====================================================================== | |
| def sig_label(p): | |
| if np.isnan(p): | |
| return "" | |
| if p < 1e-4: | |
| return "****" | |
| if p < 1e-3: | |
| return "***" | |
| if p < 1e-2: | |
| return "**" | |
| if p < 5e-2: | |
| return "*" | |
| return "ns" | |
| def plot_boxplot(df, out_path): | |
| metrics = ["area", "aspect_ratio", "circularity", "solidity"] | |
| titles = { | |
| "area": "Area (log10)", | |
| "aspect_ratio": "Aspect Ratio (h/w)", | |
| "circularity": "Circularity", | |
| "solidity": "Solidity", | |
| } | |
| diseases = sorted(df["disease"].unique()) | |
| # 4 行 1 列, 每行一个指标横向展开所有疾病 | |
| fig, axes = plt.subplots(4, 1, figsize=(max(len(diseases)*1.0 + 2, 12), 16)) | |
| for ax, metric in zip(axes, metrics): | |
| gen_data, gt_data, labels, p_values = [], [], [], [] | |
| for dis in diseases: | |
| g = df[(df.disease == dis) & (df.source == "gen")][metric].dropna().values | |
| t = df[(df.disease == dis) & (df.source == "gt")][metric].dropna().values | |
| if metric == "area": | |
| g = np.log10(g + 1) | |
| t = np.log10(t + 1) | |
| gen_data.append(g) | |
| gt_data.append(t) | |
| labels.append(f"{dis}\n(n={len(t)})") | |
| if len(g) >= 3 and len(t) >= 3: | |
| _, p = mannwhitneyu(g, t, alternative="two-sided") | |
| else: | |
| p = np.nan | |
| p_values.append(p) | |
| x = np.arange(len(diseases)) | |
| width = 0.35 | |
| bp_gen = ax.boxplot(gen_data, positions=x - width/2, widths=width*0.9, | |
| patch_artist=True, showfliers=False, | |
| medianprops=dict(color="black", linewidth=1.5)) | |
| bp_gt = ax.boxplot(gt_data, positions=x + width/2, widths=width*0.9, | |
| patch_artist=True, showfliers=False, | |
| medianprops=dict(color="black", linewidth=1.5)) | |
| for b in bp_gen["boxes"]: | |
| b.set_facecolor("#E89A6B"); b.set_edgecolor("#a66033"); b.set_alpha(0.85) | |
| for b in bp_gt["boxes"]: | |
| b.set_facecolor("#4DBBA1"); b.set_edgecolor("#2c7a66"); b.set_alpha(0.85) | |
| # 显著性星号 | |
| for i, p in enumerate(p_values): | |
| label = sig_label(p) | |
| if not label: | |
| continue | |
| all_vals = np.concatenate([gen_data[i], gt_data[i]]) if \ | |
| (len(gen_data[i]) and len(gt_data[i])) else np.array([0]) | |
| ytop = np.percentile(all_vals, 95) if len(all_vals) > 5 else ( | |
| all_vals.max() if len(all_vals) else 0) | |
| yrange = ax.get_ylim() | |
| yoff = (yrange[1] - yrange[0]) * 0.02 if yrange[1] > yrange[0] else 0.02 | |
| color = "#888" if label == "ns" else "black" | |
| fs = 9 if label == "ns" else 12 | |
| ax.text(i, ytop + yoff, label, ha="center", va="bottom", | |
| fontsize=fs, color=color, weight="bold") | |
| ax.set_xticks(x) | |
| ax.set_xticklabels(labels, rotation=30, ha="right", fontsize=9) | |
| ax.set_ylabel(titles[metric], fontsize=12, weight="bold") | |
| ax.grid(axis="y", linestyle="--", alpha=0.4) | |
| ax.set_axisbelow(True) | |
| from matplotlib.patches import Patch | |
| handles = [ | |
| Patch(facecolor="#E89A6B", edgecolor="#a66033", label="Generated"), | |
| Patch(facecolor="#4DBBA1", edgecolor="#2c7a66", label="Real (GT)"), | |
| ] | |
| fig.legend(handles=handles, loc="upper center", bbox_to_anchor=(0.5, 0.995), | |
| ncol=2, fontsize=14, frameon=False) | |
| fig.text(0.5, 0.005, | |
| "Mann-Whitney U: **** p<1e-4 *** p<1e-3 ** p<1e-2 * p<5e-2 ns p>=5e-2", | |
| ha="center", fontsize=10, color="#555") | |
| fig.tight_layout(rect=[0, 0.015, 1, 0.97]) | |
| fig.savefig(out_path, dpi=180, bbox_inches="tight") | |
| plt.close(fig) | |
| print(f"Boxplot saved: {out_path}") | |
| # ====================================================================== | |
| def main(): | |
| ap = argparse.ArgumentParser() | |
| ap.add_argument("--jsonl", required=True) | |
| ap.add_argument("--gen_root", required=True) | |
| ap.add_argument("--gt_root", required=True) | |
| ap.add_argument("--out_dir", default="./boxplot_out") | |
| args = ap.parse_args() | |
| out_dir = Path(args.out_dir); out_dir.mkdir(parents=True, exist_ok=True) | |
| df = collect_descriptors(args.jsonl, args.gen_root, args.gt_root) | |
| csv_path = out_dir / "per_sample_shape.csv" | |
| df.to_csv(csv_path, index=False) | |
| print(f"Saved per-sample CSV: {csv_path} ({len(df)} rows)") | |
| plot_boxplot(df, out_dir / "boxplot_shape.png") | |
| if __name__ == "__main__": | |
| main() | |
| """ | |
| python plot_boxplot.py \ | |
| --jsonl /home/jovyan/AURAD_infer/mask/det-test-cp9000/test_prompt_text2layout_single.json \ | |
| --gen_root /home/jovyan/AURAD_infer/mask/det-test-cp9000 \ | |
| --gt_root /home/jovyan/AURAD_dataset \ | |
| --out_dir ./mask_eval | |
| """ |