Bachstelze commited on
Commit
14f7fdb
·
1 Parent(s): 553b1f3

lint app.py and move gDrive version to A3

Browse files
Files changed (2) hide show
  1. A3/app.py +401 -0
  2. app.py +119 -99
A3/app.py ADDED
@@ -0,0 +1,401 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import pandas as pd
3
+ import pickle
4
+ import os
5
+ import gdown
6
+
7
+ # Get directory where this script is located
8
+ SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
9
+
10
+ # Google Drive file IDs for model downloads
11
+ MODEL_GDRIVE_ID = "1ORlU0OOCBkWXVO2UFAkXaKtXfkOH7w1t"
12
+ CLASSIFICATION_MODEL_GDRIVE_ID = "1qU6Q37CoToMxzBwori5V3_bonBIIb-K0"
13
+
14
+ # Local paths - models loaded from A3/models/ directory
15
+ MODEL_PATH = os.path.join(SCRIPT_DIR, "A3/models/champion_model_final_2.pkl")
16
+ CLASSIFICATION_MODEL_PATH = os.path.join(SCRIPT_DIR, "A3/models/final_champion_model_A3.pkl")
17
+ DATA_PATH = os.path.join(SCRIPT_DIR, "A3/A3_Data/train_dataset.csv")
18
+
19
+
20
+ def download_from_gdrive(file_id, destination):
21
+ """Download a file from Google Drive using gdown."""
22
+ os.makedirs(os.path.dirname(destination), exist_ok=True)
23
+ url = f"https://drive.google.com/uc?id={file_id}"
24
+ gdown.download(url, destination, quiet=False)
25
+ print(f"Downloaded to {destination}")
26
+ return True
27
+
28
+ model = None
29
+ FEATURE_NAMES = None
30
+ MODEL_METRICS = None
31
+
32
+ # Classification model
33
+ classification_model = None
34
+ CLASSIFICATION_FEATURE_NAMES = None
35
+ CLASSIFICATION_CLASSES = None
36
+ CLASSIFICATION_METRICS = None
37
+
38
+ BODY_REGION_RECOMMENDATIONS = {
39
+ 'Upper Body': "Focus on shoulder mobility, thoracic spine extension, and keeping your head neutral.",
40
+ 'Lower Body': "Work on hip mobility, ankle dorsiflexion, and knee tracking over toes."
41
+ }
42
+
43
+
44
+ def load_champion_model():
45
+ global model, FEATURE_NAMES, MODEL_METRICS
46
+
47
+ # Download from Google Drive if not exists locally
48
+ if not os.path.exists(MODEL_PATH):
49
+ print(f"Model not found locally, downloading from Google Drive...")
50
+ try:
51
+ download_from_gdrive(MODEL_GDRIVE_ID, MODEL_PATH)
52
+ except Exception as e:
53
+ print(f"Failed to download model: {e}")
54
+ return False
55
+
56
+ if os.path.exists(MODEL_PATH):
57
+ print(f"Loading champion model from {MODEL_PATH}")
58
+ with open(MODEL_PATH, "rb") as f:
59
+ artifact = pickle.load(f)
60
+
61
+ model = artifact["model"]
62
+ FEATURE_NAMES = artifact["feature_columns"]
63
+ MODEL_METRICS = artifact.get("test_metrics", {})
64
+
65
+ print(f"Model loaded: {len(FEATURE_NAMES)} features")
66
+ print(f"Test R2: {MODEL_METRICS.get('r2', 'N/A')}")
67
+ return True
68
+
69
+ print(f"Champion model not found at {MODEL_PATH}")
70
+ return False
71
+
72
+
73
+ def load_classification_model():
74
+ global classification_model, CLASSIFICATION_FEATURE_NAMES, CLASSIFICATION_CLASSES, CLASSIFICATION_METRICS
75
+
76
+ # Download from Google Drive if not exists locally
77
+ if not os.path.exists(CLASSIFICATION_MODEL_PATH):
78
+ print(f"Classification model not found locally, downloading from Google Drive...")
79
+ try:
80
+ download_from_gdrive(CLASSIFICATION_MODEL_GDRIVE_ID, CLASSIFICATION_MODEL_PATH)
81
+ except Exception as e:
82
+ print(f"Failed to download classification model: {e}")
83
+ return False
84
+
85
+ if os.path.exists(CLASSIFICATION_MODEL_PATH):
86
+ print(f"Loading classification model from {CLASSIFICATION_MODEL_PATH}")
87
+ with open(CLASSIFICATION_MODEL_PATH, "rb") as f:
88
+ artifact = pickle.load(f)
89
+
90
+ classification_model = artifact["model"]
91
+ CLASSIFICATION_FEATURE_NAMES = artifact["feature_columns"]
92
+ CLASSIFICATION_CLASSES = artifact["classes"]
93
+ CLASSIFICATION_METRICS = artifact.get("test_metrics", {})
94
+
95
+ print(f"Classification model loaded: {len(CLASSIFICATION_FEATURE_NAMES)} features")
96
+ print(f"Classes: {CLASSIFICATION_CLASSES}")
97
+ return True
98
+
99
+ print(f"Classification model not found at {CLASSIFICATION_MODEL_PATH}")
100
+ return False
101
+
102
+
103
+ load_champion_model()
104
+ load_classification_model()
105
+
106
+
107
+ def predict_score(*feature_values):
108
+ if model is None:
109
+ return "Error", "Model not loaded", ""
110
+
111
+ features_df = pd.DataFrame([feature_values], columns=FEATURE_NAMES)
112
+ raw_score = model.predict(features_df)[0]
113
+ score = max(0, min(1, raw_score)) * 100
114
+
115
+ if score >= 80:
116
+ interpretation = "Excellent, great squat form"
117
+ elif score >= 60:
118
+ interpretation = "Good, minor improvements needed"
119
+ elif score >= 40:
120
+ interpretation = "Average, a lot of areas to work on"
121
+ else:
122
+ interpretation = "Needs work, focus on proper form"
123
+
124
+ r2 = MODEL_METRICS.get('r2', 'N/A')
125
+ correlation = MODEL_METRICS.get('correlation', 'N/A')
126
+ r2_str = f"{r2:.4f}" if isinstance(r2, (int, float)) else str(r2)
127
+ corr_str = f"{correlation:.4f}" if isinstance(correlation, (int, float)) else str(correlation)
128
+
129
+ details = f"""
130
+ ### Prediction Details
131
+ - **Raw Model Output:** {raw_score:.4f}
132
+ - **Normalized Score:** {score:.1f}%
133
+ - **Assessment:** {interpretation}
134
+
135
+ ### Model Performance
136
+ - **Test R-squared:** {r2_str}
137
+ - **Test Correlation:** {corr_str}
138
+
139
+ *Lower deviation values = better form*
140
+ """
141
+
142
+ return f"{score:.1f}%", interpretation, details
143
+
144
+
145
+ def predict_weakest_link(*feature_values):
146
+ if classification_model is None:
147
+ return "Error", "Model not loaded", ""
148
+
149
+ features_df = pd.DataFrame([feature_values], columns=CLASSIFICATION_FEATURE_NAMES)
150
+
151
+ prediction = classification_model.predict(features_df)[0]
152
+ probabilities = classification_model.predict_proba(features_df)[0]
153
+
154
+ class_probs = list(zip(CLASSIFICATION_CLASSES, probabilities))
155
+ class_probs.sort(key=lambda x: x[1], reverse=True)
156
+
157
+ confidence = max(probabilities) * 100
158
+ recommendation = BODY_REGION_RECOMMENDATIONS.get(prediction, "Focus on exercises that strengthen this region.")
159
+
160
+ accuracy = CLASSIFICATION_METRICS.get('accuracy', 'N/A')
161
+ f1_weighted = CLASSIFICATION_METRICS.get('f1_weighted', 'N/A')
162
+ acc_str = f"{accuracy:.2%}" if isinstance(accuracy, (int, float)) else str(accuracy)
163
+ f1_str = f"{f1_weighted:.2%}" if isinstance(f1_weighted, (int, float)) else str(f1_weighted)
164
+
165
+ predictions_list = "\n".join([f"{i+1}. **{cp[0]}** - {cp[1]*100:.1f}%" for i, cp in enumerate(class_probs)])
166
+
167
+ details = f"""
168
+ ### Prediction Details
169
+ - **Predicted Body Region:** {prediction}
170
+ - **Confidence:** {confidence:.1f}%
171
+
172
+ ### Probability Distribution
173
+ {predictions_list}
174
+
175
+ ### Recommendation
176
+ {recommendation}
177
+
178
+ ### Model Performance
179
+ - **Test Accuracy:** {acc_str}
180
+ - **Test F1 (weighted):** {f1_str}
181
+ """
182
+
183
+ return prediction, f"Confidence: {confidence:.1f}%", details
184
+
185
+
186
+ def load_example():
187
+ if FEATURE_NAMES is None:
188
+ return [0.5] * 35
189
+
190
+ try:
191
+ df = pd.read_csv(DATA_PATH, sep=';', decimal=',')
192
+ sample_row = df.sample(1)
193
+ # Return value for each feature, using 0.5 as default if feature not in dataset
194
+ result = []
195
+ for f in FEATURE_NAMES:
196
+ if f in df.columns:
197
+ val = float(sample_row[f].values[0])
198
+ # Clamp to valid slider range [0, 1]
199
+ val = max(0.0, min(1.0, val))
200
+ result.append(val)
201
+ else:
202
+ result.append(0.5)
203
+ return result
204
+ except Exception as e:
205
+ print(f"Error loading example: {e}")
206
+ return [0.5] * len(FEATURE_NAMES)
207
+
208
+
209
+ def load_classification_example():
210
+ if CLASSIFICATION_FEATURE_NAMES is None:
211
+ return [0.5] * 40
212
+
213
+ try:
214
+ df = pd.read_csv(DATA_PATH, sep=';', decimal=',')
215
+ sample_row = df.sample(1)
216
+ # Return value for each feature, using 0.5 as default if feature not in dataset
217
+ result = []
218
+ for f in CLASSIFICATION_FEATURE_NAMES:
219
+ if f in df.columns:
220
+ val = float(sample_row[f].values[0])
221
+ # Clamp to valid slider range [0, 1]
222
+ val = max(0.0, min(1.0, val))
223
+ result.append(val)
224
+ else:
225
+ result.append(0.5)
226
+ return result
227
+ except Exception as e:
228
+ print(f"Error loading classification example: {e}")
229
+ return [0.5] * len(CLASSIFICATION_FEATURE_NAMES)
230
+
231
+
232
+ def create_interface():
233
+ if FEATURE_NAMES is None:
234
+ return gr.Interface(
235
+ fn=lambda: "Model not loaded",
236
+ inputs=[],
237
+ outputs="text",
238
+ title="Error: Model not loaded"
239
+ )
240
+
241
+ inputs = []
242
+ for name in FEATURE_NAMES:
243
+ slider = gr.Slider(minimum=0, maximum=1, value=0.5, step=0.01, label=name.replace("_", " "))
244
+ inputs.append(slider)
245
+
246
+ classification_inputs = []
247
+ if CLASSIFICATION_FEATURE_NAMES is not None:
248
+ for name in CLASSIFICATION_FEATURE_NAMES:
249
+ slider = gr.Slider(minimum=0, maximum=1, value=0.5, step=0.01, label=name.replace("_", " "))
250
+ classification_inputs.append(slider)
251
+
252
+ description = """
253
+ ## Deep Squat Movement Assessment
254
+
255
+ **How to use:**
256
+ 1. Adjust the sliders to input deviation values (0 = no deviation, 1 = maximum deviation)
257
+ 2. Click "Submit" to get your predicted score
258
+ 3. Or click "Load Random Example" to test with real data
259
+
260
+ **Score Interpretation:**
261
+ - 80-100%: Excellent form
262
+ - 60-79%: Good form
263
+ - 40-59%: Average form
264
+ - 0-39%: Needs improvement
265
+ """
266
+
267
+ classification_description = """
268
+ ## Body Region Classification
269
+
270
+ **How to use:**
271
+ 1. Adjust the sliders to input deviation values (0 = no deviation, 1 = maximum deviation)
272
+ 2. Click "Predict Body Region" to identify where to focus improvements
273
+ 3. Or click "Load Random Example" to test with real data
274
+
275
+ **Body Regions:** Upper Body, Lower Body
276
+ """
277
+
278
+ angle_features = [n for n in FEATURE_NAMES if "Angle" in n]
279
+ nasm_features = [n for n in FEATURE_NAMES if "NASM" in n]
280
+ time_features = [n for n in FEATURE_NAMES if "Time" in n]
281
+ other_features = [n for n in FEATURE_NAMES if "Angle" not in n and "NASM" not in n and "Time" not in n]
282
+
283
+ angle_indices = [FEATURE_NAMES.index(f) for f in angle_features]
284
+ nasm_indices = [FEATURE_NAMES.index(f) for f in nasm_features]
285
+ time_indices = [FEATURE_NAMES.index(f) for f in time_features]
286
+ other_indices = [FEATURE_NAMES.index(f) for f in other_features]
287
+
288
+ if CLASSIFICATION_FEATURE_NAMES is not None:
289
+ class_angle_features = [n for n in CLASSIFICATION_FEATURE_NAMES if "Angle" in n]
290
+ class_nasm_features = [n for n in CLASSIFICATION_FEATURE_NAMES if "NASM" in n]
291
+ class_time_features = [n for n in CLASSIFICATION_FEATURE_NAMES if "Time" in n]
292
+ class_other_features = [n for n in CLASSIFICATION_FEATURE_NAMES if "Angle" not in n and "NASM" not in n and "Time" not in n]
293
+ class_angle_indices = [CLASSIFICATION_FEATURE_NAMES.index(f) for f in class_angle_features]
294
+ class_nasm_indices = [CLASSIFICATION_FEATURE_NAMES.index(f) for f in class_nasm_features]
295
+ class_time_indices = [CLASSIFICATION_FEATURE_NAMES.index(f) for f in class_time_features]
296
+ class_other_indices = [CLASSIFICATION_FEATURE_NAMES.index(f) for f in class_other_features]
297
+
298
+ with gr.Blocks(title="Deep Squat Assessment") as demo:
299
+ gr.Markdown("# Deep Squat Movement Assessment")
300
+
301
+ with gr.Tabs():
302
+ with gr.TabItem("Movement Scoring"):
303
+ gr.Markdown(description)
304
+
305
+ with gr.Row():
306
+ with gr.Column(scale=2):
307
+ gr.Markdown("### Input Features")
308
+ gr.Markdown(f"*{len(FEATURE_NAMES)} features loaded from champion model*")
309
+ gr.Markdown("*Deviation values: 0 = perfect, 1 = maximum deviation*")
310
+
311
+ with gr.Tabs():
312
+ with gr.TabItem(f"Angle Deviations ({len(angle_indices)})"):
313
+ for idx in angle_indices:
314
+ inputs[idx].render()
315
+
316
+ with gr.TabItem(f"NASM Deviations ({len(nasm_indices)})"):
317
+ for idx in nasm_indices:
318
+ inputs[idx].render()
319
+
320
+ with gr.TabItem(f"Time Deviations ({len(time_indices)})"):
321
+ for idx in time_indices:
322
+ inputs[idx].render()
323
+
324
+ if other_indices:
325
+ with gr.TabItem(f"Other ({len(other_indices)})"):
326
+ for idx in other_indices:
327
+ inputs[idx].render()
328
+
329
+ with gr.Column(scale=1):
330
+ gr.Markdown("### Results")
331
+ score_output = gr.Textbox(label="Predicted Score")
332
+ interp_output = gr.Textbox(label="Assessment")
333
+ details_output = gr.Markdown(label="Details")
334
+
335
+ with gr.Row():
336
+ submit_btn = gr.Button("Submit", variant="primary")
337
+ example_btn = gr.Button("Load Random Example")
338
+ clear_btn = gr.Button("Clear")
339
+
340
+ submit_btn.click(fn=predict_score, inputs=inputs, outputs=[score_output, interp_output, details_output])
341
+ example_btn.click(fn=load_example, inputs=[], outputs=inputs)
342
+ clear_btn.click(
343
+ fn=lambda: [0.5] * len(FEATURE_NAMES) + ["", "", ""],
344
+ inputs=[],
345
+ outputs=inputs + [score_output, interp_output, details_output],
346
+ )
347
+
348
+ if CLASSIFICATION_FEATURE_NAMES is not None:
349
+ with gr.TabItem("Body Region Classification"):
350
+ gr.Markdown(classification_description)
351
+
352
+ with gr.Row():
353
+ with gr.Column(scale=2):
354
+ gr.Markdown("### Input Features")
355
+ gr.Markdown(f"*{len(CLASSIFICATION_FEATURE_NAMES)} features for classification*")
356
+ gr.Markdown("*Deviation values: 0 = perfect, 1 = maximum deviation*")
357
+
358
+ with gr.Tabs():
359
+ with gr.TabItem(f"Angle Deviations ({len(class_angle_indices)})"):
360
+ for idx in class_angle_indices:
361
+ classification_inputs[idx].render()
362
+
363
+ with gr.TabItem(f"NASM Deviations ({len(class_nasm_indices)})"):
364
+ for idx in class_nasm_indices:
365
+ classification_inputs[idx].render()
366
+
367
+ with gr.TabItem(f"Time Deviations ({len(class_time_indices)})"):
368
+ for idx in class_time_indices:
369
+ classification_inputs[idx].render()
370
+
371
+ if class_other_indices:
372
+ with gr.TabItem(f"Other ({len(class_other_indices)})"):
373
+ for idx in class_other_indices:
374
+ classification_inputs[idx].render()
375
+
376
+ with gr.Column(scale=1):
377
+ gr.Markdown("### Results")
378
+ class_output = gr.Textbox(label="Predicted Body Region")
379
+ class_interp_output = gr.Textbox(label="Confidence")
380
+ class_details_output = gr.Markdown(label="Details")
381
+
382
+ with gr.Row():
383
+ class_submit_btn = gr.Button("Predict Body Region", variant="primary")
384
+ class_example_btn = gr.Button("Load Random Example")
385
+ class_clear_btn = gr.Button("Clear")
386
+
387
+ class_submit_btn.click(fn=predict_weakest_link, inputs=classification_inputs, outputs=[class_output, class_interp_output, class_details_output])
388
+ class_example_btn.click(fn=load_classification_example, inputs=[], outputs=classification_inputs)
389
+ class_clear_btn.click(
390
+ fn=lambda: [0.5] * len(CLASSIFICATION_FEATURE_NAMES) + ["", "", ""],
391
+ inputs=[],
392
+ outputs=classification_inputs + [class_output, class_interp_output, class_details_output],
393
+ )
394
+
395
+ return demo
396
+
397
+
398
+ demo = create_interface()
399
+
400
+ if __name__ == "__main__":
401
+ demo.launch(share=False, server_name="0.0.0.0", server_port=7860)
app.py CHANGED
@@ -2,28 +2,23 @@ import gradio as gr
2
  import pandas as pd
