kshitiz14 commited on
Commit
fa0d1cd
·
0 Parent(s):

Initial commit with Hugging Face Docker and LFS configurations

Browse files
.dockerignore ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Git and local configs
2
+ .git
3
+ .gitignore
4
+ .env
5
+ README.md
6
+
7
+ # Node modules and build output
8
+ **/node_modules/
9
+ **/dist/
10
+
11
+ # Python environment and cache
12
+ venv/
13
+ .venv/
14
+ __pycache__/
15
+ *.pyc
16
+ *.pyo
17
+ *.pyd
18
+
19
+ # Raw training data and duplicate models
20
+ MP_Data/
21
+ best_model.h5
.gitattributes ADDED
@@ -0,0 +1 @@
 
 
1
+ *.h5 filter=lfs diff=lfs merge=lfs -text
.gitignore ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ venv/
2
+ .venv/
3
+ node_modules/
4
+ MP_Data/
5
+ best_model.h5
6
+ __pycache__/
7
+ *.pyc
8
+ *.pyo
9
+ *.pyd
10
+ .DS_Store
11
+ .env
12
+ dist/
13
+ build/
Dockerfile ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Stage 1: Build the React frontend
2
+ FROM node:20-alpine AS frontend-builder
3
+ WORKDIR /frontend
4
+
5
+ # Copy dependency configs
6
+ COPY frontend/gesture_words/package*.json ./
7
+
8
+ # Install packages
9
+ RUN npm install
10
+
11
+ # Copy source code and build
12
+ COPY frontend/gesture_words/ ./
13
+ RUN npm run build
14
+
15
+ # Stage 2: Build the Python backend
16
+ FROM python:3.10-slim
17
+
18
+ # Install system dependencies for MediaPipe / OpenCV
19
+ RUN apt-get update && apt-get install -y --no-install-recommends \
20
+ libgl1 \
21
+ libglib2.0-0 \
22
+ && rm -rf /var/lib/apt/lists/*
23
+
24
+ WORKDIR /app
25
+
26
+ # Install Python requirements
27
+ COPY backend/requirements.txt ./
28
+ RUN pip install --no-cache-dir -r requirements.txt
29
+
30
+ # Copy backend files
31
+ COPY backend/ /app/backend/
32
+ COPY sign_language_model.h5 /app/backend/sign_language_model.h5
33
+
34
+ # Copy built frontend assets
35
+ COPY --from=frontend-builder /frontend/dist /app/dist
36
+
37
+ # Expose port (Hugging Face Space default)
38
+ ENV PORT=7860
39
+ EXPOSE 7860
40
+
41
+ # Set Hugging Face non-root user permissions
42
+ RUN useradd -m -u 1000 user
43
+ RUN chown -R user:user /app
44
+ USER user
45
+
46
+ # Run Flask application from backend directory
47
+ WORKDIR /app/backend
48
+ CMD ["python", "result.py"]
backend/requirements.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ tensorflow-cpu
2
+ mediapipe
3
+ flask
4
+ flask-cors
5
+ opencv-python-headless
6
+ numpy
backend/result.py ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import numpy as np
3
+ from flask import Flask, request, jsonify, send_from_directory
4
+ from flask_cors import CORS
5
+ from tensorflow.keras.models import load_model
6
+ import base64
7
+ import cv2
8
+ import mediapipe as mp
9
+
10
+ app = Flask(__name__, static_folder='../dist', static_url_path='/')
11
+ CORS(app) # Allow requests from frontend (e.g., React)
12
+
13
+ @app.route('/')
14
+ def serve():
15
+ return send_from_directory(app.static_folder, 'index.html')
16
+
17
+ @app.route('/<path:path>')
18
+ def serve_static(path):
19
+ if path != "" and os.path.exists(os.path.join(app.static_folder, path)):
20
+ return send_from_directory(app.static_folder, path)
21
+ else:
22
+ return send_from_directory(app.static_folder, 'index.html')
23
+
24
+ # Load modp
25
+ model = load_model('sign_language_model.h5')
26
+ actions = np.array(['hi', 'me', 'thank you'])
27
+
28
+ mp_hands = mp.solutions.hands
29
+ hands = mp_hands.Hands(static_image_mode=False, max_num_hands=1, min_detection_confidence=0.7)
30
+
31
+ @app.route('/predict', methods=['POST'])
32
+ def predict():
33
+ data = request.get_json()
34
+ if not data or 'image' not in data:
35
+ return jsonify({'error': 'No image provided'}), 400
36
+
37
+ try:
38
+ # Decode base64 image
39
+ image_data = base64.b64decode(data['image'].split(',')[1])
40
+ nparr = np.frombuffer(image_data, np.uint8)
41
+ image = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
42
+
43
+ # Hand detection
44
+ rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
45
+ results = hands.process(rgb)
46
+
47
+ if results.multi_hand_landmarks:
48
+ hand_landmarks = results.multi_hand_landmarks[0]
49
+ keypoints = []
50
+ for lm in hand_landmarks.landmark:
51
+ keypoints.extend([lm.x, lm.y, lm.z])
52
+
53
+ # Ensure consistent input shape: (30, 63)
54
+ sequence = np.array([keypoints] * 30) # 30 identical frames
55
+ input_data = np.expand_dims(sequence, axis=0)
56
+ input_data = input_data / np.max(input_data + 1e-6) # Avoid divide-by-zero
57
+
58
+ prediction = model.predict(input_data)[0]
59
+ confidence = float(np.max(prediction))
60
+ label = actions[np.argmax(prediction)]
61
+
62
+ return jsonify({
63
+ 'prediction': label,
64
+ 'confidence': round(confidence, 2)
65
+ }), 200
66
+ else:
67
+ return jsonify({'prediction': 'No hand detected', 'confidence': 0.0}), 200
68
+
69
+ except Exception as e:
70
+ return jsonify({'error': str(e)}), 500
71
+
72
+ if __name__ == '__main__':
73
+ port = int(os.environ.get('PORT', 5000))
74
+ app.run(host='0.0.0.0', port=port, debug=True)
75
+
76
+
backend/sign.py ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import mediapipe as mp
2
+ import cv2
3
+ import numpy as np
4
+ import os
5
+ import time
6
+
7
+ # Setup MediaPipe
8
+ mp_hands = mp.solutions.hands
9
+ hands = mp_hands.Hands(static_image_mode=False, max_num_hands=1, min_detection_confidence=0.5)
10
+ mp_drawing = mp.solutions.drawing_utils
11
+
12
+ # Create dataset directory
13
+ DATA_PATH = os.path.join('MP_Data')
14
+ actions = np.array(['hi', 'me', 'thank you']) # Remove extra space
15
+ no_sequences = 30
16
+ sequence_length = 30
17
+
18
+ # Create folders
19
+ for action in actions:
20
+ for sequence in range(no_sequences):
21
+ os.makedirs(os.path.join(DATA_PATH, action, str(sequence)), exist_ok=True)
22
+
23
+ # Start video capture
24
+ cap = cv2.VideoCapture(0)
25
+ for action in actions:
26
+ for sequence in range(no_sequences):
27
+ print(f"\nCollecting data for '{action}', sequence {sequence}")
28
+ time.sleep(2) # Delay before collecting
29
+
30
+ for frame_num in range(sequence_length):
31
+ ret, frame = cap.read()
32
+ if not ret:
33
+ print("Failed to grab frame.")
34
+ continue
35
+
36
+ image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
37
+ results = hands.process(image)
38
+ image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
39
+
40
+ if results.multi_hand_landmarks:
41
+ for hand_landmarks in results.multi_hand_landmarks:
42
+ mp_drawing.draw_landmarks(image, hand_landmarks, mp_hands.HAND_CONNECTIONS)
43
+
44
+ # Extract keypoints
45
+ keypoints = []
46
+ for lm in hand_landmarks.landmark:
47
+ keypoints.extend([lm.x, lm.y, lm.z])
48
+
49
+ # Save keypoints
50
+ npy_path = os.path.join(DATA_PATH, action, str(sequence), f"{frame_num}.npy")
51
+ np.save(npy_path, keypoints)
52
+ print(f"Saved: {npy_path}")
53
+
54
+ else:
55
+ print(f"No hand detected for frame {frame_num} in sequence {sequence}.")
56
+
57
+ # Add feedback to frame
58
+ cv2.putText(image, f'{action} - Seq {sequence}, Frame {frame_num}', (10, 30),
59
+ cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 0, 0), 2)
60
+
61
+ cv2.imshow('Hand Tracking', image)
62
+ if cv2.waitKey(10) & 0xFF == ord('q'):
63
+ cap.release()
64
+ cv2.destroyAllWindows()
65
+ exit()
66
+
67
+ cap.release()
68
+ cv2.destroyAllWindows()
69
+
backend/tain.py ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import numpy as np
3
+ from sklearn.model_selection import train_test_split
4
+ from tensorflow.keras.models import Sequential
5
+ from tensorflow.keras.layers import LSTM, Dense
6
+ from tensorflow.keras.utils import to_categorical
7
+ from tensorflow.keras.callbacks import ModelCheckpoint, EarlyStopping
8
+
9
+ # ----- CONFIGURATION -----
10
+ DATA_PATH = os.path.join('MP_Data')
11
+ actions = np.array(['hi', 'me', 'thank you'])
12
+ no_sequences = 30
13
+ sequence_length = 30
14
+ label_map = {label: num for num, label in enumerate(actions)}
15
+
16
+ # ----- LOAD DATA -----
17
+ sequences, labels = [], []
18
+
19
+ for action in actions:
20
+ for sequence in range(no_sequences):
21
+ window = []
22
+ for frame_num in range(sequence_length):
23
+ try:
24
+ path = os.path.join(DATA_PATH, action, str(sequence), f"{frame_num}.npy")
25
+ res = np.load(path)
26
+ if res.shape == (63,):
27
+ window.append(res)
28
+ except Exception as e:
29
+ print(f"Error loading {path}: {e}")
30
+ if len(window) == sequence_length:
31
+ sequences.append(window)
32
+ labels.append(label_map[action])
33
+
34
+ X = np.array(sequences)
35
+ y = to_categorical(labels).astype(int)
36
+
37
+ # ----- NORMALIZE INPUT -----
38
+ X = X / np.max(X)
39
+
40
+ # ----- TRAIN TEST SPLIT -----
41
+ X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
42
+
43
+ # ----- BUILD MODEL -----
44
+ model = Sequential()
45
+ model.add(LSTM(64, return_sequences=True, activation='relu', input_shape=(30, 63)))
46
+ model.add(LSTM(128, return_sequences=False, activation='relu'))
47
+ model.add(Dense(64, activation='relu'))
48
+ model.add(Dense(len(actions), activation='softmax'))
49
+
50
+ model.compile(optimizer='Adam', loss='categorical_crossentropy', metrics=['accuracy'])
51
+
52
+ # ----- CALLBACKS -----
53
+ checkpoint = ModelCheckpoint('best_model.h5', monitor='val_loss', save_best_only=True)
54
+ earlystop = EarlyStopping(monitor='val_loss', patience=5)
55
+
56
+ # ----- TRAIN MODEL -----
57
+ model.fit(X_train, y_train, epochs=50, validation_data=(X_test, y_test),
58
+ callbacks=[checkpoint, earlystop])
59
+
60
+ # ----- SAVE FINAL MODEL -----
61
+ model.save('sign_language_model.h5')
frontend/gesture_words/.gitignore ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Logs
2
+ logs
3
+ *.log
4
+ npm-debug.log*
5
+ yarn-debug.log*
6
+ yarn-error.log*
7
+ pnpm-debug.log*
8
+ lerna-debug.log*
9
+
10
+ node_modules
11
+ dist
12
+ dist-ssr
13
+ *.local
14
+
15
+ # Editor directories and files
16
+ .vscode/*
17
+ !.vscode/extensions.json
18
+ .idea
19
+ .DS_Store
20
+ *.suo
21
+ *.ntvs*
22
+ *.njsproj
23
+ *.sln
24
+ *.sw?
frontend/gesture_words/README.md ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # React + Vite
2
+
3
+ This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
4
+
5
+ Currently, two official plugins are available:
6
+
7
+ - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) for Fast Refresh
8
+ - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
9
+
10
+ ## Expanding the ESLint configuration
11
+
12
+ If you are developing a production application, we recommend using TypeScript with type-aware lint rules enabled. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) for information on how to integrate TypeScript and [`typescript-eslint`](https://typescript-eslint.io) in your project.
frontend/gesture_words/eslint.config.js ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import js from '@eslint/js'
2
+ import globals from 'globals'
3
+ import reactHooks from 'eslint-plugin-react-hooks'
4
+ import reactRefresh from 'eslint-plugin-react-refresh'
5
+
6
+ export default [
7
+ { ignores: ['dist'] },
8
+ {
9
+ files: ['**/*.{js,jsx}'],
10
+ languageOptions: {
11
+ ecmaVersion: 2020,
12
+ globals: globals.browser,
13
+ parserOptions: {
14
+ ecmaVersion: 'latest',
15
+ ecmaFeatures: { jsx: true },
16
+ sourceType: 'module',
17
+ },
18
+ },
19
+ plugins: {
20
+ 'react-hooks': reactHooks,
21
+ 'react-refresh': reactRefresh,
22
+ },
23
+ rules: {
24
+ ...js.configs.recommended.rules,
25
+ ...reactHooks.configs.recommended.rules,
26
+ 'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }],
27
+ 'react-refresh/only-export-components': [
28
+ 'warn',
29
+ { allowConstantExport: true },
30
+ ],
31
+ },
32
+ },
33
+ ]
frontend/gesture_words/index.html ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link rel="icon" type="image/svg+xml" href="/vite.svg" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>Vite + React</title>
8
+ </head>
9
+ <body>
10
+ <div id="root"></div>
11
+ <script type="module" src="/src/main.jsx"></script>
12
+ </body>
13
+ </html>
frontend/gesture_words/package-lock.json ADDED
The diff for this file is too large to render. See raw diff
 
