satellite / inference /pipeline.py
prateeksharmacoder's picture
Deploy ZeroGPU compatible code with Sen2SR
8d928e8 verified
Raw History Blame Contribute Delete
10.9 kB
from pathlib import Path
from PIL import Image
from .tiling import create_tiles
from .stitching import stitch_tiles
from models.hatsat.hatsat_inference import HATSATInference
from models.esrgan.esrgan_inference import ESRGANInference
# ==========================================================
# Project paths
# ==========================================================
# satellite_sr_deploy/ is the parent of inference/
ROOT = Path(__file__).resolve().parent.parent
HATSAT_CHECKPOINT = (
ROOT /
"weights/hatsat/net_g_150000.pth"
)
ESRGAN_CHECKPOINT = (
ROOT /
"weights/esrgan/RRDB_ESRGAN_x4.pth"
)
# ==========================================================
# Model cache
# ==========================================================
_MODEL_CACHE = {}
def get_model(model_name, device=None):
"""
Load a model once and reuse it.
Parameters
----------
model_name : str
'hatsat' or 'esrgan'
device : optional
'cuda' or 'cpu'
Returns
-------
Loaded inference model.
"""
model_name = model_name.lower().strip()
if model_name not in ("hatsat", "esrgan"):
raise ValueError(
"model_name must be 'hatsat' or 'esrgan'."
)
cache_key = (
model_name,
str(device) if device is not None else "auto"
)
if cache_key in _MODEL_CACHE:
return _MODEL_CACHE[cache_key]
# ------------------------------------------------------
# HATSAT
# ------------------------------------------------------
if model_name == "hatsat":
model = HATSATInference(
checkpoint_path=HATSAT_CHECKPOINT,
device=device
)
# ------------------------------------------------------
# ESRGAN
# ------------------------------------------------------
else:
model = ESRGANInference(
checkpoint_path=ESRGAN_CHECKPOINT,
device=device
)
_MODEL_CACHE[cache_key] = model
return model
# ==========================================================
# Main pipeline
# ==========================================================
def super_resolve(
image,
model_name="hatsat",
tile_size=128,
overlap=32,
device=None
):
"""
Complete satellite super-resolution pipeline.
Pipeline:
Input image
↓
Tiling
↓
Selected model
↓
4x SR
↓
Overlap stitching
↓
Final SR image
Parameters
----------
image : PIL.Image.Image
model_name : str
'hatsat' or 'esrgan'
tile_size : int
Input tile size.
overlap : int
Input overlap.
device : optional
'cuda' or 'cpu'
Returns
-------
PIL.Image.Image
Final 4x super-resolved image.
"""
# ------------------------------------------------------
# Validate image
# ------------------------------------------------------
if not isinstance(image, Image.Image):
raise TypeError(
"image must be a PIL.Image.Image"
)
image = image.convert("RGB")
original_size = image.size
print(
f"Pipeline input: "
f"{original_size[0]} × {original_size[1]}"
)
# ------------------------------------------------------
# Get selected model
# ------------------------------------------------------
model = get_model(
model_name,
device=device
)
print(
f"Selected model: "
f"{model_name.upper()}"
)
# ------------------------------------------------------
# Create tiles
# ------------------------------------------------------
tiles = create_tiles(
image,
tile_size=tile_size,
overlap=overlap
)
print(
f"Number of tiles: {len(tiles)}"
)
# ------------------------------------------------------
# Run SR on each tile
# ------------------------------------------------------
sr_tiles = []
for tile_info in tiles:
print(
f"Processing tile "
f"{tile_info['index'] + 1}/"
f"{len(tiles)} "
f"(x={tile_info['x']}, "
f"y={tile_info['y']})"
)
sr_image = model.predict(
tile_info["image"]
)
sr_tiles.append(
{
"image": sr_image,
"x": tile_info["x"],
"y": tile_info["y"]
}
)
# ------------------------------------------------------
# Stitch SR tiles
# ------------------------------------------------------
result = stitch_tiles(
sr_tiles,
scale=4,
original_size=original_size,
overlap=overlap
)
print(
f"Pipeline output: "
f"{result.size[0]} × "
f"{result.size[1]}"
)
return result
# ==========================================================
# Convenience functions
# ==========================================================
def run_hatsat_pipeline(
image,
tile_size=128,
overlap=32,
device=None
):
return super_resolve(
image=image,
model_name="hatsat",
tile_size=tile_size,
overlap=overlap,
device=device
)
def run_esrgan_pipeline(
image,
tile_size=128,
overlap=32,
device=None
):
return super_resolve(
image=image,
model_name="esrgan",
tile_size=tile_size,
overlap=overlap,
device=device
)
# ==========================================================
# COMPLETE SATELLITE PROCESSING
# ==========================================================
from .geotiff import (
sentinel_rgb_from_bands,
save_sr_geotiff
)
def process_satellite(
b02_path,
b03_path,
b04_path,
model_name="hatsat",
output_dir=None,
tile_size=128,
overlap=32,
device=None
):
"""
Complete Sentinel-2 satellite super-resolution workflow.
Input:
B02, B03 and B04 Sentinel-2 GeoTIFF files.
Processing:
B02/B03/B04
↓
RGB conversion
↓
Tiling
↓
HATSAT / ESRGAN
↓
Stitching
↓
4x SR
Output:
PNG preview
Georeferenced RGB GeoTIFF
Returns
-------
dict
{
"model": model_name,
"input_size": (...),
"output_size": (...),
"png": Path,
"geotiff": Path
}
"""
# ------------------------------------------------------
# Paths
# ------------------------------------------------------
b02_path = Path(b02_path)
b03_path = Path(b03_path)
b04_path = Path(b04_path)
if output_dir is None:
output_dir = (
ROOT /
"outputs" /
model_name.lower() /
"results"
)
output_dir = Path(output_dir)
output_dir.mkdir(
parents=True,
exist_ok=True
)
# ------------------------------------------------------
# Validate inputs
# ------------------------------------------------------
for path in [
b02_path,
b03_path,
b04_path
]:
if not path.exists():
raise FileNotFoundError(
f"Input file not found: {path}"
)
# ------------------------------------------------------
# Read Sentinel-2 bands
# ------------------------------------------------------
print("\n========================================")
print("SATELLITE SUPER-RESOLUTION PIPELINE")
print("========================================")
print("\nReading Sentinel-2 bands...")
image, source_profile = sentinel_rgb_from_bands(
b02_path,
b03_path,
b04_path
)
print(
"Input RGB size:",
image.size
)
print(
"Source CRS:",
source_profile["crs"]
)
# ------------------------------------------------------
# Run existing SR pipeline
# ------------------------------------------------------
print(
"\nRunning model:",
model_name.upper()
)
sr_image = super_resolve(
image=image,
model_name=model_name,
tile_size=tile_size,
overlap=overlap,
device=device
)
print(
"\nSR image size:",
sr_image.size
)
# ------------------------------------------------------
# Output filenames
# ------------------------------------------------------
model_tag = model_name.lower()
png_path = (
output_dir /
f"sentinel2_{model_tag}_4x.png"
)
geotiff_path = (
output_dir /
f"sentinel2_{model_tag}_4x_georef.tif"
)
# ------------------------------------------------------
# Save PNG preview
# ------------------------------------------------------
sr_image.save(
png_path,
format="PNG"
)
print(
"\nPNG saved:",
png_path
)
# ------------------------------------------------------
# Save georeferenced GeoTIFF
# ------------------------------------------------------
save_sr_geotiff(
sr_image=sr_image,
source_profile=source_profile,
output_path=geotiff_path,
scale=4
)
print(
"GeoTIFF saved:",
geotiff_path
)
# ------------------------------------------------------
# Final summary
# ------------------------------------------------------
print("\n========================================")
print("PROCESSING COMPLETE")
print("========================================")
print(
"Model:",
model_name.upper()
)
print(
"Input:",
image.size
)
print(
"Output:",
sr_image.size
)
print(
"Scale: 4x"
)
print(
"PNG:",
png_path
)
print(
"GeoTIFF:",
geotiff_path
)
return {
"model": model_name.lower(),
"input_size": image.size,
"output_size": sr_image.size,
"png": png_path,
"geotiff": geotiff_path
}