"""Side-by-side HiResCAM comparison across the 3 training regimes. Picks 4 representative images, runs HiResCAM with each of: - best_original.pth (raw baseline) - best_sam.pth (SAM bg-removal) - best_strongaug.pth (strong augmentation) Builds a 4x4 grid (rows = images, cols = original | 3 CAMs). """ import sys, os sys.path.insert(0, "/mnt/d/SpiceNet" if os.path.exists("/mnt/d/SpiceNet") else "D:/SpiceNet") import numpy as np import torch import cv2 import matplotlib matplotlib.use("Agg") import matplotlib.pyplot as plt from PIL import Image import albumentations as A from albumentations.pytorch import ToTensorV2 import config from src.model import load_checkpoint from src.gradcam import GradCAM from src.features import extract_all as _extract_all _RESIZE = int(config.IMG_SIZE * 256 / 224) _CROP = config.IMG_SIZE def model_view(img_path): img = np.array(Image.open(img_path).convert("RGB")) crop_tf = A.Compose([A.Resize(_RESIZE, _RESIZE), A.CenterCrop(_CROP, _CROP)]) display = crop_tf(image=img)["image"] norm_tf = A.Compose([A.Normalize(mean=config.IMG_MEAN, std=config.IMG_STD), ToTensorV2()]) tensor = norm_tf(image=display)["image"].unsqueeze(0) tex_np, col_np = _extract_all(img) tex = torch.from_numpy(tex_np).unsqueeze(0).float() col = torch.from_numpy(col_np).unsqueeze(0).float() return tensor, display, tex, col def compute_cam(model, tensor, tex, col, device, smooth_sigma=1.2): gcam = GradCAM(model, mode="fusion", algorithm="hirescam", smooth_sigma=smooth_sigma) tensor = tensor.to(device); tex = tex.to(device); col = col.to(device) cam, pred = gcam(tensor, tex=tex, col=col) gcam.remove() return cam, pred def overlay(display, cam, smooth_sigma): heat = cv2.resize(cam, (display.shape[1], display.shape[0]), interpolation=cv2.INTER_CUBIC) if smooth_sigma > 0: k = max(3, int(2 * round(3 * smooth_sigma) + 1)) heat = cv2.GaussianBlur(heat, (k, k), smooth_sigma) heat = heat - heat.min() if heat.max() > 0: heat = heat / heat.max() heat_u8 = cv2.applyColorMap(np.uint8(255 * heat), cv2.COLORMAP_JET) heat_u8 = cv2.cvtColor(heat_u8, cv2.COLOR_BGR2RGB) return cv2.addWeighted(display, 0.5, heat_u8, 0.5, 0) def main(): device = torch.device("cuda" if torch.cuda.is_available() else "cpu") # 4 representative images: powder, scattered seeds, single piece, painted plate samples = [ ("Spice_Spectrum/turmeric/turmeric_10.jpg", "turmeric"), ("Spice_Spectrum/cumin/cumin_50.jpg", "cumin"), ("Spice_Spectrum/cinnamon/cinnamon_100.jpg", "cinnamon"), ("Spice_Spectrum/saffron/saffron_200.jpg", "saffron"), ] pipelines = [ ("Raw baseline", "best_original.pth"), ("SAM bg-removal", "best_sam.pth"), ("Strong aug", "best_strongaug.pth"), ] fig, axes = plt.subplots(len(samples), 1 + len(pipelines), figsize=(3 * (1 + len(pipelines)), 3 * len(samples))) for row, (path, lbl) in enumerate(samples): tensor, display, tex, col = model_view(path) axes[row, 0].imshow(display) axes[row, 0].set_title("Input" if row == 0 else "", fontsize=10) axes[row, 0].set_ylabel(lbl, fontsize=10) axes[row, 0].set_xticks([]); axes[row, 0].set_yticks([]) for col_idx, (pname, ckpt_name) in enumerate(pipelines, start=1): ckpt_path = config.CHECKPOINT_DIR / ckpt_name if not ckpt_path.exists(): axes[row, col_idx].text(0.5, 0.5, f"missing: {ckpt_name}", ha="center", va="center") axes[row, col_idx].set_xticks([]); axes[row, col_idx].set_yticks([]) continue model, *_ = load_checkpoint(str(ckpt_path), device) cam, pred_idx = compute_cam(model, tensor, tex, col, device) ov = overlay(display, cam, smooth_sigma=1.2) axes[row, col_idx].imshow(ov) pred_name = config.CLASSES[pred_idx] color = "green" if pred_name == lbl else "red" axes[row, col_idx].set_title( f"{pname}\npred: {pred_name}" if row == 0 else f"pred: {pred_name}", fontsize=9, color=color, ) axes[row, col_idx].set_xticks([]); axes[row, col_idx].set_yticks([]) del model if device.type == "cuda": torch.cuda.empty_cache() plt.suptitle("HiResCAM comparison — same images, 3 training regimes", fontsize=13) plt.tight_layout() out = config.OUTPUT_DIR / "cam_comparison_3regimes.png" plt.savefig(out, dpi=150, bbox_inches="tight") plt.close() print(f"Saved -> {out}") if __name__ == "__main__": main()