StormCast / scripts /result.py
yzt15806542928's picture
Upload folder using huggingface_hub
006ea64 verified
Raw
History Blame Contribute Delete
7.76 kB
from __future__ import annotations
import argparse
from datetime import datetime
from pathlib import Path
from typing import Any
import h5py
import matplotlib
import numpy as np
import yaml
matplotlib.use("Agg")
from matplotlib import pyplot as plt
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Visualize StormCast HDF5 forecasts")
parser.add_argument("--config", type=Path, default=Path("conf/config.yaml"))
parser.add_argument("--input", type=Path, default="./outputs/inference/forecast.h5")
parser.add_argument("--output-dir", type=Path)
parser.add_argument("--state-variable")
parser.add_argument("--background-variable")
parser.add_argument("--step", type=int, action="append")
parser.add_argument(
"--normalized",
action="store_true",
help="Plot model-space values instead of applying dataset statistics",
)
return parser.parse_args()
def main() -> None:
args = parse_args()
config_path = args.config.resolve()
with config_path.open("r", encoding="utf-8") as handle:
config = yaml.safe_load(handle)
project_root = config_path.parent.parent
data_root = Path(config["data"]["root_dir"])
if not data_root.is_absolute():
data_root = (project_root / data_root).resolve()
output_dir = args.output_dir or Path(config["inference"]["output_dir"]) / "plots"
if not output_dir.is_absolute():
output_dir = (project_root / output_dir).resolve()
visualize(
input_path=args.input,
output_dir=output_dir,
data_root=data_root,
state_variable=args.state_variable
or config["inference"]["plot_state_variable"],
background_variable=args.background_variable
or config["inference"]["plot_background_variable"],
steps=args.step,
denormalize=not args.normalized,
)
def visualize(
input_path: Path,
output_dir: Path,
data_root: Path,
state_variable: str,
background_variable: str,
steps: list[int] | None = None,
denormalize: bool = True,
) -> list[Path]:
output_dir.mkdir(parents=True, exist_ok=True)
outputs: list[Path] = []
with h5py.File(input_path, "r") as handle:
state_variables = _decode_strings(handle.attrs["state_variables"])
background_variables = _decode_strings(handle.attrs["background_variables"])
state_index = _variable_index(state_variables, state_variable, "state")
background_index = _variable_index(
background_variables, background_variable, "background"
)
selected_steps = steps or list(range(handle["prediction"].shape[0]))
for step in selected_steps:
if not 0 <= step < handle["prediction"].shape[0]:
raise IndexError(f"Step {step} is outside the forecast range")
source_normalized = bool(handle.attrs.get("normalized", False))
stats_by_year: dict[
int,
tuple[
tuple[np.ndarray, np.ndarray],
tuple[np.ndarray, np.ndarray],
],
] = {}
for step in selected_steps:
prediction = handle["prediction"][step, state_index]
target = handle["target"][step, state_index]
background = handle["background"][step, background_index]
time_index = int(handle["time_index"][step])
if denormalize and source_normalized:
year = int(str(time_index)[:4])
if year not in stats_by_year:
stats_by_year[year] = (
_read_stats(
data_root / "hrrr" / "data" / f"{year}.h5",
state_variables,
),
_read_stats(
data_root / "era5" / "data" / f"{year}.h5",
background_variables,
),
)
state_stats, background_stats = stats_by_year[year]
prediction = _denormalize(prediction, state_stats, state_index)
target = _denormalize(target, state_stats, state_index)
background = _denormalize(
background, background_stats, background_index
)
output = output_dir / f"forecast_{step:03d}_{state_variable}.png"
_save_four_panel(
prediction,
target,
background,
state_variable,
background_variable,
time_index,
output,
normalized=source_normalized and not denormalize,
)
outputs.append(output)
print(f"plot={output}")
return outputs
def _save_four_panel(
prediction: np.ndarray,
target: np.ndarray,
background: np.ndarray,
state_variable: str,
background_variable: str,
time_index: int,
output: Path,
normalized: bool,
) -> None:
error = prediction - target
state_min = float(min(np.nanmin(prediction), np.nanmin(target)))
state_max = float(max(np.nanmax(prediction), np.nanmax(target)))
error_limit = max(float(np.nanmax(np.abs(error))), np.finfo(np.float32).eps)
time_label = datetime.strptime(str(time_index), "%Y%m%d%H").strftime(
"%Y-%m-%d %H:00"
)
units = " (normalized)" if normalized else ""
figure, axes = plt.subplots(1, 4, figsize=(19, 4.8), constrained_layout=True)
panels = (
(
prediction,
f"StormCast {state_variable}{units}",
"viridis",
state_min,
state_max,
),
(target, f"Target {state_variable}{units}", "viridis", state_min, state_max),
(background, f"ERA5 {background_variable}{units}", "magma", None, None),
(
error,
f"Error {state_variable}{units}",
"RdBu_r",
-error_limit,
error_limit,
),
)
for axis, (data, title, cmap, vmin, vmax) in zip(axes, panels):
image = axis.imshow(
data, origin="lower", cmap=cmap, vmin=vmin, vmax=vmax, aspect="auto"
)
axis.set_title(title, fontsize=10)
axis.set_xticks([])
axis.set_yticks([])
figure.colorbar(image, ax=axis, fraction=0.046, pad=0.03)
figure.suptitle(f"StormCast valid time: {time_label}", fontsize=13)
figure.savefig(output, dpi=160)
plt.close(figure)
def _read_stats(
path: Path, expected_variables: list[str]
) -> tuple[np.ndarray, np.ndarray]:
if not path.is_file():
raise FileNotFoundError(f"Missing statistics file: {path}")
with h5py.File(path, "r") as handle:
variables = _decode_strings(handle["fields"].attrs["variables"])
if variables != expected_variables:
raise ValueError(f"Variable order in {path} differs from inference output")
means = np.asarray(handle["global_means"][:], dtype=np.float32).reshape(-1)
stds = np.asarray(handle["global_stds"][:], dtype=np.float32).reshape(-1)
return means, stds
def _denormalize(
data: np.ndarray,
stats: tuple[np.ndarray, np.ndarray],
index: int,
) -> np.ndarray:
means, stds = stats
return data * stds[index] + means[index]
def _decode_strings(values: Any) -> list[str]:
return [
value.decode() if isinstance(value, bytes) else str(value) for value in values
]
def _variable_index(variables: list[str], name: str, kind: str) -> int:
try:
return variables.index(name)
except ValueError as error:
raise ValueError(f"Unknown {kind} variable {name!r}") from error
if __name__ == "__main__":
main()