Spaces:
Running on Zero
Running on Zero
Download app.py from hugging-apps/ivme-denoise: direct link, hf CLI and curl.
- Browser
- Download file 5.67 kB
-
https://huggingface.co/spaces/hugging-apps/ivme-denoise/resolve/main/app.py
- Command line
-
hf download hf://spaces/hugging-apps/ivme-denoise/app.py
-
curl -L -o app.py https://huggingface.co/spaces/hugging-apps/ivme-denoise/resolve/main/app.py
5.67 kB
| """ | |
| İvme-Denoise-v1 (İvmetron-Guanine1) — 2.97 M-parameter blind image denoiser. | |
| Demo Space for IvmeLabs/Ivme-Denoise-v1: a tiny Bayesian-shrinkage U-Net | |
| that beats DnCNN on every standard benchmark at 65 % of its FLOPs. | |
| """ | |
| import os | |
| import spaces # MUST come before torch | |
| import gradio as gr | |
| import numpy as np | |
| import torch # noqa: F401 (imported after spaces on purpose) | |
| from PIL import Image | |
| from modeling_ivmetron import load_model, denoise_tiled, estimate_sigma_map | |
| MODEL_ID = "IvmeLabs/Ivme-Denoise-v1" | |
| # Model is tiny (12 MB) — load eagerly at module scope, on the GPU. | |
| model = load_model(MODEL_ID, device="cuda") | |
| MAX_SIDE = 3072 # cap gigantic uploads; up to this size the tiled path stays fast | |
| CSS = """ | |
| #col-container { max-width: 1100px; margin: 0 auto; } | |
| .dark .gradio-container { color: var(--body-text-color); } | |
| """ | |
| def _to_float_array(pil_img): | |
| """PIL image -> HxWx3 float32 in [0,1], downscaled if huge.""" | |
| img = pil_img.convert("RGB") | |
| w, h = img.size | |
| if max(w, h) > MAX_SIDE: | |
| scale = MAX_SIDE / max(w, h) | |
| img = img.resize( | |
| (max(1, int(round(w * scale))), max(1, int(round(h * scale)))), | |
| Image.LANCZOS, | |
| ) | |
| return np.asarray(img, dtype=np.float32) / 255.0 | |
| def denoise_image(image, sigma_mode, sigma_value, progress=gr.Progress()): | |
| """Denoise an sRGB photograph with İvme-Denoise-v1. | |
| Args: | |
| image: the noisy input photograph (sRGB, any resolution). | |
| sigma_mode: "Auto (estimate per-pixel sigma map)" or "Manual sigma". | |
| sigma_value: manual noise level on the 0-255 scale (5-50), used only | |
| when sigma_mode is "Manual sigma". | |
| Returns: | |
| The denoised image. | |
| """ | |
| if image is None: | |
| raise gr.Error("Please upload an image first.") | |
| progress(0.1, desc="Preparing image") | |
| img = _to_float_array(image) | |
| if str(sigma_mode).startswith("Auto"): | |
| progress(0.3, desc="Estimating noise map") | |
| sigma = estimate_sigma_map(img) | |
| mode_note = f"auto σ ≈ {float(np.asarray(sigma).mean()):.1f}" | |
| else: | |
| sigma = float(sigma_value) | |
| mode_note = f"manual σ = {sigma:.0f}" | |
| progress(0.5, desc="Denoising") | |
| out = denoise_tiled(model, img, sigma, device="cuda") | |
| progress(1.0, desc=f"Done ({mode_note})") | |
| return Image.fromarray((np.clip(out, 0, 1) * 255).round().astype(np.uint8)) | |
| with gr.Blocks() as demo: | |
| gr.Markdown( | |
| f""" | |
| # İvme-Denoise-v1 · *Margay 1* | |
| **2.97 M-parameter blind image denoiser** (İvmetron-Guanine1) — every | |
| non-linearity is the exact posterior mean of a Gaussian scale mixture. | |
| Beats DnCNN on every standard benchmark using **35 % fewer FLOPs**, within | |
| 0.22–0.54 dB of DRUNet / Restormer at a fraction of their size. | |
| \n | |
| Model: [{MODEL_ID}](https://huggingface.co/{MODEL_ID}) · Apache-2.0 · | |
| 12 MB weights · 17.9 ms per 512² frame | |
| """ | |
| ) | |
| with gr.Column(elem_id="col-container"): | |
| with gr.Row(): | |
| with gr.Column(): | |
| image_in = gr.Image( | |
| type="pil", label="Noisy input", height=340, | |
| sources=["upload", "clipboard"], | |
| ) | |
| sigma_mode = gr.Radio( | |
| ["Auto (estimate per-pixel sigma map)", "Manual sigma"], | |
| value="Auto (estimate per-pixel sigma map)", | |
| label="Noise level", | |
| ) | |
| sigma_value = gr.Slider( | |
| 5, 50, value=15, step=1, | |
| label="Manual σ (0–255 scale)", | |
| info="Only used in Manual mode. The model is trained for σ ∈ [5, 50].", | |
| ) | |
| run_btn = gr.Button("Denoise", variant="primary") | |
| with gr.Column(): | |
| image_out = gr.Image(type="pil", label="Denoised output", height=340) | |
| gr.Examples( | |
| examples=[ | |
| ["examples/park_original.png", | |
| "Auto (estimate per-pixel sigma map)", 15], | |
| ["examples/cat_noisy_sigma15.png", "Manual sigma", 15], | |
| ["examples/owl_noisy_sigma25.png", "Manual sigma", 25], | |
| ["examples/night_city_noisy_sigma50.png", "Manual sigma", 50], | |
| ], | |
| inputs=[image_in, sigma_mode, sigma_value], | |
| outputs=[image_out], | |
| fn=denoise_image, | |
| cache_examples=True, | |
| cache_mode="lazy", | |
| ) | |
| gr.Markdown( | |
| """ | |
| **How to use** — upload any sRGB photo (real high-ISO camera output works | |
| best). Leave the noise level on *Auto* to let the built-in robust | |
| estimator produce a per-pixel σ map; or switch to *Manual* and set σ | |
| yourself (the benchmark convention: 15 / 25 / 50 on the 0–255 scale). | |
| **Notes** — sRGB only (no linear RAW/HDR). Huge uploads are | |
| downscaled to 3072 px on the long side. Images above ~5 MP are | |
| denoised with tiled inference (896-px tiles, 96-px overlap, Hann | |
| blending) exactly as in the reference implementation. | |
| *Example images: the park frame is from the model card | |
| (Apache-2.0); the others are CC0 images with synthetic Gaussian | |
| noise at the model's benchmark σ levels.* | |
| """ | |
| ) | |
| run_btn.click( | |
| denoise_image, | |
| inputs=[image_in, sigma_mode, sigma_value], | |
| outputs=[image_out], | |
| api_name="denoise", | |
| concurrency_limit=1, | |
| ) | |
| demo.launch(theme=gr.themes.Citrus(), css=CSS, mcp_server=True) | |