morefaat69 commited on
Commit
f72cfbb
ยท
verified ยท
1 Parent(s): fb7ca6c

Upload 5 files

Browse files
Files changed (5) hide show
  1. .gitignore +7 -0
  2. Dockerfile +33 -0
  3. README.md +61 -5
  4. app.py +240 -0
  5. requirements.txt +21 -0
.gitignore ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ __pycache__/
2
+ *.pyc
3
+ *.pyo
4
+ .env
5
+ generated_audio/
6
+ *.mp3
7
+ .DS_Store
Dockerfile ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # โ”€โ”€โ”€ Base image โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
2
+ FROM python:3.10-slim
3
+
4
+ # โ”€โ”€โ”€ System deps needed by OpenCV & MediaPipe โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
5
+ RUN apt-get update && apt-get install -y --no-install-recommends \
6
+ libgl1 \
7
+ libglib2.0-0 \
8
+ libsm6 \
9
+ libxext6 \
10
+ libxrender-dev \
11
+ && rm -rf /var/lib/apt/lists/*
12
+
13
+ # โ”€โ”€โ”€ Working directory โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
14
+ WORKDIR /app
15
+
16
+ # โ”€โ”€โ”€ Python deps โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
17
+ COPY requirements.txt .
18
+ RUN pip install --no-cache-dir -r requirements.txt
19
+
20
+ # โ”€โ”€โ”€ App code โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
21
+ COPY app.py .
22
+
23
+ # โ”€โ”€โ”€ Models directory (upload your .keras files here) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
24
+ # Structure expected:
25
+ # models/asl_landmark_cnn_lstm_model.keras โ† English
26
+ # models/arsl_landmark_cnn_lstm_model.keras โ† Arabic
27
+ COPY models/ ./models/
28
+
29
+ # โ”€โ”€โ”€ HuggingFace Spaces uses port 7860 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
30
+ EXPOSE 7860
31
+
32
+ # โ”€โ”€โ”€ Start โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
33
+ CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
README.md CHANGED
@@ -1,10 +1,66 @@
1
  ---
2
- title: Sign Language
3
- emoji: ๐Ÿƒ
4
- colorFrom: yellow
5
- colorTo: red
6
  sdk: docker
7
  pinned: false
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: Sign Language Translator API
3
+ emoji: ๐ŸคŸ
4
+ colorFrom: blue
5
+ colorTo: purple
6
  sdk: docker
7
  pinned: false
8
  ---
9
 
10
+ # ๐ŸคŸ Sign Language Translator API
11
+
12
+ Real-time **Arabic & English** Sign Language recognition using **CNN-BiLSTM + MediaPipe**.
13
+
14
+ ## Endpoints
15
+
16
+ | Method | URL | Description |
17
+ |--------|-----|-------------|
18
+ | GET | `/` | API info |
19
+ | GET | `/health` | Model status |
20
+ | POST | `/predict/english` | Predict English ASL letter |
21
+ | POST | `/predict/arabic` | Predict Arabic ARSL letter |
22
+
23
+ ## How to use
24
+
25
+ Send a hand-gesture image (JPG/PNG) as `multipart/form-data`:
26
+
27
+ ```bash
28
+ curl -X POST "https://<your-space>.hf.space/predict/english" \
29
+ -F "file=@hand.jpg"
30
+ ```
31
+
32
+ ### Response example
33
+
34
+ ```json
35
+ {
36
+ "language": "english",
37
+ "predicted_label": "A",
38
+ "predicted_char": "A",
39
+ "confidence": 0.9823,
40
+ "above_threshold": true,
41
+ "message": "OK"
42
+ }
43
+ ```
44
+
45
+ ```json
46
+ {
47
+ "language": "arabic",
48
+ "predicted_label": "Alef",
49
+ "predicted_char": "ุง",
50
+ "confidence": 0.9412,
51
+ "above_threshold": true,
52
+ "message": "OK"
53
+ }
54
+ ```
55
+
56
+ ## Model details
57
+
58
+ - Input: single hand-gesture image
59
+ - MediaPipe extracts 21 landmarks โ†’ 63 features
60
+ - Sequence replicated to 23 timesteps โ†’ shape `(1, 23, 63)`
61
+ - CNN + BiLSTM โ†’ Softmax
62
+ - Confidence threshold: **0.8**
63
+ - English classes: 28 (A-Z + del + space)
64
+ - Arabic classes: 33
65
+
66
+ ## Built by Ahmed Sobhy
app.py ADDED
@@ -0,0 +1,240 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import cv2
3
+ import uuid
4
+ import base64
5
+ import numpy as np
6
+ import tensorflow as tf
7
+ import mediapipe as mp
8
+
9
+ from fastapi import FastAPI, File, UploadFile, HTTPException
10
+ from fastapi.middleware.cors import CORSMiddleware
11
+ from pydantic import BaseModel
12
+ from typing import Optional
13
+ from contextlib import asynccontextmanager
14
+
15
+
16
+ # PATHS
17
+ BASE_DIR = os.path.dirname(os.path.abspath(__file__))
18
+
19
+ ENGLISH_MODEL_PATH = os.path.join(BASE_DIR, "models", "asl_landmark_cnn_lstm_model.keras")
20
+ ARABIC_MODEL_PATH = os.path.join(BASE_DIR, "models", "arsl_landmark_cnn_lstm_model.keras")
21
+
22
+
23
+ # CLASSES
24
+ ENGLISH_CLASSES = list("ABCDEFGHIJKLMNOPQRSTUVWXYZ") + ["del", "space"]
25
+
26
+ ARABIC_CLASSES = [
27
+ "Ain", "Al", "Alef", "Beh", "Dad", "Dal", "Feh", "Ghain", "Hah", "Heh",
28
+ "Jeem", "Kaf", "Khah", "Laa", "Lam", "Meem", "Noon", "Qaf", "Reh", "Sad",
29
+ "Seen", "Sheen", "Tah", "Teh", "Teh_Marbuta", "Thal", "Theh", "Waw", "Yeh",
30
+ "Zah", "Zain", "del", "space"
31
+ ]
32
+
33
+ ARABIC_MAP = {
34
+ "Alef": "ุง", "Beh": "ุจ", "Teh": "ุช", "Theh": "ุซ", "Jeem": "ุฌ",
35
+ "Hah": "ุญ", "Khah": "ุฎ", "Dal": "ุฏ", "Thal": "ุฐ", "Reh": "ุฑ",
36
+ "Zain": "ุฒ", "Seen": "ุณ", "Sheen": "ุด", "Sad": "ุต", "Dad": "ุถ",
37
+ "Tah": "ุท", "Zah": "ุธ", "Ain": "ุน", "Ghain": "ุบ", "Feh": "ู",
38
+ "Qaf": "ู‚", "Kaf": "ูƒ", "Lam": "ู„", "Meem": "ู…", "Noon": "ู†",
39
+ "Heh": "ู‡", "Waw": "ูˆ", "Yeh": "ูŠ", "Laa": "ู„ุง", "Al": "ุงู„",
40
+ "Teh_Marbuta": "ุฉ"
41
+ }
42
+
43
+
44
+ # CONFIG
45
+ TIMESTEPS = 23
46
+ CONFIDENCE_THRESHOLD = 0.8
47
+
48
+
49
+ # GLOBAL MODELS & MEDIAPIPE
50
+ english_model = None
51
+ arabic_model = None
52
+ hands_detector = None
53
+
54
+
55
+ @asynccontextmanager
56
+ async def lifespan(app: FastAPI):
57
+ global english_model, arabic_model, hands_detector
58
+
59
+ print("Loading models...")
60
+
61
+ if os.path.exists(ENGLISH_MODEL_PATH):
62
+ english_model = tf.keras.models.load_model(ENGLISH_MODEL_PATH)
63
+ print(" English model loaded")
64
+ else:
65
+ print(f" English model not found at {ENGLISH_MODEL_PATH}")
66
+
67
+ if os.path.exists(ARABIC_MODEL_PATH):
68
+ arabic_model = tf.keras.models.load_model(ARABIC_MODEL_PATH)
69
+ print(" Arabic model loaded")
70
+ else:
71
+ print(f" Arabic model not found at {ARABIC_MODEL_PATH}")
72
+
73
+ mp_hands = mp.solutions.hands
74
+ hands_detector = mp_hands.Hands(
75
+ static_image_mode=True,
76
+ max_num_hands=1,
77
+ min_detection_confidence=0.5
78
+ )
79
+ print(" MediaPipe ready")
80
+
81
+ yield
82
+
83
+ # cleanup
84
+ if hands_detector:
85
+ hands_detector.close()
86
+ print("Shutting down...")
87
+
88
+
89
+
90
+ # APP
91
+ app = FastAPI(
92
+ title="Sign Language Translator API",
93
+ description="Real-time Arabic & English Sign Language recognition using CNN-BiLSTM + MediaPipe",
94
+ version="1.0.0",
95
+ lifespan=lifespan
96
+ )
97
+
98
+ app.add_middleware(
99
+ CORSMiddleware,
100
+ allow_origins=["*"],
101
+ allow_methods=["*"],
102
+ allow_headers=["*"],
103
+ )
104
+
105
+
106
+
107
+ # SCHEMAS
108
+ class PredictResponse(BaseModel):
109
+ language: str
110
+ predicted_label: str # raw class name e.g. "Alef"
111
+ predicted_char: str # display char e.g. "ุง"
112
+ confidence: float
113
+ above_threshold: bool
114
+ message: str
115
+
116
+
117
+
118
+ # HELPERS
119
+ def decode_image(data: bytes) -> np.ndarray:
120
+ """Decode uploaded image bytes โ†’ BGR numpy array."""
121
+ arr = np.frombuffer(data, dtype=np.uint8)
122
+ img = cv2.imdecode(arr, cv2.IMREAD_COLOR)
123
+ if img is None:
124
+ raise HTTPException(status_code=400, detail="Cannot decode image. Send a valid JPG/PNG.")
125
+ return img
126
+
127
+
128
+ def extract_landmarks(img_bgr: np.ndarray) -> Optional[np.ndarray]:
129
+ """Run MediaPipe on a BGR image โ†’ 63-d landmark vector or None."""
130
+ img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)
131
+ results = hands_detector.process(img_rgb)
132
+
133
+ if not results.multi_hand_landmarks:
134
+ return None
135
+
136
+ hand = results.multi_hand_landmarks[0]
137
+ coords = []
138
+ for lm in hand.landmark:
139
+ coords.extend([lm.x, lm.y, lm.z])
140
+
141
+ lm_arr = np.array(coords, dtype=np.float32)
142
+
143
+ # Mirror right hand so model always sees left-hand orientation
144
+ if results.multi_handedness:
145
+ label = results.multi_handedness[0].classification[0].label
146
+ if label == "Right":
147
+ lm_arr[0::3] = 1.0 - lm_arr[0::3]
148
+
149
+ return lm_arr
150
+
151
+
152
+ def build_sequence(lm: np.ndarray) -> np.ndarray:
153
+ """Repeat single frame 23ร— โ†’ (1, 23, 63) normalised tensor."""
154
+ x_seq = np.repeat(lm[np.newaxis, :], TIMESTEPS, axis=0) # (23, 63)
155
+ x_seq = x_seq[np.newaxis, :, :] # (1, 23, 63)
156
+ max_val = np.max(np.abs(x_seq))
157
+ if max_val != 0:
158
+ x_seq = x_seq / max_val
159
+ return x_seq
160
+
161
+
162
+ def run_inference(model, x_seq: np.ndarray, classes: list) -> tuple[str, float]:
163
+ """Return (predicted_class_name, confidence)."""
164
+ probs = model.predict(x_seq, verbose=0)
165
+ idx = int(np.argmax(probs))
166
+ conf = float(probs[0][idx])
167
+ label = classes[idx] if idx < len(classes) else "unknown"
168
+ return label, conf
169
+
170
+
171
+
172
+ # ROUTES
173
+ @app.get("/")
174
+ def root():
175
+ return {
176
+ "name": "Sign Language Translator API",
177
+ "endpoints": {
178
+ "predict_english": "POST /predict/english",
179
+ "predict_arabic": "POST /predict/arabic",
180
+ "health": "GET /health"
181
+ }
182
+ }
183
+
184
+
185
+ @app.get("/health")
186
+ def health():
187
+ return {
188
+ "status": "ok",
189
+ "english_model_loaded": english_model is not None,
190
+ "arabic_model_loaded": arabic_model is not None,
191
+ }
192
+
193
+
194
+ @app.post("/predict/english", response_model=PredictResponse)
195
+ async def predict_english(file: UploadFile = File(...)):
196
+ if english_model is None:
197
+ raise HTTPException(status_code=503, detail="English model not loaded.")
198
+
199
+ img = decode_image(await file.read())
200
+ lm = extract_landmarks(img)
201
+
202
+ if lm is None:
203
+ raise HTTPException(status_code=422, detail="No hand detected in the image.")
204
+
205
+ x_seq = build_sequence(lm)
206
+ label, conf = run_inference(english_model, x_seq, ENGLISH_CLASSES)
207
+
208
+ return PredictResponse(
209
+ language = "english",
210
+ predicted_label = label,
211
+ predicted_char = label, # same for English
212
+ confidence = round(conf, 4),
213
+ above_threshold = conf >= CONFIDENCE_THRESHOLD,
214
+ message = "OK" if conf >= CONFIDENCE_THRESHOLD else f"Low confidence ({conf:.2f})"
215
+ )
216
+
217
+
218
+ @app.post("/predict/arabic", response_model=PredictResponse)
219
+ async def predict_arabic(file: UploadFile = File(...)):
220
+ if arabic_model is None:
221
+ raise HTTPException(status_code=503, detail="Arabic model not loaded.")
222
+
223
+ img = decode_image(await file.read())
224
+ lm = extract_landmarks(img)
225
+
226
+ if lm is None:
227
+ raise HTTPException(status_code=422, detail="No hand detected in the image.")
228
+
229
+ x_seq = build_sequence(lm)
230
+ label, conf = run_inference(arabic_model, x_seq, ARABIC_CLASSES)
231
+ char = ARABIC_MAP.get(label, label)
232
+
233
+ return PredictResponse(
234
+ language = "arabic",
235
+ predicted_label = label,
236
+ predicted_char = char,
237
+ confidence = round(conf, 4),
238
+ above_threshold = conf >= CONFIDENCE_THRESHOLD,
239
+ message = "OK" if conf >= CONFIDENCE_THRESHOLD else f"Low confidence ({conf:.2f})"
240
+ )
requirements.txt ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # โ”€โ”€ Web framework โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
2
+ fastapi==0.115.12
3
+ uvicorn[standard]==0.34.0
4
+ python-multipart==0.0.20
5
+ pydantic==2.11.3
6
+
7
+ # โ”€โ”€ ML / CV โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
8
+ # tensorflow-cpu ุฃุฎู ุจูƒุชูŠุฑ ุนู„ู‰ ุงู„ุณูŠุฑูุฑ (ู…ููŠุด GPU ุนู„ู‰ HuggingFace Free)
9
+ tensorflow-cpu==2.17.0
10
+ keras==3.13.2
11
+ mediapipe==0.10.14
12
+ opencv-python-headless==4.11.0.86
13
+ numpy==1.26.4
14
+
15
+ # โ”€โ”€ Arabic support โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
16
+ arabic-reshaper==3.0.0
17
+ python-bidi==0.6.7
18
+ pillow==12.1.1
19
+
20
+ # โ”€โ”€ Utils โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
21
+ requests==2.32.5