crangana commited on
Commit
2da6509
Β·
verified Β·
1 Parent(s): 746322d

Upload 3 files

Browse files
Files changed (3) hide show
  1. DEPLOY.md +111 -0
  2. README.md +11 -6
  3. app.py +98 -0
DEPLOY.md ADDED
@@ -0,0 +1,111 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Deploying the Face Attribute Classifier (Gradio) to Hugging Face Spaces
2
+
3
+ ## Why Gradio instead of Docker
4
+ Hugging Face changed its free-tier policy: creating a Docker Space now
5
+ requires a paid plan. The one remaining free path for personal accounts
6
+ is **up to 2 Gradio Spaces on ZeroGPU hardware** β€” free, no card needed.
7
+
8
+ "ZeroGPU hardware" sounds GPU-specific, but it isn't mandatory to use one.
9
+ A GPU is only allocated to functions you explicitly wrap with
10
+ `@spaces.GPU`. Since `app.py` in this package doesn't use that decorator
11
+ anywhere, the app just runs on the container's CPU β€” which is exactly
12
+ what three small classification models need.
13
+
14
+ ## What this app does differently from your original Flask repo
15
+ Same logic as before β€” face-crop via Roboflow, then classify age/gender/
16
+ race β€” but:
17
+ - **Rebuilt as a Gradio app** (`gr.Interface`) instead of Flask routes +
18
+ HTML templates, since that's what the free tier now requires.
19
+ - **Age/gender/race run locally** via `transformers.pipeline(...)`
20
+ instead of calling Hugging Face's shared Inference API β€” that's the
21
+ service that stopped serving the gender/race models, so this sidesteps
22
+ the problem entirely rather than depending on it.
23
+ - **Roboflow key read from an environment variable** (a Space secret),
24
+ not hardcoded in the source.
25
+
26
+ ## 1. Create the Space
27
+ 1. Go to https://huggingface.co/new-space (free account is fine).
28
+ 2. Space name: `face-attribute-classifier` (or whatever you like).
29
+ 3. License: anything (MIT is fine).
30
+ 4. SDK: **Gradio**.
31
+ 5. Hardware: it will likely default to or only offer **ZeroGPU** on a
32
+ free account β€” that's fine, leave it. We're not using the GPU.
33
+ 6. Visibility: **Public**.
34
+ 7. Click **Create Space**.
35
+
36
+ ## 2. Add the files
37
+ ```bash
38
+ git clone https://huggingface.co/spaces/<your-username>/face-attribute-classifier
39
+ cd face-attribute-classifier
40
+
41
+ cp /path/to/face-classifier-deploy/app.py .
42
+ cp /path/to/face-classifier-deploy/README.md .
43
+ ```
44
+ You also need a `requirements.txt` with at minimum:
45
+ ```
46
+ gradio
47
+ transformers
48
+ torch --index-url https://download.pytorch.org/whl/cpu
49
+ pillow
50
+ requests
51
+ ```
52
+ (Gradio Spaces install `requirements.txt` automatically β€” no Dockerfile
53
+ needed this time.)
54
+
55
+ ## 3. Set the Roboflow key as a secret
56
+ Space **Settings** tab β†’ **Repository secrets** β†’ add:
57
+ - Name: `ROBOFLOW_API_KEY`
58
+ - Value: your Roboflow key (worth regenerating it first, since the
59
+ original was sitting in plain text in a public GitHub repo)
60
+
61
+ ## 4. Push
62
+ ```bash
63
+ git add .
64
+ git commit -m "Deploy face attribute classifier (Gradio, local inference)"
65
+ git push
66
+ ```
67
+ First build will take a few minutes β€” downloading PyTorch plus three
68
+ model checkpoints. Watch the **Building** then **Logs** tab; you should
69
+ see "Loading race/age/gender models" on startup.
70
+
71
+ ## 5. Test it
72
+ Once **Running**, open:
73
+ ```
74
+ https://<your-username>-face-attribute-classifier.hf.space
75
+ ```
76
+ You'll see Gradio's own UI (an upload box + three output fields) β€”
77
+ upload a face photo and confirm age/gender/ethnicity all come back
78
+ correctly.
79
+
80
+ ## 6. Calling it programmatically (for the Cloudflare Worker)
81
+ Gradio apps expose a REST API automatically. Because `api_name="classify"`
82
+ is set in `app.py`, the calling pattern is:
83
+
84
+ 1. **POST** to `https://<your-space-url>/gradio_api/call/classify`
85
+ with a JSON body containing the image (as a data URI) in Gradio's
86
+ expected input format. This returns an `event_id`.
87
+ 2. **GET** `https://<your-space-url>/gradio_api/call/classify/<event_id>`
88
+ to stream the result back (Server-Sent Events).
89
+
90
+ This two-step flow is a bit more involved than the single POST/JSON
91
+ round-trip Roboflow used. Rather than guess the exact payload shape
92
+ sight-unseen, once your Space is live:
93
+ - Open it in a browser, use the built-in UI to classify a photo once
94
+ - Open DevTools β†’ Network tab, find the two calls above, and copy their
95
+ exact request/response bodies
96
+
97
+ Send those to me and I'll write the Worker's `/face` route to match
98
+ exactly, and re-enable the "Try It" button on the portfolio.
99
+
100
+ ## Notes
101
+ - Free tier sleeps after inactivity, cold start on next visit.
102
+ - Model weights get cached after the first load, so cold starts are
103
+ "reload from cache," not "re-download from the internet."
104
+ - **Heads up on sketchful.AI**: that Docker Space deployment we set up
105
+ earlier β€” if you haven't actually created and pushed that Space yet,
106
+ it's now blocked by this same policy change (creating a *new* Docker
107
+ Space requires a paid plan). If you already created it before the
108
+ policy changed, it should keep running fine; only new creation is
109
+ affected. Worth checking its status β€” if it needs to move too, that
110
+ one could go Gradio as well, or we could weigh a $9/mo Pro plan if
111
+ you end up wanting more than 2 Spaces total.
README.md CHANGED
@@ -1,13 +1,18 @@
1
  ---