3
  import pickle
4
  import os
5
- import gdown
6
 
7
  # Get directory where this script is located
8
  SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
9
 
10
- # Google Drive file IDs for model downloads
11
- MODEL_GDRIVE_ID = "1ORlU0OOCBkWXVO2UFAkXaKtXfkOH7w1t"
12
- CLASSIFICATION_MODEL_GDRIVE_ID = "1qU6Q37CoToMxzBwori5V3_bonBIIb-K0"
13
-
14
- # Local paths - models loaded from A3/models/ directory
15
- MODEL_PATH = os.path.join(SCRIPT_DIR, "A3/models/champion_model_final_2.pkl")
16
- CLASSIFICATION_MODEL_PATH = os.path.join(SCRIPT_DIR, "A3/models/final_champion_model_A3.pkl")
17
- DATA_PATH = os.path.join(SCRIPT_DIR, "A3/A3_Data/train_dataset.csv")
18
-
19
-
20
- def download_from_gdrive(file_id, destination):
21
- """Download a file from Google Drive using gdown."""
22
- os.makedirs(os.path.dirname(destination), exist_ok=True)
23
- url = f"https://drive.google.com/uc?id={file_id}"
24
- gdown.download(url, destination, quiet=False)
25
- print(f"Downloaded to {destination}")
26
- return True
27
 
