Download scripts/inference.py from OneScience-Group/DOFA: direct link, hf CLI and curl.
- Browser
- Download file 5.33 kB
-
https://huggingface.co/OneScience-Group/DOFA/resolve/main/scripts/inference.py
- Command line
-
hf download hf://OneScience-Group/DOFA/scripts/inference.py
-
curl -L -o inference.py https://huggingface.co/OneScience-Group/DOFA/resolve/main/scripts/inference.py
5.33 kB
| """Reconstruct validated sensor NPZ files in bounded device batches.""" | |
| import importlib.util | |
| from pathlib import Path | |
| import numpy as np | |
| import torch | |
| import yaml | |
| ROOT = Path(__file__).resolve().parents[1] | |
| def load_model_class(): | |
| spec = importlib.util.spec_from_file_location("dofa_model", ROOT / "model" / "dofa.py") | |
| module = importlib.util.module_from_spec(spec) | |
| spec.loader.exec_module(module) | |
| return module.DOFA | |
| def expected_wavelengths(modality): | |
| if modality.get("wavelength_mode") == "synthetic_uniform": | |
| return modality["wavelength_start"] + np.arange(modality["channels"], dtype=np.float32) * modality["wavelength_step"] | |
| return np.asarray(modality["wavelengths"], dtype=np.float32) | |
| def scalar(archive, key, default=None): | |
| if key not in archive: | |
| if default is not None: | |
| return default | |
| raise ValueError(f"NPZ is missing required metadata: {key}") | |
| if archive[key].ndim != 0: | |
| raise ValueError(f"NPZ metadata {key} must be a scalar") | |
| return archive[key].item() | |
| def validate_archive(archive, path, name, data_config): | |
| if "images" not in archive or "wavelengths" not in archive: | |
| raise ValueError(f"{path}: images and wavelengths are required") | |
| images, wavelengths = archive["images"], archive["wavelengths"] | |
| modality_config = data_config["modalities"][name] | |
| if images.ndim != 4 or images.dtype != np.float32: | |
| raise ValueError(f"{path}: images must be float32 NCHW") | |
| expected_shape = (modality_config["channels"], data_config["image_size"], data_config["image_size"]) | |
| if images.shape[1:] != expected_shape: | |
| raise ValueError(f"{path}: expected [N,{expected_shape[0]},224,224], got {images.shape}") | |
| expected = expected_wavelengths(modality_config) | |
| if wavelengths.shape != (modality_config["channels"],) or not np.issubdtype(wavelengths.dtype, np.floating) or not np.isfinite(wavelengths).all() or not np.allclose(wavelengths, expected, rtol=1e-5, atol=1e-6): | |
| raise ValueError(f"{path}: wavelengths do not match configured sensor wavelengths") | |
| modality = str(scalar(archive, "modality")) | |
| protocol = str(scalar(archive, "protocol")) | |
| source = str(scalar(archive, "data_source", "unknown")) | |
| if modality != name or protocol != data_config["protocol"]: | |
| raise ValueError(f"{path}: modality/protocol metadata does not match config") | |
| data_range = float(scalar(archive, "data_range", data_config.get("data_range"))) | |
| if not np.isfinite(data_range) or data_range <= 0: | |
| raise ValueError(f"{path}: PSNR requires a positive data_range metadata or config value") | |
| return images, wavelengths.astype("float32", copy=False), protocol, source, data_range | |
| def main(): | |
| config = yaml.safe_load((ROOT / "conf/config.yaml").read_text(encoding="utf-8")) | |
| checkpoint_path = ROOT / config["paths"]["checkpoint"] | |
| if not checkpoint_path.exists(): | |
| raise FileNotFoundError("Missing checkpoint. Run `python scripts/train.py` first.") | |
| device = torch.device("cuda" if torch.cuda.is_available() and config["runtime"]["device"] != "cpu" else "cpu") | |
| checkpoint = torch.load(checkpoint_path, map_location="cpu", weights_only=False) | |
| if checkpoint.get("protocol") != config["data"]["protocol"]: | |
| raise ValueError("Checkpoint and configured protocols do not match") | |
| model = load_model_class()(**config["model"]).to(device) | |
| model.load_state_dict(checkpoint["model"]) | |
| model.eval() | |
| output_dir = ROOT / config["paths"]["inference_dir"] | |
| output_dir.mkdir(parents=True, exist_ok=True) | |
| data_root = ROOT / config["data"]["root"] | |
| batch_size = config["runtime"]["inference_batch_size"] | |
| torch.manual_seed(config["seed"]) | |
| for modality in config["data"]["modalities"]: | |
| path = data_root / f"test_{modality}.npz" | |
| if not path.exists(): | |
| raise FileNotFoundError(f"Missing test data: {path.relative_to(ROOT)}") | |
| archive = np.load(path) | |
| images, wavelengths, protocol, source, data_range = validate_archive( | |
| archive, path, modality, config["data"]) | |
| reconstructions = np.empty_like(images) | |
| masks = np.empty((len(images), model.num_patches), dtype=bool) | |
| wavelength_tensor = torch.from_numpy(wavelengths).to(device) | |
| with torch.inference_mode(): | |
| for start in range(0, len(images), batch_size): | |
| stop = min(start + batch_size, len(images)) | |
| batch = torch.from_numpy(images[start:stop]).to(device) | |
| output = model(batch, wavelength_tensor) | |
| reconstructions[start:stop] = output["reconstruction"].cpu().numpy() | |
| masks[start:stop] = output["mask"].cpu().numpy() | |
| del batch, output | |
| target = output_dir / f"{modality}_reconstruction.npz" | |
| np.savez_compressed(target, inputs=images, reconstructions=reconstructions, masks=masks, | |
| wavelengths=wavelengths, modality=np.asarray(modality), | |
| data_source=np.asarray(source), protocol=np.asarray(protocol), | |
| data_range=np.asarray(data_range, dtype=np.float32)) | |
| print(f"output={target.relative_to(ROOT)} channels={images.shape[1]} batch_size={batch_size}") | |
| if __name__ == "__main__": | |
| main() | |