NowcastNet_Earth / scripts /data_loader.py
yzt15806542928's picture
Upload folder using huggingface_hub
439c523 verified
Raw
History Blame Contribute Delete
4.25 kB
"""MRMS data loading utilities matching the official NowcastNet contract."""
from __future__ import annotations
import struct
import zlib
from pathlib import Path
import numpy as np
import torch
from torch.utils.data import Dataset
def _read_png_gray16(path: Path) -> np.ndarray:
"""Read the 16-bit grayscale PNG subset used by MRMS."""
raw = path.read_bytes()
if raw[:8] != b"\x89PNG\r\n\x1a\n":
raise ValueError(f"Not a PNG file: {path}")
pos = 8
idat: list[bytes] = []
width = height = bit_depth = color_type = None
while pos < len(raw):
length = struct.unpack(">I", raw[pos:pos + 4])[0]
kind = raw[pos + 4:pos + 8]
payload = raw[pos + 8:pos + 8 + length]
pos += length + 12
if kind == b"IHDR":
width, height, bit_depth, color_type = struct.unpack(">IIBB", payload[:10])
elif kind == b"IDAT":
idat.append(payload)
elif kind == b"IEND":
break
if width is None or height is None or bit_depth != 16 or color_type != 0:
raise ValueError(f"Expected 16-bit grayscale PNG: {path}")
decoded = zlib.decompress(b"".join(idat))
row_bytes = width * 2
previous = np.zeros(row_bytes, dtype=np.uint8)
rows: list[np.ndarray] = []
cursor = 0
for _ in range(height):
filter_type = decoded[cursor]
row = np.frombuffer(decoded[cursor + 1:cursor + 1 + row_bytes], dtype=np.uint8).copy()
cursor += row_bytes + 1
for index in range(row_bytes):
left = row[index - 2] if index >= 2 else 0
up = previous[index]
upper_left = previous[index - 2] if index >= 2 else 0
if filter_type == 1:
value = left
elif filter_type == 2:
value = up
elif filter_type == 3:
value = (int(left) + int(up)) // 2
elif filter_type == 4:
predictor = int(left) + int(up) - int(upper_left)
distances = (abs(predictor - int(left)), abs(predictor - int(up)), abs(predictor - int(upper_left)))
value = (left, up, upper_left)[int(np.argmin(distances))]
elif filter_type == 0:
value = 0
else:
raise ValueError(f"Unsupported PNG filter {filter_type}")
row[index] = (int(row[index]) + int(value)) & 255
rows.append(row)
previous = row
return np.frombuffer(b"".join(row.tobytes() for row in rows), dtype=">u2").astype(np.uint16).reshape(height, width)
def decode_mrms_event(event_dir: str | Path, image_height: int = 512, image_width: int = 512, total_length: int = 29) -> np.ndarray:
event_dir = Path(event_dir)
event_name = event_dir.name
frames = [_read_png_gray16(event_dir / f"{event_name}-{index:02d}.png") for index in range(total_length)]
data = np.stack(frames).astype(np.float32) / 10.0 - 3.0
if data.shape[1:] != (image_height, image_width):
raise ValueError(f"MRMS frame shape {data.shape[1:]} != {(image_height, image_width)}")
mask = np.ones_like(data, dtype=np.float32)
mask[data < 0] = 0
data[data < 0] = 0
data = np.clip(data, 0, 128)
return np.stack([data, mask], axis=-1)
class MRMSDataset(Dataset):
def __init__(self, data_dir: str | Path, image_height: int = 512, image_width: int = 512, total_length: int = 29, split: str = "train"):
self.data_dir = Path(data_dir)
self.events = sorted(path for path in self.data_dir.iterdir() if path.is_dir())
if split == "train":
self.events = [path for path in self.events if len(list(path.glob("*.png"))) == total_length]
elif split != "test":
raise ValueError("split must be train or test")
self.image_height = image_height
self.image_width = image_width
self.total_length = total_length
def __len__(self) -> int:
return len(self.events)
def __getitem__(self, index: int) -> dict[str, torch.Tensor | str]:
event = self.events[index]
frames = decode_mrms_event(event, self.image_height, self.image_width, self.total_length)
return {"radar_frames": torch.from_numpy(frames), "event": event.name}