28
  model = None
29
  FEATURE_NAMES = None
@@ -36,66 +31,57 @@ CLASSIFICATION_CLASSES = None
36
  CLASSIFICATION_METRICS = None
37
 
38
  BODY_REGION_RECOMMENDATIONS = {
39
- 'Upper Body': "Focus on shoulder mobility, thoracic spine extension, and keeping your head neutral.",
40
- 'Lower Body': "Work on hip mobility, ankle dorsiflexion, and knee tracking over toes."
 
 
 
 
41
  }
42
 
43
 
44
  def load_champion_model():
45
  global model, FEATURE_NAMES, MODEL_METRICS
46
-
47
- # Download from Google Drive if not exists locally
48
- if not os.path.exists(MODEL_PATH):
49
- print(f"Model not found locally, downloading from Google Drive...")
50
- try:
51
- download_from_gdrive(MODEL_GDRIVE_ID, MODEL_PATH)
52
- except Exception as e:
53
- print(f"Failed to download model: {e}")
54
- return False
55
-
56
  if os.path.exists(MODEL_PATH):
57
  print(f"Loading champion model from {MODEL_PATH}")
58
  with open(MODEL_PATH, "rb") as f:
59
  artifact = pickle.load(f)
60
-
61
  model = artifact["model"]
62
  FEATURE_NAMES = artifact["feature_columns"]
