Download evaluate_attention.py from kabiriitb/rockglacierNET: direct link, hf CLI and curl.
- Browser
- Download file 16.6 kB
-
https://huggingface.co/kabiriitb/rockglacierNET/resolve/main/evaluate_attention.py
- Command line
-
hf download hf://kabiriitb/rockglacierNET/evaluate_attention.py
-
curl -L -o evaluate_attention.py https://huggingface.co/kabiriitb/rockglacierNET/resolve/main/evaluate_attention.py
16.6 kB
| """ | |
| Evaluation script β Attention U-Net. | |
| Loads each test GeoTIFF, normalizes and pads/crops to 128Γ128 (same as | |
| training), predicts, then evaluates only on the VALID (non-padded) pixels | |
| so zero-padding does not inflate scores. | |
| All outputs go directly into outputs/ β no subdirectories. | |
| Outputs | |
| ------- | |
| outputs/ | |
| attn_evaluation_metrics.csv β global threshold sweep 0.30β0.70 | |
| attn_per_image_metrics.csv β per-image TP/FP/FN/TN, IoU, F1, etc. | |
| attn_confusion_matrix.csv β aggregate CM at best IoU threshold | |
| attn_evaluation_summary.txt β human-readable summary | |
| attn_pred_*.png β RGB / GT / prediction side-by-side | |
| """ | |
| import os | |
| import glob | |
| import numpy as np | |
| np.Inf = np.inf | |
| import pandas as pd | |
| import rasterio | |
| import tensorflow as tf | |
| from sklearn.metrics import confusion_matrix | |
| from data_generator import GLOBAL_MEAN, GLOBAL_STD | |
| from models.attention_unet import build_attention_unet | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Config | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| TEST_FEAT = "dataset/test/features" | |
| TEST_MASK = "dataset/test/masks" | |
| CKPT_PATH = "outputs/checkpoints/attention_unet_best.weights.h5" | |
| OUT_DIR = "outputs" | |
| INPUT_SHAPE = (128, 128, 12) | |
| TARGET = INPUT_SHAPE[:2] | |
| BATCH_SIZE = 8 | |
| THRESHOLDS = [0.30, 0.35, 0.40, 0.45, 0.50, 0.55, 0.60, 0.65, 0.70] | |
| os.makedirs(OUT_DIR, exist_ok=True) | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Loss / metric stubs (needed for model.compile) | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def dice_loss(y_true, y_pred): | |
| smooth = 1e-6 | |
| yt = tf.cast(y_true, tf.float32) | |
| i = tf.reduce_sum(yt * y_pred, axis=[1, 2, 3]) | |
| u = tf.reduce_sum(yt, axis=[1, 2, 3]) + tf.reduce_sum(y_pred, axis=[1, 2, 3]) | |
| return 1.0 - tf.reduce_mean((2.0 * i + smooth) / (u + smooth)) | |
| def focal_loss(alpha=0.75, gamma=2.0): | |
| def _l(y_true, y_pred): | |
| yt = tf.cast(y_true, tf.float32) | |
| yp = tf.clip_by_value(y_pred, tf.keras.backend.epsilon(), 1-tf.keras.backend.epsilon()) | |
| at = yt*alpha + (1-yt)*(1-alpha) | |
| pt = yt*yp + (1-yt)*(1-yp) | |
| return tf.reduce_mean(-at * tf.pow(1-pt, gamma) * tf.math.log(pt)) | |
| return _l | |
| def combined_loss(y_true, y_pred): | |
| return focal_loss()(y_true, y_pred)*2.0 + dice_loss(y_true, y_pred) | |
| def dice_coef(y_true, y_pred): | |
| yp = tf.cast(y_pred > 0.5, tf.float32); yt = tf.cast(y_true, tf.float32) | |
| return (2*tf.reduce_sum(yt*yp)+1e-6)/(tf.reduce_sum(yt)+tf.reduce_sum(yp)+1e-6) | |
| def precision_m(y_true, y_pred): | |
| yp = tf.cast(y_pred > 0.5, tf.float32); yt = tf.cast(y_true, tf.float32) | |
| tp = tf.reduce_sum(yt*yp); fp = tf.reduce_sum(yp*(1-yt)) | |
| return (tp+1e-6)/(tp+fp+1e-6) | |
| def recall_m(y_true, y_pred): | |
| yp = tf.cast(y_pred > 0.5, tf.float32); yt = tf.cast(y_true, tf.float32) | |
| tp = tf.reduce_sum(yt*yp); fn = tf.reduce_sum(yt*(1-yp)) | |
| return (tp+1e-6)/(tp+fn+1e-6) | |
| def f1_m(y_true, y_pred): | |
| p = precision_m(y_true, y_pred); r = recall_m(y_true, y_pred) | |
| return (2*p*r+1e-6)/(p+r+1e-6) | |
| def iou_m(y_true, y_pred): | |
| yp = tf.cast(y_pred > 0.5, tf.float32); yt = tf.cast(y_true, tf.float32) | |
| i = tf.reduce_sum(yt*yp); u = tf.reduce_sum(yt)+tf.reduce_sum(yp)-i | |
| return (i+1e-6)/(u+1e-6) | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Image preprocessing (identical to training β no data leakage) | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def load_and_prep(f_path, m_path): | |
| with rasterio.open(f_path) as s: | |
| feat = np.moveaxis(s.read(), 0, -1).astype(np.float32) | |
| with rasterio.open(m_path) as s: | |
| mask = np.expand_dims(s.read(1), -1).astype(np.float32) | |
| H, W = feat.shape[:2] | |
| feat = np.where((feat == -9999) | ~np.isfinite(feat), 0, feat) | |
| feat = (feat - GLOBAL_MEAN) / (GLOBAL_STD + 1e-7) | |
| th, tw = TARGET | |
| pad_h, pad_w = max(0, th - H), max(0, tw - W) | |
| if pad_h or pad_w: | |
| feat = np.pad(feat, ((0, pad_h), (0, pad_w), (0, 0))) | |
| mask = np.pad(mask, ((0, pad_h), (0, pad_w), (0, 0))) | |
| h, w = feat.shape[:2] | |
| if h > th or w > tw: | |
| hs = (h - th) // 2 if h > th else 0 | |
| ws = (w - tw) // 2 if w > tw else 0 | |
| feat = feat[hs:hs+th, ws:ws+tw, :] | |
| mask = mask[hs:hs+th, ws:ws+tw, :] | |
| valid_h = min(H, th) | |
| valid_w = min(W, tw) | |
| padded = pad_h > 0 or pad_w > 0 | |
| cropped = H > th or W > tw | |
| return feat, mask, H, W, valid_h, valid_w, padded, cropped | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Build + load weights | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| print("Building Attention U-Netβ¦") | |
| model = build_attention_unet(input_shape=INPUT_SHAPE) | |
| model.compile(loss=combined_loss, | |
| metrics=['accuracy', dice_coef, precision_m, recall_m, f1_m, iou_m]) | |
| if not os.path.exists(CKPT_PATH): | |
| raise FileNotFoundError( | |
| f"Checkpoint not found: {CKPT_PATH}\n" | |
| "Run: conda run -n brats python train_attention.py") | |
| model.load_weights(CKPT_PATH) | |
| print("Weights loaded.") | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Load all test images | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| feat_files = sorted(glob.glob(os.path.join(TEST_FEAT, 'feature_*.tif'))) | |
| mask_files = sorted(glob.glob(os.path.join(TEST_MASK, 'mask_*.tif'))) | |
| assert len(feat_files) > 0, f"No test images found in {TEST_FEAT}" | |
| assert len(feat_files) == len(mask_files) | |
| print(f"\nLoading {len(feat_files)} test imagesβ¦") | |
| X, Y, meta, ids = [], [], [], [] | |
| for fp, mp in zip(feat_files, mask_files): | |
| feat, mask, H, W, vh, vw, padded, cropped = load_and_prep(fp, mp) | |
| X.append(feat); Y.append(mask) | |
| meta.append((H, W, vh, vw, padded, cropped)) | |
| ids.append(os.path.basename(fp).replace('feature_', '').replace('.tif', '')) | |
| X = np.array(X, dtype=np.float32) | |
| Y = np.array(Y, dtype=np.float32) | |
| print("Predictingβ¦") | |
| Y_prob = model.predict(X, batch_size=BATCH_SIZE, verbose=1) | |
| # Keras evaluate at t=0.5 | |
| print("\nmodel.evaluate (t=0.5):") | |
| eval_vals = model.evaluate(X, Y, batch_size=BATCH_SIZE, verbose=1) | |
| eval_names = model.metrics_names | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Helper | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def cm_metrics(tp, fp, fn, tn): | |
| prec = tp / (tp + fp + 1e-9) | |
| rec = tp / (tp + fn + 1e-9) | |
| f1 = 2 * prec * rec / (prec + rec + 1e-9) | |
| iou = tp / (tp + fp + fn + 1e-9) | |
| dice = 2 * tp / (2*tp + fp + fn + 1e-9) | |
| acc = (tp + tn) / (tp + tn + fp + fn + 1e-9) | |
| spec = tn / (tn + fp + 1e-9) | |
| return prec, rec, f1, iou, dice, acc, spec | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Per-image metrics @ t=0.5 (valid region only β no padding pixels) | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| all_true, all_prob = [], [] | |
| per_image = [] | |
| for i, iid in enumerate(ids): | |
| H, W, vh, vw, padded, cropped = meta[i] | |
| yt = Y[i, :vh, :vw, 0].flatten().astype(np.int32) | |
| yprob = Y_prob[i,:vh, :vw, 0].flatten() | |
| all_true.append(yt); all_prob.append(yprob) | |
| yp = (yprob >= 0.5).astype(np.int32) | |
| tn, fp, fn, tp = confusion_matrix(yt, yp, labels=[0,1]).ravel() | |
| prec, rec, f1, iou, dice, acc, spec = cm_metrics(tp, fp, fn, tn) | |
| per_image.append({ | |
| 'image_id': iid, | |
| 'orig_H': H, 'orig_W': W, | |
| 'valid_px': int(yt.size), | |
| 'was_padded': bool(padded), | |
| 'was_cropped': bool(cropped), | |
| 'glacier_px_gt': int(yt.sum()), | |
| 'glacier_frac_gt': round(float(yt.mean()), 4), | |
| 'glacier_px_pred': int(yp.sum()), | |
| 'TP': int(tp), 'FP': int(fp), 'FN': int(fn), 'TN': int(tn), | |
| 'precision': round(prec, 4), | |
| 'recall': round(rec, 4), | |
| 'f1_score': round(f1, 4), | |
| 'iou': round(iou, 4), | |
| 'dice': round(dice, 4), | |
| 'accuracy': round(acc, 4), | |
| 'specificity':round(spec, 4), | |
| }) | |
| per_img_df = pd.DataFrame(per_image).sort_values('iou') | |
| per_img_df.to_csv(os.path.join(OUT_DIR, 'attn_per_image_metrics.csv'), index=False) | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Global threshold sweep | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| y_true_all = np.concatenate(all_true) | |
| y_prob_all = np.concatenate(all_prob) | |
| thresh_rows = [] | |
| for t in THRESHOLDS: | |
| yp = (y_prob_all >= t).astype(np.int32) | |
| tn, fp, fn, tp = confusion_matrix(y_true_all, yp, labels=[0,1]).ravel() | |
| prec, rec, f1, iou, dice, acc, spec = cm_metrics(tp, fp, fn, tn) | |
| thresh_rows.append({ | |
| 'threshold': t, | |
| 'TP': int(tp), 'FP': int(fp), 'TN': int(tn), 'FN': int(fn), | |
| 'precision': round(prec, 6), | |
| 'recall': round(rec, 6), | |
| 'specificity': round(spec, 6), | |
| 'f1_score': round(f1, 6), | |
| 'iou': round(iou, 6), | |
| 'dice': round(dice, 6), | |
| 'accuracy': round(acc, 6), | |
| }) | |
| thresh_df = pd.DataFrame(thresh_rows) | |
| thresh_df.to_csv(os.path.join(OUT_DIR, 'attn_evaluation_metrics.csv'), index=False) | |
| # Best threshold by IoU | |
| best_row = thresh_df.loc[thresh_df['iou'].idxmax()] | |
| best_t = float(best_row['threshold']) | |
| # Aggregate confusion matrix at best threshold | |
| cm = confusion_matrix(y_true_all, (y_prob_all >= best_t).astype(int), labels=[0,1]) | |
| pd.DataFrame(cm, | |
| index=['actual_background', 'actual_glacier'], | |
| columns=['pred_background', 'pred_glacier'] | |
| ).to_csv(os.path.join(OUT_DIR, 'attn_confusion_matrix.csv')) | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Human-readable summary | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| lines = [ | |
| "=" * 58, | |
| " ATTENTION U-NET β EVALUATION SUMMARY", | |
| "=" * 58, | |
| "", | |
| "model.evaluate (threshold = 0.5):", | |
| ] | |
| for n, v in zip(eval_names, eval_vals): | |
| lines.append(f" {n:<22}: {v:.6f}") | |
| lines += [ | |
| "", | |
| f"Best threshold by IoU: {best_t}", | |
| "", | |
| " {:<10} {:<10} {:<10} {:<10} {:<10} {:<10}".format( | |
| "thresh", "precision", "recall", "f1", "iou", "dice"), | |
| " " + "-" * 60, | |
| ] | |
| for _, r in thresh_df.iterrows(): | |
| tag = " β best" if r['threshold'] == best_t else "" | |
| lines.append(" {:<10} {:<10.4f} {:<10.4f} {:<10.4f} {:<10.4f} {:<10.4f}{}".format( | |
| r['threshold'], r['precision'], r['recall'], | |
| r['f1_score'], r['iou'], r['dice'], tag)) | |
| lines += [ | |
| "", | |
| "Per-image stats (valid pixels only, t=0.5):", | |
| f" Mean IoU : {per_img_df['iou'].mean():.4f}", | |
| f" Median IoU: {per_img_df['iou'].median():.4f}", | |
| f" Worst 5 :", | |
| ] | |
| for _, r in per_img_df.head(5).iterrows(): | |
| lines.append(f" {r['image_id']} β IoU={r['iou']:.4f} F1={r['f1_score']:.4f}" | |
| f" glacier={r['glacier_frac_gt']*100:.1f}% padded={r['was_padded']}") | |
| lines += [ | |
| "", | |
| f"Total test images : {len(feat_files)}", | |
| f"Output dir : {OUT_DIR}/", | |
| "", | |
| "Files written:", | |
| " attn_evaluation_metrics.csv β full threshold sweep", | |
| " attn_per_image_metrics.csv β per-image metrics (sorted worst IoU first)", | |
| " attn_confusion_matrix.csv β aggregate CM at best threshold", | |
| " attn_evaluation_summary.txt", | |
| " attn_pred_*.png β comparison figures", | |
| "", | |
| "β" * 58, | |
| "Baseline (SwinUNet 128Γ128, 70 epochs):", | |
| " Precision=0.6233 Recall=0.8990 F1=0.7362 IoU=0.5825", | |
| "β" * 58, | |
| ] | |
| summary = "\n".join(lines) | |
| print("\n" + summary) | |
| with open(os.path.join(OUT_DIR, 'attn_evaluation_summary.txt'), 'w') as f: | |
| f.write(summary) | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Save comparison PNGs for top 20 worst + 5 best images | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| try: | |
| import matplotlib | |
| matplotlib.use('Agg') | |
| import matplotlib.pyplot as plt | |
| y_pred_bin = (Y_prob >= best_t).astype(np.float32) | |
| save_indices = list(per_img_df.head(10).index) + list(per_img_df.tail(5).index) | |
| save_indices = list(dict.fromkeys(save_indices))[:20] | |
| for i in save_indices: | |
| iid = ids[i] | |
| H, W, vh, vw, _, _ = meta[i] | |
| rgb = X[i, :, :, [2, 1, 0]] | |
| lo, hi = np.percentile(rgb, 2), np.percentile(rgb, 98) | |
| rgb = np.clip((rgb - lo) / (hi - lo + 1e-8), 0, 1) | |
| fig, axes = plt.subplots(1, 3, figsize=(9, 3)) | |
| axes[0].imshow(rgb); axes[0].set_title('RGB'); axes[0].axis('off') | |
| axes[1].imshow(Y[i,:,:,0], cmap='gray'); axes[1].set_title('Ground Truth'); axes[1].axis('off') | |
| axes[2].imshow(y_pred_bin[i,:,:,0], cmap='gray') | |
| iou_val = per_img_df.loc[per_img_df['image_id']==iid, 'iou'].values | |
| axes[2].set_title(f'Pred IoU={iou_val[0]:.3f}' if len(iou_val) else 'Pred') | |
| axes[2].axis('off') | |
| plt.tight_layout() | |
| plt.savefig(os.path.join(OUT_DIR, f'attn_pred_{iid}.png'), dpi=80) | |
| plt.close() | |
| print(f"Saved {len(save_indices)} PNGs to {OUT_DIR}/") | |
| except Exception as e: | |
| print(f"PNG save skipped: {e}") | |
| print("\nEvaluation complete.") | |