frontend/gesture_words/package.json ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "gesture_words",
3
+ "private": true,
4
+ "version": "0.0.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "vite build",
9
+ "lint": "eslint .",
10
+ "preview": "vite preview"
11
+ },
12
+ "dependencies": {
13
+ "@tailwindcss/vite": "^4.1.5",
14
+ "axios": "^1.9.0",
15
+ "react": "^19.0.0",
16
+ "react-dom": "^19.0.0",
17
+ "react-webcam": "^7.2.0",
18
+ "tailwindcss": "^4.1.5"
19
+ },
20
+ "devDependencies": {
21
+ "@eslint/js": "^9.22.0",
22
+ "@types/react": "^19.0.10",
23
+ "@types/react-dom": "^19.0.4",
24
+ "@vitejs/plugin-react": "^4.3.4",
25
+ "eslint": "^9.22.0",
26
+ "eslint-plugin-react-hooks": "^5.2.0",
27
+ "eslint-plugin-react-refresh": "^0.4.19",
28
+ "globals": "^16.0.0",
29
+ "vite": "^6.3.4"
30
+ }
31
+ }
frontend/gesture_words/public/vite.svg ADDED
frontend/gesture_words/src/App.jsx ADDED
@@ -0,0 +1,149 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useRef, useState, useEffect } from 'react';
2
+ import Webcam from 'react-webcam';
3
+ import axios from 'axios';
4
+
5
+ const SignDetection = () => {
6
+ const webcamRef = useRef(null);
7
+ const [prediction, setPrediction] = useState('');
8
+ const [loading, setLoading] = useState(false);
9
+ const [history, setHistory] = useState([]);
10
+
11
+ useEffect(() => {
12
+ const interval = setInterval(async () => {
13
+ if (webcamRef.current && webcamRef.current.getScreenshot && !loading) {
14
+ const imageSrc = webcamRef.current.getScreenshot();
15
+ if (imageSrc) {
16
+ setLoading(true);
17
+ try {
18
+ const apiBase = import.meta.env.DEV ? 'http://localhost:5000' : '';
19
+ const response = await axios.post(`${apiBase}/predict`, {
20
+ image: imageSrc
21
+ });
22
+
23
+ const result = response.data.prediction;
24
+ setPrediction(result);
25
+ setHistory((prev) => [result, ...prev.slice(0, 4)]);
26
+
27
+ const utterance = new SpeechSynthesisUtterance(result);
28
+ speechSynthesis.speak(utterance);
29
+ } catch (error) {
30
+ console.error("Prediction failed", error);
31
+ setPrediction("Error predicting sign.");
32
+ }
33
+ setLoading(false);
34
+ }
35
+ }
36
+ }, 2000);
37
+
38
+ return () => clearInterval(interval);
39
+ }, [loading]);
40
+
41
+ return (
42
+ <div className="flex h-screen bg-gradient-to-br from-gray-100 to-gray-300">
43
+
44
+ {/* Sidebar */}
45
+ <aside className="w-72 bg-white/80 backdrop-blur-lg shadow-2xl
46
+ px-8 py-10 hidden sm:flex flex-col rounded-r-3xl">
47
+ <h2 className="text-3xl font-extrabold text-gray-800 mb-12 tracking-wide">
48
+ Dashboard
49
+ </h2>
50
+
51
+ <nav className="flex flex-col gap-5 text-gray-700 font-medium text-lg">
52
+ {["Home", "Sign Detection", "Training Module", "History", "Settings"].map((item) => (
53
+ <a
54
+ key={item}
55
+ href="#"
56
+ className="hover:text-blue-600 hover:bg-blue-100
57
+ rounded-lg px-4 py-2 transition-all"
58
+ >
59
+ {item}
60
+ </a>
61
+ ))}
62
+ </nav>
63
+ </aside>
64
+
65
+ {/* Main Content */}
66
+ <div className="flex-1 flex flex-col">
67
+
68
+ {/* Navbar */}
69
+ <header className="bg-white/80 backdrop-blur-md shadow-lg
70
+ px-10 py-6 flex items-center justify-between border-b">
71
+ <h1 className="text-3xl font-bold text-gray-800 tracking-wide">
72
+ GESTURE WORDS
73
+ </h1>
74
+
75
+ <button
76
+ className="bg-blue-600 text-white px-6 py-3 rounded-xl font-semibold
77
+ shadow hover:bg-blue-700 transition-all text-lg"
78
+ >
79
+ Login
80
+ </button>
81
+ </header>
82
+
83
+ {/* Main Area */}
84
+ <main className="flex flex-col items-center justify-start flex-1 p-12 overflow-y-auto">
85
+
86
+ <div className="bg-white/70 backdrop-blur-xl rounded-3xl
87
+ shadow-2xl p-10 w-full max-w-4xl text-center
88
+ ring-1 ring-gray-300">
89
+
90
+ <h2 className="text-3xl font-semibold text-gray-700 mb-10 tracking-wide">
91
+ Live Sign Detection
92
+ </h2>
93
+
94
+ {/* Webcam Container */}
95
+ <div className="border-4 border-gray-300 rounded-2xl overflow-hidden shadow-xl
96
+ w-[480px] h-[360px] mx-auto mb-8">
97
+ <Webcam
98
+ audio={false}
99
+ ref={webcamRef}
100
+ screenshotFormat="image/jpeg"
101
+ width={480}
102
+ height={360}
103
+ className="rounded-2xl"
104
+ />
105
+ </div>
106
+
107
+ {/* Status */}
108
+ <p className="text-lg text-gray-600 mb-6 animate-pulse">
109
+ {loading ? "Analyzing..." : "Waiting for next frame..."}
110
+ </p>
111
+
112
+ {/* Prediction */}
113
+ {prediction && (
114
+ <div className="mt-4">
115
+ <h3 className="text-xl text-gray-600">Prediction:</h3>
116
+ <p className="text-blue-600 text-5xl font-extrabold drop-shadow-sm mt-2">
117
+ {prediction}
118
+ </p>
119
+ </div>
120
+ )}
121
+
122
+ {/* History */}
123
+ {history.length > 0 && (
124
+ <div className="mt-10 text-left">
125
+ <h4 className="text-lg font-semibold text-gray-600 mb-3">
126
+ Recent Predictions:
127
+ </h4>
128
+
129
+ <ul className="text-gray-700 text-lg space-y-3">
130
+ {history.map((item, index) => (
131
+ <li
132
+ key={index}
133
+ className="border py-3 px-4 rounded-xl bg-gray-50 shadow-sm"
134
+ >
135
+ {item}
136
+ </li>
137
+ ))}
138
+ </ul>
139
+ </div>
140
+ )}
141
+ </div>
142
+
143
+ </main>
144
+ </div>
145
+ </div>
146
+ );
147
+ };
148
+
149
+ export default SignDetection;
frontend/gesture_words/src/api.js ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ await axios.post('http://localhost:5000/predict', {
2
+ image: imageSrc // base64 string
3
+ });
4
+
frontend/gesture_words/src/assets/react.svg ADDED
frontend/gesture_words/src/index.css ADDED
@@ -0,0 +1 @@
 
 
1
+ @import "tailwindcss";
frontend/gesture_words/src/main.jsx ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ import { StrictMode } from 'react'
2
+ import { createRoot } from 'react-dom/client'
3
+ import './index.css'
4
+ import App from './App.jsx'
5
+
6
+ createRoot(document.getElementById('root')).render(
7
+ <StrictMode>
8
+ <App />
9
+ </StrictMode>,
10
+ )
frontend/gesture_words/vite.config.js ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ import { defineConfig } from 'vite'
2
+ import react from '@vitejs/plugin-react'
3
+ import tailwindcss from '@tailwindcss/vite'
4
+
5
+
6
+ // https://vite.dev/config/
7
+ export default defineConfig({
8
+ plugins: [react(), tailwindcss()],
9
+ })
sign_language_model.h5 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c52bd12dc1182f0d52535edfa998f78d73f0d375df31e81a50820590ab264e06
3
+ size 1724272