63
  MODEL_METRICS = artifact.get("test_metrics", {})
64
-
65
  print(f"Model loaded: {len(FEATURE_NAMES)} features")
66
  print(f"Test R2: {MODEL_METRICS.get('r2', 'N/A')}")
67
  return True
68
-
69
  print(f"Champion model not found at {MODEL_PATH}")
70
  return False
71
 
72
 
73
  def load_classification_model():
74
- global classification_model, CLASSIFICATION_FEATURE_NAMES, CLASSIFICATION_CLASSES, CLASSIFICATION_METRICS
75
-
76
- # Download from Google Drive if not exists locally
77
- if not os.path.exists(CLASSIFICATION_MODEL_PATH):
78
- print(f"Classification model not found locally, downloading from Google Drive...")
79
- try:
80
- download_from_gdrive(CLASSIFICATION_MODEL_GDRIVE_ID, CLASSIFICATION_MODEL_PATH)
81
- except Exception as e:
82
- print(f"Failed to download classification model: {e}")
83
- return False
84
-
85
  if os.path.exists(CLASSIFICATION_MODEL_PATH):
86
  print(f"Loading classification model from {CLASSIFICATION_MODEL_PATH}")
87
  with open(CLASSIFICATION_MODEL_PATH, "rb") as f:
88
  artifact = pickle.load(f)
89
-
90
  classification_model = artifact["model"]
91
  CLASSIFICATION_FEATURE_NAMES = artifact["feature_columns"]
92
  CLASSIFICATION_CLASSES = artifact["classes"]
93
  CLASSIFICATION_METRICS = artifact.get("test_metrics", {})
94
-
95
- print(f"Classification model loaded: {len(CLASSIFICATION_FEATURE_NAMES)} features")
 
 
96
  print(f"Classes: {CLASSIFICATION_CLASSES}")
97
  return True
98
-
99
  print(f"Classification model not found at {CLASSIFICATION_MODEL_PATH}")
100
  return False
101
 
@@ -107,7 +93,7 @@ load_classification_model()
107
  def predict_score(*feature_values):
108
  if model is None:
109
  return "Error", "Model not loaded", ""
