File size: 6,236 Bytes
5d31c67
 
 
 
 
 
 
c9b579e
 
 
5d31c67
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d07aeb2
 
5d31c67
 
 
 
 
 
c9b579e
 
 
 
 
 
5d31c67
 
 
 
 
d07aeb2
 
 
 
 
 
 
5d31c67
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c9b579e
 
 
 
 
 
 
 
 
 
5d31c67
 
 
5c73a43
5d31c67
5c73a43
 
d07aeb2
 
 
 
 
 
 
5c73a43
 
 
 
 
 
 
 
 
 
 
 
 
5d31c67
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
#!/usr/bin/env python3
"""Create / update the CellQuant Space on Hugging Face and wait until it runs.

    python scripts/deploy_hf.py --repo <user>/cellquant [--public] [--no-wait]

Uses the token from `hf auth login` (or HF_TOKEN). Large files (the .pt checkpoint)
are uploaded through LFS automatically. Then polls the Space runtime and, once it is
RUNNING, checks that the Space serves the bundled example image as a real PNG (the Hub
stores binary files as LFS objects, and a checkout only resolves them for extensions
listed in .gitattributes) and calls the API with it to prove the model works.
"""
from __future__ import annotations

import argparse
import os
import sys
import time

from huggingface_hub import HfApi, get_token, whoami

ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
IGNORE = ["runs/**", "runs/*", ".pytest_cache/**", "**/__pycache__/**", "__pycache__/**", "*.pyc",
          "reports/final/*.png", "reports/cv/*.png", "*.log", ".gitignore", ".DS_Store"]


def main(argv=None):
    ap = argparse.ArgumentParser()
    ap.add_argument("--repo", default=None, help="<user>/<space-name>; default <me>/cellquant")
    ap.add_argument("--public", action="store_true")
    ap.add_argument("--no-wait", action="store_true")
    ap.add_argument("--timeout", type=int, default=1800)
    ap.add_argument("--hardware", default="", help="Space hardware to request after the upload, e.g. zero-a10g (ZeroGPU) or cpu-basic")
    ap.add_argument("--bench", action="store_true", help="after the smoke test, time a full validation image with no TTA and with 8x TTA")
    a = ap.parse_args(argv)
    api = HfApi()
    me = whoami()["name"]
    repo = a.repo or f"{me}/cellquant"
    if not os.path.exists(os.path.join(ROOT, "weights", "cellquant.pt")):
        raise SystemExit("weights/cellquant.pt missing: train first")
    n_val = len([f for f in os.listdir(os.path.join(ROOT, "validation")) if f.lower().endswith(".png")]) if os.path.isdir(os.path.join(ROOT, "validation")) else 0
    if n_val == 0:
        print("warning: validation/ holds no images, the Space will have no 'Check against the annotations' tab "
              "(build it with scripts/make_validation_set.py)")
    else:
        print(f"validation bundle: {n_val} annotated images")
    url = api.create_repo(repo, repo_type="space", space_sdk="gradio", private=not a.public, exist_ok=True)
    print("space:", url)
    api.upload_folder(folder_path=ROOT, repo_id=repo, repo_type="space", ignore_patterns=IGNORE,
                      commit_message="Deploy CellQuant")
    print("uploaded")
    if a.hardware:
        rt = api.get_space_runtime(repo)
        if str(rt.hardware) != a.hardware or (rt.requested_hardware and str(rt.requested_hardware) != a.hardware):
            api.request_space_hardware(repo, a.hardware)
            print(f"requested hardware {a.hardware} (was {rt.hardware})")
        else:
            print(f"hardware already {a.hardware}")
    if a.no_wait:
        return
    t0 = time.time()
    last = None
    while time.time() - t0 < a.timeout:
        rt = api.get_space_runtime(repo)
        stage = str(rt.stage)
        if stage != last:
            print(f"[{time.time()-t0:5.0f}s] stage {stage} hardware {rt.hardware}", flush=True)
            last = stage
        if stage.endswith("RUNNING"):
            break
        if "ERROR" in stage or stage.endswith("STOPPED"):
            print("build failed; last log lines:")
            try:
                print(api.get_space_runtime(repo))
            except Exception as e:  # noqa: BLE001
                print(e)
            sys.exit(1)
        time.sleep(15)
    else:
        raise SystemExit("timed out waiting for the Space")
    # the example image as the Space itself serves it must be a PNG, not an LFS pointer
    import requests
    host = api.space_info(repo).host or f"https://{repo.replace('/', '-')}.hf.space"
    served = f"{host.rstrip('/')}/gradio_api/file=/home/user/app/examples/F100_crop_512.png"
    r = requests.get(served, timeout=60)
    if r.status_code != 200 or not r.content.startswith(b"\x89PNG"):
        raise SystemExit(f"Space serves {served} as {r.status_code} / {len(r.content)} bytes starting {r.content[:40]!r}: "
                         "not a PNG (unresolved LFS pointer? check .gitattributes)")
    print(f"served example ok: {len(r.content)} bytes")
    # smoke test through the Gradio API with that served copy
    from gradio_client import Client, handle_file
    client = Client(repo, hf_token=get_token())
    t = time.time()
    out = client.predict([handle_file(served)], 0.5, 0.5, 50, "head", "contains", api_name="/analyze_files")
    print(f"API call ok in {time.time()-t:.1f}s; counts row: {out[0]['data'][0] if isinstance(out[0], dict) else out[0]}")
    lines = [l for l in str(out[3]).splitlines() if l.strip()]
    print(f"  app reports: {lines[0] if lines else '(no summary line)'} | summary: {lines[-1][:90] if len(lines) > 1 else '(none)'}")
    if a.bench:
        val_dir = os.path.join(ROOT, "validation")
        pngs = sorted(f for f in os.listdir(val_dir) if f.lower().endswith(".png")) if os.path.isdir(val_dir) else []
        if not pngs:
            print("bench: no validation image to time")
            return
        full = os.path.join(val_dir, pngs[0])
        t = time.time()
        out = client.predict([handle_file(full)], 0.5, 0.5, 50, "head", "contains", api_name="/analyze_files")
        row = out[0]["data"][0] if isinstance(out[0], dict) else out[0]
        lines = [l for l in str(out[3]).splitlines() if l.strip()]
        print(f"bench section 1, {os.path.basename(full)}: round trip {time.time()-t:.1f}s; {lines[0] if lines else ''}; nuclei {sum(row[1:5])}")
        sys.path.insert(0, ROOT)
        from cellquant.validation import ValidationSet
        key = ValidationSet(val_dir).keys[0]
        t = time.time()
        out = client.predict(key, api_name="/validate_image")
        lines = [l for l in str(out[0]).splitlines() if l.strip()]
        print(f"bench section 2, {key}: round trip {time.time()-t:.1f}s; {lines[0] if lines else ''}")
        print(f"  {[l for l in lines if 'Run time' in l] or lines[-1:]}")


if __name__ == "__main__":
    main()