Spaces:
Running on Zero
Running on Zero
| try: | |
| import spaces | |
| except ImportError: | |
| spaces = None | |
| import os | |
| import torch | |
| import numpy as np | |
| from PIL import Image | |
| import torchvision.transforms as transforms | |
| import gradio as gr | |
| from models.networks import define_G | |
| # --- 1. Model Configuration --- | |
| class Args: | |
| n_class = 2 | |
| net_G = 'base_transformer_pos_s4_dd8_dedim8' | |
| gpu_ids = [] # [] for CPU inference | |
| img_size = 256 | |
| args = Args() | |
| device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") | |
| # --- 2. Load Model & Weights --- | |
| print(f"Loading model on {device}...") | |
| model = define_G(args=args, gpu_ids=args.gpu_ids) | |
| checkpoint_path = os.path.join("checkpoints", "BIT_LEVIR", "best_ckpt.pt") | |
| if not os.path.exists(checkpoint_path): | |
| raise FileNotFoundError(f"Checkpoint not found at: {checkpoint_path}") | |
| try: | |
| checkpoint = torch.load(checkpoint_path, map_location=device, weights_only=False) | |
| except TypeError: | |
| checkpoint = torch.load(checkpoint_path, map_location=device) | |
| model.load_state_dict(checkpoint["model_G_state_dict"]) | |
| model.to(device) | |
| model.eval() | |
| # --- 3. Image Preprocessing --- | |
| transform = transforms.Compose([ | |
| transforms.Resize((256, 256)), | |
| transforms.ToTensor(), | |
| transforms.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5]) | |
| ]) | |
| # --- 4. Prediction Function --- | |
| def _predict_change(img_a_pil, img_b_pil): | |
| if img_a_pil is None or img_b_pil is None: | |
| return None, None | |
| # Convert to RGB | |
| img_a_rgb = img_a_pil.convert("RGB") | |
| img_b_rgb = img_b_pil.convert("RGB") | |
| # Determine runtime device (cuda if ZeroGPU allocated, else cpu) | |
| runtime_device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") | |
| model.to(runtime_device) | |
| # Transform to tensor | |
| tensor_a = transform(img_a_rgb).unsqueeze(0).to(runtime_device) | |
| tensor_b = transform(img_b_rgb).unsqueeze(0).to(runtime_device) | |
| with torch.no_grad(): | |
| output = model(tensor_a, tensor_b) | |
| pred = torch.argmax(output, dim=1).squeeze(0).cpu().numpy() | |
| # Binary mask: 0 (No change, Black), 255 (Change, White) | |
| mask = (pred * 255).astype(np.uint8) | |
| mask_pil = Image.fromarray(mask, mode="L") | |
| # Create a red change overlay on Image B for clear visualization | |
| img_b_resized = img_b_rgb.resize((256, 256)) | |
| img_b_np = np.array(img_b_resized) | |
| overlay_np = img_b_np.copy() | |
| overlay_np[mask == 255] = [255, 50, 50] # Highlight changes in Red | |
| blended = Image.blend(img_b_resized, Image.fromarray(overlay_np), alpha=0.5) | |
| return mask_pil, blended | |
| if spaces is not None: | |
| predict_change = spaces.GPU(_predict_change) | |
| else: | |
| predict_change = _predict_change | |
| # --- 5. Gradio Web UI --- | |
| demo = gr.Interface( | |
| fn=predict_change, | |
| inputs=[ | |
| gr.Image(type="pil", label="Image A (Before)"), | |
| gr.Image(type="pil", label="Image B (After)") | |
| ], | |
| outputs=[ | |
| gr.Image(type="pil", label="Change Detection Mask"), | |
| gr.Image(type="pil", label="Overlay on Image B (Red = Change)") | |
| ], | |
| title="🛰️ Remote Sensing Change Detection (BIT_CD)", | |
| description="Upload two satellite/aerial images (Before & After) to detect structural and landscape changes.", | |
| examples=[ | |
| ["samples/A/test_2_0000_0000.png", "samples/B/test_2_0000_0000.png"] | |
| ] if os.path.exists("samples/A/test_2_0000_0000.png") else None | |
| ) | |
| if __name__ == "__main__": | |
| demo.launch() | |