2
  title: Face Attribute Classifier
3
- emoji: 🐒
4
- colorFrom: indigo
5
- colorTo: purple
6
  sdk: gradio
7
- sdk_version: 6.25.0
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: Face Attribute Classifier
3
+ emoji: πŸ§‘
4
+ colorFrom: gray
5
+ colorTo: blue
6
  sdk: gradio
 
 
7
  app_file: app.py
8
  pinned: false
9
  ---
10
 
11
+ # Face Attribute Classifier
12
+
13
+ Predicts age, gender, and ethnicity from a face image. Runs entirely
14
+ locally in this Space (via `transformers`) β€” no dependency on Hugging
15
+ Face's shared serverless Inference API.
16
+
17
+ Built with Gradio. See the original Flask version for reference:
18
+ https://github.com/bUZgy/race-age-gender-classifier
app.py ADDED
@@ -0,0 +1,98 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import base64
3
+ import requests
4
+ import gradio as gr
5
+ from PIL import Image
6
+ from transformers import pipeline
7
+
8
+ # --- Roboflow face-detection key, read from a Space secret (not hardcoded) ---
9
+ ROBOFLOW_API_KEY = os.environ.get("ROBOFLOW_API_KEY", "")
10
+
11
+ # --- Load all three classifiers once at startup ---
12
+ # These run locally in this container via `transformers`, instead of calling
13
+ # Hugging Face's shared serverless Inference API (which no longer serves the
14
+ # gender/race models β€” only age currently has an attached provider).
15
+ # No @spaces.GPU decorator needed β€” these are small, CPU-friendly models,
16
+ # so they just run on the free CPU container as normal.
17
+ print("Loading race/age/gender models β€” this can take a bit on first boot...")
18
+ race_pipe = pipeline("image-classification", model="crangana/trained-race")
19
+ age_pipe = pipeline("image-classification", model="crangana/trained-age")
20
+ gender_pipe = pipeline("image-classification", model="crangana/trained-gender")
21
+ print("Models loaded.")
22
+
23
+
24
+ def crop_face(image_path):
25
+ """Detect and crop the face using Roboflow's hosted face detector."""
26
+ with open(image_path, "rb") as image_file:
27
+ image_base64 = base64.b64encode(image_file.read()).decode("utf-8")
28
+
29
+ url = "https://detect.roboflow.com/face-det-npvna/1"
30
+ params = {"api_key": ROBOFLOW_API_KEY}
31
+ headers = {"Content-Type": "application/x-www-form-urlencoded"}
32
+
33
+ response = requests.post(url, params=params, data=image_base64, headers=headers)
34
+ crop_data = response.json()
35
+
36
+ if not crop_data.get("predictions"):
37
+ raise ValueError("No face detected in the image.")
38
+
39
+ pred = crop_data["predictions"][0]
40
+ width, height = pred["width"], pred["height"]
41
+ x = pred["x"] - width / 2
42
+ y = pred["y"] - height / 2
43
+
44
+ image = Image.open(image_path)
45
+ cropped = image.crop((x, y, x + width, y + height))
46
+
47
+ cropped_path = "/tmp/cropped_face.jpg"
48
+ cropped.save(cropped_path)
49
+ return cropped_path
50
+
51
+
52
+ def get_top(results):
53
+ """Pick the highest-confidence label from a list of {label, score} dicts."""
54
+ best = max(results, key=lambda r: r["score"])
55
+ return best["label"], best["score"]
56
+
57
+
58
+ def classify(image):
59
+ if image is None:
60
+ return "No image provided.", "", ""
61
+
62
+ tmp_path = "/tmp/input_face.jpg"
63
+ image.convert("RGB").save(tmp_path)
64
+
65
+ try:
66
+ cropped_path = crop_face(tmp_path)
67
+ except Exception as e:
68
+ return f"Face detection failed: {e}", "β€”", "β€”"
69
+
70
+ try:
71
+ age_label, age_score = get_top(age_pipe(cropped_path))
72
+ gender_label, gender_score = get_top(gender_pipe(cropped_path))
73
+ race_label, race_score = get_top(race_pipe(cropped_path))
74
+ except Exception as e:
75
+ return f"Classification failed: {e}", "β€”", "β€”"
76
+
77
+ return (
78
+ f"{age_label} ({age_score:.0%})",
79
+ f"{gender_label} ({gender_score:.0%})",
80
+ f"{race_label} ({race_score:.0%})",
81
+ )
82
+
83
+
84
+ demo = gr.Interface(
85
+ fn=classify,
86
+ inputs=gr.Image(type="pil", label="Upload a face photo"),
87
+ outputs=[
88
+ gr.Textbox(label="Age"),
89
+ gr.Textbox(label="Gender"),
90
+ gr.Textbox(label="Ethnicity"),
91
+ ],
92
+ title="Face Attribute Classifier",
93
+ description="Predicts age, gender, and ethnicity from a face photo.",
94
+ api_name="classify", # names the programmatic API endpoint /gradio_api/call/classify
95
+ )
96
+
97
+ if __name__ == "__main__":
98
+ demo.launch()