110
-
111
  features_df = pd.DataFrame([feature_values], columns=FEATURE_NAMES)
112
  raw_score = model.predict(features_df)[0]
113
  score = max(0, min(1, raw_score)) * 100
@@ -124,8 +110,9 @@ def predict_score(*feature_values):
124
  r2 = MODEL_METRICS.get('r2', 'N/A')
125
  correlation = MODEL_METRICS.get('correlation', 'N/A')
126
  r2_str = f"{r2:.4f}" if isinstance(r2, (int, float)) else str(r2)
127
- corr_str = f"{correlation:.4f}" if isinstance(correlation, (int, float)) else str(correlation)
128
-
 
129
  details = f"""
130
  ### Prediction Details
131
  - **Raw Model Output:** {raw_score:.4f}
@@ -145,25 +132,31 @@ def predict_score(*feature_values):
145
  def predict_weakest_link(*feature_values):
146
  if classification_model is None:
147
  return "Error", "Model not loaded", ""
148
-
149
- features_df = pd.DataFrame([feature_values], columns=CLASSIFICATION_FEATURE_NAMES)
150
-
 
151
  prediction = classification_model.predict(features_df)[0]
152
  probabilities = classification_model.predict_proba(features_df)[0]
153
-
154
  class_probs = list(zip(CLASSIFICATION_CLASSES, probabilities))
155
  class_probs.sort(key=lambda x: x[1], reverse=True)
156
-
157
  confidence = max(probabilities) * 100
158
- recommendation = BODY_REGION_RECOMMENDATIONS.get(prediction, "Focus on exercises that strengthen this region.")
159
-
 
160
  accuracy = CLASSIFICATION_METRICS.get('accuracy', 'N/A')
161
  f1_weighted = CLASSIFICATION_METRICS.get('f1_weighted', 'N/A')
162
- acc_str = f"{accuracy:.2%}" if isinstance(accuracy, (int, float)) else str(accuracy)
163
- f1_str = f"{f1_weighted:.2%}" if isinstance(f1_weighted, (int, float)) else str(f1_weighted)
164
-
165
- predictions_list = "\n".join([f"{i+1}. **{cp[0]}** - {cp[1]*100:.1f}%" for i, cp in enumerate(class_probs)])
166
-
 
 
 
 
167
  details = f"""
168
  ### Prediction Details
169
  - **Predicted Body Region:** {prediction}
@@ -179,18 +172,18 @@ def predict_weakest_link(*feature_values):
179
  - **Test Accuracy:** {acc_str}
180
  - **Test F1 (weighted):** {f1_str}
181
  """
182
-
183
  return prediction, f"Confidence: {confidence:.1f}%", details
184
 
185
 
186
  def load_example():
187
  if FEATURE_NAMES is None:
188
  return [0.5] * 35
189
-
190
  try:
191
  df = pd.read_csv(DATA_PATH, sep=';', decimal=',')
192
  sample_row = df.sample(1)
193
- # Return value for each feature, using 0.5 as default if feature not in dataset
194
  result = []
195
  for f in FEATURE_NAMES:
196
  if f in df.columns:
@@ -198,6 +191,7 @@ def load_example():
198
  # Clamp to valid slider range [0, 1]
199
  val = max(0.0, min(1.0, val))
200
  result.append(val)
 
201
  else:
202
  result.append(0.5)
203
  return result
@@ -209,11 +203,11 @@ def load_example():
209
  def load_classification_example():
210
  if CLASSIFICATION_FEATURE_NAMES is None:
211
  return [0.5] * 40
212
-
213
  try:
214
  df = pd.read_csv(DATA_PATH, sep=';', decimal=',')
215
  sample_row = df.sample(1)
216
- # Return value for each feature, using 0.5 as default if feature not in dataset
217
  result = []
218
  for f in CLASSIFICATION_FEATURE_NAMES:
219
  if f in df.columns:
@@ -221,6 +215,7 @@ def load_classification_example():
221
  # Clamp to valid slider range [0, 1]
222
  val = max(0.0, min(1.0, val))
223
  result.append(val)
 
224
  else:
225
  result.append(0.5)
226
  return result
@@ -237,29 +232,32 @@ def create_interface():
237
  outputs="text",
238
  title="Error: Model not loaded"
239
  )
240
-
241
  inputs = []
242
  for name in FEATURE_NAMES:
243
- slider = gr.Slider(minimum=0, maximum=1, value=0.5, step=0.01, label=name.replace("_", " "))
 
244
  inputs.append(slider)
245
 
246
  classification_inputs = []
247
  if CLASSIFICATION_FEATURE_NAMES is not None:
248
  for name in CLASSIFICATION_FEATURE_NAMES:
249
- slider = gr.Slider(minimum=0, maximum=1, value=0.5, step=0.01, label=name.replace("_", " "))
 
250
  classification_inputs.append(slider)
251
 
252
  description = """
253
  ## Deep Squat Movement Assessment
254
 
255
  **How to use:**
256
- 1. Adjust the sliders to input deviation values (0 = no deviation, 1 = maximum deviation)
 
257
  2. Click "Submit" to get your predicted score
258
  3. Or click "Load Random Example" to test with real data
259
 
260
  **Score Interpretation:**
261
  - 80-100%: Excellent form
262
- - 60-79%: Good form
263
  - 40-59%: Average form
264
  - 0-39%: Needs improvement
