| from io import BytesIO |
| from pathlib import Path |
|
|
| import torch |
| import gradio as gr |
| from torchvision import transforms |
| from PIL import Image |
|
|
| from model import SimpleNet, CLASS_NAMES |
|
|
|
|
| WEIGHTS_DIR = Path("weights") |
| EXAMPLES = [ |
| ["examples/annualcrop_sample.jpg"], |
| ["examples/forest_sample.jpg"], |
| ["examples/highway_sample.jpg"], |
| ["examples/industrial_sample.jpg"], |
| ["examples/residential_sample.jpg"], |
| ["examples/sealake_sample.jpg"], |
| ] |
|
|
|
|
| def load_model(): |
| model = SimpleNet(num_classes=10) |
| weight_bytes = b"".join( |
| path.read_bytes() for path in sorted(WEIGHTS_DIR.glob("simple_net_v1.part*")) |
| ) |
| state_dict = torch.load(BytesIO(weight_bytes), map_location="cpu") |
| state_dict = { |
| name: tensor.float() if torch.is_floating_point(tensor) else tensor |
| for name, tensor in state_dict.items() |
| } |
| model.load_state_dict(state_dict) |
| model.eval() |
| return model |
|
|
|
|
| model = load_model() |
|
|
| preprocess = transforms.Compose([ |
| transforms.Resize((64, 64)), |
| transforms.ToTensor(), |
| transforms.Normalize(mean=[0.485, 0.456, 0.406], |
| std=[0.229, 0.224, 0.225]), |
| ]) |
|
|
|
|
| def predict(image: Image.Image) -> dict[str, float]: |
| if image is None: |
| return {} |
|
|
| image = image.convert("RGB") |
| tensor = preprocess(image).unsqueeze(0) |
|
|
| with torch.no_grad(): |
| logits = model(tensor) |
| probs = torch.nn.functional.softmax(logits, dim=1)[0] |
|
|
| return {CLASS_NAMES[i]: float(probs[i]) for i in range(len(CLASS_NAMES))} |
|
|
|
|
| demo = gr.Interface( |
| fn=predict, |
| inputs=gr.Image(type="pil", label="Upload a Sentinel-style land image"), |
| outputs=gr.Label(num_top_classes=5, label="Top land-use guesses"), |
| title="EuroSAT Field Scout", |
| description=( |
| "A small local-first Gradio classifier for quick land-use triage. " |
| "It runs a custom PyTorch CNN trained on EuroSAT and returns the closest scene class." |
| ), |
| article=( |
| "Built for the Build Small Hackathon Backyard AI track. " |
| "No cloud inference API, no giant model: the Space loads local weights " |
| "and runs CPU inference inside the app." |
| ), |
| examples=EXAMPLES, |
| cache_examples=False, |
| allow_flagging="never", |
| theme=gr.themes.Soft(), |
| ) |
|
|
| if __name__ == "__main__": |
| demo.queue(default_concurrency_limit=2).launch() |
|
|