SR-GAN / examples /infer_tensor.py
simon-donike's picture
Organize versioned models, MIT model cards, examples and compatible downloads
b31d272 verified
Raw History Blame Contribute Delete
4.83 kB
# SPDX-License-Identifier: MIT
"""Run an SR-GAN preset on one small BCHW tensor saved with ``torch.save``."""
from __future__ import annotations
import argparse
from collections.abc import Mapping, Sequence
from pathlib import Path
from typing import Any
import torch
from opensr_srgan import load_inference_model
def _tensor_from_payload(payload: Any, *, source: str) -> torch.Tensor:
"""Extract an LR tensor from common dataset serialization layouts."""
if isinstance(payload, torch.Tensor):
return payload
if isinstance(payload, Mapping):
for key in ("lr", "lr_imgs", "input", "image", "x"):
if key in payload:
return _tensor_from_payload(payload[key], source=f"{source}[{key!r}]")
tensors = [value for value in payload.values() if isinstance(value, torch.Tensor)]
if len(tensors) == 1:
return tensors[0]
raise ValueError(
f"{source} is a dictionary without a recognized LR key. "
"Use one of: lr, lr_imgs, input, image, x."
)
if isinstance(payload, Sequence) and not isinstance(payload, (str, bytes)):
for index, value in enumerate(payload):
if isinstance(value, torch.Tensor):
return value
raise ValueError(f"{source} contains no tensor; expected an LR tensor or (lr, hr).")
raise TypeError(f"{source} must contain a torch.Tensor, got {type(payload).__name__}.")
def _load_input(path: Path) -> torch.Tensor:
try:
payload = torch.load(path, map_location="cpu", weights_only=True)
except TypeError: # PyTorch versions before ``weights_only`` accepted this argument.
payload = torch.load(path, map_location="cpu")
return _tensor_from_payload(payload, source=str(path))
def _as_bchw(tensor: torch.Tensor, *, channels: int) -> torch.Tensor:
if tensor.dtype == torch.bool or tensor.is_complex():
raise TypeError(f"Expected real-valued input, got dtype {tensor.dtype}.")
if tensor.ndim == 3:
tensor = tensor.unsqueeze(0)
if tensor.ndim != 4:
raise ValueError(f"Expected CHW or BCHW input, got shape {tuple(tensor.shape)}.")
if tensor.shape[1] != channels:
raise ValueError(f"Preset expects {channels} channels, got shape {tuple(tensor.shape)}.")
tensor = tensor.float()
if not torch.isfinite(tensor).all():
raise ValueError("Input contains NaN or infinite values.")
return tensor.contiguous()
def _parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--preset", choices=("SEN2-SR", "RGB-NIR", "SWIR"), default="SEN2-SR")
parser.add_argument("--device", default="cpu", help="Torch device, for example cpu or cuda.")
parser.add_argument("--input", type=Path, help="A torch.save tensor, (lr, hr), or supported mapping.")
parser.add_argument("--output", type=Path, default=Path("sr.pt"), help="Destination BCHW tensor.")
parser.add_argument("--revision", help="Optional Hugging Face commit, tag, or branch to resolve.")
parser.add_argument(
"--raw",
action="store_true",
help="Use forward on an already normalized 0..1 tensor; skip predict_step handling.",
)
parser.add_argument(
"--smoke",
action="store_true",
help="Use a generated zero tensor only to verify installation and output shape.",
)
args = parser.parse_args()
if args.smoke == (args.input is not None):
parser.error("provide exactly one of --input or --smoke")
return args
def main() -> None:
args = _parse_args()
device = torch.device(args.device)
model = load_inference_model(
args.preset, map_location=device, revision=args.revision
).to(device)
model.eval()
channels = int(model.config.Model.in_bands)
scale = int(model.config.Generator.scaling_factor)
input_tensor = (
torch.zeros((1, channels, 16, 16), dtype=torch.float32)
if args.smoke
else _load_input(args.input)
)
input_tensor = _as_bchw(input_tensor, channels=channels)
with torch.inference_mode():
if args.raw:
output = model(input_tensor.to(device)).detach().cpu()
convention = "normalized network output (forward; no post-processing)"
else:
output = model.predict_step(input_tensor)
convention = "raw DN/reflectance output (predict_step)"
args.output.parent.mkdir(parents=True, exist_ok=True)
torch.save(output, args.output)
source = "generated smoke tensor" if args.smoke else str(args.input)
print(
f"Saved {tuple(output.shape)} to {args.output} from {source}; "
f"preset={args.preset}, scale={scale}, convention={convention}."
)
if __name__ == "__main__":
main()