265
  """
@@ -278,26 +276,35 @@ def create_interface():
278
  angle_features = [n for n in FEATURE_NAMES if "Angle" in n]
279
  nasm_features = [n for n in FEATURE_NAMES if "NASM" in n]
280
  time_features = [n for n in FEATURE_NAMES if "Time" in n]
281
- other_features = [n for n in FEATURE_NAMES if "Angle" not in n and "NASM" not in n and "Time" not in n]
282
-
 
283
  angle_indices = [FEATURE_NAMES.index(f) for f in angle_features]
284
  nasm_indices = [FEATURE_NAMES.index(f) for f in nasm_features]
285
  time_indices = [FEATURE_NAMES.index(f) for f in time_features]
286
  other_indices = [FEATURE_NAMES.index(f) for f in other_features]
287
 
288
  if CLASSIFICATION_FEATURE_NAMES is not None:
289
- class_angle_features = [n for n in CLASSIFICATION_FEATURE_NAMES if "Angle" in n]
290
- class_nasm_features = [n for n in CLASSIFICATION_FEATURE_NAMES if "NASM" in n]
291
- class_time_features = [n for n in CLASSIFICATION_FEATURE_NAMES if "Time" in n]
292
- class_other_features = [n for n in CLASSIFICATION_FEATURE_NAMES if "Angle" not in n and "NASM" not in n and "Time" not in n]
293
- class_angle_indices = [CLASSIFICATION_FEATURE_NAMES.index(f) for f in class_angle_features]
294
- class_nasm_indices = [CLASSIFICATION_FEATURE_NAMES.index(f) for f in class_nasm_features]
295
- class_time_indices = [CLASSIFICATION_FEATURE_NAMES.index(f) for f in class_time_features]
296
- class_other_indices = [CLASSIFICATION_FEATURE_NAMES.index(f) for f in class_other_features]
 
 
 
 
 
 
 
 
297
 
298
  with gr.Blocks(title="Deep Squat Assessment") as demo:
299
  gr.Markdown("# Deep Squat Movement Assessment")
300
-
301
  with gr.Tabs():
302
  with gr.TabItem("Movement Scoring"):
303
  gr.Markdown(description)
@@ -305,8 +312,10 @@ def create_interface():
305
  with gr.Row():
306
  with gr.Column(scale=2):
307
  gr.Markdown("### Input Features")
308
- gr.Markdown(f"*{len(FEATURE_NAMES)} features loaded from champion model*")
309
- gr.Markdown("*Deviation values: 0 = perfect, 1 = maximum deviation*")
 
 
310
 
311
  with gr.Tabs():
312
  with gr.TabItem(f"Angle Deviations ({len(angle_indices)})"):
@@ -334,15 +343,17 @@ def create_interface():
334
 
335
  with gr.Row():
336
  submit_btn = gr.Button("Submit", variant="primary")
337
- example_btn = gr.Button("Load Random Example")
338
  clear_btn = gr.Button("Clear")
339
 
340
- submit_btn.click(fn=predict_score, inputs=inputs, outputs=[score_output, interp_output, details_output])
 
341
  example_btn.click(fn=load_example, inputs=[], outputs=inputs)
342
  clear_btn.click(
343
  fn=lambda: [0.5] * len(FEATURE_NAMES) + ["", "", ""],
344
  inputs=[],
345
- outputs=inputs + [score_output, interp_output, details_output],
 
346
  )
347
 
348
  if CLASSIFICATION_FEATURE_NAMES is not None:
@@ -352,8 +363,10 @@ def create_interface():
352
  with gr.Row():
353
  with gr.Column(scale=2):
354
  gr.Markdown("### Input Features")
355
- gr.Markdown(f"*{len(CLASSIFICATION_FEATURE_NAMES)} features for classification*")
356
- gr.Markdown("*Deviation values: 0 = perfect, 1 = maximum deviation*")
 
 
357
 
358
  with gr.Tabs():
359
  with gr.TabItem(f"Angle Deviations ({len(class_angle_indices)})"):
@@ -375,23 +388,30 @@ def create_interface():
375
 
376
  with gr.Column(scale=1):
377
  gr.Markdown("### Results")
378
- class_output = gr.Textbox(label="Predicted Body Region")
379
- class_interp_output = gr.Textbox(label="Confidence")
 
 
380
  class_details_output = gr.Markdown(label="Details")
381
 
382
  with gr.Row():
383
- class_submit_btn = gr.Button("Predict Body Region", variant="primary")
 
384
  class_example_btn = gr.Button("Load Random Example")
385
  class_clear_btn = gr.Button("Clear")
386
 
387
- class_submit_btn.click(fn=predict_weakest_link, inputs=classification_inputs, outputs=[class_output, class_interp_output, class_details_output])
388
- class_example_btn.click(fn=load_classification_example, inputs=[], outputs=classification_inputs)
 
 
 
389
  class_clear_btn.click(
390
- fn=lambda: [0.5] * len(CLASSIFICATION_FEATURE_NAMES) + ["", "", ""],
 
391
  inputs=[],
392
- outputs=classification_inputs + [class_output, class_interp_output, class_details_output],
393
  )
394
-
395
  return demo
396
 
397
 
 
2
  import pandas as pd
3
  import pickle
4
  import os
 
5
 
6
  # Get directory where this script is located
7
  SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
8
 
9
+ # Local paths - models loaded from A4/models/ directory
10
+ MODEL_PATH = os.path.join(
11
+ SCRIPT_DIR,
12
+ "A4/models/gDriveVersion/champion_model_final_2.pkl"
13
+ )
14
+ CLASSIFICATION_MODEL_PATH = os.path.join(
15
+ SCRIPT_DIR,
16
+ "A4/models/gDriveVersion/final_champion_model_A3.pkl"
17
+ )
18
+ DATA_PATH = os.path.join(
19
+ SCRIPT_DIR,
20
+ "A3/A3_Data/train_dataset.csv"
21
+ )
 
 
 
 
22
 
23
  model = None
24
  FEATURE_NAMES = None
 
31
  CLASSIFICATION_METRICS = None
32
 
33
  BODY_REGION_RECOMMENDATIONS = {
34
+ 'Upper Body': (
35
+ "Focus on shoulder mobility, thoracic spine extension, "
36
+ "and keeping your head neutral."),
37
+ 'Lower Body': (
38
+ "Work on hip mobility, ankle dorsiflexion, "
39
+ "and knee tracking over toes.")
40
  }
41
 
42
 
43
  def load_champion_model():
44
  global model, FEATURE_NAMES, MODEL_METRICS
45
+
 
 
 
 
 
 
 
 
 
46
  if os.path.exists(MODEL_PATH):
47
  print(f"Loading champion model from {MODEL_PATH}")
48
  with open(MODEL_PATH, "rb") as f:
49
  artifact = pickle.load(f)
50
+
51
  model = artifact["model"]
52
  FEATURE_NAMES = artifact["feature_columns"]
53
  MODEL_METRICS = artifact.get("test_metrics", {})
54
+
55
  print(f"Model loaded: {len(FEATURE_NAMES)} features")
56
  print(f"Test R2: {MODEL_METRICS.get('r2', 'N/A')}")
57
  return True
58
+
59
  print(f"Champion model not found at {MODEL_PATH}")
60
  return False
61
 
62
 
63
  def load_classification_model():
64
+ global classification_model
65
+ global CLASSIFICATION_FEATURE_NAMES
66
+ global CLASSIFICATION_CLASSES
67
+ global CLASSIFICATION_METRICS
68
+
 
 
 
 
 
 
69
  if os.path.exists(CLASSIFICATION_MODEL_PATH):
70
  print(f"Loading classification model from {CLASSIFICATION_MODEL_PATH}")
71
  with open(CLASSIFICATION_MODEL_PATH, "rb") as f:
72
  artifact = pickle.load(f)
73
+
74
  classification_model = artifact["model"]
75
  CLASSIFICATION_FEATURE_NAMES = artifact["feature_columns"]
76
  CLASSIFICATION_CLASSES = artifact["classes"]
77
  CLASSIFICATION_METRICS = artifact.get("test_metrics", {})
78
+
79
+ len_features = len(CLASSIFICATION_FEATURE_NAMES)
80
+ print(
81
+ f"Classification model loaded: {len_features} features")
82
  print(f"Classes: {CLASSIFICATION_CLASSES}")
83
  return True
84
+
85
  print(f"Classification model not found at {CLASSIFICATION_MODEL_PATH}")
86
  return False
87
 
 
93
  def predict_score(*feature_values):
94
  if model is None:
95
  return "Error", "Model not loaded", ""
96
+
97
  features_df = pd.DataFrame([feature_values], columns=FEATURE_NAMES)
98
  raw_score = model.predict(features_df)[0]
99
  score = max(0, min(1, raw_score)) * 100
 
110
  r2 = MODEL_METRICS.get('r2', 'N/A')
111
  correlation = MODEL_METRICS.get('correlation', 'N/A')
112
  r2_str = f"{r2:.4f}" if isinstance(r2, (int, float)) else str(r2)
113
+ corr_str = f"{correlation:.4f}" if isinstance(
114
+ correlation, (int, float)) else str(correlation)
115
+
116
  details = f"""
