| |
| """Qualitative failure cases: real in-the-wild images that the studio-trained |
| SpiceFusionNet misclassifies, captioned with the (wrong) predicted class. The |
| emotional core of a shortcut-learning result. Saves outputs/failure_cases.{png,pdf}. |
| """ |
| import sys, os |
| from pathlib import Path |
| import numpy as np |
| import torch |
| from torch.utils.data import DataLoader |
| import matplotlib |
| matplotlib.use("Agg") |
| import matplotlib.pyplot as plt |
| import matplotlib.image as mpimg |
|
|
| _base = "/mnt/d/SpiceNet" if os.path.exists("/mnt/d/SpiceNet") else "D:/SpiceNet" |
| sys.path.insert(0, _base) |
| from src.model import SpiceFusionNet |
| from src.dataset import load_manifest_splits, SpiceDataset, get_val_transform |
|
|
| ROOT = Path(_base) |
| CKPT = ROOT / "outputs/checkpoints/overlap_indian_s42/p1_best.pth" |
| SS_M = str(ROOT / "outputs/manifest_overlap_ss.json") |
| PRIORITY = ["coriander", "black_pepper", "green_cardamom", "cumin", "cloves", "nutmeg"] |
|
|
|
|
| def nice(c): return c.replace("_", " ") |
|
|
|
|
| def main(): |
| device = torch.device("cuda" if torch.cuda.is_available() else "cpu") |
| splits, classes = load_manifest_splits(SS_M) |
| paths, labels = splits["test"] |
| labels = np.array(labels) |
| model = SpiceFusionNet(num_classes=len(classes)).to(device) |
| ck = torch.load(CKPT, map_location=device, weights_only=False) |
| model.load_state_dict(ck["model_state"]); model.eval() |
|
|
| ds = SpiceDataset(paths, labels.tolist(), get_val_transform(), multimodal=False) |
| dl = DataLoader(ds, batch_size=64, num_workers=2) |
| preds, conf = [], [] |
| with torch.no_grad(): |
| for imgs, tex, col, y in dl: |
| p = torch.softmax(model.forward_image(imgs.to(device)), 1) |
| c, k = p.max(1) |
| preds.extend(k.cpu().tolist()); conf.extend(c.cpu().tolist()) |
| preds, conf = np.array(preds), np.array(conf) |
| cls_idx = {c: i for i, c in enumerate(classes)} |
|
|
| |
| picks = [] |
| for c in PRIORITY: |
| if c not in cls_idx: |
| continue |
| ti = cls_idx[c] |
| cand = np.where((labels == ti) & (preds != ti))[0] |
| if len(cand): |
| picks.append(cand[np.argmax(conf[cand])]) |
| if len(picks) == 6: |
| break |
|
|
| fig, axes = plt.subplots(2, 3, figsize=(9, 6.4)) |
| for ax, i in zip(axes.ravel(), picks): |
| try: |
| ax.imshow(mpimg.imread(paths[i])) |
| except Exception: |
| ax.set_facecolor("#eee") |
| ax.set_xticks([]); ax.set_yticks([]) |
| ax.set_title(f"true: {nice(classes[labels[i]])}\npredicted: {nice(classes[preds[i]])}" |
| f" ({conf[i]*100:.0f}%)", fontsize=9) |
| for s in ax.spines.values(): |
| s.set_edgecolor("#c0504d"); s.set_linewidth(1.6) |
| for ax in axes.ravel()[len(picks):]: |
| ax.axis("off") |
| fig.suptitle("In-the-wild images the studio-trained model gets wrong", |
| fontsize=11, y=0.99) |
| fig.tight_layout(rect=[0, 0, 1, 0.97]) |
| out = ROOT / "outputs" / "failure_cases" |
| fig.savefig(str(out) + ".png", dpi=300, bbox_inches="tight") |
| fig.savefig(str(out) + ".pdf", bbox_inches="tight") |
| print("saved", out, "| picks:", [f"{nice(classes[labels[i]])}->{nice(classes[preds[i]])}" for i in picks]) |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|