deepsky / app.py
jessholbrook's picture
Upload app.py with huggingface_hub
aa36f06 verified
Raw
History Blame Contribute Delete
3.7 kB
"""Gradio demo for the deepsky diffusion model β€” live 128px generation.
Loads the trained EMA weights from the Hugging Face Hub and samples with DDIM.
Runs on the Space's CPU by default (or GPU if the Space has one).
"""
import random
import sys
import gradio as gr
import numpy as np
import torch
from huggingface_hub import hf_hub_download
from PIL import Image
from deepsky.config import ModelCfg
from deepsky.diffusion.gaussian import GaussianDiffusion
from deepsky.diffusion.samplers import ddim_sample
from deepsky.diffusion.schedule import make_schedule
from deepsky.models.unet import UNet
REPO_ID = "jessholbrook/deepsky-128px"
CKPT = "ckpt_0200000.pt"
IMAGE_SIZE = 128
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
# 128px architecture β€” matches configs/cloud-128px-full.yaml.
cfg = ModelCfg(
base_channels=128,
channel_mults=(1, 2, 3, 4),
num_res_blocks=2,
attn_resolutions=(16, 8),
time_emb_dim=512,
)
# Lazy singletons β€” built on first generate() so the Gradio server binds its
# port immediately and the Space reaches "Running" instead of timing out while
# a 1.39 GB checkpoint downloads + loads on the free CPU tier.
_model = None
_diffusion = None
def _load():
global _model, _diffusion
if _model is not None:
return _model, _diffusion
print("Downloading checkpoint from the Hub…", flush=True)
ckpt_path = hf_hub_download(REPO_ID, CKPT)
print(f"Loading weights from {ckpt_path} onto {device}…", flush=True)
state = torch.load(ckpt_path, map_location=device)
m = UNet(cfg, IMAGE_SIZE).to(device)
m.load_state_dict(state["ema"]["shadow"]) # EMA weights = best quality
m.eval()
_model = m
_diffusion = GaussianDiffusion(make_schedule("cosine", 1000).to(device))
print("Model ready.", flush=True)
return _model, _diffusion
@torch.no_grad()
def generate(steps, seed):
model, diffusion = _load()
if seed is None or int(seed) < 0:
seed = random.randint(0, 2**31 - 1)
torch.manual_seed(int(seed))
samples = ddim_sample(
model, diffusion, (1, 3, IMAGE_SIZE, IMAGE_SIZE),
device, steps=int(steps), progress=False,
)
arr = ((samples[0].clamp(-1, 1) + 1) / 2 * 255).byte().permute(1, 2, 0).cpu().numpy()
return Image.fromarray(np.ascontiguousarray(arr)), int(seed)
with gr.Blocks(title="deepsky") as demo:
gr.Markdown(
"# 🌌 deepsky\n"
"A **from-scratch** diffusion model generating deep-sky astronomical images β€” "
"nebulae, galaxies, star clusters β€” trained on public ESA/Hubble, ESA/Webb, "
"ESO, and NASA imagery. Every pixel is generated; nothing is retrieved.\n\n"
"Model: [jessholbrook/deepsky-128px](https://huggingface.co/jessholbrook/deepsky-128px) Β· "
"Code: [github.com/jessholbrook/deepsky](https://github.com/jessholbrook/deepsky)\n\n"
"*The first generation also loads the model (one-time ~1–2 min). After that, "
"each 128px image takes ~30–90s on the free CPU tier β€” lower the step count for speed.*"
)
with gr.Row():
with gr.Column(scale=1):
steps = gr.Slider(20, 150, value=50, step=5,
label="DDIM steps (more = sharper, slower)")
seed = gr.Number(value=-1, label="Seed (βˆ’1 = random)", precision=0)
btn = gr.Button("✨ Generate", variant="primary")
used = gr.Number(label="Seed used", interactive=False, precision=0)
with gr.Column(scale=1):
out = gr.Image(label="Generated 128px sample", type="pil", height=384)
btn.click(generate, [steps, seed], [out, used])
demo.queue(max_size=8).launch()