117
  ### Prediction Details
118
  - **Raw Model Output:** {raw_score:.4f}
 
132
  def predict_weakest_link(*feature_values):
133
  if classification_model is None:
134
  return "Error", "Model not loaded", ""
135
+
136
+ features_df = pd.DataFrame(
137
+ [feature_values], columns=CLASSIFICATION_FEATURE_NAMES)
138
+
139
  prediction = classification_model.predict(features_df)[0]
140
  probabilities = classification_model.predict_proba(features_df)[0]
141
+
142
  class_probs = list(zip(CLASSIFICATION_CLASSES, probabilities))
143
  class_probs.sort(key=lambda x: x[1], reverse=True)
144
+
145
  confidence = max(probabilities) * 100
146
+ recommendation = BODY_REGION_RECOMMENDATIONS.get(
147
+ prediction, "Focus on exercises that strengthen this region.")
148
+
149
  accuracy = CLASSIFICATION_METRICS.get('accuracy', 'N/A')
150
  f1_weighted = CLASSIFICATION_METRICS.get('f1_weighted', 'N/A')
151
+ acc_str = f"{accuracy:.2%}" if isinstance(
152
+ accuracy, (int, float)) else str(accuracy)
153
+ f1_str = f"{f1_weighted:.2%}" if isinstance(
154
+ f1_weighted, (int, float)) else str(f1_weighted)
155
+
156
+ predictions_list = "\n".join(
157
+ [f"{i+1}. **{cp[0]}** - {cp[1]*100:.1f}%" for i, cp in enumerate(class_probs)]
158
+ )
159
+
160
  details = f"""
161
  ### Prediction Details
162
  - **Predicted Body Region:** {prediction}
 
172
  - **Test Accuracy:** {acc_str}
173
  - **Test F1 (weighted):** {f1_str}
174
  """
175
+
176
  return prediction, f"Confidence: {confidence:.1f}%", details
177
 
178
 
179
  def load_example():
180
  if FEATURE_NAMES is None:
181
  return [0.5] * 35
182
+
183
  try:
184
  df = pd.read_csv(DATA_PATH, sep=';', decimal=',')
185
  sample_row = df.sample(1)
186
+ # Return value for each feature
187
  result = []
188
  for f in FEATURE_NAMES:
189
  if f in df.columns:
 
191
  # Clamp to valid slider range [0, 1]
192
  val = max(0.0, min(1.0, val))
193
  result.append(val)
194
+ # using 0.5 as default if feature not in dataset
195
  else:
196
  result.append(0.5)
197
  return result
 
203
  def load_classification_example():
204
  if CLASSIFICATION_FEATURE_NAMES is None:
205
  return [0.5] * 40
206
+
207
  try:
208
  df = pd.read_csv(DATA_PATH, sep=';', decimal=',')
209
  sample_row = df.sample(1)
210
+ # Return value for each feature
211
  result = []
212
  for f in CLASSIFICATION_FEATURE_NAMES:
213
  if f in df.columns:
 
215
  # Clamp to valid slider range [0, 1]
216
  val = max(0.0, min(1.0, val))
217
  result.append(val)
218
+ # using 0.5 as default if feature not in dataset
219
  else:
220
  result.append(0.5)
221
  return result
 
232
  outputs="text",
233
  title="Error: Model not loaded"
234
  )
235
+
236
  inputs = []
237
  for name in FEATURE_NAMES:
238
+ slider = gr.Slider(minimum=0, maximum=1, value=0.5,
239
+ step=0.01, label=name.replace("_", " "))
240
  inputs.append(slider)
