Imsachinsingh00 commited on
Commit
43317b5
·
1 Parent(s): eb8e0ec
.DS_Store ADDED
Binary file (6.15 kB). View file
 
Dockerfile ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Use an official Python runtime as a parent image
2
+ FROM python:3.9
3
+
4
+ # Set the working directory
5
+ WORKDIR /app
6
+
7
+ # Copy the requirements file
8
+ COPY requirements.txt .
9
+
10
+ # Install dependencies
11
+ RUN pip install --no-cache-dir -r requirements.txt
12
+
13
+ # Copy the app code
14
+ COPY . .
15
+
16
+ # Expose port (Flask runs on 5000 by default)
17
+ EXPOSE 5000
18
+
19
+ # Run the application
20
+ CMD ["python", "app.py"]
Language.py ADDED
@@ -0,0 +1,135 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pickle
2
+ from ultralytics import YOLO
3
+ import cv2
4
+ import mediapipe as mp
5
+ import numpy as np
6
+
7
+
8
+
9
+ model = YOLO('best.pt')
10
+
11
+ cap = cv2.VideoCapture(0)
12
+
13
+ mp_hands = mp.solutions.hands
14
+ mp_drawing = mp.solutions.drawing_utils
15
+ mp_drawing_styles = mp.solutions.drawing_styles
16
+
17
+ hands = mp_hands.Hands(static_image_mode=True, min_detection_confidence=0.3)
18
+ language = ''
19
+ labels_dict = {0: '0', 1: 'B', 2: 'C', 3: 'D', 4: 'E', 5: 'F', 6: 'G', 7: 'H', 8: 'I', 9: 'J', 10: 'K', 11: 'L', 12: 'M', 13: 'N', 14: 'O', 15: 'P', 16: 'Q', 17: 'R', 18: 'S', 19: 'T', 20: 'U', 21: 'V', 22: 'W', 23: 'X', 24: 'Y', 25: 'Z', 26: 'del', 27: 'nothing', 28: 'space'}
20
+ while True:
21
+
22
+ data_aux = []
23
+ x_ = []
24
+ y_ = []
25
+
26
+
27
+ ret, frame = cap.read()
28
+ if not ret:
29
+ print("Failed to capture frame. Exiting...")
30
+ break
31
+
32
+ H, W, _ = frame.shape
33
+
34
+ frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
35
+
36
+ results = hands.process(frame_rgb)
37
+ if results.multi_hand_landmarks:
38
+ for hand_landmarks in results.multi_hand_landmarks:
39
+ mp_drawing.draw_landmarks(
40
+ frame,
41
+ hand_landmarks,
42
+ mp_hands.HAND_CONNECTIONS,
43
+ mp_drawing_styles.get_default_hand_landmarks_style(),
44
+ mp_drawing_styles.get_default_hand_connections_style())
45
+
46
+ for hand_landmarks in results.multi_hand_landmarks:
47
+ for i in range(len(hand_landmarks.landmark)):
48
+ x = hand_landmarks.landmark[i].x
49
+ y = hand_landmarks.landmark[i].y
50
+
51
+ x_.append(x)
52
+ y_.append(y)
53
+
54
+ for i in range(len(hand_landmarks.landmark)):
55
+ x = hand_landmarks.landmark[i].x
56
+ y = hand_landmarks.landmark[i].y
57
+ data_aux.append(x - min(x_))
58
+ data_aux.append(y - min(y_))
59
+
60
+ x1 = int(min(x_) * W) - 10
61
+ y1 = int(min(y_) * H) - 10
62
+
63
+ x2 = int(max(x_) * W) - 10
64
+ y2 = int(max(y_) * H) - 10
65
+
66
+ prediction = model.predict(frame, conf=0.25, iou=0.45)
67
+ names_dict = prediction[0].names
68
+ probs = prediction[0].probs.data.numpy()
69
+ detected_gesture = names_dict[np.argmax(probs)]
70
+ print(names_dict[np.argmax(probs)])
71
+ print("Gesture:", detected_gesture)
72
+ if detected_gesture == 'A':
73
+ language = 'Arabic'
74
+ elif detected_gesture == 'B':
75
+ language = 'Bengali'
76
+ elif detected_gesture == 'C':
77
+ language = 'Chinese'
78
+ elif detected_gesture == 'D':
79
+ language = 'Dutch'
80
+ elif detected_gesture == 'E':
81
+ language = 'English'
82
+ elif detected_gesture == 'F':
83
+ language = 'French'
84
+ elif detected_gesture == 'G':
85
+ language = 'German'
86
+ elif detected_gesture == 'H':
87
+ language = 'Hindi'
88
+ elif detected_gesture == 'I':
89
+ language = 'Italian'
90
+ elif detected_gesture == 'J':
91
+ language = 'Japanese'
92
+ elif detected_gesture == 'K':
93
+ language = 'Korean'
94
+ elif detected_gesture == 'L':
95
+ language = 'Latin'
96
+ elif detected_gesture == 'M':
97
+ language = 'Malay'
98
+ elif detected_gesture == 'N':
99
+ language = 'Norwegian'
100
+ elif detected_gesture == 'O':
101
+ language = 'Oriya'
102
+ elif detected_gesture == 'P':
103
+ language = 'Polish'
104
+ elif detected_gesture == 'Q':
105
+ language = 'Quechua'
106
+ elif detected_gesture == 'R':
107
+ language = 'Russian'
108
+ elif detected_gesture == 'S':
109
+ language = 'Spanish'
110
+ elif detected_gesture == 'T':
111
+ language = 'Turkish'
112
+ elif detected_gesture == 'U':
113
+ language = 'Urdu'
114
+ elif detected_gesture == 'V':
115
+ language = 'Vietnamese'
116
+ elif detected_gesture == 'W':
117
+ language = 'Welsh'
118
+ elif detected_gesture == 'X':
119
+ language = 'Xhosa'
120
+ elif detected_gesture == 'Y':
121
+ language = 'Yoruba'
122
+ elif detected_gesture == 'Z':
123
+ language = 'Zulu'
124
+
125
+
126
+ cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 0, 0), 4)
127
+ cv2.putText(frame, language, (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 1.3, (0, 0, 0), 3,
128
+ cv2.LINE_AA)
129
+
130
+ cv2.imshow('frame', frame)
131
+ cv2.waitKey(1)
132
+
133
+
134
+ cap.release()
135
+ cv2.destroyAllWindows()
README.md CHANGED
@@ -1,10 +1,13 @@
1
- ---
2
- title: HandyLabel
3
- emoji: 🚀
4
- colorFrom: pink
5
- colorTo: green
6
- sdk: docker
7
- pinned: false
8
- ---
9
-
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
1
+ # handgesture-recognition-system
2
+ # handgesture-recognition-system
3
+
4
+ main.py - file to run the system and give camera access to the python.
5
+
6
+
7
+
8
+
9
+
10
+
11
+
12
+ yolo.ipynb - the file that is used to train the ASL dataset to get our custom model
13
+
Web app/.DS_Store ADDED
Binary file (8.2 kB). View file
 
Web app/app.py ADDED
@@ -0,0 +1,664 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import csv
2
+ import time
3
+ from datetime import datetime, timedelta
4
+ from ultralytics import YOLO
5
+ import cv2
6
+ import mediapipe as mp
7
+ import numpy as np
8
+ from flask import Flask, render_template, Response, redirect, url_for, request, send_from_directory, flash
9
+ import os
10
+ import plotly.express as px
11
+ import pandas as pd
12
+ from werkzeug.utils import secure_filename
13
+ import json
14
+ import matplotlib.pyplot as plt
15
+ import uuid
16
+ import random # Make sure to import the random module
17
+ from datetime import datetime # Import datetime for timestamp
18
+ import string # Add this line to use string.ascii_letters and string.digits
19
+ import pyaudio
20
+ import wave
21
+ import whisper
22
+ from transformers import pipeline
23
+ import csv
24
+ import os
25
+ import pandas as pd
26
+ from flask import Flask, render_template, request, redirect, url_for, send_from_directory, flash
27
+ from flask import jsonify
28
+ from transformers import T5Tokenizer, T5ForConditionalGeneration
29
+ import torch
30
+ from openai import OpenAI
31
+ from io import StringIO
32
+ import re
33
+ from flask import session
34
+
35
+
36
+ # Flask app setup
37
+ app = Flask(__name__)
38
+ app.config['UPLOAD_FOLDER'] = 'uploads'
39
+ app.secret_key = 'supersecretkey'
40
+
41
+ # Custom function to load YOLO model safely
42
+ def safe_load_yolo_model(model_path):
43
+ try:
44
+ return YOLO(model_path)
45
+ except Exception as e:
46
+ print(f"Failed to load model: {e}")
47
+ raise
48
+
49
+ # Load YOLO model
50
+ model_path = 'best.pt'
51
+ model = safe_load_yolo_model(model_path)
52
+
53
+
54
+
55
+ mp_hands = mp.solutions.hands
56
+ mp_drawing = mp.solutions.drawing_utils
57
+ mp_drawing_styles = mp.solutions.drawing_styles
58
+
59
+ # Load Whisper model for speech-to-text
60
+ whisper_model = whisper.load_model("base")
61
+
62
+ # Variables to hold CSV data and other states between requests
63
+ original_data = None
64
+ updated_data = None
65
+ csv_filename = None
66
+
67
+
68
+ hands = mp_hands.Hands(static_image_mode=True, min_detection_confidence=0.3)
69
+
70
+ # Initialize variables for tracking gestures
71
+ previous_gesture = None
72
+ gesture_start_time = None
73
+ gesture_data_list = []
74
+ capture_flag = True # This flag is used to indicate when to capture
75
+ start_recording_time = None # To record the start time of the session
76
+
77
+ # Default labels dictionary
78
+ labels_dict = {0: 'fist', 1: 'ok', 2: 'peace', 3: 'stop', 4: 'two up'}
79
+ custom_labels_dict = labels_dict.copy() # To store custom labels set by user
80
+
81
+ # Initialize OpenAI client
82
+ client = OpenAI(api_key=os.environ.get("OPENAI_API_KEY"))
83
+
84
+ # Function to interact with OpenAI's GPT model using streaming
85
+ def get_gpt_instruction_response(instruction, csv_data):
86
+ messages = [
87
+ {"role": "system", "content": "You are a helpful assistant that processes CSV files."},
88
+ {"role": "user", "content": f"Here is a CSV data:\n\n{csv_data}\n\nThe user has requested the following change: {instruction}\n\nPlease process the data accordingly and return the modified CSV."}
89
+ ]
90
+
91
+ # Stream response from OpenAI API
92
+ stream = client.chat.completions.create(
93
+ model="gpt-4o-mini", # or "gpt-3.5-turbo"
94
+ messages=messages,
95
+ stream=True,
96
+ )
97
+
98
+ response = ""
99
+ for chunk in stream:
100
+ if chunk.choices[0].delta.content is not None:
101
+ response += chunk.choices[0].delta.content
102
+
103
+ return response.strip()
104
+
105
+
106
+ # Function to read CSV and convert it to string
107
+ def read_csv_to_string(file_path):
108
+ df = pd.read_csv(file_path)
109
+ return df.to_csv(index=False)
110
+
111
+
112
+ # Function to write modified CSV string to a file
113
+ def write_csv_from_string(csv_string, output_file_path):
114
+ with open(output_file_path, 'w') as file:
115
+ file.write(csv_string)
116
+
117
+ # Function to record audio
118
+ def record_audio(filename, duration=10):
119
+ chunk = 1024
120
+ sample_format = pyaudio.paInt16
121
+ channels = 1
122
+ fs = 44100
123
+
124
+ p = pyaudio.PyAudio()
125
+
126
+ print('Recording...')
127
+ stream = p.open(format=sample_format, channels=channels, rate=fs, frames_per_buffer=chunk, input=True)
128
+ frames = []
129
+
130
+ for _ in range(0, int(fs / chunk * duration)):
131
+ data = stream.read(chunk)
132
+ frames.append(data)
133
+
134
+ stream.stop_stream()
135
+ stream.close()
136
+ p.terminate()
137
+ print('Finished recording.')
138
+
139
+ wf = wave.open(filename, 'wb')
140
+ wf.setnchannels(channels)
141
+ wf.setsampwidth(p.get_sample_size(sample_format))
142
+ wf.setframerate(fs)
143
+ wf.writeframes(b''.join(frames))
144
+ wf.close()
145
+
146
+ # Function to transcribe audio using Whisper
147
+ def transcribe_audio(file_path):
148
+ result = whisper_model.transcribe(file_path)
149
+ return result["text"]
150
+
151
+
152
+
153
+ @app.route('/')
154
+ def index():
155
+ return render_template('index.html')
156
+
157
+ @app.route('/set_labels', methods=['GET', 'POST'])
158
+ def set_labels():
159
+ global custom_labels_dict
160
+ if request.method == 'POST':
161
+ custom_labels_dict[0] = request.form['label1']
162
+ custom_labels_dict[1] = request.form['label2']
163
+ custom_labels_dict[2] = request.form['label3']
164
+ custom_labels_dict[3] = request.form['label4']
165
+ custom_labels_dict[4] = request.form['label5']
166
+ # Remove empty labels
167
+ custom_labels_dict = {k: v for k, v in custom_labels_dict.items() if v}
168
+ return redirect(url_for('recognize'))
169
+ return render_template('set_labels.html')
170
+
171
+ @app.route('/recognize')
172
+ def recognize():
173
+ return render_template('recognize.html')
174
+
175
+ @app.route('/video_feed')
176
+ def video_feed():
177
+ return Response(generate_frames(), mimetype='multipart/x-mixed-replace; boundary=frame')
178
+
179
+ def generate_frames():
180
+ global previous_gesture, gesture_start_time, gesture_data_list, capture_flag, start_recording_time
181
+
182
+ # Initialize start recording time
183
+ start_recording_time = datetime.now()
184
+
185
+ cap = cv2.VideoCapture(0)
186
+
187
+ while capture_flag:
188
+ data_aux = []
189
+ x_ = []
190
+ y_ = []
191
+
192
+ ret, frame = cap.read()
193
+ if not ret:
194
+ break
195
+
196
+ H, W, _ = frame.shape
197
+
198
+ frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
199
+
200
+ results = hands.process(frame_rgb)
201
+ if results.multi_hand_landmarks:
202
+ for hand_landmarks in results.multi_hand_landmarks:
203
+ mp_drawing.draw_landmarks(
204
+ frame,
205
+ hand_landmarks,
206
+ mp_hands.HAND_CONNECTIONS,
207
+ mp_drawing_styles.get_default_hand_landmarks_style(),
208
+ mp_drawing_styles.get_default_hand_connections_style())
209
+
210
+ for hand_landmarks in results.multi_hand_landmarks:
211
+ for i in range(len(hand_landmarks.landmark)):
212
+ x = hand_landmarks.landmark[i].x
213
+ y = hand_landmarks.landmark[i].y
214
+
215
+ x_.append(x)
216
+ y_.append(y)
217
+
218
+ for i in range(len(hand_landmarks.landmark)):
219
+ x = hand_landmarks.landmark[i].x
220
+ y = hand_landmarks.landmark[i].y
221
+ data_aux.append(x - min(x_))
222
+ data_aux.append(y - min(y_))
223
+
224
+ x1 = int(min(x_) * W) - 10
225
+ y1 = int(min(y_) * H) - 10
226
+
227
+ x2 = int(max(x_) * W) + 10
228
+ y2 = int(max(y_) * H) + 10
229
+
230
+ prediction = model.predict(frame, conf=0.25, iou=0.45)
231
+ probs = prediction[0].probs.data.numpy()
232
+ detected_gesture_index = np.argmax(probs)
233
+ detected_gesture = custom_labels_dict.get(detected_gesture_index, None)
234
+
235
+ if detected_gesture is None:
236
+ continue
237
+
238
+ # Get the current timestamp and calculate relative time from the start
239
+ current_time = datetime.now()
240
+ relative_time = current_time - start_recording_time
241
+
242
+ # Check if the detected gesture has changed
243
+ if detected_gesture != previous_gesture:
244
+ # If the detected gesture has changed, calculate the duration of the previous gesture
245
+ if previous_gesture is not None:
246
+ gesture_end_time = relative_time.total_seconds()
247
+ gesture_duration = gesture_end_time - gesture_start_time
248
+ # Store the detected gesture, start time, end time, and duration in the list
249
+ gesture_data_list.append([previous_gesture, gesture_start_time, gesture_end_time, round(gesture_duration, 2)])
250
+
251
+ # Update the previous gesture and its start time
252
+ previous_gesture = detected_gesture
253
+ gesture_start_time = relative_time.total_seconds()
254
+
255
+ # Draw rectangle around the detected gesture
256
+ cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 0, 0), 4)
257
+ cv2.putText(frame, detected_gesture, (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 1.3, (0, 255, 0), 3, cv2.LINE_AA)
258
+
259
+ ret, buffer = cv2.imencode('.jpg', frame)
260
+ frame = buffer.tobytes()
261
+
262
+ yield (b'--frame\r\n'
263
+ b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n')
264
+
265
+ cap.release()
266
+
267
+
268
+ @app.route('/upload_csv', methods=['POST'])
269
+ def upload_csv():
270
+ try:
271
+ # Handle file upload
272
+ file = request.files.get('csv_file')
273
+ if file:
274
+ file_path = os.path.join(app.config['UPLOAD_FOLDER'], secure_filename(file.filename))
275
+ file.save(file_path)
276
+ flash("CSV file uploaded successfully!", "success")
277
+
278
+ # Load the uploaded CSV file as original data
279
+ original_df = pd.read_csv(file_path)
280
+ original_data = original_df.to_dict('records')
281
+ columns = original_df.columns.tolist()
282
+
283
+ # Store the original data and file path in the session
284
+ session['original_data'] = original_data
285
+ session['columns'] = columns
286
+ session['file_path'] = file_path
287
+
288
+ else:
289
+ flash("Please upload a CSV file.", "warning")
290
+ except Exception as e:
291
+ app.logger.error(f"Error in upload_csv route: {e}")
292
+ flash("An unexpected error occurred. Please check the logs.", "danger")
293
+
294
+ return redirect(url_for('edit_csv'))
295
+
296
+
297
+ @app.route('/edit_csv', methods=['GET', 'POST'])
298
+ def edit_csv():
299
+ updated_data = None
300
+ original_data = session.get('original_data', None)
301
+ columns = session.get('columns', None)
302
+
303
+ if request.method == 'POST':
304
+ try:
305
+ # Ensure a file has been uploaded
306
+ file_path = session.get('file_path')
307
+ if not file_path:
308
+ flash("Please upload a CSV file first.", "warning")
309
+ return redirect(url_for('edit_csv'))
310
+
311
+ # Load the CSV data as string for processing
312
+ csv_data = read_csv_to_string(file_path)
313
+
314
+ # Get the instruction from the form
315
+ instruction = request.form.get('transcription', "").strip()
316
+ if not instruction:
317
+ flash("Please provide an instruction.", "warning")
318
+ return redirect(url_for('edit_csv'))
319
+
320
+ # Process the CSV using OpenAI API
321
+ raw_output = get_gpt_instruction_response(instruction, csv_data)
322
+
323
+ # Extract and clean only the CSV part from the GPT output
324
+ csv_pattern = re.compile(r"(?<=```)([\s\S]*?)(?=```)|([\s\S]*)", re.DOTALL)
325
+ match = csv_pattern.search(raw_output)
326
+ if match:
327
+ csv_content = match.group(1) or match.group(2)
328
+ csv_content = csv_content.strip() # Clean up leading/trailing spaces
329
+ else:
330
+ raise ValueError("No valid CSV content found in GPT output.")
331
+
332
+ # Further cleaning: Remove any lines not starting with valid CSV columns
333
+ csv_lines = csv_content.splitlines()
334
+ cleaned_csv_lines = [
335
+ line for line in csv_lines if ',' in line and not line.startswith("Here is")
336
+ ]
337
+ cleaned_csv_content = "\n".join(cleaned_csv_lines)
338
+
339
+ # Save the modified CSV to a file
340
+ modified_file_path = os.path.join(app.config['UPLOAD_FOLDER'], 'modified_gesture_data.csv')
341
+ with open(modified_file_path, 'w') as f:
342
+ f.write(cleaned_csv_content)
343
+
344
+ # Load the modified data
345
+ updated_data = pd.read_csv(StringIO(cleaned_csv_content)).to_dict('records')
346
+
347
+ # Store the updated data in the session
348
+ session['updated_data'] = updated_data
349
+
350
+ except Exception as e:
351
+ app.logger.error(f"Error in edit_csv route: {e}")
352
+ flash("An unexpected error occurred. Please check the logs.", "danger")
353
+
354
+ # Load updated data from session if available
355
+ updated_data = session.get('updated_data', None)
356
+
357
+ return render_template('edit_csv.html', original_data=original_data, updated_data=updated_data, columns=columns)
358
+
359
+
360
+
361
+ # Route: Download Modified CSV
362
+ @app.route('/download_csv_updated')
363
+ def download_csv_updated():
364
+ file_path = os.path.join(app.config['UPLOAD_FOLDER'], 'modified_gesture_data.csv')
365
+ if not os.path.isfile(file_path):
366
+ flash("Updated CSV file not found!", "warning")
367
+ return redirect(url_for('edit_csv'))
368
+ return send_from_directory(app.config['UPLOAD_FOLDER'], 'modified_gesture_data.csv', as_attachment=True)
369
+
370
+
371
+
372
+ # Process uploaded audio using Whisper
373
+ @app.route('/process_audio', methods=['POST'])
374
+ def process_audio():
375
+ if 'audio' not in request.files:
376
+ return jsonify({'error': 'No audio file provided'}), 400
377
+
378
+ audio_file = request.files['audio']
379
+ audio_file_path = 'recorded_audio.wav'
380
+ audio_file.save(audio_file_path)
381
+
382
+ # Transcribe audio using Whisper
383
+ transcription = transcribe_audio(audio_file_path)
384
+ return jsonify({'transcription': transcription})
385
+
386
+
387
+
388
+ @app.route('/data_view', methods=['GET'])
389
+ def data_view():
390
+ csv_file = request.args.get('csv_file', 'static/gesture_data.csv')
391
+ gesture_data = load_csv_data(csv_file)
392
+
393
+ df = pd.DataFrame(gesture_data, columns=['Gesture', 'Start Time', 'End Time', 'Duration'])
394
+ gesture_counts = df['Gesture'].value_counts().reset_index()
395
+ gesture_counts.columns = ['Gesture', 'Count']
396
+ fig = px.pie(gesture_counts, values='Count', names='Gesture', title='Gesture Distribution')
397
+ html_chart = fig.to_html(full_html=False)
398
+
399
+ return render_template('data.html', gesture_data=gesture_data, html_chart=html_chart)
400
+
401
+
402
+ import pandas as pd
403
+ from flask import render_template
404
+
405
+ @app.route('/datadiff')
406
+ def datadiff():
407
+ # Load original and modified CSV files
408
+ original_csv_path = os.path.join(app.config['UPLOAD_FOLDER'], 'gesture_data.csv')
409
+ modified_csv_path = os.path.join(app.config['UPLOAD_FOLDER'], 'modified_gesture_data.csv')
410
+
411
+ # Read the CSVs into pandas DataFrames
412
+ original_csv = pd.read_csv(original_csv_path)
413
+ modified_csv = pd.read_csv(modified_csv_path)
414
+
415
+ # Render the datadiff.html page with the data for comparison
416
+ return render_template('datadiff.html', original_csv=original_csv, modified_csv=modified_csv)
417
+
418
+
419
+ def load_csv_data(file_path):
420
+ gesture_data = []
421
+ with open(file_path, 'r') as csvfile:
422
+ reader = csv.reader(csvfile)
423
+ next(reader)
424
+ for row in reader:
425
+ gesture_data.append(row)
426
+ return gesture_data
427
+
428
+ @app.route('/save_data')
429
+ def save_gesture_data():
430
+ global capture_flag
431
+ capture_flag = False
432
+
433
+ # Ensure gesture data is actually populated
434
+ print("Saving gesture data:", gesture_data_list)
435
+
436
+ # Ensure the static directory exists
437
+ os.makedirs('static', exist_ok=True)
438
+
439
+ # Save data to JSON file in Label Studio-compatible format
440
+ json_file_path = os.path.join('static', 'gesture_data_labelstudio.json')
441
+ save_label_studio_json(gesture_data_list, json_file_path)
442
+
443
+ # Save data to CSV file for visualization
444
+ csv_file_path = os.path.join('static', 'gesture_data.csv')
445
+ save_gesture_csv(gesture_data_list, csv_file_path)
446
+
447
+ return redirect(url_for('data'))
448
+
449
+ import random # Make sure to import the random module
450
+ import uuid # Make sure to import uuid for unique IDs
451
+ from datetime import datetime # Import datetime for timestamp
452
+
453
+ def generate_alphanumeric_id(length=5):
454
+ """Generates a random alphanumeric ID."""
455
+ return ''.join(random.choices(string.ascii_letters + string.digits, k=length))
456
+
457
+ def save_label_studio_json(gesture_data, file_path):
458
+ current_time = datetime.utcnow().isoformat() + "Z"
459
+
460
+ # Create a single task with all annotations
461
+ annotations = {
462
+ "id": 1, # Task ID
463
+ "annotations": [
464
+ {
465
+ "id": 1, # Annotation ID
466
+ "completed_by": 1,
467
+ "result": [],
468
+ "was_cancelled": False,
469
+ "ground_truth": False,
470
+ "created_at": current_time,
471
+ "updated_at": current_time,
472
+ "draft_created_at": current_time,
473
+ "lead_time": sum(duration for _, _, _, duration in gesture_data),
474
+ "prediction": {},
475
+ "result_count": 0,
476
+ "unique_id": str(uuid.uuid4()),
477
+ "import_id": None,
478
+ "last_action": None,
479
+ "task": 1,
480
+ "project": 25,
481
+ "updated_by": 1,
482
+ "parent_prediction": None,
483
+ "parent_annotation": None,
484
+ "last_created_by": None
485
+ }
486
+ ],
487
+ "file_upload": "1212df4d-HandyLabels.MP4",
488
+ "drafts": [],
489
+ "predictions": [],
490
+ "data": {
491
+ "video_url": "/data/upload/30/030cca83-Video_1.mp4"
492
+ },
493
+ "meta": {},
494
+ "created_at": current_time,
495
+ "updated_at": current_time,
496
+ "inner_id": 1,
497
+ "total_annotations": 1,
498
+ "cancelled_annotations": 0,
499
+ "total_predictions": 0,
500
+ "comment_count": 0,
501
+ "unresolved_comment_count": 0,
502
+ "last_comment_updated_at": None,
503
+ "project": 25,
504
+ "updated_by": 1,
505
+ "comment_authors": []
506
+ }
507
+
508
+ # Add each gesture as an individual result within the annotation
509
+ for gesture, start_time, end_time, duration in gesture_data:
510
+ annotation_result = {
511
+ "original_length": end_time - start_time,
512
+ "value": {
513
+ "start": start_time,
514
+ "end": end_time,
515
+ "channel": 0,
516
+ "labels": [gesture]
517
+ },
518
+ "id": generate_alphanumeric_id(), # Generate a unique 5-character alphanumeric ID for each result
519
+ "from_name": "tricks",
520
+ "to_name": "audio",
521
+ "type": "labels",
522
+ "origin": "manual"
523
+ }
524
+ annotations["annotations"][0]["result"].append(annotation_result)
525
+
526
+ # Save the consolidated JSON to the file
527
+ with open(file_path, 'w') as json_file:
528
+ json.dump([annotations], json_file, indent=2)
529
+
530
+ print(f"Label Studio JSON saved to: {file_path}")
531
+
532
+
533
+ def save_gesture_csv(gesture_data, file_path):
534
+ with open(file_path, 'w', newline='') as csvfile:
535
+ writer = csv.writer(csvfile)
536
+ writer.writerow(['Gesture', 'Start Time', 'End Time', 'Duration'])
537
+ for gesture, start_time, end_time, duration in gesture_data:
538
+ writer.writerow([gesture, start_time, end_time, duration])
539
+
540
+ @app.route('/data')
541
+ def data():
542
+ gesture_data = load_csv_data()
543
+
544
+ # Convert to DataFrame for easier manipulation
545
+ df = pd.DataFrame(gesture_data, columns=['Gesture', 'Start Time', 'End Time', 'Duration'])
546
+
547
+ # Count occurrences of each gesture
548
+ gesture_counts = df['Gesture'].value_counts().reset_index()
549
+ gesture_counts.columns = ['Gesture', 'Count']
550
+
551
+ # Create the pie chart using Plotly
552
+ fig = px.pie(gesture_counts, values='Count', names='Gesture', title='Gesture Distribution')
553
+
554
+ # Convert the plotly chart to HTML
555
+ html_chart = fig.to_html(full_html=False)
556
+
557
+ return render_template('data.html', gesture_data=gesture_data, html_chart=html_chart)
558
+
559
+ def load_csv_data():
560
+ gesture_data = []
561
+ with open('static/gesture_data.csv', 'r') as csvfile:
562
+ reader = csv.reader(csvfile)
563
+ next(reader) # Skip the header row
564
+ for row in reader:
565
+ gesture_data.append(row)
566
+ return gesture_data
567
+
568
+ @app.route('/download_json')
569
+ def download_json():
570
+ file_path = os.path.join('static', 'gesture_data_labelstudio.json')
571
+
572
+ if not os.path.isfile(file_path):
573
+ return "JSON file not found!", 404
574
+
575
+ return send_from_directory('static', 'gesture_data_labelstudio.json', as_attachment=True)
576
+
577
+ @app.route('/download_csv')
578
+ def download_csv():
579
+ filename = request.args.get('filename')
580
+ if filename == 'original':
581
+ path = os.path.join(app.config['UPLOAD_FOLDER'], 'gesture_data.csv')
582
+ elif filename == 'updated':
583
+ path = os.path.join(app.config['UPLOAD_FOLDER'], 'modified_gesture_data.csv')
584
+ else:
585
+ flash('Invalid file requested')
586
+ return redirect(url_for('edit_csv'))
587
+
588
+ if not os.path.exists(path):
589
+ flash('File not found!')
590
+ return redirect(url_for('edit_csv'))
591
+
592
+ return send_from_directory(app.config['UPLOAD_FOLDER'], os.path.basename(path), as_attachment=True)
593
+
594
+
595
+
596
+ # New route to download the modified CSV
597
+ @app.route('/download_csv_modified')
598
+ def download_csv_modified():
599
+ file_path = os.path.join(app.config['UPLOAD_FOLDER'], 'modified_gesture_data.csv')
600
+
601
+ if not os.path.isfile(file_path):
602
+ return "Modified CSV file not found!", 404
603
+
604
+ return send_from_directory(app.config['UPLOAD_FOLDER'], 'modified_gesture_data.csv', as_attachment=True)
605
+
606
+
607
+ # Import Data Functionality to Visualize Imported CSV
608
+ @app.route('/import_data', methods=['GET', 'POST'])
609
+ def import_data():
610
+ if request.method == 'POST':
611
+ if 'file' not in request.files:
612
+ flash('No file part')
613
+ return redirect(request.url)
614
+ file = request.files['file']
615
+ if file.filename == '':
616
+ flash('No selected file')
617
+ return redirect(request.url)
618
+ if file:
619
+ filename = secure_filename(file.filename)
620
+ file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
621
+ file.save(file_path)
622
+ return redirect(url_for('visualize_data', file_path=file_path))
623
+ return render_template('import_data.html')
624
+
625
+ @app.route('/visualize_data')
626
+ def visualize_data():
627
+ file_path = request.args.get('file_path')
628
+
629
+ if not os.path.exists(file_path):
630
+ return "The file could not be found.", 404
631
+
632
+ return visualize_csv(file_path)
633
+
634
+ def visualize_csv(file_path):
635
+ try:
636
+ # Load gesture data from CSV and process it for visualization
637
+ data = pd.read_csv(file_path)
638
+
639
+ # Check if necessary columns are present
640
+ required_columns = ['Gesture', 'Start Time', 'End Time', 'Duration']
641
+ if not set(required_columns).issubset(data.columns):
642
+ return f"The uploaded CSV must contain the following columns: {required_columns}", 400
643
+
644
+ # Extract relevant columns
645
+ gesture_df = data[required_columns]
646
+
647
+ # Generate a pie chart for gesture distribution
648
+ gesture_counts = gesture_df['Gesture'].value_counts().reset_index()
649
+ gesture_counts.columns = ['Gesture', 'Count']
650
+
651
+ # Create the pie chart using Plotly
652
+ fig = px.pie(gesture_counts, values='Count', names='Gesture', title='Gesture Distribution')
653
+
654
+ # Convert the plotly chart to HTML
655
+ html_chart = fig.to_html(full_html=False)
656
+
657
+ # Render the data.html template with the gesture data and chart
658
+ return render_template('data.html', gesture_data=gesture_df.to_dict('records'), html_chart=html_chart)
659
+
660
+ except Exception as e:
661
+ return f"An error occurred while processing the file: {str(e)}", 500
662
+
663
+ if __name__ == '__main__':
664
+ app.run(debug=True)
Web app/fist.png ADDED
Web app/ok.png ADDED
Web app/peace.png ADDED
Web app/requirements.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ Flask
2
+ opencv-python-headless
3
+ mediapipe
4
+ numpy
5
+ ultralytics
Web app/static/.DS_Store ADDED
Binary file (6.15 kB). View file
 
Web app/static/gesture_data.csv ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Gesture,Start Time,End Time,Duration
2
+ a,2.703457,2.791474,0.09
3
+ c,2.791474,3.177169,0.39
4
+ a,3.177169,3.477513,0.3
5
+ c,3.477513,3.548062,0.07
6
+ a,3.548062,3.834808,0.29
7
+ c,3.834808,4.306889,0.47
8
+ a,4.306889,4.577862,0.27
9
+ d,4.577862,5.560682,0.98
10
+ a,5.560682,6.556111,1.0
11
+ d,6.556111,7.764478,1.21
12
+ a,7.764478,8.422632,0.66
13
+ d,8.422632,8.488039,0.07
14
+ b,8.488039,8.556642,0.07
15
+ d,8.556642,8.630307,0.07
16
+ b,8.630307,9.021776,0.39
17
+ a,9.021776,9.45754,0.44
18
+ b,9.45754,9.521933,0.06
19
+ c,9.521933,9.657519,0.14
20
+ b,9.657519,9.762675,0.11
21
+ c,9.762675,9.821062,0.06
22
+ b,9.821062,9.892455,0.07
23
+ a,9.892455,9.954735,0.06
24
+ c,9.954735,10.020776,0.07
25
+ a,10.020776,10.08684,0.07
26
+ b,10.08684,10.153284,0.07
27
+ a,10.153284,10.221532,0.07
28
+ c,10.221532,10.419941,0.2
29
+ a,10.419941,10.624734,0.2
30
+ c,10.624734,10.691169,0.07
Web app/static/gesture_data_labelstudio.json ADDED
@@ -0,0 +1,513 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ {
3
+ "id": 1,
4
+ "annotations": [
5
+ {
6
+ "id": 1,
7
+ "completed_by": 1,
8
+ "result": [
9
+ {
10
+ "original_length": 0.08801699999999979,
11
+ "value": {
12
+ "start": 2.703457,
13
+ "end": 2.791474,
14
+ "channel": 0,
15
+ "labels": [
16
+ "a"
17
+ ]
18
+ },
19
+ "id": "KOibD",
20
+ "from_name": "tricks",
21
+ "to_name": "audio",
22
+ "type": "labels",
23
+ "origin": "manual"
24
+ },
25
+ {
26
+ "original_length": 0.3856950000000001,
27
+ "value": {
28
+ "start": 2.791474,
29
+ "end": 3.177169,
30
+ "channel": 0,
31
+ "labels": [
32
+ "c"
33
+ ]
34
+ },
35
+ "id": "W7eFd",
36
+ "from_name": "tricks",
37
+ "to_name": "audio",
38
+ "type": "labels",
39
+ "origin": "manual"
40
+ },
41
+ {
42
+ "original_length": 0.30034399999999994,
43
+ "value": {
44
+ "start": 3.177169,
45
+ "end": 3.477513,
46
+ "channel": 0,
47
+ "labels": [
48
+ "a"
49
+ ]
50
+ },
51
+ "id": "kKjBN",
52
+ "from_name": "tricks",
53
+ "to_name": "audio",
54
+ "type": "labels",
55
+ "origin": "manual"
56
+ },
57
+ {
58
+ "original_length": 0.07054899999999975,
59
+ "value": {
60
+ "start": 3.477513,
61
+ "end": 3.548062,
62
+ "channel": 0,
63
+ "labels": [
64
+ "c"
65
+ ]
66
+ },
67
+ "id": "1vHtz",
68
+ "from_name": "tricks",
69
+ "to_name": "audio",
70
+ "type": "labels",
71
+ "origin": "manual"
72
+ },
73
+ {
74
+ "original_length": 0.2867460000000004,
75
+ "value": {
76
+ "start": 3.548062,
77
+ "end": 3.834808,
78
+ "channel": 0,
79
+ "labels": [
80
+ "a"
81
+ ]
82
+ },
83
+ "id": "YWcJm",
84
+ "from_name": "tricks",
85
+ "to_name": "audio",
86
+ "type": "labels",
87
+ "origin": "manual"
88
+ },
89
+ {
90
+ "original_length": 0.47208099999999975,
91
+ "value": {
92
+ "start": 3.834808,
93
+ "end": 4.306889,
94
+ "channel": 0,
95
+ "labels": [
96
+ "c"
97
+ ]
98
+ },
99
+ "id": "02CzC",
100
+ "from_name": "tricks",
101
+ "to_name": "audio",
102
+ "type": "labels",
103
+ "origin": "manual"
104
+ },
105
+ {
106
+ "original_length": 0.2709729999999997,
107
+ "value": {
108
+ "start": 4.306889,
109
+ "end": 4.577862,
110
+ "channel": 0,
111
+ "labels": [
112
+ "a"
113
+ ]
114
+ },
115
+ "id": "bEN1o",
116
+ "from_name": "tricks",
117
+ "to_name": "audio",
118
+ "type": "labels",
119
+ "origin": "manual"
120
+ },
121
+ {
122
+ "original_length": 0.9828200000000002,
123
+ "value": {
124
+ "start": 4.577862,
125
+ "end": 5.560682,
126
+ "channel": 0,
127
+ "labels": [
128
+ "d"
129
+ ]
130
+ },
131
+ "id": "mBtVB",
132
+ "from_name": "tricks",
133
+ "to_name": "audio",
134
+ "type": "labels",
135
+ "origin": "manual"
136
+ },
137
+ {
138
+ "original_length": 0.9954289999999997,
139
+ "value": {
140
+ "start": 5.560682,
141
+ "end": 6.556111,
142
+ "channel": 0,
143
+ "labels": [
144
+ "a"
145
+ ]
146
+ },
147
+ "id": "yiqVI",
148
+ "from_name": "tricks",
149
+ "to_name": "audio",
150
+ "type": "labels",
151
+ "origin": "manual"
152
+ },
153
+ {
154
+ "original_length": 1.2083670000000009,
155
+ "value": {
156
+ "start": 6.556111,
157
+ "end": 7.764478,
158
+ "channel": 0,
159
+ "labels": [
160
+ "d"
161
+ ]
162
+ },
163
+ "id": "WbZ1U",
164
+ "from_name": "tricks",
165
+ "to_name": "audio",
166
+ "type": "labels",
167
+ "origin": "manual"
168
+ },
169
+ {
170
+ "original_length": 0.6581539999999997,
171
+ "value": {
172
+ "start": 7.764478,
173
+ "end": 8.422632,
174
+ "channel": 0,
175
+ "labels": [
176
+ "a"
177
+ ]
178
+ },
179
+ "id": "3eHhx",
180
+ "from_name": "tricks",
181
+ "to_name": "audio",
182
+ "type": "labels",
183
+ "origin": "manual"
184
+ },
185
+ {
186
+ "original_length": 0.06540700000000044,
187
+ "value": {
188
+ "start": 8.422632,
189
+ "end": 8.488039,
190
+ "channel": 0,
191
+ "labels": [
192
+ "d"
193
+ ]
194
+ },
195
+ "id": "Ohri7",
196
+ "from_name": "tricks",
197
+ "to_name": "audio",
198
+ "type": "labels",
199
+ "origin": "manual"
200
+ },
201
+ {
202
+ "original_length": 0.06860299999999953,
203
+ "value": {
204
+ "start": 8.488039,
205
+ "end": 8.556642,
206
+ "channel": 0,
207
+ "labels": [
208
+ "b"
209
+ ]
210
+ },
211
+ "id": "OvB28",
212
+ "from_name": "tricks",
213
+ "to_name": "audio",
214
+ "type": "labels",
215
+ "origin": "manual"
216
+ },
217
+ {
218
+ "original_length": 0.07366500000000009,
219
+ "value": {
220
+ "start": 8.556642,
221
+ "end": 8.630307,
222
+ "channel": 0,
223
+ "labels": [
224
+ "d"
225
+ ]
226
+ },
227
+ "id": "N2Jwo",
228
+ "from_name": "tricks",
229
+ "to_name": "audio",
230
+ "type": "labels",
231
+ "origin": "manual"
232
+ },
233
+ {
234
+ "original_length": 0.39146899999999896,
235
+ "value": {
236
+ "start": 8.630307,
237
+ "end": 9.021776,
238
+ "channel": 0,
239
+ "labels": [
240
+ "b"
241
+ ]
242
+ },
243
+ "id": "RHjHA",
244
+ "from_name": "tricks",
245
+ "to_name": "audio",
246
+ "type": "labels",
247
+ "origin": "manual"
248
+ },
249
+ {
250
+ "original_length": 0.4357640000000007,
251
+ "value": {
252
+ "start": 9.021776,
253
+ "end": 9.45754,
254
+ "channel": 0,
255
+ "labels": [
256
+ "a"
257
+ ]
258
+ },
259
+ "id": "IBLPf",
260
+ "from_name": "tricks",
261
+ "to_name": "audio",
262
+ "type": "labels",
263
+ "origin": "manual"
264
+ },
265
+ {
266
+ "original_length": 0.06439300000000081,
267
+ "value": {
268
+ "start": 9.45754,
269
+ "end": 9.521933,
270
+ "channel": 0,
271
+ "labels": [
272
+ "b"
273
+ ]
274
+ },
275
+ "id": "S4wb0",
276
+ "from_name": "tricks",
277
+ "to_name": "audio",
278
+ "type": "labels",
279
+ "origin": "manual"
280
+ },
281
+ {
282
+ "original_length": 0.13558599999999998,
283
+ "value": {
284
+ "start": 9.521933,
285
+ "end": 9.657519,
286
+ "channel": 0,
287
+ "labels": [
288
+ "c"
289
+ ]
290
+ },
291
+ "id": "djRDv",
292
+ "from_name": "tricks",
293
+ "to_name": "audio",
294
+ "type": "labels",
295
+ "origin": "manual"
296
+ },
297
+ {
298
+ "original_length": 0.10515599999999914,
299
+ "value": {
300
+ "start": 9.657519,
301
+ "end": 9.762675,
302
+ "channel": 0,
303
+ "labels": [
304
+ "b"
305
+ ]
306
+ },
307
+ "id": "ZEXyc",
308
+ "from_name": "tricks",
309
+ "to_name": "audio",
310
+ "type": "labels",
311
+ "origin": "manual"
312
+ },
313
+ {
314
+ "original_length": 0.058386999999999745,
315
+ "value": {
316
+ "start": 9.762675,
317
+ "end": 9.821062,
318
+ "channel": 0,
319
+ "labels": [
320
+ "c"
321
+ ]
322
+ },
323
+ "id": "bCt3N",
324
+ "from_name": "tricks",
325
+ "to_name": "audio",
326
+ "type": "labels",
327
+ "origin": "manual"
328
+ },
329
+ {
330
+ "original_length": 0.07139300000000048,
331
+ "value": {
332
+ "start": 9.821062,
333
+ "end": 9.892455,
334
+ "channel": 0,
335
+ "labels": [
336
+ "b"
337
+ ]
338
+ },
339
+ "id": "UKpDs",
340
+ "from_name": "tricks",
341
+ "to_name": "audio",
342
+ "type": "labels",
343
+ "origin": "manual"
344
+ },
345
+ {
346
+ "original_length": 0.06227999999999945,
347
+ "value": {
348
+ "start": 9.892455,
349
+ "end": 9.954735,
350
+ "channel": 0,
351
+ "labels": [
352
+ "a"
353
+ ]
354
+ },
355
+ "id": "4MMP5",
356
+ "from_name": "tricks",
357
+ "to_name": "audio",
358
+ "type": "labels",
359
+ "origin": "manual"
360
+ },
361
+ {
362
+ "original_length": 0.06604100000000024,
363
+ "value": {
364
+ "start": 9.954735,
365
+ "end": 10.020776,
366
+ "channel": 0,
367
+ "labels": [
368
+ "c"
369
+ ]
370
+ },
371
+ "id": "lWUrb",
372
+ "from_name": "tricks",
373
+ "to_name": "audio",
374
+ "type": "labels",
375
+ "origin": "manual"
376
+ },
377
+ {
378
+ "original_length": 0.06606400000000079,
379
+ "value": {
380
+ "start": 10.020776,
381
+ "end": 10.08684,
382
+ "channel": 0,
383
+ "labels": [
384
+ "a"
385
+ ]
386
+ },
387
+ "id": "o59rB",
388
+ "from_name": "tricks",
389
+ "to_name": "audio",
390
+ "type": "labels",
391
+ "origin": "manual"
392
+ },
393
+ {
394
+ "original_length": 0.06644399999999884,
395
+ "value": {
396
+ "start": 10.08684,
397
+ "end": 10.153284,
398
+ "channel": 0,
399
+ "labels": [
400
+ "b"
401
+ ]
402
+ },
403
+ "id": "pUPWY",
404
+ "from_name": "tricks",
405
+ "to_name": "audio",
406
+ "type": "labels",
407
+ "origin": "manual"
408
+ },
409
+ {
410
+ "original_length": 0.06824800000000053,
411
+ "value": {
412
+ "start": 10.153284,
413
+ "end": 10.221532,
414
+ "channel": 0,
415
+ "labels": [
416
+ "a"
417
+ ]
418
+ },
419
+ "id": "WilaI",
420
+ "from_name": "tricks",
421
+ "to_name": "audio",
422
+ "type": "labels",
423
+ "origin": "manual"
424
+ },
425
+ {
426
+ "original_length": 0.19840899999999984,
427
+ "value": {
428
+ "start": 10.221532,
429
+ "end": 10.419941,
430
+ "channel": 0,
431
+ "labels": [
432
+ "c"
433
+ ]
434
+ },
435
+ "id": "YBJjW",
436
+ "from_name": "tricks",
437
+ "to_name": "audio",
438
+ "type": "labels",
439
+ "origin": "manual"
440
+ },
441
+ {
442
+ "original_length": 0.20479300000000045,
443
+ "value": {
444
+ "start": 10.419941,
445
+ "end": 10.624734,
446
+ "channel": 0,
447
+ "labels": [
448
+ "a"
449
+ ]
450
+ },
451
+ "id": "cU0uW",
452
+ "from_name": "tricks",
453
+ "to_name": "audio",
454
+ "type": "labels",
455
+ "origin": "manual"
456
+ },
457
+ {
458
+ "original_length": 0.06643500000000024,
459
+ "value": {
460
+ "start": 10.624734,
461
+ "end": 10.691169,
462
+ "channel": 0,
463
+ "labels": [
464
+ "c"
465
+ ]
466
+ },
467
+ "id": "mceJf",
468
+ "from_name": "tricks",
469
+ "to_name": "audio",
470
+ "type": "labels",
471
+ "origin": "manual"
472
+ }
473
+ ],
474
+ "was_cancelled": false,
475
+ "ground_truth": false,
476
+ "created_at": "2024-11-09T18:25:42.902443Z",
477
+ "updated_at": "2024-11-09T18:25:42.902443Z",
478
+ "draft_created_at": "2024-11-09T18:25:42.902443Z",
479
+ "lead_time": 8.020000000000001,
480
+ "prediction": {},
481
+ "result_count": 0,
482
+ "unique_id": "f2f0c0dc-d8fa-4eb6-ac07-ba75ee6b3d61",
483
+ "import_id": null,
484
+ "last_action": null,
485
+ "task": 1,
486
+ "project": 25,
487
+ "updated_by": 1,
488
+ "parent_prediction": null,
489
+ "parent_annotation": null,
490
+ "last_created_by": null
491
+ }
492
+ ],
493
+ "file_upload": "1212df4d-HandyLabels.MP4",
494
+ "drafts": [],
495
+ "predictions": [],
496
+ "data": {
497
+ "video_url": "/data/upload/30/030cca83-Video_1.mp4"
498
+ },
499
+ "meta": {},
500
+ "created_at": "2024-11-09T18:25:42.902443Z",
501
+ "updated_at": "2024-11-09T18:25:42.902443Z",
502
+ "inner_id": 1,
503
+ "total_annotations": 1,
504
+ "cancelled_annotations": 0,
505
+ "total_predictions": 0,
506
+ "comment_count": 0,
507
+ "unresolved_comment_count": 0,
508
+ "last_comment_updated_at": null,
509
+ "project": 25,
510
+ "updated_by": 1,
511
+ "comment_authors": []
512
+ }
513
+ ]
Web app/static/images/fist.png ADDED
Web app/static/images/ok.png ADDED
Web app/static/images/peace.png ADDED
Web app/static/images/stop.png ADDED
Web app/static/images/two_up.png ADDED
Web app/static/imported_gesture_data.csv ADDED
File without changes
Web app/static/placeholder.txt ADDED
File without changes
Web app/stop.png ADDED
Web app/templates/compare_data.html ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>Compare CSV Data</title>
8
+ <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
9
+ </head>
10
+
11
+ <body>
12
+ <div class="container mt-5">
13
+ <h2 class="mb-4">Compare Original and Updated CSV</h2>
14
+ <div class="row">
15
+ <div class="col-md-6">
16
+ <h3>Original Data</h3>
17
+ <table class="table table-bordered">
18
+ <thead>
19
+ <tr>
20
+ {% for column in columns %}
21
+ <th>{{ column }}</th>
22
+ {% endfor %}
23
+ </tr>
24
+ </thead>
25
+ <tbody>
26
+ {% for row in original_data %}
27
+ <tr>
28
+ {% for column in columns %}
29
+ <td>{{ row[column] }}</td>
30
+ {% endfor %}
31
+ </tr>
32
+ {% endfor %}
33
+ </tbody>
34
+ </table>
35
+ </div>
36
+ <div class="col-md-6">
37
+ <h3>Updated Data</h3>
38
+ <table class="table table-bordered">
39
+ <thead>
40
+ <tr>
41
+ {% for column in columns %}
42
+ <th>{{ column }}</th>
43
+ {% endfor %}
44
+ </tr>
45
+ </thead>
46
+ <tbody>
47
+ {% for row in updated_data %}
48
+ <tr>
49
+ {% for column in columns %}
50
+ <td>{{ row[column] }}</td>
51
+ {% endfor %}
52
+ </tr>
53
+ {% endfor %}
54
+ </tbody>
55
+ </table>
56
+ </div>
57
+ </div>
58
+ <a href="{{ url_for('download_csv_updated') }}" class="btn btn-primary mt-4">Download Updated CSV</a>
59
+ </div>
60
+ </body>
61
+
62
+ </html>
Web app/templates/csv_file.html ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>CSV File Viewer</title>
7
+ <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
8
+ </head>
9
+ <body>
10
+ <div class="container mt-5">
11
+ <h2 class="mb-4">Upload and View CSV File</h2>
12
+ <div class="form-group">
13
+ <input type="file" id="fileInput" class="form-control-file" accept=".csv">
14
+ </div>
15
+ <table id="csvTable" class="table table-bordered mt-4">
16
+ <thead>
17
+ <tr id="tableHeader"></tr>
18
+ </thead>
19
+ <tbody id="tableBody"></tbody>
20
+ </table>
21
+ </div>
22
+
23
+ <script>
24
+ document.getElementById('fileInput').addEventListener('change', function(event) {
25
+ const file = event.target.files[0];
26
+ if (file && file.type.match('text/csv')) {
27
+ const reader = new FileReader();
28
+ reader.onload = function(e) {
29
+ const text = e.target.result;
30
+ displayCSVData(text);
31
+ };
32
+ reader.readAsText(file);
33
+ } else {
34
+ alert('Please upload a valid CSV file.');
35
+ }
36
+ });
37
+
38
+ function displayCSVData(csvText) {
39
+ const rows = csvText.split('\n');
40
+ const headerRow = rows[0].split(',');
41
+ const headerElement = document.getElementById('tableHeader');
42
+ headerElement.innerHTML = '';
43
+ headerRow.forEach(header => {
44
+ const th = document.createElement('th');
45
+ th.textContent = header.trim();
46
+ headerElement.appendChild(th);
47
+ });
48
+
49
+ const bodyElement = document.getElementById('tableBody');
50
+ bodyElement.innerHTML = '';
51
+ rows.slice(1).forEach(row => {
52
+ if (row.trim() !== '') {
53
+ const rowElement = document.createElement('tr');
54
+ const cells = row.split(',');
55
+ cells.forEach(cell => {
56
+ const td = document.createElement('td');
57
+ td.textContent = cell.trim();
58
+ rowElement.appendChild(td);
59
+ });
60
+ bodyElement.appendChild(rowElement);
61
+ }
62
+ });
63
+ }
64
+ </script>
65
+ </body>
66
+ </html>
Web app/templates/data.html ADDED
@@ -0,0 +1,99 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Gesture Data</title>
7
+ <style>
8
+ body {
9
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
10
+ padding: 20px;
11
+ background-color: #fafafa;
12
+ color: #333;
13
+ }
14
+ .container {
15
+ display: grid;
16
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
17
+ gap: 20px;
18
+ margin-top: 20px;
19
+ }
20
+ .table-container, .chart-container {
21
+ border: 1px solid #ddd;
22
+ padding: 20px;
23
+ border-radius: 8px;
24
+ background-color: #ffffff;
25
+ box-shadow: 0 2px 5px rgba(0,0,0,0.1);
26
+ }
27
+ table {
28
+ width: 100%;
29
+ border-collapse: collapse;
30
+ }
31
+ th, td {
32
+ padding: 10px;
33
+ border: 1px solid #ddd;
34
+ text-align: left;
35
+ }
36
+ th {
37
+ background-color: #f8f8f8;
38
+ font-weight: 600;
39
+ }
40
+ tr:nth-child(even) {
41
+ background-color: #f9f9f9;
42
+ }
43
+ tr:hover {
44
+ background-color: #f1f1f1;
45
+ }
46
+ a {
47
+ display: inline-block;
48
+ margin-top: 20px;
49
+ padding: 10px 15px;
50
+ background-color: #4CAF50;
51
+ color: white;
52
+ text-decoration: none;
53
+ border-radius: 5px;
54
+ font-weight: bold;
55
+ }
56
+ a:hover {
57
+ background-color: #45a049;
58
+ }
59
+ h1, h2 {
60
+ color: #333;
61
+ }
62
+ </style>
63
+ </head>
64
+ <body>
65
+ <h1>Gesture Data Overview</h1>
66
+ <div class="container">
67
+ <div class="table-container">
68
+ <h2>Gesture Data Table</h2>
69
+ <table>
70
+ <thead>
71
+ <tr>
72
+ <th>Gesture</th>
73
+ <th>Start Time</th>
74
+ <th>End Time</th>
75
+ <th>Duration</th>
76
+ </tr>
77
+ </thead>
78
+ <tbody>
79
+ {% for row in gesture_data %}
80
+ <tr>
81
+ <td>{{ row[0] }}</td>
82
+ <td>{{ row[1] }}</td>
83
+ <td>{{ row[2] }}</td>
84
+ <td>{{ row[3] }}</td>
85
+ </tr>
86
+ {% endfor %}
87
+ </tbody>
88
+ </table>
89
+ </div>
90
+ <div class="chart-container">
91
+ <h2>Gesture Distribution Chart</h2>
92
+ {{ html_chart|safe }}
93
+ </div>
94
+ </div>
95
+ <a href="{{ url_for('download_csv') }}">Download Captured Gesture Data CSV</a>
96
+ <a href="{{ url_for('download_json') }}">Download Captured Gesture Data JSON</a>
97
+
98
+ </body>
99
+ </html>
Web app/templates/data1.html ADDED
@@ -0,0 +1,147 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Edit CSV via Speech - Gesture Data</title>
7
+ <style>
8
+ body {
9
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
10
+ padding: 20px;
11
+ background-color: #fafafa;
12
+ color: #333;
13
+ }
14
+ .container {
15
+ display: grid;
16
+ grid-template-columns: 1fr 1fr;
17
+ gap: 20px;
18
+ margin-top: 20px;
19
+ }
20
+ .table-container, .chart-container {
21
+ border: 1px solid #ddd;
22
+ padding: 20px;
23
+ border-radius: 8px;
24
+ background-color: #ffffff;
25
+ box-shadow: 0 2px 5px rgba(0,0,0,0.1);
26
+ }
27
+ table {
28
+ width: 100%;
29
+ border-collapse: collapse;
30
+ }
31
+ th, td {
32
+ padding: 10px;
33
+ border: 1px solid #ddd;
34
+ text-align: left;
35
+ }
36
+ th {
37
+ background-color: #f8f8f8;
38
+ font-weight: 600;
39
+ }
40
+ tr:nth-child(even) {
41
+ background-color: #f9f9f9;
42
+ }
43
+ tr:hover {
44
+ background-color: #f1f1f1;
45
+ }
46
+ a, button {
47
+ display: inline-block;
48
+ margin-top: 20px;
49
+ padding: 10px 15px;
50
+ background-color: #4CAF50;
51
+ color: white;
52
+ text-decoration: none;
53
+ border-radius: 5px;
54
+ font-weight: bold;
55
+ cursor: pointer;
56
+ }
57
+ a:hover, button:hover {
58
+ background-color: #45a049;
59
+ }
60
+ h1, h2 {
61
+ color: #333;
62
+ }
63
+ </style>
64
+ </head>
65
+ <body>
66
+ <h1>Edit CSV via Speech - Gesture Data Overview</h1>
67
+
68
+ <!-- CSV Upload Section -->
69
+ <form method="POST" enctype="multipart/form-data">
70
+ <div>
71
+ <input type="file" name="csv_file" accept=".csv" onchange="this.form.submit()">
72
+ </div>
73
+
74
+ <!-- Recording Section -->
75
+ <div>
76
+ <label for="transcription">Transcription:</label>
77
+ <textarea id="transcription" name="transcription" rows="4" cols="50" readonly>{{ transcription }}</textarea>
78
+ </div>
79
+
80
+ <div>
81
+ <button type="submit" name="record">🎤 Record</button>
82
+ <button type="submit" name="clear">Clear</button>
83
+ </div>
84
+ </form>
85
+
86
+ <!-- Data Comparison Section -->
87
+ {% if original_data is not none and updated_data is not none %}
88
+ <div class="container">
89
+ <!-- Original Data Table -->
90
+ <div class="table-container">
91
+ <h2>Original Gesture Data</h2>
92
+ <table>
93
+ <thead>
94
+ <tr>
95
+ {% for col in original_data.columns %}
96
+ <th>{{ col }}</th>
97
+ {% endfor %}
98
+ </tr>
99
+ </thead>
100
+ <tbody>
101
+ {% for row in original_data.itertuples() %}
102
+ <tr>
103
+ {% for col in row[1:] %}
104
+ <td>{{ col }}</td>
105
+ {% endfor %}
106
+ </tr>
107
+ {% endfor %}
108
+ </tbody>
109
+ </table>
110
+ </div>
111
+
112
+ <!-- Updated Data Table -->
113
+ <div class="table-container">
114
+ <h2>Updated Gesture Data</h2>
115
+ <table>
116
+ <thead>
117
+ <tr>
118
+ {% for col in updated_data.columns %}
119
+ <th>{{ col }}</th>
120
+ {% endfor %}
121
+ </tr>
122
+ </thead>
123
+ <tbody>
124
+ {% for row in updated_data.itertuples() %}
125
+ <tr>
126
+ {% for col in row[1:] %}
127
+ <td>{{ col }}</td>
128
+ {% endfor %}
129
+ </tr>
130
+ {% endfor %}
131
+ </tbody>
132
+ </table>
133
+ </div>
134
+ </div>
135
+
136
+ <!-- Download Buttons -->
137
+ <div>
138
+ <form method="GET" action="{{ url_for('download_csv', filename='original') }}">
139
+ <button type="submit">Download Original CSV</button>
140
+ </form>
141
+ <form method="GET" action="{{ url_for('download_csv', filename='updated') }}">
142
+ <button type="submit">Download Updated CSV</button>
143
+ </form>
144
+ </div>
145
+ {% endif %}
146
+ </body>
147
+ </html>
Web app/templates/edit_csv.html ADDED
@@ -0,0 +1,183 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>CSV File Editor</title>
8
+ <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
9
+ <style>
10
+ .container {
11
+ margin-top: 20px;
12
+ }
13
+
14
+ .btn {
15
+ margin-top: 20px;
16
+ }
17
+
18
+ .form-control {
19
+ width: 100%;
20
+ }
21
+
22
+ .data-section {
23
+ margin-top: 40px;
24
+ }
25
+
26
+ .table-container {
27
+ overflow-x: auto;
28
+ }
29
+
30
+ .table-wrapper {
31
+ display: flex;
32
+ justify-content: space-between;
33
+ gap: 20px;
34
+ }
35
+
36
+ .table-wrapper table {
37
+ width: 100%;
38
+ }
39
+ </style>
40
+ </head>
41
+
42
+ <body>
43
+ <div class="container">
44
+ <h2 class="mb-4">Upload, View, and Edit CSV File</h2>
45
+
46
+ <!-- CSV Upload Section -->
47
+ <form method="POST" action="/upload_csv" enctype="multipart/form-data">
48
+ <div class="form-group">
49
+ <label for="csvFile">Upload CSV File:</label>
50
+ <input type="file" name="csv_file" id="csvFile" class="form-control-file" accept=".csv" required>
51
+
52
+ <button type="submit" class="btn btn-primary">Upload CSV</button>
53
+ </form>
54
+
55
+ <!-- Instruction Section -->
56
+ <form method="POST" action="/edit_csv">
57
+ <!-- Buttons for Speech Recording -->
58
+ <div class="mt-4">
59
+ <button type="button" id="recordButton" class="btn btn-danger">🎤 Record</button>
60
+ <button type="button" id="stopButton" class="btn btn-secondary" disabled>⏹ Stop</button>
61
+ </div>
62
+ <div class="form-group mt-4">
63
+ <label for="transcription">Instruction:</label>
64
+ <textarea name="transcription" id="transcription" class="form-control" rows="3" placeholder="Provide instructions for CSV manipulation..."></textarea>
65
+ </div>
66
+ <button type="submit" class="btn btn-success">Process Instruction</button>
67
+ </form>
68
+
69
+
70
+ <!-- Original and Updated Data Section -->
71
+ <div class="data-section">
72
+ <h3>Data Comparison</h3>
73
+ <div class="table-wrapper">
74
+ <!-- Original Data -->
75
+ {% if original_data %}
76
+ <div>
77
+ <h4>Original Data</h4>
78
+ <div class="table-container">
79
+ <table class="table table-bordered">
80
+ <thead>
81
+ <tr>
82
+ {% for column in columns %}
83
+ <th>{{ column }}</th>
84
+ {% endfor %}
85
+ </tr>
86
+ </thead>
87
+ <tbody>
88
+ {% for row in original_data %}
89
+ <tr>
90
+ {% for column in columns %}
91
+ <td>{{ row[column] }}</td>
92
+ {% endfor %}
93
+ </tr>
94
+ {% endfor %}
95
+ </tbody>
96
+ </table>
97
+ </div>
98
+ </div>
99
+ {% endif %}
100
+
101
+ <!-- Updated Data -->
102
+ {% if updated_data %}
103
+ <div>
104
+ <h4>Updated Data</h4>
105
+ <div class="table-container">
106
+ <table class="table table-bordered">
107
+ <thead>
108
+ <tr>
109
+ {% for column in columns %}
110
+ <th>{{ column }}</th>
111
+ {% endfor %}
112
+ </tr>
113
+ </thead>
114
+ <tbody>
115
+ {% for row in updated_data %}
116
+ <tr>
117
+ {% for column in columns %}
118
+ <td>{{ row[column] }}</td>
119
+ {% endfor %}
120
+ </tr>
121
+ {% endfor %}
122
+ </tbody>
123
+ </table>
124
+ </div>
125
+ </div>
126
+ {% endif %}
127
+ </div>
128
+ </div>
129
+ </div>
130
+ <script>
131
+ let mediaRecorder;
132
+ let audioChunks = [];
133
+
134
+ document.getElementById('recordButton').addEventListener('click', function () {
135
+ navigator.mediaDevices.getUserMedia({ audio: true })
136
+ .then(stream => {
137
+ mediaRecorder = new MediaRecorder(stream);
138
+ mediaRecorder.start();
139
+
140
+ audioChunks = [];
141
+ mediaRecorder.ondataavailable = event => {
142
+ audioChunks.push(event.data);
143
+ };
144
+
145
+ mediaRecorder.onstart = () => {
146
+ document.getElementById('recordButton').disabled = true;
147
+ document.getElementById('stopButton').disabled = false;
148
+ };
149
+
150
+ mediaRecorder.onstop = () => {
151
+ document.getElementById('recordButton').disabled = false;
152
+ document.getElementById('stopButton').disabled = true;
153
+
154
+ const audioBlob = new Blob(audioChunks, { type: 'audio/wav' });
155
+ const formData = new FormData();
156
+ formData.append('audio', audioBlob, 'recording.wav');
157
+
158
+ // Send the audio blob to the backend for transcription
159
+ fetch('/process_audio', {
160
+ method: 'POST',
161
+ body: formData
162
+ })
163
+ .then(response => response.json())
164
+ .then(data => {
165
+ document.getElementById('transcription').value = data.transcription;
166
+ })
167
+ .catch(error => {
168
+ console.error('Error:', error);
169
+ });
170
+ };
171
+ })
172
+ .catch(error => {
173
+ console.error('Error accessing microphone:', error);
174
+ });
175
+ });
176
+
177
+ document.getElementById('stopButton').addEventListener('click', function () {
178
+ mediaRecorder.stop();
179
+ });
180
+ </script>
181
+ </body>
182
+
183
+ </html>
Web app/templates/import_data.html ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Import Gesture Data</title>
7
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
8
+ <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap" rel="stylesheet">
9
+ <style>
10
+ body {
11
+ font-family: 'Roboto', sans-serif;
12
+ background-color: #f4f4f9;
13
+ margin: 0;
14
+ padding: 0;
15
+ display: flex;
16
+ align-items: center;
17
+ justify-content: center;
18
+ min-height: 100vh;
19
+ }
20
+ .container {
21
+ background: #ffffff;
22
+ box-shadow: 0px 4px 15px rgba(0, 0, 0, 0.1);
23
+ padding: 30px;
24
+ border-radius: 10px;
25
+ text-align: center;
26
+ max-width: 500px;
27
+ width: 100%;
28
+ }
29
+ h1 {
30
+ color: #333;
31
+ margin-bottom: 20px;
32
+ }
33
+ form {
34
+ margin-bottom: 20px;
35
+ }
36
+ input[type="file"] {
37
+ padding: 10px;
38
+ font-size: 16px;
39
+ margin-bottom: 20px;
40
+ border: 1px solid #ccc;
41
+ border-radius: 5px;
42
+ width: 100%;
43
+ box-sizing: border-box;
44
+ }
45
+ button {
46
+ background-color: #4CAF50;
47
+ color: white;
48
+ padding: 10px 20px;
49
+ border: none;
50
+ border-radius: 5px;
51
+ font-size: 16px;
52
+ cursor: pointer;
53
+ transition: background-color 0.3s ease;
54
+ }
55
+ button:hover {
56
+ background-color: #4CAF50;
57
+ }
58
+ .back-link {
59
+ display: inline-block;
60
+ margin-top: 20px;
61
+ color: #4CAF50;
62
+ text-decoration: none;
63
+ font-weight: 700;
64
+ transition: color 0.3s ease;
65
+ }
66
+ .back-link:hover {
67
+ color: #4CAF50;
68
+ }
69
+ .fa-upload {
70
+ margin-right: 8px;
71
+ }
72
+ </style>
73
+ </head>
74
+ <body>
75
+ <div class="container">
76
+ <h1><i class="fas fa-file-upload"></i> Import Gesture Data</h1>
77
+ <p>Upload your gesture data CSV file to visualize the results.</p>
78
+ <form action="{{ url_for('import_data') }}" method="POST" enctype="multipart/form-data">
79
+ <input type="file" name="file" accept=".csv" required>
80
+ <button type="submit"><i class="fas fa-upload"></i> Upload and Visualize</button>
81
+ </form>
82
+ <a href="{{ url_for('index') }}" class="back-link"><i class="fas fa-arrow-left"></i> Back to Home</a>
83
+ </div>
84
+ </body>
85
+ </html>
86
+ ''
Web app/templates/import_data1.html ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Import Gesture Data</title>
7
+ <style>
8
+ body {
9
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
10
+ padding: 20px;
11
+ background-color: #fafafa;
12
+ color: #333;
13
+ }
14
+ .container {
15
+ max-width: 600px;
16
+ margin: 0 auto;
17
+ border: 1px solid #ddd;
18
+ padding: 20px;
19
+ border-radius: 8px;
20
+ background-color: #ffffff;
21
+ box-shadow: 0 2px 5px rgba(0,0,0,0.1);
22
+ text-align: center;
23
+ }
24
+ h1 {
25
+ color: #333;
26
+ }
27
+ form {
28
+ margin-top: 20px;
29
+ }
30
+ input[type="file"] {
31
+ padding: 8px;
32
+ margin-bottom: 20px;
33
+ font-size: 16px;
34
+ border: 1px solid #ddd;
35
+ border-radius: 5px;
36
+ width: calc(100% - 16px);
37
+ box-sizing: border-box;
38
+ }
39
+ button {
40
+ padding: 10px 20px;
41
+ font-size: 16px;
42
+ background-color: #4CAF50;
43
+ color: white;
44
+ border: none;
45
+ border-radius: 5px;
46
+ cursor: pointer;
47
+ font-weight: bold;
48
+ transition: background-color 0.3s ease;
49
+ }
50
+ button:hover {
51
+ background-color: #45a049;
52
+ }
53
+ a {
54
+ display: inline-block;
55
+ margin-top: 20px;
56
+ padding: 10px 15px;
57
+ background-color: #4CAF50;
58
+ color: white;
59
+ text-decoration: none;
60
+ border-radius: 5px;
61
+ font-weight: bold;
62
+ }
63
+ a:hover {
64
+ background-color: #45a049;
65
+ }
66
+ </style>
67
+ </head>
68
+ <body>
69
+ <div class="container">
70
+ <h1>Import Gesture Data from CSV</h1>
71
+ <form action="{{ url_for('import_data') }}" method="POST" enctype="multipart/form-data">
72
+ <input type="file" name="file" accept=".csv" required>
73
+ <button type="submit">Upload and Visualize</button>
74
+ </form>
75
+ <a href="{{ url_for('index') }}">Back to Home</a>
76
+ </div>
77
+ </body>
78
+ </html>
Web app/templates/index.html ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>HandyLabels: Realtime Hand Gesture Recognition</title>
6
+ <style>
7
+ body {
8
+ font-family: 'Arial', sans-serif;
9
+ background-color: #f4f4f9;
10
+ color: #333;
11
+ text-align: center;
12
+ padding: 20px;
13
+ }
14
+
15
+ h1 {
16
+ color: #4A90E2;
17
+ font-size: 70px; /* Larger font size for the main title */
18
+ margin-bottom: 5px; /* Reduces space below the main title */
19
+ }
20
+
21
+ h2 {
22
+ color: #333;
23
+ font-size: 24px; /* Smaller font size for the subtitle */
24
+ margin-top: 0;
25
+ margin-bottom: 50px; /* Reduces space above the subtitle */
26
+ }
27
+
28
+ a {
29
+ display: inline-block;
30
+ background-color: #4CAF50;
31
+ color: white;
32
+ padding: 10px 20px;
33
+ margin: 20px;
34
+ text-decoration: none;
35
+ font-size: 16px;
36
+ border-radius: 5px;
37
+ transition: background-color 0.3s;
38
+ }
39
+
40
+ a:hover {
41
+ background-color: #45a049;
42
+ }
43
+
44
+ </style>
45
+ </head>
46
+ <body>
47
+ <h1>HandyLabels</h1>
48
+ <h2>Realtime Hand Gesture Recognition as Annotation Tool</h2>
49
+ <a href="{{ url_for('set_labels') }}">Set Custom Labels</a>
50
+ <a href="{{ url_for('import_data') }}">Import Data from Label Studio</a>
51
+ <a href="{{ url_for('edit_csv') }}" class="btn btn-primary">Edit File with LLM</a>
52
+
53
+
54
+
55
+ </body>
56
+ </html>
Web app/templates/recognize.html ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>Recognize Gestures</title>
6
+ <style>
7
+ body {
8
+ font-family: Arial, sans-serif;
9
+ display: flex;
10
+ flex-direction: column;
11
+ align-items: center;
12
+ justify-content: center;
13
+ height: 100vh;
14
+ margin: 0;
15
+ }
16
+
17
+ h1 {
18
+ text-align: center;
19
+ margin-bottom: 20px;
20
+ }
21
+
22
+ .video-container {
23
+ display: flex;
24
+ justify-content: center;
25
+ width: 100%;
26
+ max-width: 720px;
27
+ }
28
+
29
+ img {
30
+ border: 1px solid #ddd; /* Gray border */
31
+ border-radius: 4px; /* Rounded border */
32
+ padding: 5px; /* Some padding */
33
+ width: 100%; /* Set width to 100% of the container */
34
+ max-width: 720px; /* Maximum width */
35
+ }
36
+
37
+ a.button {
38
+ background-color: #4CAF50; /* Green */
39
+ border: none;
40
+ color: white;
41
+ padding: 10px 20px;
42
+ text-align: center;
43
+ text-decoration: none;
44
+ display: inline-block;
45
+ font-size: 16px;
46
+ margin: 20px 0;
47
+ cursor: pointer;
48
+ border-radius: 5px;
49
+ }
50
+
51
+ </style>
52
+ </head>
53
+ <body>
54
+ <h1>Gesture Recognition Application</h1>
55
+
56
+ <div class="video-container">
57
+ <img src="{{ url_for('video_feed') }}" alt="Video Stream">
58
+ </div>
59
+
60
+ <a href="{{ url_for('save_gesture_data') }}" class="button">Save Data</a>
61
+
62
+ </body>
63
+ </html>
Web app/templates/set_labels.html ADDED
@@ -0,0 +1,133 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Set Custom Labels</title>
7
+ <style>
8
+ body {
9
+ font-family: 'Arial', sans-serif;
10
+ background-color: #f4f4f9;
11
+ color: #333;
12
+ text-align: center;
13
+ padding: 20px;
14
+ }
15
+
16
+ h1 {
17
+ color: #4A90E2;
18
+ margin-bottom: 20px;
19
+ }
20
+
21
+ .container {
22
+ background-color: #fff;
23
+ padding: 20px;
24
+ border-radius: 8px;
25
+ box-shadow: 0 0 10px rgba(0,0,0,0.1);
26
+ margin: auto;
27
+ width: 500px;
28
+ display: flex;
29
+ flex-direction: column;
30
+ gap: 2px;
31
+ }
32
+
33
+ .gesture-container {
34
+ display: flex;
35
+ flex-direction: column;
36
+ align-items: flex-start;
37
+ gap: 10px;
38
+ }
39
+
40
+ .input-img-container {
41
+ display: flex;
42
+ align-items: center;
43
+ justify-content: space-between;
44
+ width: 100%;
45
+ }
46
+
47
+ .input-img-container img {
48
+ width: 100px;
49
+ height: auto;
50
+ border-radius: 8px;
51
+ }
52
+
53
+ label {
54
+ text-align: left;
55
+ font-size: 16px;
56
+ font-weight: bold;
57
+ flex: 1;
58
+ white-space: nowrap;
59
+ }
60
+
61
+ input[type="text"] {
62
+ padding: 8px;
63
+ border: 2px solid #ccc;
64
+ border-radius: 4px;
65
+ width: calc(100% - 120px); /* Subtract image width and padding */
66
+ margin-right: 10px;
67
+ }
68
+
69
+ button {
70
+ background-color: #4CAF50;
71
+ color: white;
72
+ padding: 10px 15px;
73
+ border: none;
74
+ border-radius: 4px;
75
+ cursor: pointer;
76
+ font-size: 16px;
77
+ transition: background-color 0.3s;
78
+ }
79
+
80
+ button:hover {
81
+ background-color: #45a049;
82
+ }
83
+ </style>
84
+ </head>
85
+ <body>
86
+ <h1>Set Custom Labels for Gestures</h1>
87
+ <form method="POST" action="{{ url_for('set_labels') }}">
88
+ <div class="container">
89
+ <div class="gesture-container">
90
+ <label for="label1">Label for Gesture 1 (Fist):</label>
91
+ <div class="input-img-container">
92
+ <input type="text" id="label1" name="label1" placeholder="Enter label for Gesture 1" required>
93
+ <img src="{{ url_for('static', filename='images/fist.png') }}" alt="Fist Gesture">
94
+ </div>
95
+ </div>
96
+
97
+ <div class="gesture-container">
98
+ <label for="label2">Label for Gesture 2 (OK):</label>
99
+ <div class="input-img-container">
100
+ <input type="text" id="label2" name="label2" placeholder="Enter label for Gesture 2" required>
101
+ <img src="{{ url_for('static', filename='images/ok.png') }}" alt="OK Gesture">
102
+ </div>
103
+ </div>
104
+
105
+ <div class="gesture-container">
106
+ <label for="label3">Label for Gesture 3 (Peace):</label>
107
+ <div class="input-img-container">
108
+ <input type="text" id="label3" name="label3" placeholder="Enter label for Gesture 3" required>
109
+ <img src="{{ url_for('static', filename='images/peace.png') }}" alt="Peace Gesture">
110
+ </div>
111
+ </div>
112
+
113
+ <div class="gesture-container">
114
+ <label for="label4">Label for Gesture 4 (Stop):</label>
115
+ <div class="input-img-container">
116
+ <input type="text" id="label4" name="label4" placeholder="Enter label for Gesture 4" required>
117
+ <img src="{{ url_for('static', filename='images/stop.png') }}" alt="Stop Gesture">
118
+ </div>
119
+ </div>
120
+
121
+ <div class="gesture-container">
122
+ <label for="label5">Label for Gesture 5 (Two Up):</label>
123
+ <div class="input-img-container">
124
+ <input type="text" id="label5" name="label5" placeholder="Enter label for Gesture 5" required>
125
+ <img src="{{ url_for('static', filename='images/two_up.png') }}" alt="Two Up Gesture">
126
+ </div>
127
+ </div>
128
+
129
+ <button type="submit">Save Labels</button>
130
+ </div>
131
+ </form>
132
+ </body>
133
+ </html>
Web app/templates/speech copy.html ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
7
+ <link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
8
+ <title>Edit CSV via Speech</title>
9
+ </head>
10
+ <body>
11
+ <!-- Navigation Bar -->
12
+ <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
13
+ <a class="navbar-brand" href="{{ url_for('index') }}">Gesture Recognition</a>
14
+ <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
15
+ <span class="navbar-toggler-icon"></span>
16
+ </button>
17
+ <div class="collapse navbar-collapse" id="navbarNav">
18
+ <ul class="navbar-nav ml-auto">
19
+ <li class="nav-item">
20
+ <a class="nav-link" href="{{ url_for('index') }}">Home</a>
21
+ </li>
22
+ <li class="nav-item">
23
+ <a class="nav-link" href="{{ url_for('recognize') }}">Recognize Gestures</a>
24
+ </li>
25
+ <li class="nav-item">
26
+ <a class="nav-link" href="{{ url_for('import_data') }}">Import Data</a>
27
+ </li>
28
+ <li class="nav-item">
29
+ <a class="nav-link active" href="{{ url_for('edit_csv') }}">Edit CSV via Speech</a>
30
+ </li>
31
+ </ul>
32
+ </div>
33
+ </nav>
34
+
35
+ <!-- Main Container -->
36
+ <div class="container mt-5">
37
+ <div class="card">
38
+ <div class="card-header text-center bg-primary text-white">
39
+ <h2>Edit CSV via Speech</h2>
40
+ </div>
41
+ <div class="card-body">
42
+ <form method="POST" enctype="multipart/form-data">
43
+ <!-- Record Button -->
44
+ <div class="form-group text-center">
45
+ <button class="btn btn-danger btn-lg" type="submit" name="record">🎤 Record</button>
46
+ </div>
47
+
48
+ <!-- Transcription Text Area -->
49
+ <div class="form-group">
50
+ <label for="transcription">Transcription:</label>
51
+ <textarea class="form-control" id="transcription" name="transcription" rows="4" readonly>{{ transcription }}</textarea>
52
+ </div>
53
+
54
+ <!-- File Upload -->
55
+ <div class="form-group">
56
+ <label for="csv_file">Upload CSV File:</label>
57
+ <input type="file" class="form-control-file" id="csv_file" name="csv_file" accept=".csv">
58
+ </div>
59
+
60
+ <!-- Action Buttons -->
61
+ <div class="form-group text-center">
62
+ <button class="btn btn-success mr-2" type="submit" name="process">Process</button>
63
+ <button class="btn btn-warning" type="submit" name="clear">Clear</button>
64
+ </div>
65
+ </form>
66
+ </div>
67
+ </div>
68
+ </div>
69
+
70
+ <!-- Optional JavaScript -->
71
+ <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>
72
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@4.5.2/dist/js/bootstrap.bundle.min.js"></script>
73
+ </body>
74
+ </html>
Web app/templates/speech.html ADDED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Edit CSV via Speech</title>
7
+ <link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
8
+ </head>
9
+ <body>
10
+ <h1>Edit CSV via Speech</h1>
11
+
12
+ <form method="POST" enctype="multipart/form-data">
13
+ <!-- Recording Button -->
14
+ <div>
15
+ <button type="submit" name="record">🎤 Record</button>
16
+ </div>
17
+
18
+ <!-- Display Transcription -->
19
+ <div>
20
+ <label for="transcription">Transcription:</label>
21
+ <textarea id="transcription" name="transcription" rows="4" cols="50" readonly>{{ transcription }}</textarea>
22
+ </div>
23
+
24
+ <!-- Upload CSV File -->
25
+ <div>
26
+ <input type="file" name="csv_file" accept=".csv">
27
+ </div>
28
+
29
+ <!-- Process and Clear Buttons -->
30
+ <div>
31
+ <button type="submit" name="process">Process</button>
32
+ <button type="submit" name="clear">Clear</button>
33
+ </div>
34
+ </form>
35
+
36
+ <!-- Data Comparison Section -->
37
+ {% if original_data and updated_data %}
38
+ <div class="data-diff-section">
39
+ <h2>Original vs Updated Data</h2>
40
+ <div class="data-comparison">
41
+ <!-- Original Data Table -->
42
+ <div class="data-table">
43
+ <h3>Original Data</h3>
44
+ <table>
45
+ <thead>
46
+ <tr>
47
+ {% for col in original_data.columns %}
48
+ <th>{{ col }}</th>
49
+ {% endfor %}
50
+ </tr>
51
+ </thead>
52
+ <tbody>
53
+ {% for row in original_data.itertuples() %}
54
+ <tr>
55
+ {% for col in row[1:] %}
56
+ <td>{{ col }}</td>
57
+ {% endfor %}
58
+ </tr>
59
+ {% endfor %}
60
+ </tbody>
61
+ </table>
62
+ </div>
63
+
64
+ <!-- Updated Data Table -->
65
+ <div class="data-table">
66
+ <h3>Updated Data</h3>
67
+ <table>
68
+ <thead>
69
+ <tr>
70
+ {% for col in updated_data.columns %}
71
+ <th>{{ col }}</th>
72
+ {% endfor %}
73
+ </tr>
74
+ </thead>
75
+ <tbody>
76
+ {% for row in updated_data.itertuples() %}
77
+ <tr>
78
+ {% for col in row[1:] %}
79
+ <td>{{ col }}</td>
80
+ {% endfor %}
81
+ </tr>
82
+ {% endfor %}
83
+ </tbody>
84
+ </table>
85
+ </div>
86
+ </div>
87
+ </div>
88
+
89
+ <!-- Download Buttons -->
90
+ <div>
91
+ <form method="GET" action="{{ url_for('download_csv', filename='original') }}">
92
+ <button type="submit">Download Original CSV</button>
93
+ </form>
94
+ <form method="GET" action="{{ url_for('download_csv', filename='updated') }}">
95
+ <button type="submit">Download Updated CSV</button>
96
+ </form>
97
+ </div>
98
+ {% endif %}
99
+ </body>
100
+ </html>
Web app/two_up.png ADDED
action.py ADDED
@@ -0,0 +1,130 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import csv
2
+ import time
3
+ from datetime import datetime
4
+ from ultralytics import YOLO
5
+ import cv2
6
+ import mediapipe as mp
7
+ import numpy as np
8
+
9
+ model = YOLO('best_5.pt')
10
+
11
+ cap = cv2.VideoCapture(0)
12
+
13
+ mp_hands = mp.solutions.hands
14
+ mp_drawing = mp.solutions.drawing_utils
15
+ mp_drawing_styles = mp.solutions.drawing_styles
16
+
17
+ hands = mp_hands.Hands(static_image_mode=True, min_detection_confidence=0.3)
18
+ labels_dict = {0: 'A', 1: 'B', 2: 'C', 3: 'H'}
19
+
20
+ # File to save gesture data
21
+ csv_file = 'gesture_data.csv'
22
+
23
+ # Initialize variables for tracking gestures
24
+ previous_gesture = None
25
+ gesture_start_time = None
26
+
27
+ # List to store gesture data for CSV
28
+ gesture_data_list = []
29
+
30
+ while True:
31
+ data_aux = []
32
+ x_ = []
33
+ y_ = []
34
+
35
+ ret, frame = cap.read()
36
+ if not ret:
37
+ print("Failed to capture frame. Exiting...")
38
+ break
39
+
40
+ H, W, _ = frame.shape
41
+
42
+ frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
43
+
44
+ results = hands.process(frame_rgb)
45
+ if results.multi_hand_landmarks:
46
+ for hand_landmarks in results.multi_hand_landmarks:
47
+ mp_drawing.draw_landmarks(
48
+ frame,
49
+ hand_landmarks,
50
+ mp_hands.HAND_CONNECTIONS,
51
+ mp_drawing_styles.get_default_hand_landmarks_style(),
52
+ mp_drawing_styles.get_default_hand_connections_style())
53
+
54
+ for hand_landmarks in results.multi_hand_landmarks:
55
+ for i in range(len(hand_landmarks.landmark)):
56
+ x = hand_landmarks.landmark[i].x
57
+ y = hand_landmarks.landmark[i].y
58
+
59
+ x_.append(x)
60
+ y_.append(y)
61
+
62
+ for i in range(len(hand_landmarks.landmark)):
63
+ x = hand_landmarks.landmark[i].x
64
+ y = hand_landmarks.landmark[i].y
65
+ data_aux.append(x - min(x_))
66
+ data_aux.append(y - min(y_))
67
+
68
+ x1 = int(min(x_) * W) - 10
69
+ y1 = int(min(y_) * H) - 10
70
+
71
+ x2 = int(max(x_) * W) + 10
72
+ y2 = int(max(y_) * H) + 10
73
+
74
+ prediction = model.predict(frame, conf=0.25, iou=0.45)
75
+ names_dict = prediction[0].names
76
+ probs = prediction[0].probs.data.numpy()
77
+ detected_gesture = names_dict[np.argmax(probs)]
78
+ print("Gesture:", detected_gesture)
79
+
80
+ if detected_gesture == 'A':
81
+ language = 'Arabic'
82
+ elif detected_gesture == 'B':
83
+ language = 'Bengali'
84
+ elif detected_gesture == 'C':
85
+ language = 'Chinese'
86
+ elif detected_gesture == 'H':
87
+ language = 'Hindi'
88
+
89
+ # Get the current timestamp
90
+ current_time = time.time()
91
+
92
+ # Check if the detected gesture has changed
93
+ if detected_gesture != previous_gesture:
94
+ # If the detected gesture has changed, calculate the duration of the previous gesture
95
+ if previous_gesture is not None:
96
+ gesture_end_time = current_time
97
+ gesture_duration = gesture_end_time - gesture_start_time
98
+ # Store the detected gesture, start time, end time, and duration in the list
99
+ gesture_data_list.append([previous_gesture,
100
+ datetime.fromtimestamp(gesture_start_time).strftime('%H:%M:%S.%f'),
101
+ datetime.fromtimestamp(gesture_end_time).strftime('%H:%M:%S.%f'),
102
+ round(gesture_duration, 2)])
103
+
104
+ # Update the previous gesture and its start time
105
+ previous_gesture = detected_gesture
106
+ gesture_start_time = current_time
107
+
108
+ # Calculate the duration of the current gesture
109
+ gesture_duration = current_time - gesture_start_time
110
+
111
+ # Draw rectangle around the detected gesture
112
+ cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 0, 0), 4)
113
+ cv2.putText(frame, detected_gesture, (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 1.3, (0, 255, 0), 3, cv2.LINE_AA)
114
+
115
+ # Display the frame
116
+ cv2.imshow('frame', frame)
117
+
118
+ # Check if 'q' key is pressed to stop the program
119
+ if cv2.waitKey(1) & 0xFF == ord('q'):
120
+ # Save data to CSV file
121
+ with open(csv_file, 'w', newline='') as csvfile:
122
+ writer = csv.writer(csvfile)
123
+ writer.writerow(['Gesture', 'Start Time', 'End Time', 'Duration'])
124
+ for gesture, start_time, end_time, duration in gesture_data_list:
125
+ writer.writerow([gesture, start_time, end_time, duration])
126
+ break
127
+
128
+ # Release resources
129
+ cap.release()
130
+ cv2.destroyAllWindows()
best.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:9480d18aae80ec7c7a4c3c3e1a85bfd674d3d061e608baf2b2da401318b68d2a
3
+ size 5897040
image_I.jpeg ADDED
main.py ADDED
@@ -0,0 +1,127 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import csv
2
+ import pickle
3
+ import time
4
+ from ultralytics import YOLO
5
+ import cv2
6
+ import mediapipe as mp
7
+ import numpy as np
8
+
9
+ model = YOLO('best_5.pt')
10
+
11
+ cap = cv2.VideoCapture(0)
12
+
13
+ mp_hands = mp.solutions.hands
14
+ mp_drawing = mp.solutions.drawing_utils
15
+ mp_drawing_styles = mp.solutions.drawing_styles
16
+
17
+ hands = mp_hands.Hands(static_image_mode=True, min_detection_confidence=0.3)
18
+ language = ''
19
+ labels_dict = {0: 'A', 1: 'B', 2: 'C', 3: 'H'}
20
+
21
+ # Dictionary to store the start time of each gesture
22
+ gesture_start_time = {gesture: None for gesture in labels_dict.values()}
23
+ previous_gesture = None
24
+
25
+ # File to save gesture data
26
+ csv_file = 'gesture_data.csv'
27
+
28
+ # Dictionary to store detected gestures and their durations
29
+ gesture_duration_dict = {}
30
+
31
+ while True:
32
+ data_aux = []
33
+ x_ = []
34
+ y_ = []
35
+
36
+ ret, frame = cap.read()
37
+ if not ret:
38
+ print("Failed to capture frame. Exiting...")
39
+ break
40
+
41
+ H, W, _ = frame.shape
42
+
43
+ frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
44
+
45
+ results = hands.process(frame_rgb)
46
+ if results.multi_hand_landmarks:
47
+ for hand_landmarks in results.multi_hand_landmarks:
48
+ mp_drawing.draw_landmarks(
49
+ frame,
50
+ hand_landmarks,
51
+ mp_hands.HAND_CONNECTIONS,
52
+ mp_drawing_styles.get_default_hand_landmarks_style(),
53
+ mp_drawing_styles.get_default_hand_connections_style())
54
+
55
+ for hand_landmarks in results.multi_hand_landmarks:
56
+ for i in range(len(hand_landmarks.landmark)):
57
+ x = hand_landmarks.landmark[i].x
58
+ y = hand_landmarks.landmark[i].y
59
+
60
+ x_.append(x)
61
+ y_.append(y)
62
+
63
+ for i in range(len(hand_landmarks.landmark)):
64
+ x = hand_landmarks.landmark[i].x
65
+ y = hand_landmarks.landmark[i].y
66
+ data_aux.append(x - min(x_))
67
+ data_aux.append(y - min(y_))
68
+
69
+ x1 = int(min(x_) * W) - 10
70
+ y1 = int(min(y_) * H) - 10
71
+
72
+ x2 = int(max(x_) * W) - 10
73
+ y2 = int(max(y_) * H) - 10
74
+
75
+ prediction = model.predict(frame, conf=0.25, iou=0.45)
76
+ names_dict = prediction[0].names
77
+ probs = prediction[0].probs.data.numpy()
78
+ detected_gesture = names_dict[np.argmax(probs)]
79
+ print(names_dict[np.argmax(probs)])
80
+ print("Gesture:", detected_gesture)
81
+ if detected_gesture == 'A':
82
+ language = 'Arabic'
83
+ elif detected_gesture == 'B':
84
+ language = 'Bengali'
85
+ elif detected_gesture == 'C':
86
+ language = 'Chinese'
87
+ elif detected_gesture == 'H':
88
+ language = 'Hindi'
89
+
90
+ # Get the current timestamp
91
+ current_time = time.time()
92
+
93
+ # Check if the detected gesture has changed
94
+ if detected_gesture != previous_gesture:
95
+ # If the detected gesture has changed, calculate the duration of the previous gesture
96
+ if previous_gesture is not None:
97
+ gesture_duration = current_time - gesture_start_time[previous_gesture]
98
+ # Store the detected gesture and its duration in the dictionary
99
+ gesture_duration_dict[previous_gesture] = gesture_duration
100
+
101
+ # Update the previous gesture and its start time
102
+ previous_gesture = detected_gesture
103
+ gesture_start_time[detected_gesture] = current_time
104
+
105
+ # Calculate the duration of the current gesture
106
+ gesture_duration = current_time - gesture_start_time[detected_gesture]
107
+
108
+ cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 0, 0), 4)
109
+ cv2.putText(frame, detected_gesture, (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 1.3, (0, 255, 0), 3,
110
+ cv2.LINE_AA)
111
+ # Display the frame
112
+ cv2.imshow('frame', frame)
113
+
114
+ # Check if 'q' key is pressed to stop the program
115
+ if cv2.waitKey(1) & 0xFF == ord('q'):
116
+ # Save data to CSV file
117
+ with open(csv_file, 'w', newline='') as csvfile:
118
+ writer = csv.writer(csvfile)
119
+ writer.writerow(['Gesture', 'Duration'])
120
+ for gesture, duration in gesture_duration_dict.items():
121
+ writer.writerow([gesture, duration])
122
+ break
123
+
124
+ # Release resources
125
+ cap.release()
126
+ cv2.destroyAllWindows()
127
+
output.csv ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ Gesture,Start Time,End Time,Duration
2
+ B,15:52:42.804,15:52:43.391,0.59
3
+ A,15:52:43.391,15:52:43.457,0.07
4
+ B,15:52:43.457,15:52:43.523,0.07
5
+ A,15:52:43.523,15:52:43.590,0.07
6
+ B,15:52:43.590,15:52:43.727,0.14
7
+ C,15:52:43.727,15:52:43.990,0.26
8
+ A,15:52:43.990,15:52:44.524,0.53
9
+ C,15:52:44.524,15:52:44.591,0.07
requirements.txt ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ absl-py==2.1.0
2
+ annotated-types==0.7.0
3
+ anyio==4.9.0
4
+ attrs==25.3.0
5
+ blinker==1.9.0
6
+ certifi==2025.1.31
7
+ cffi==1.17.1
8
+ charset-normalizer==3.4.1
9
+ click==8.1.8
10
+ contourpy==1.3.0
11
+ cycler==0.12.1
12
+ DateTime==5.5
13
+ distro==1.9.0
14
+ exceptiongroup==1.2.2
15
+ filelock==3.18.0
16
+ Flask==3.1.0
17
+ flatbuffers==25.2.10
18
+ fonttools==4.56.0
19
+ fsspec==2025.3.0
20
+ h11==0.14.0
21
+ httpcore==1.0.7
22
+ httpx==0.28.1
23
+ huggingface-hub==0.29.3
24
+ idna==3.10
25
+ importlib_metadata==8.6.1
26
+ importlib_resources==6.5.2
27
+ itsdangerous==2.2.0
28
+ jax==0.4.30
29
+ jaxlib==0.4.30
30
+ Jinja2==3.1.6
31
+ jiter==0.9.0
32
+ kiwisolver==1.4.7
33
+ MarkupSafe==3.0.2
34
+ matplotlib==3.9.4
35
+ mediapipe==0.10.21
36
+ ml_dtypes==0.5.1
37
+ mpmath==1.3.0
38
+ narwhals==1.31.0
39
+ networkx==3.2.1
40
+ numpy==2.0.2
41
+ openai==1.68.0
42
+ opencv-contrib-python==4.11.0.86
43
+ opencv-python==4.11.0.86
44
+ opt_einsum==3.4.0
45
+ packaging==24.2
46
+ pandas==2.2.3
47
+ pillow==11.1.0
48
+ plotly==6.0.1
49
+ protobuf==4.25.6
50
+ psutil==7.0.0
51
+ py-cpuinfo==9.0.0
52
+ PyAudio==0.2.14
53
+ pycparser==2.22
54
+ pydantic==2.10.6
55
+ pydantic_core==2.27.2
56
+ pyparsing==3.2.1
57
+ python-dateutil==2.9.0.post0
58
+ pytz==2025.1
59
+ PyYAML==6.0.2
60
+ regex==2024.11.6
61
+ requests==2.32.3
62
+ safetensors==0.5.3
63
+ scipy==1.13.1
64
+ seaborn==0.13.2
65
+ sentencepiece==0.2.0
66
+ six==1.17.0
67
+ sniffio==1.3.1
68
+ sounddevice==0.5.1
69
+ sympy==1.13.1
70
+ tokenizers==0.21.1
71
+ torch==2.6.0
72
+ torchvision==0.21.0
73
+ tqdm==4.67.1
74
+ transformers==4.49.0
75
+ typing_extensions==4.12.2
76
+ tzdata==2025.1
77
+ ultralytics==8.3.94
78
+ ultralytics-thop==2.0.14
79
+ urllib3==2.3.0
80
+ Wave==0.0.2
81
+ Werkzeug==3.1.3
82
+ whisper==1.1.10
83
+ zipp==3.21.0
84
+ zope.interface==7.2