Download scripts/result.py from OneScience-Group/StormCast: direct link, hf CLI and curl.
- Browser
- Download file 7.76 kB
-
https://huggingface.co/OneScience-Group/StormCast/resolve/main/scripts/result.py
- Command line
-
hf download hf://OneScience-Group/StormCast/scripts/result.py
-
curl -L -o result.py https://huggingface.co/OneScience-Group/StormCast/resolve/main/scripts/result.py
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() | |