"""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()