Files changed (3) hide show
  1. README.md +12 -7
  2. app.py +112 -0
  3. requirements-2.txt +11 -0
README.md CHANGED
@@ -1,13 +1,18 @@
1
  ---
2
- title: Testroom
3
- emoji: 🏃
4
- colorFrom: indigo
5
- colorTo: gray
6
  sdk: gradio
7
- sdk_version: 6.15.2
8
- python_version: '3.12'
9
  app_file: app.py
10
  pinned: false
11
  ---
12
 
13
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
1
  ---
2
+ title: DARKROOM
3
+ emoji: 🎨
4
+ colorFrom: gray
5
+ colorTo: yellow
6
  sdk: gradio
7
+ sdk_version: 5.49.1
 
8
  app_file: app.py
9
  pinned: false
10
  ---
11
 
12
+ # DARKROOM AI art page restorer + HandRefiner (ZeroGPU)
13
+
14
+ Serves the DARKROOM web tool and runs HandRefiner (MeshGraphormer depth +
15
+ ControlNet inpainting) on a free, on-demand ZeroGPU. Open the Space and use it.
16
+
17
+ **Setup:** New Space → SDK **Gradio** → Hardware **ZeroGPU** → upload `app.py`,
18
+ `requirements.txt`, `README.md`.
app.py CHANGED
@@ -0,0 +1,112 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ DARKROOM HandRefiner — Hugging Face ZeroGPU Space
3
+ =================================================
4
+ Standard Gradio Interface (the pattern ZeroGPU actually supports): upload an
5
+ image, optionally paint a mask, get the hands structurally fixed on a free
6
+ on-demand GPU. This is the reliable shape — the previous "custom FastAPI route"
7
+ build failed with "No @spaces.GPU function detected" because ZeroGPU only
8
+ detects GPU functions wired into a normal Gradio app.
9
+
10
+ PIPELINE: MeshGraphormer hand-mesh -> depth map -> depth ControlNet ->
11
+ Stable Diffusion inpainting (HandRefiner). Fixes only the hand region.
12
+
13
+ --------------------------------------------------------------------------
14
+ DEPLOY (needs a HF PRO account to CREATE a ZeroGPU Space — $9/mo)
15
+ --------------------------------------------------------------------------
16
+ 1. huggingface.co -> New Space -> SDK: Gradio -> Hardware: ZeroGPU
17
+ 2. Upload: app.py, requirements.txt, README.md
18
+ 3. Wait for build, then use the Space UI (or call it from the DARKROOM tool
19
+ via the gradio_client endpoint shown on the Space's "View API" page).
20
+
21
+ HONEST LIMITS:
22
+ * Creating a ZeroGPU Space requires PRO. Using one is free within a daily quota
23
+ (resets 24h after first use); each fix is a few GPU-seconds.
24
+ * GPU duration is capped (~120s max). We request 90s.
25
+ * Stock depth ControlNet is okay-not-perfect; swap CONTROLNET_ID to
26
+ hr16/ControlNet-HandRefiner-pruned for finetuned quality.
27
+ * MeshGraphormer can't fix unreadable hands or crossed fingers.
28
+ """
29
+
30
+ import spaces # must precede torch for ZeroGPU
31
+ import torch
32
+ from PIL import Image, ImageFilter
33
+ import gradio as gr
34
+
35
+ SD_INPAINT_ID = "runwayml/stable-diffusion-inpainting"
36
+ CONTROLNET_ID = "lllyasviel/control_v11f1p_sd15_depth" # -> hr16/ControlNet-HandRefiner-pruned for best
37
+ MESHGRAPHORMER_ID = "hr16/ControlNet-HandRefiner-pruned"
38
+ MAX_SIDE = 768
39
+ DEFAULT_PROMPT = "a detailed, anatomically correct hand with five fingers, natural proportions, same art style and lighting"
40
+ NEG = "extra fingers, fused fingers, missing fingers, deformed, mutated, blurry, low quality"
41
+
42
+ _PIPE = None
43
+ _MESH = None
44
+
45
+ def _load():
46
+ global _PIPE, _MESH
47
+ if _PIPE is not None:
48
+ return
49
+ from diffusers import StableDiffusionControlNetInpaintPipeline, ControlNetModel, UniPCMultistepScheduler
50
+ from controlnet_aux import MeshGraphormerDetector
51
+ _MESH = MeshGraphormerDetector.from_pretrained(MESHGRAPHORMER_ID).to("cuda")
52
+ cn = ControlNetModel.from_pretrained(CONTROLNET_ID, torch_dtype=torch.float16)
53
+ pipe = StableDiffusionControlNetInpaintPipeline.from_pretrained(
54
+ SD_INPAINT_ID, controlnet=cn, torch_dtype=torch.float16, safety_checker=None
55
+ )
56
+ pipe.scheduler = UniPCMultistepScheduler.from_config(pipe.scheduler.config)
57
+ _PIPE = pipe.to("cuda")
58
+
59
+ def _fit(img):
60
+ w, h = img.size
61
+ s = min(1.0, MAX_SIDE / max(w, h))
62
+ return img.resize((max(8, int(round(w*s/8))*8), max(8, int(round(h*s/8))*8)), Image.LANCZOS), (w, h)
63
+
64
+ @spaces.GPU(duration=90)
65
+ def fix_hands(image, mask_layers, prompt, strength):
66
+ """ZeroGPU-allocated worker, wired directly into the Gradio Interface below."""
67
+ if image is None:
68
+ raise gr.Error("Upload an image first.")
69
+ _load()
70
+ init, (ow, oh) = _fit(image.convert("RGB"))
71
+ W, H = init.size
72
+
73
+ # optional hand-drawn mask from the ImageMask/Sketchpad component
74
+ sent_mask = None
75
+ if isinstance(mask_layers, dict):
76
+ layers = mask_layers.get("layers") or []
77
+ if layers:
78
+ m = layers[0].convert("L").resize((W, H), Image.LANCZOS)
79
+ if m.getbbox() is not None:
80
+ sent_mask = m
81
+
82
+ mg = _MESH(init)
83
+ depth_img, auto_mask = (mg[0], (mg[1] if len(mg) > 1 else None)) if isinstance(mg, tuple) else (mg, None)
84
+ depth_img = depth_img.convert("RGB").resize((W, H), Image.LANCZOS)
85
+ mask_img = sent_mask or (auto_mask.convert("L").resize((W, H), Image.LANCZOS) if auto_mask else None)
86
+ if mask_img is None:
87
+ raise gr.Error("No hands detected. Paint a mask over the hand and try again.")
88
+
89
+ mask_img = mask_img.filter(ImageFilter.GaussianBlur(2))
90
+ out = _PIPE(
91
+ prompt=prompt or DEFAULT_PROMPT, negative_prompt=NEG, image=init, mask_image=mask_img,
92
+ control_image=depth_img, num_inference_steps=30, strength=float(strength),
93
+ guidance_scale=7.5, controlnet_conditioning_scale=0.7,
94
+ ).images[0]
95
+ return out.resize((ow, oh), Image.LANCZOS)
96
+
97
+ with gr.Blocks(title="DARKROOM HandRefiner", theme=gr.themes.Base()) as demo:
98
+ gr.Markdown("## 🖐️ DARKROOM HandRefiner\nUpload AI art with bad hands. It auto-detects hands "
99
+ "(MeshGraphormer) and regenerates them with correct geometry. Optionally paint a mask "
100
+ "to target a specific hand. Free GPU runs a few seconds per fix.")
101
+ with gr.Row():
102
+ with gr.Column():
103
+ inp = gr.ImageMask(type="pil", label="Image (optionally paint over the bad hand)")
104
+ prompt = gr.Textbox(value=DEFAULT_PROMPT, label="Prompt", lines=2)
105
+ strength = gr.Slider(0.3, 1.0, value=0.75, step=0.05, label="Fix strength (denoise)")
106
+ btn = gr.Button("Fix hands", variant="primary")
107
+ with gr.Column():
108
+ out = gr.Image(type="pil", label="Result")
109
+ btn.click(fix_hands, inputs=[inp, inp, prompt, strength], outputs=out, api_name="fix_hands")
110
+
111
+ if __name__ == "__main__":
112
+ demo.queue().launch()
requirements-2.txt ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ spaces
2
+ gradio==5.49.1
3
+ torch
4
+ diffusers
5
+ transformers
6
+ accelerate
7
+ controlnet_aux
8
+ pillow
9
+ numpy
10
+ scipy
11
+ python-multipart