rockglacierNET / preprocess /visualize_patches.py
kabiriitb's picture
Upload folder using huggingface_hub
80e070e verified
Raw History Blame Contribute Delete
10 kB
"""
Patch visualization utilities for RockGlacierNet.
Generates:
outputs/patch_viz/
sample_grid_{split}.png β€” 4Γ—8 grid of RGB+mask overlays
class_distribution.png β€” glacier % histogram per split
patch_size_distribution.png β€” original image H/W scatter
band_histograms.png β€” per-band value distributions (raw)
Usage (from project root):
conda run -n brats python preprocess/visualize_patches.py
"""
import os
import sys
import glob
import numpy as np
import pandas as pd
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from preprocess.patch_config import PATCH_CONFIG
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
PATCH_DIR = PATCH_CONFIG['output_dir']
SPLITS = PATCH_CONFIG['splits']
VIZ_DIR = "outputs/patch_viz"
META_CSV = os.path.join(PATCH_DIR, "metadata", "patch_metadata.csv")
IMG_CSV = os.path.join(PATCH_DIR, "metadata", "dataset_summary.csv")
BAND_NAMES = ['B2', 'B3', 'B4', 'B8', 'B11', 'B12',
'NDVI', 'NDWI', 'NDSI', 'Elev', 'Slope', 'Aspect']
os.makedirs(VIZ_DIR, exist_ok=True)
# ─────────────────────────────────────────────────────────────────────────────
# 1. Sample grid: RGB + glacier mask overlay
# ─────────────────────────────────────────────────────────────────────────────
def _rgb(feat: np.ndarray) -> np.ndarray:
"""Extract normalized RGB from raw (64,64,12) patch."""
rgb = feat[:, :, [2, 1, 0]].astype(np.float32)
lo, hi = np.percentile(rgb, 2), np.percentile(rgb, 98)
return np.clip((rgb - lo) / (hi - lo + 1e-8), 0, 1)
def plot_sample_grid(split: str, n_cols: int = 8, n_rows: int = 4):
feat_dir = os.path.join(PATCH_DIR, split, 'features')
feat_files = sorted(glob.glob(os.path.join(feat_dir, '*.npy')))
if not feat_files:
print(f" No patches for {split}, skipping grid.")
return
rng = np.random.RandomState(42)
n_show = min(n_cols * n_rows, len(feat_files))
picks = rng.choice(feat_files, size=n_show, replace=False)
fig, axes = plt.subplots(n_rows, n_cols,
figsize=(n_cols * 2, n_rows * 2.2))
fig.suptitle(f"Sample Patches β€” {split} (RGB + glacier overlay)",
fontsize=12, y=1.01)
for ax_row in axes:
for ax in ax_row:
ax.axis('off')
for i, fp in enumerate(sorted(picks)):
row, col = divmod(i, n_cols)
ax = axes[row][col]
feat = np.load(fp)
mask = np.load(fp.replace('features', 'masks'))
rgb = _rgb(feat)
ax.imshow(rgb)
# Semi-transparent glacier overlay in red
overlay = np.zeros((*mask.shape[:2], 4), dtype=np.float32)
overlay[mask[:, :, 0] > 0.5] = [1, 0, 0, 0.45]
ax.imshow(overlay)
g_pct = 100.0 * (mask > 0.5).sum() / mask.size
ax.set_title(f"{g_pct:.1f}%", fontsize=7, pad=2)
red_patch = mpatches.Patch(color='red', alpha=0.45, label='Glacier')
fig.legend(handles=[red_patch], loc='lower center', ncol=1, fontsize=9)
plt.tight_layout()
out = os.path.join(VIZ_DIR, f'sample_grid_{split}.png')
plt.savefig(out, dpi=100, bbox_inches='tight')
plt.close()
print(f" Saved {out}")
# ─────────────────────────────────────────────────────────────────────────────
# 2. Class distribution histograms
# ─────────────────────────────────────────────────────────────────────────────
def plot_class_distribution():
if not os.path.exists(META_CSV):
print(f" Metadata not found: {META_CSV}")
return
df = pd.read_csv(META_CSV)
kept = df[df['kept_or_filtered'] == 'kept']
colors = {'train': '#2196F3', 'val': '#4CAF50', 'test': '#FF9800'}
fig, axes = plt.subplots(1, 3, figsize=(15, 4), sharey=False)
fig.suptitle("Glacier Coverage Distribution in Kept Patches", fontsize=12)
for ax, split in zip(axes, SPLITS):
sub = kept[kept['split'] == split]['glacier_percentage']
if sub.empty:
ax.set_title(split); continue
ax.hist(sub, bins=30, color=colors.get(split, 'gray'),
edgecolor='white', linewidth=0.5)
ax.set_xlabel('Glacier coverage (%)')
ax.set_ylabel('Patch count')
ax.set_title(f"{split} (n={len(sub)})\nmean={sub.mean():.1f}% median={sub.median():.1f}%")
ax.axvline(sub.mean(), color='black', linestyle='--', linewidth=1, label='mean')
ax.legend(fontsize=8)
plt.tight_layout()
out = os.path.join(VIZ_DIR, 'class_distribution.png')
plt.savefig(out, dpi=100, bbox_inches='tight')
plt.close()
print(f" Saved {out}")
# ─────────────────────────────────────────────────────────────────────────────
# 3. Image size scatter
# ─────────────────────────────────────────────────────────────────────────────
def plot_image_sizes():
if not os.path.exists(IMG_CSV):
print(f" Image summary not found: {IMG_CSV}")
return
df = pd.read_csv(IMG_CSV)
colors = {'train': '#2196F3', 'val': '#4CAF50', 'test': '#FF9800'}
fig, ax = plt.subplots(figsize=(7, 6))
for split in SPLITS:
sub = df[df['split'] == split]
ax.scatter(sub['original_width'], sub['original_height'],
alpha=0.5, s=20, label=split, color=colors.get(split, 'gray'))
patch_size = PATCH_CONFIG['patch_size']
ax.axvline(patch_size, color='red', linestyle='--', linewidth=1,
label=f'patch_size={patch_size}')
ax.axhline(patch_size, color='red', linestyle='--', linewidth=1)
ax.set_xlabel('Original image width (px)')
ax.set_ylabel('Original image height (px)')
ax.set_title('Original Image Size Distribution')
ax.legend(fontsize=9)
plt.tight_layout()
out = os.path.join(VIZ_DIR, 'patch_size_distribution.png')
plt.savefig(out, dpi=100, bbox_inches='tight')
plt.close()
print(f" Saved {out}")
# ─────────────────────────────────────────────────────────────────────────────
# 4. Per-band histograms from a random sample of patches
# ─────────────────────────────────────────────────────────────────────────────
def plot_band_histograms(n_samples: int = 200):
feat_files = []
for split in SPLITS:
feat_files += glob.glob(os.path.join(PATCH_DIR, split, 'features', '*.npy'))
if not feat_files:
print(" No patches found for band histograms.")
return
rng = np.random.RandomState(0)
picks = rng.choice(feat_files, size=min(n_samples, len(feat_files)), replace=False)
# Collect band data
band_data = [[] for _ in range(12)]
for fp in picks:
feat = np.load(fp) # (64, 64, 12) raw
for b in range(12):
band_data[b].append(feat[:, :, b].flatten())
fig, axes = plt.subplots(3, 4, figsize=(16, 10))
fig.suptitle(f"Per-Band Value Distributions (raw, {len(picks)} random patches)", fontsize=12)
for b, ax in enumerate(axes.flatten()):
vals = np.concatenate(band_data[b])
# Clip extreme outliers for readability
lo, hi = np.percentile(vals, 1), np.percentile(vals, 99)
vals_clip = vals[(vals >= lo) & (vals <= hi)]
ax.hist(vals_clip, bins=60, color='#1976D2', edgecolor='none', alpha=0.8)
ax.set_title(BAND_NAMES[b], fontsize=9)
ax.set_xlabel('Value', fontsize=7)
ax.set_ylabel('Count', fontsize=7)
ax.tick_params(labelsize=7)
ax.text(0.97, 0.93, f"μ={vals.mean():.3f}\nσ={vals.std():.3f}",
transform=ax.transAxes, ha='right', va='top', fontsize=7,
bbox=dict(boxstyle='round', fc='white', alpha=0.7))
plt.tight_layout()
out = os.path.join(VIZ_DIR, 'band_histograms.png')
plt.savefig(out, dpi=100, bbox_inches='tight')
plt.close()
print(f" Saved {out}")
# ─────────────────────────────────────────────────────────────────────────────
# Main
# ─────────────────────────────────────────────────────────────────────────────
def main():
print("Generating patch visualizations…")
print("\n1. Sample grids…")
for split in SPLITS:
plot_sample_grid(split)
print("\n2. Class distribution…")
plot_class_distribution()
print("\n3. Image size scatter…")
plot_image_sizes()
print("\n4. Band histograms…")
plot_band_histograms()
print(f"\nAll figures saved to {VIZ_DIR}/")
if __name__ == '__main__':
main()