241
 
242
  classification_inputs = []
243
  if CLASSIFICATION_FEATURE_NAMES is not None:
244
  for name in CLASSIFICATION_FEATURE_NAMES:
245
+ slider = gr.Slider(minimum=0, maximum=1, value=0.5,
246
+ step=0.01, label=name.replace("_", " "))
247
  classification_inputs.append(slider)
248
 
249
  description = """
250
  ## Deep Squat Movement Assessment
251
 
252
  **How to use:**
253
+ 1. Adjust the sliders to input deviation values
254
+ (0 = no deviation, 1 = maximum deviation)
255
  2. Click "Submit" to get your predicted score
256
  3. Or click "Load Random Example" to test with real data
257
 
258
  **Score Interpretation:**
259
  - 80-100%: Excellent form
260
+ - 60-79%: Good form
261
  - 40-59%: Average form
262
  - 0-39%: Needs improvement
263
  """
 
276
  angle_features = [n for n in FEATURE_NAMES if "Angle" in n]
277
  nasm_features = [n for n in FEATURE_NAMES if "NASM" in n]
278
  time_features = [n for n in FEATURE_NAMES if "Time" in n]
279
+ other_features = [
280
+ n for n in FEATURE_NAMES if "Angle" not in n and "NASM" not in n and "Time" not in n]
281
+
282
  angle_indices = [FEATURE_NAMES.index(f) for f in angle_features]
283
  nasm_indices = [FEATURE_NAMES.index(f) for f in nasm_features]
284
  time_indices = [FEATURE_NAMES.index(f) for f in time_features]
285
  other_indices = [FEATURE_NAMES.index(f) for f in other_features]
286
 
287
  if CLASSIFICATION_FEATURE_NAMES is not None:
288
+ class_angle_features = [
289
+ n for n in CLASSIFICATION_FEATURE_NAMES if "Angle" in n]
290
+ class_nasm_features = [
291
+ n for n in CLASSIFICATION_FEATURE_NAMES if "NASM" in n]
292
+ class_time_features = [
293
+ n for n in CLASSIFICATION_FEATURE_NAMES if "Time" in n]
294
+ class_other_features = [
295
+ n for n in CLASSIFICATION_FEATURE_NAMES if "Angle" not in n and "NASM" not in n and "Time" not in n]
296
+ class_angle_indices = [CLASSIFICATION_FEATURE_NAMES.index(
297
+ f) for f in class_angle_features]
298
+ class_nasm_indices = [CLASSIFICATION_FEATURE_NAMES.index(
299
+ f) for f in class_nasm_features]
300
+ class_time_indices = [CLASSIFICATION_FEATURE_NAMES.index(
301
+ f) for f in class_time_features]
302
+ class_other_indices = [CLASSIFICATION_FEATURE_NAMES.index(
303
+ f) for f in class_other_features]
304
 
305
  with gr.Blocks(title="Deep Squat Assessment") as demo:
306
  gr.Markdown("# Deep Squat Movement Assessment")
307
+
308
  with gr.Tabs():
309
  with gr.TabItem("Movement Scoring"):
310
  gr.Markdown(description)
 
312
  with gr.Row():
313
  with gr.Column(scale=2):
314
  gr.Markdown("### Input Features")
315
+ gr.Markdown(
316
+ f"*{len(FEATURE_NAMES)} features loaded from champion model*")
317
+ gr.Markdown(
318
+ "*Deviation values: 0 = perfect, 1 = maximum deviation*")
319
 
320
  with gr.Tabs():
321
  with gr.TabItem(f"Angle Deviations ({len(angle_indices)})"):
 
343
 
344
  with gr.Row():
345
  submit_btn = gr.Button("Submit", variant="primary")
346
+ example_btn = gr.Button("Load Sample")
347
  clear_btn = gr.Button("Clear")
348
 
349
+ submit_btn.click(fn=predict_score, inputs=inputs, outputs=[
350
+ score_output, interp_output, details_output])
351
  example_btn.click(fn=load_example, inputs=[], outputs=inputs)
352
  clear_btn.click(
353
  fn=lambda: [0.5] * len(FEATURE_NAMES) + ["", "", ""],
354
  inputs=[],
355
+ outputs=inputs + [score_output,
356
+ interp_output, details_output],
357
  )
358
 
359
  if CLASSIFICATION_FEATURE_NAMES is not None:
 
363
  with gr.Row():
364
  with gr.Column(scale=2):
365
  gr.Markdown("### Input Features")
366
+ gr.Markdown(
367
+ f"*{len(CLASSIFICATION_FEATURE_NAMES)} features for classification*")
368
+ gr.Markdown(
369
+ "*Deviation values: 0 = perfect, 1 = maximum deviation*")
370
 
371
  with gr.Tabs():
372
  with gr.TabItem(f"Angle Deviations ({len(class_angle_indices)})"):
 
388
 
389
  with gr.Column(scale=1):
390
  gr.Markdown("### Results")
391
+ class_output = gr.Textbox(
392
+ label="Predicted Body Region")
393
+ class_interp_output = gr.Textbox(
394
+ label="Confidence")
395
  class_details_output = gr.Markdown(label="Details")
396
 
397
  with gr.Row():
398
+ class_submit_btn = gr.Button(
399
+ "Predict Body Region", variant="primary")
400
  class_example_btn = gr.Button("Load Random Example")
401
  class_clear_btn = gr.Button("Clear")
402
 
403
+ class_submit_btn.click(fn=predict_weakest_link, inputs=classification_inputs, outputs=[
404
+ class_output, class_interp_output, class_details_output])
405
+ class_example_btn.click(fn=load_classification_example, inputs=[
406
+ ], outputs=classification_inputs)
407
+ output_list = [class_output, class_interp_output, class_details_output]
408
  class_clear_btn.click(
409
+ fn=lambda: [
410
+ 0.5] * len(CLASSIFICATION_FEATURE_NAMES) + ["", "", ""],
411
  inputs=[],
412
+ outputs=classification_inputs + output_list,
413
  )
414
+
415
  return demo
416
 
417