webVishnu commited on
Commit
9a6b7b7
·
1 Parent(s): 17a8b39

Desert segmentation Gradio Space

Browse files
.gitattributes ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ # Use Git LFS for checkpoints when pushing to Hugging Face (avoids hard size limits).
2
+ *.pt filter=lfs diff=lfs merge=lfs -text
HUGGINGFACE_SPACES.md ADDED
@@ -0,0 +1,93 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Deploy this project to Hugging Face Spaces
2
+
3
+ Your Space: `https://huggingface.co/spaces/webVishnu/code-wizards` (replace if yours differs).
4
+
5
+ ## 1. One-time: access token
6
+
7
+ Create a token with **write** access: [Settings → Access Tokens](https://huggingface.co/settings/tokens).
8
+
9
+ ## 2. Connect git to the Space
10
+
11
+ From **this repository root** (where `app.py` lives). If this folder is **already** a git repo, skip `git init`.
12
+
13
+ ```powershell
14
+ git remote add space https://huggingface.co/spaces/webVishnu/code-wizards
15
+ ```
16
+
17
+ If `git remote add` fails because `space` exists, use `git remote set-url space https://huggingface.co/spaces/webVishnu/code-wizards`.
18
+
19
+ Stage what the Space needs (trim paths you do not want public):
20
+
21
+ ```powershell
22
+ git add app.py requirements.txt packages.txt .gitattributes desert_segmentation scripts
23
+ git add HUGGINGFACE_SPACES.md README_HF_SPACES.md
24
+ ```
25
+
26
+ Optional: `git add eval_summary.json` (validation accuracy in the UI). Optional: `git add checkpoints/best.pt` (only with **Git LFS**; see step 3).
27
+
28
+ - Add **`checkpoints/best.pt`** only if you use a file in the repo (use **Git LFS**; see below). Otherwise use Hub download (step 4).
29
+ - Do **not** commit huge `training/` folders unless you intend to; the demo only needs code + weights + optional `eval_summary.json`.
30
+
31
+ **Space README (card + Gradio metadata):** Hugging Face reads **`README.md`** at the Space repo root (YAML frontmatter + markdown).
32
+
33
+ - **Easiest (does not touch your local GitHub README):** After the first successful push, open the Space → **Files** → `README.md` → **Edit**, delete the boilerplate, and **paste the full contents of `README_HF_SPACES.md`**, then commit on the Hub.
34
+ - **If this folder is only for the Space:** you can `git add` a `README.md` that is a copy of `README_HF_SPACES.md` (rename the file to `README.md` before committing).
35
+ - **If you use one git repo for both GitHub and HF:** do **not** overwrite your long `README.md`; use the Hub web editor for the Space card, or maintain a dedicated branch for the Space with a short `README.md`.
36
+
37
+ ## 3. Git LFS for `best.pt` (if the checkpoint lives in the repo)
38
+
39
+ ```powershell
40
+ git lfs install
41
+ git lfs track "*.pt"
42
+ git add .gitattributes checkpoints/best.pt
43
+ ```
44
+
45
+ ## 4. Weights without committing the file (recommended for large checkpoints)
46
+
47
+ 1. Create a **Model** repository on the Hub and upload `best.pt` (with LFS).
48
+ 2. In the Space → **Settings → Repository secrets**, add nothing extra for public models.
49
+ 3. In the Space → **Settings → Variables** (or **Secrets**), add:
50
+ - **`HF_HUB_CHECKPOINT_REPO`** = your model repo id, e.g. `webVishnu/desert-seg-best`
51
+ - **`HF_HUB_CHECKPOINT_FILENAME`** = `best.pt` (optional if the filename is `best.pt`)
52
+
53
+ `app.py` will download the file on startup. For a **private** model repo, add a read token as **`HF_TOKEN`** (secret).
54
+
55
+ Alternatively set **`CHECKPOINT_PATH`** to an absolute path inside the container (rare).
56
+
57
+ ## 5. Optional: validation accuracy line in the UI
58
+
59
+ Commit **`eval_summary.json`** at the repo root (same layout as local). The demo shows **Accuracy (val)** when `global_pixel_accuracy` is present.
60
+
61
+ ## 6. Hardware
62
+
63
+ Space **Settings → Hardware**: start with **CPU** to verify the build; switch to **GPU** (e.g. T4) for usable speed. Update the `hardware:` field in the Space `README.md` YAML if you use a template that expects it.
64
+
65
+ ## 7. Push
66
+
67
+ ```powershell
68
+ git commit -m "Add Gradio Space app and dependencies"
69
+ git push space main
70
+ ```
71
+
72
+ If the default branch on the Hub is `main`, use `main`. Use your **token** as the password when Git asks.
73
+
74
+ ## 8. Verify
75
+
76
+ Open the **App** tab. When the container builds, you should see the demo (first load downloads weights if using Hub env vars).
77
+
78
+ ## Environment variables reference
79
+
80
+ | Variable | Purpose |
81
+ |----------|---------|
82
+ | `CHECKPOINT_PATH` | Path to `.pt` if bundled or mounted |
83
+ | `HF_HUB_CHECKPOINT_REPO` | Model repo id for `hf_hub_download` |
84
+ | `HF_HUB_CHECKPOINT_FILENAME` | Filename in that repo (default `best.pt`) |
85
+ | `HF_TOKEN` | Read token for private Hub weights |
86
+ | `DEMO_MAX_SIDE` | Max image side (default `4096`) |
87
+ | `DEMO_MAX_MEGAPIXELS` | Max megapixels (default `16`) |
88
+
89
+ Local run still works:
90
+
91
+ ```powershell
92
+ python scripts/demo_gradio.py --checkpoint checkpoints/best.pt
93
+ ```
README_HF_SPACES.md ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Desert semantic segmentation
3
+ emoji: 🏜️
4
+ colorFrom: yellow
5
+ colorTo: orange
6
+ sdk: gradio
7
+ app_file: app.py
8
+ pinned: false
9
+ license: apache-2.0
10
+ short_description: Upload a desert/off-road RGB image and get a per-pixel class mask, overlay, and metrics.
11
+ ---
12
+
13
+ # Desert semantic segmentation
14
+
15
+ Interactive **semantic segmentation** demo: each pixel is classified into terrain / scene categories. Upload an RGB image, click **Run segmentation**, and view the colored mask, overlay, strip, timing, optional **validation accuracy**, and dominant classes.
16
+
17
+ **Weights:** configure `CHECKPOINT_PATH`, or `HF_HUB_CHECKPOINT_REPO` (+ optional `HF_HUB_CHECKPOINT_FILENAME`), or commit `checkpoints/best.pt` (prefer **Git LFS**). See `HUGGINGFACE_SPACES.md` in the project repository.
18
+
19
+ **Hardware:** CPU works but is slow; enable a **GPU** in Space settings for responsive inference.
app.py ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Hugging Face Spaces entrypoint: exposes ``demo`` for the Gradio SDK.
3
+
4
+ Checkpoint resolution (first match wins):
5
+ 1. ``CHECKPOINT_PATH`` — absolute or relative to repo root, file must exist.
6
+ 2. ``HF_HUB_CHECKPOINT_REPO`` + optional ``HF_HUB_CHECKPOINT_FILENAME`` (default ``best.pt``)
7
+ — downloads from the Hub (use a Model repo; set Space secret ``HF_TOKEN`` if private).
8
+ 3. ``<repo_root>/checkpoints/best.pt``
9
+
10
+ Optional: ``eval_summary.json`` at repo root enables **Accuracy (val)** in the UI.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import logging
16
+ import os
17
+ import sys
18
+ from pathlib import Path
19
+ from typing import Optional
20
+
21
+ ROOT = Path(__file__).resolve().parent
22
+ if str(ROOT) not in sys.path:
23
+ sys.path.insert(0, str(ROOT))
24
+
25
+ logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
26
+
27
+ logger = logging.getLogger("app")
28
+
29
+
30
+ def _resolve_checkpoint(root: Path) -> Path:
31
+ env_path = os.environ.get("CHECKPOINT_PATH", "").strip()
32
+ if env_path:
33
+ p = Path(env_path)
34
+ if not p.is_absolute():
35
+ p = (root / p).resolve()
36
+ else:
37
+ p = p.resolve()
38
+ if p.is_file():
39
+ logger.info("Using checkpoint from CHECKPOINT_PATH=%s", p)
40
+ return p
41
+ logger.warning("CHECKPOINT_PATH set but not a file: %s", p)
42
+
43
+ hub_repo = os.environ.get("HF_HUB_CHECKPOINT_REPO", "").strip()
44
+ if hub_repo:
45
+ from huggingface_hub import hf_hub_download
46
+
47
+ fn = os.environ.get("HF_HUB_CHECKPOINT_FILENAME", "best.pt").strip() or "best.pt"
48
+ token = os.environ.get("HF_TOKEN") or os.environ.get("HUGGING_FACE_HUB_TOKEN")
49
+ path = hf_hub_download(repo_id=hub_repo, filename=fn, token=token)
50
+ p = Path(path).resolve()
51
+ logger.info("Downloaded checkpoint from Hub repo=%s file=%s -> %s", hub_repo, fn, p)
52
+ return p
53
+
54
+ cand = root / "checkpoints" / "best.pt"
55
+ if cand.is_file():
56
+ logger.info("Using bundled checkpoint %s", cand)
57
+ return cand.resolve()
58
+
59
+ raise FileNotFoundError(
60
+ "No weights found. Do one of: "
61
+ "(1) Set Space secret / env CHECKPOINT_PATH to your .pt file, "
62
+ "(2) Set HF_HUB_CHECKPOINT_REPO (and optional HF_HUB_CHECKPOINT_FILENAME) to a Model repo, "
63
+ f"(3) Add checkpoints/best.pt under {root} (use Git LFS for large files)."
64
+ )
65
+
66
+
67
+ def _resolve_eval_summary(root: Path) -> Optional[Path]:
68
+ p = root / "eval_summary.json"
69
+ return p if p.is_file() else None
70
+
71
+
72
+ _checkpoint = _resolve_checkpoint(ROOT)
73
+ _eval_summary = _resolve_eval_summary(ROOT)
74
+
75
+ from scripts.demo_gradio import create_demo # noqa: E402
76
+
77
+ _max_side = int(os.environ.get("DEMO_MAX_SIDE", "4096"))
78
+ _max_mp = float(os.environ.get("DEMO_MAX_MEGAPIXELS", "16"))
79
+
80
+ demo = create_demo(
81
+ _checkpoint,
82
+ _eval_summary,
83
+ max_side=_max_side,
84
+ max_megapixels=_max_mp,
85
+ )
packages.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ # System packages for Spaces (OpenCV / headless image stack). Safe to ignore locally.
2
+ libglib2.0-0
3
+ libsm6
4
+ libxext6
5
+ libxrender1
requirements.txt CHANGED
@@ -8,3 +8,5 @@ albumentations>=1.3.1,<1.5
8
  segmentation-models-pytorch>=0.3.3
9
  tqdm>=4.66.0
10
  pytest>=7.4.0
 
 
 
8
  segmentation-models-pytorch>=0.3.3
9
  tqdm>=4.66.0
10
  pytest>=7.4.0
11
+ gradio>=4.44.0,<6
12
+ huggingface_hub>=0.20.0
scripts/demo_gradio.py CHANGED
@@ -176,49 +176,20 @@ def _run(
176
  return rgb, colored, overlay, strip, stats, dominant
177
 
178
 
179
- def main() -> None:
180
- logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
181
-
182
- parser = argparse.ArgumentParser(description="Gradio demo for desert semantic segmentation")
183
- parser.add_argument("--root", type=str, default=os.environ.get("ROOT"), help="Workspace root (default: repo root or env ROOT)")
184
- parser.add_argument(
185
- "--checkpoint",
186
- type=str,
187
- default=os.environ.get("CHECKPOINT_PATH"),
188
- help="Path to best.pt (default: env CHECKPOINT_PATH or <root>/checkpoints/best.pt)",
189
- )
190
- parser.add_argument("--host", type=str, default="127.0.0.1")
191
- parser.add_argument("--port", type=int, default=7860)
192
- parser.add_argument("--share", action="store_true", help="Create a temporary public Gradio link")
193
- parser.add_argument("--max-side", type=int, default=4096)
194
- parser.add_argument("--max-megapixels", type=float, default=16.0)
195
- parser.add_argument(
196
- "--eval-summary",
197
- type=str,
198
- default=None,
199
- help="Optional path to eval_summary.json (default: <root>/eval_summary.json if that file exists)",
200
- )
201
- args = parser.parse_args()
202
-
203
- root = Path(args.root or ROOT).resolve()
204
- ckpt_arg = args.checkpoint or str(root / "checkpoints" / "best.pt")
205
- ckpt = Path(ckpt_arg)
206
- if not ckpt.is_absolute():
207
- ckpt = (root / ckpt).resolve()
208
  if not ckpt.is_file():
209
- raise SystemExit(f"Checkpoint not found: {ckpt}")
210
-
211
- eval_summary_arg = args.eval_summary
212
- if eval_summary_arg:
213
- eval_summary = Path(eval_summary_arg)
214
- if not eval_summary.is_absolute():
215
- eval_summary = (root / eval_summary).resolve()
216
- else:
217
- cand = root / "eval_summary.json"
218
- eval_summary = cand if cand.is_file() else None
219
 
220
- device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
221
- _init_state(ckpt, device, eval_summary)
222
 
223
  icfg = _STATE["icfg"]
224
  def_tta = bool(icfg.get("tta_flip", True))
@@ -239,7 +210,7 @@ _Confidence heatmaps for full-resolution sliding windows are not in this demo (v
239
  """
240
 
241
  cpu_note = ""
242
- if device.type == "cpu":
243
  cpu_note = "\n\n> Running on **CPU** — expect slower inference. Use a CUDA GPU for best speed.\n"
244
 
245
  with gr.Blocks(title="Desert segmentation", theme=gr.themes.Soft()) as demo:
@@ -261,8 +232,8 @@ _Confidence heatmaps for full-resolution sliding windows are not in this demo (v
261
  gr.Markdown("### Class legend (fixed palette)")
262
  gr.HTML(_STATE["legend_html_static"])
263
 
264
- def _fn(img, tta, ov, ts):
265
- return _run(img, tta, ov, ts, args.max_side, args.max_megapixels)
266
 
267
  run_btn.click(
268
  fn=_fn,
@@ -270,6 +241,59 @@ _Confidence heatmaps for full-resolution sliding windows are not in this demo (v
270
  outputs=[out_orig, out_mask, out_overlay, out_strip, stats_md, dominant_md],
271
  )
272
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
273
  logger.info("Launching Gradio on http://%s:%s", args.host, args.port)
274
  demo.launch(server_name=args.host, server_port=args.port, share=args.share)
275
 
 
176
  return rgb, colored, overlay, strip, stats, dominant
177
 
178
 
179
+ def create_demo(
180
+ checkpoint: Path,
181
+ eval_summary: Optional[Path],
182
+ max_side: int = 4096,
183
+ max_megapixels: float = 16.0,
184
+ device: Optional[torch.device] = None,
185
+ ) -> gr.Blocks:
186
+ """Build the Gradio Blocks app (Hugging Face Spaces: assign ``demo = create_demo(...)``; do not call ``launch``)."""
187
+ ckpt = checkpoint.resolve()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
188
  if not ckpt.is_file():
189
+ raise FileNotFoundError(f"Checkpoint not found: {ckpt}")
 
 
 
 
 
 
 
 
 
190
 
191
+ dev = device or torch.device("cuda" if torch.cuda.is_available() else "cpu")
192
+ _init_state(ckpt, dev, eval_summary)
193
 
194
  icfg = _STATE["icfg"]
195
  def_tta = bool(icfg.get("tta_flip", True))
 
210
  """
211
 
212
  cpu_note = ""
213
+ if dev.type == "cpu":
214
  cpu_note = "\n\n> Running on **CPU** — expect slower inference. Use a CUDA GPU for best speed.\n"
215
 
216
  with gr.Blocks(title="Desert segmentation", theme=gr.themes.Soft()) as demo:
 
232
  gr.Markdown("### Class legend (fixed palette)")
233
  gr.HTML(_STATE["legend_html_static"])
234
 
235
+ def _fn(img: Any, tta: bool, ov: float, ts: float) -> Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray, str, str]:
236
+ return _run(img, tta, ov, ts, max_side, max_megapixels)
237
 
238
  run_btn.click(
239
  fn=_fn,
 
241
  outputs=[out_orig, out_mask, out_overlay, out_strip, stats_md, dominant_md],
242
  )
243
 
244
+ return demo
245
+
246
+
247
+ def main() -> None:
248
+ logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
249
+
250
+ parser = argparse.ArgumentParser(description="Gradio demo for desert semantic segmentation")
251
+ parser.add_argument("--root", type=str, default=os.environ.get("ROOT"), help="Workspace root (default: repo root or env ROOT)")
252
+ parser.add_argument(
253
+ "--checkpoint",
254
+ type=str,
255
+ default=os.environ.get("CHECKPOINT_PATH"),
256
+ help="Path to best.pt (default: env CHECKPOINT_PATH or <root>/checkpoints/best.pt)",
257
+ )
258
+ parser.add_argument("--host", type=str, default="127.0.0.1")
259
+ parser.add_argument("--port", type=int, default=7860)
260
+ parser.add_argument("--share", action="store_true", help="Create a temporary public Gradio link")
261
+ parser.add_argument("--max-side", type=int, default=4096)
262
+ parser.add_argument("--max-megapixels", type=float, default=16.0)
263
+ parser.add_argument(
264
+ "--eval-summary",
265
+ type=str,
266
+ default=None,
267
+ help="Optional path to eval_summary.json (default: <root>/eval_summary.json if that file exists)",
268
+ )
269
+ args = parser.parse_args()
270
+
271
+ root = Path(args.root or ROOT).resolve()
272
+ ckpt_arg = args.checkpoint or str(root / "checkpoints" / "best.pt")
273
+ ckpt = Path(ckpt_arg)
274
+ if not ckpt.is_absolute():
275
+ ckpt = (root / ckpt).resolve()
276
+ if not ckpt.is_file():
277
+ raise SystemExit(f"Checkpoint not found: {ckpt}")
278
+
279
+ eval_summary_arg = args.eval_summary
280
+ if eval_summary_arg:
281
+ eval_summary = Path(eval_summary_arg)
282
+ if not eval_summary.is_absolute():
283
+ eval_summary = (root / eval_summary).resolve()
284
+ else:
285
+ cand = root / "eval_summary.json"
286
+ eval_summary = cand if cand.is_file() else None
287
+
288
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
289
+ demo = create_demo(
290
+ ckpt,
291
+ eval_summary,
292
+ max_side=args.max_side,
293
+ max_megapixels=args.max_megapixels,
294
+ device=device,
295
+ )
296
+
297
  logger.info("Launching Gradio on http://%s:%s", args.host, args.port)
298
  demo.launch(server_name=args.host, server_port=args.port, share=args.share)
299