Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| import numpy as np | |
| import torch | |
| import torch.nn as nn | |
| from torch.nn.utils import spectral_norm | |
| from PIL import Image | |
| device = torch.device('cpu') | |
| print(f"Device: {device}") | |
| # ── SR Model Architecture ───────────────────────────────── | |
| class ResidualBlock(nn.Module): | |
| def __init__(self, ch): | |
| super().__init__() | |
| self.conv1 = nn.Conv2d(ch, ch, 3, padding=1) | |
| self.bn1 = nn.BatchNorm2d(ch) | |
| self.conv2 = nn.Conv2d(ch, ch, 3, padding=1) | |
| self.bn2 = nn.BatchNorm2d(ch) | |
| self.relu = nn.PReLU() | |
| def forward(self, x): | |
| return x + self.bn2(self.conv2(self.relu(self.bn1(self.conv1(x))))) | |
| class SRNet(nn.Module): | |
| def __init__(self, num_res_blocks=8, channels=64): | |
| super().__init__() | |
| self.entry = nn.Sequential(nn.Conv2d(1, channels, 9, padding=4), nn.PReLU()) | |
| self.res_blocks = nn.Sequential(*[ResidualBlock(channels) for _ in range(num_res_blocks)]) | |
| self.mid_conv = nn.Sequential(nn.Conv2d(channels, channels, 3, padding=1), nn.BatchNorm2d(channels)) | |
| self.upsample = nn.Sequential(nn.Conv2d(channels, channels*4, 3, padding=1), nn.PixelShuffle(2), nn.PReLU()) | |
| self.exit = nn.Conv2d(channels, 1, 9, padding=4) | |
| self.sigmoid = nn.Sigmoid() | |
| def forward(self, x): | |
| e = self.entry(x) | |
| return self.sigmoid(self.exit(self.upsample(self.mid_conv(self.res_blocks(e)) + e))) | |
| # ── Colorization Model Architecture ────────────────────── | |
| def sn_conv(i, o, k, s=1, p=0): | |
| return spectral_norm(nn.Conv2d(i, o, k, s, p)) | |
| class DownBlock(nn.Module): | |
| def __init__(self, i, o, norm=True): | |
| super().__init__() | |
| layers = [sn_conv(i, o, 4, s=2, p=1)] | |
| if norm: layers.append(nn.InstanceNorm2d(o)) | |
| layers.append(nn.LeakyReLU(0.2, True)) | |
| self.block = nn.Sequential(*layers) | |
| def forward(self, x): return self.block(x) | |
| class UpBlock(nn.Module): | |
| def __init__(self, i, o, drop=False): | |
| super().__init__() | |
| self.up = nn.Upsample(scale_factor=2, mode='bilinear', align_corners=False) | |
| self.conv = nn.Sequential(sn_conv(i, o, 3, s=1, p=1), nn.InstanceNorm2d(o), nn.ReLU(True)) | |
| self.drop = nn.Dropout(0.5) if drop else nn.Identity() | |
| def forward(self, x): return self.drop(self.conv(self.up(x))) | |
| class UNetGenerator(nn.Module): | |
| def __init__(self): | |
| super().__init__() | |
| self.e1 = nn.Sequential(sn_conv(1, 64, 4, s=2, p=1), nn.LeakyReLU(0.2, True)) | |
| self.e2 = DownBlock(64, 128); self.e3 = DownBlock(128, 256) | |
| self.e4 = DownBlock(256, 512); self.e5 = DownBlock(512, 512) | |
| self.e6 = DownBlock(512, 512); self.e7 = DownBlock(512, 512) | |
| self.e8 = DownBlock(512, 512, norm=False) | |
| self.d1 = UpBlock(512, 512, drop=True); self.d2 = UpBlock(1024, 512, drop=True) | |
| self.d3 = UpBlock(1024, 512, drop=True); self.d4 = UpBlock(1024, 512) | |
| self.d5 = UpBlock(1024, 256); self.d6 = UpBlock(512, 128) | |
| self.d7 = UpBlock(256, 64) | |
| self.final = nn.Sequential( | |
| nn.Upsample(scale_factor=2, mode='bilinear', align_corners=False), | |
| sn_conv(128, 3, 3, s=1, p=1), nn.Tanh() | |
| ) | |
| def forward(self, x): | |
| e1=self.e1(x); e2=self.e2(e1); e3=self.e3(e2); e4=self.e4(e3) | |
| e5=self.e5(e4); e6=self.e6(e5); e7=self.e7(e6); e8=self.e8(e7) | |
| d1=self.d1(e8) | |
| d2=self.d2(torch.cat([d1,e7],1)); d3=self.d3(torch.cat([d2,e6],1)) | |
| d4=self.d4(torch.cat([d3,e5],1)); d5=self.d5(torch.cat([d4,e4],1)) | |
| d6=self.d6(torch.cat([d5,e3],1)); d7=self.d7(torch.cat([d6,e2],1)) | |
| return self.final(torch.cat([d7,e1],1)) | |
| # ── Load models ─────────────────────────────────────────── | |
| print("Loading SR model...") | |
| sr_model = SRNet().to(device) | |
| sr_model.load_state_dict(torch.load('sr_model_best_psnr.pth', map_location=device)) | |
| sr_model.eval() | |
| print("SR model loaded ✓") | |
| print("Loading colorization model...") | |
| G_color = UNetGenerator().to(device) | |
| G_color.load_state_dict(torch.load('color_G_best.pth', map_location=device)) | |
| G_color.eval() | |
| print("Colorization model loaded ✓") | |
| # ── Inference function ──────────────────────────────────── | |
| def colorize(npy_file): | |
| if npy_file is None: | |
| return None | |
| # Load the .npy file | |
| tir = np.load(npy_file.name).astype(np.float32) | |
| print(f"Input shape: {tir.shape}, min={tir.min():.4f}, max={tir.max():.4f}") | |
| # Validate shape | |
| if tir.ndim == 2: | |
| tir = tir[np.newaxis] # add channel dim if missing | |
| if tir.shape != (1, 256, 256): | |
| return None # wrong shape | |
| # Normalize to [0, 1] | |
| lo, hi = tir.min(), tir.max() | |
| tir_norm = (tir - lo) / (hi - lo + 1e-8) | |
| with torch.no_grad(): | |
| # Stage A: Super-Resolution (256x256 → 512x512) | |
| tir_sr = sr_model( | |
| torch.from_numpy(tir_norm).unsqueeze(0).to(device) | |
| ).squeeze(0).cpu().numpy() | |
| # Stage B: Colorization (TIR → RGB) | |
| rgb_fake = G_color( | |
| torch.from_numpy(tir_sr).unsqueeze(0).to(device) | |
| ).squeeze(0).cpu().numpy() | |
| # Convert from [-1,1] to [0,255] uint8 | |
| rgb = np.clip((rgb_fake + 1) / 2, 0, 1) | |
| rgb_uint8 = (rgb * 255).astype(np.uint8).transpose(1, 2, 0) # CHW → HWC | |
| return Image.fromarray(rgb_uint8, 'RGB') | |
| # ── Gradio UI ───────────────────────────────────────────── | |
| demo = gr.Interface( | |
| fn=colorize, | |
| inputs=gr.File(label="Upload tir_200m.npy patch (shape: 1×256×256)"), | |
| outputs=gr.Image(label="Colorized RGB Output (512×512)", type="pil"), | |
| title="🛰️ IR2RGB-Net — Thermal Infrared to RGB Colorization", | |
| description=""" | |
| **Team VoidBit | BAH2026 PS10** | |
| Upload a Landsat 9 thermal infrared patch (`.npy` file, shape `1×256×256`) | |
| and the model will: | |
| 1. **Super-resolve** it from 200m → 100m resolution (256×256 → 512×512) | |
| 2. **Colorize** it into a synthetic RGB representation | |
| The output is a 512×512 RGB image showing an interpretable color version of the thermal data. | |
| """, | |
| flagging_mode="never" | |
| ) | |
| demo.launch() |