ClayFoundation / scripts /fake_data.py
zhangrenchao's picture
Add engineering reproduction package
53becf5 verified
Raw
History Blame Contribute Delete
3.93 kB
"""Generate deterministic multi-sensor chips for the Clay engineering workflow."""
import argparse
from pathlib import Path
import numpy as np
import yaml
ROOT = Path(__file__).resolve().parents[1]
def load_config():
return yaml.safe_load((ROOT / "conf/config.yaml").read_text())
def make_split(path, count, config, seed):
rng = np.random.default_rng(seed)
size = int(config["data"]["image_size"])
y, x = np.mgrid[-1:1:complex(size), -1:1:complex(size)].astype(np.float32)
payload = {
"format_version": np.asarray(config["data"]["format_version"]),
"data_source": np.asarray("synthetic"),
"time": np.empty((count, 2), np.float32),
"latlon": np.empty((count, 2), np.float32),
"class_target": np.empty(count, np.int64),
"regression_target": np.empty(count, np.float32),
"teacher_target": np.empty((count, config["model"]["teacher_dim"]), np.float32),
}
projection = rng.normal(size=(6, config["model"]["teacher_dim"])).astype(np.float32)
for sample in range(count):
phase = rng.uniform(0, 2 * np.pi)
week = rng.uniform(0, 2 * np.pi)
hour = rng.uniform(0, 2 * np.pi)
latitude = rng.uniform(-math_pi_over_two(), math_pi_over_two())
longitude = rng.uniform(-np.pi, np.pi)
payload["time"][sample] = (week, hour)
payload["latlon"][sample] = (latitude, longitude)
payload["class_target"][sample] = int(np.sin(phase) > 0)
payload["regression_target"][sample] = np.cos(phase) + 0.2 * np.sin(latitude)
descriptor = np.asarray([
np.sin(phase), np.cos(phase), np.sin(week), np.cos(week),
np.sin(latitude), np.cos(longitude),
], np.float32)
target = descriptor @ projection
payload["teacher_target"][sample] = target / np.linalg.norm(target).clip(1e-6)
for sensor_index, (name, spec) in enumerate(config["data"]["sensors"].items()):
channels = int(spec["channels"])
pixels = np.empty((count, channels, size, size), np.float32)
valid = np.ones((count, 1, size, size), np.float32)
for sample in range(count):
phase = np.arctan2(payload["teacher_target"][sample, 0], payload["teacher_target"][sample, 1])
base = np.sin((2.0 + sensor_index) * np.pi * x + phase) * np.cos(2 * np.pi * y - phase)
base += 0.35 * x + 0.15 * y
for channel in range(channels):
spectral = 0.12 * channel + 0.2 * np.sin(phase + channel / max(channels, 1))
pixels[sample, channel] = base + spectral + rng.normal(0, 0.03, (size, size))
valid[sample, :, :2] = 0
pixels -= pixels.mean(axis=(0, 2, 3), keepdims=True)
pixels /= pixels.std(axis=(0, 2, 3), keepdims=True).clip(1e-6)
payload[f"pixels_{name}"] = np.clip(pixels, -6, 6).astype(np.float32)
payload[f"valid_{name}"] = valid
payload[f"wavelengths_{name}"] = np.tile(np.asarray(spec["wavelengths_nm"], np.float32), (count, 1))
payload[f"gsd_{name}"] = np.full(count, float(spec["gsd"]), np.float32)
np.savez_compressed(path, **payload)
def math_pi_over_two():
return np.pi / 2
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--force", action="store_true")
args = parser.parse_args()
config = load_config()
data_dir = ROOT / config["data"]["root"]
data_dir.mkdir(parents=True, exist_ok=True)
for offset, (filename, count) in enumerate((
("train.npz", config["data"]["train_samples"]),
("test.npz", config["data"]["test_samples"]),
)):
path = data_dir / filename
if args.force or not path.exists():
make_split(path, int(count), config, int(config["seed"]) + offset)
print(f"generated={path.relative_to(ROOT)} samples={count} format={config['data']['format_version']}")
if __name__ == "__main__":
main()