UnetDif / scripts /result.py
zhangrenchao's picture
Upload folder using huggingface_hub
30f7852 verified
Raw
History Blame Contribute Delete
4.63 kB
"""Evaluate 24-hour rainfall and produce per-step metrics and maps."""
import json
from pathlib import Path
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np
import torch
import yaml
ROOT = Path(__file__).resolve().parents[1]
def categorical(prediction, target, threshold):
forecast, observed = prediction >= threshold, target >= threshold
hits = np.logical_and(forecast, observed).sum(); false = np.logical_and(forecast, ~observed).sum()
misses = np.logical_and(~forecast, observed).sum(); correct_negative = np.logical_and(~forecast, ~observed).sum()
total = hits + false + misses + correct_negative; eps = 1e-8
random_hits = (hits + misses) * (hits + false) / max(total, 1)
return {
"ACC": float((hits + correct_negative) / (total + eps)),
"TS": float(hits / (hits + misses + false + eps)),
"ETS": float((hits - random_hits) / (hits + misses + false - random_hits + eps)),
"BS": float((hits + false) / (hits + misses + eps)),
"FAR": float(false / (hits + false + eps)),
}
def fss(prediction, target, threshold, window):
forecast = torch.from_numpy((prediction >= threshold).astype(np.float32))[:, None]
observed = torch.from_numpy((target >= threshold).astype(np.float32))[:, None]
pf = torch.nn.functional.avg_pool2d(forecast, window, stride=1, padding=window // 2)
po = torch.nn.functional.avg_pool2d(observed, window, stride=1, padding=window // 2)
return float(1 - (pf - po).square().sum() / (pf.square().sum() + po.square().sum() + 1e-8))
def main():
config = yaml.safe_load((ROOT / "conf/config.yaml").read_text())
archive = np.load(ROOT / config["paths"]["inference"], allow_pickle=False)
steps = int(config["data"]["time_steps"])
lead_hours = [int(value) for value in archive["lead_hours"]]
expected_3h = (steps, int(config["data"]["height"]), int(config["data"]["width"]))
for key in ("raw_3h", "target_3h", "corrected_3h"):
if archive[key].shape[1:] != expected_3h:
raise ValueError(f"{key} must have per-day shape {expected_3h}")
if lead_hours != config["data"]["lead_hours"]:
raise ValueError("inference NPZ lead_hours do not match configuration")
thresholds = [float(value) for value in config["evaluation"]["thresholds_mm_24h"]]
windows = [int(value) for value in config["evaluation"]["fss_windows"]]
metrics = {"units_24h": "mm/24h", "units_per_step": "mm/3h", "per_step_length": steps,
"lead_hours": lead_hours, "raw_24h": {}, "corrected_24h": {}, "per_step": {}}
for label in ("raw", "corrected"):
prediction_24h = archive[f"{label}_24h"]
metrics[f"{label}_24h"] = {str(int(t)): categorical(prediction_24h, archive["target_24h"], t) for t in thresholds}
metrics[f"{label}_24h"]["FSS@50"] = {str(w): fss(prediction_24h, archive["target_24h"], 50, w) for w in windows}
per_step = []
for step in range(steps):
item = {"step": step, "lead_hour": lead_hours[step]}
item.update({str(int(t)): categorical(archive[f"{label}_3h"][:, step], archive["target_3h"][:, step], t) for t in thresholds})
item["FSS@50"] = {str(w): fss(archive[f"{label}_3h"][:, step], archive["target_3h"][:, step], 50, w) for w in windows}
per_step.append(item)
metrics["per_step"][label] = per_step
output = ROOT / config["paths"]["evaluation_dir"]
output.mkdir(parents=True, exist_ok=True)
(output / "metrics.json").write_text(json.dumps(metrics, indent=2) + "\n")
figure, axes = plt.subplots(4, steps, figsize=(18, 9), constrained_layout=True)
rows = (archive["raw_3h"][0], archive["target_3h"][0], archive["corrected_3h"][0], archive["corrected_3h"][0] - archive["target_3h"][0])
labels = ("ECMWF", "CMPA target", "Corrected", "Error")
vmax = max(float(rows[0].max()), float(rows[1].max()), 1)
for row, (fields, label) in enumerate(zip(rows, labels)):
for step in range(steps):
cmap = "RdBu_r" if row == 3 else "Blues"
limit = vmax if row < 3 else max(vmax / 2, 1)
axes[row, step].imshow(fields[step], cmap=cmap, vmin=-limit if row == 3 else 0, vmax=limit)
axes[row, step].axis("off")
if row == 0: axes[row, step].set_title(f"+{archive['lead_hours'][step]} h")
if step == 0: axes[row, step].set_ylabel(label)
figure.savefig(output / "comparison.png", dpi=150); plt.close(figure)
print(f"metrics={output / 'metrics.json'} figure={output / 'comparison.png'}")
if __name__ == "__main__":
main()