github_sync / app.py
Bachstelze
use the improved classifier with classes attribute
5389691
import gradio as gr
import pandas as pd
import pickle
import os
from A5.CorrelationFilter import CorrelationFilter
# Get directory where this script is located
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
# Local paths - models loaded from A4/models/ directory
MODEL_PATH = os.path.join(
SCRIPT_DIR,
"A5/models/aimoscores_improved_A4.pkl"
)
CLASSIFICATION_MODEL_PATH = os.path.join(
SCRIPT_DIR,
"A4/models/weaklink_classifier_rf.pkl",
# new classifier without "classes" key "A5/models/weaklink_classifier_rfc_A4.pkl"
)
DATA_PATH = os.path.join(
SCRIPT_DIR,
"A3/A3_Data/train_dataset.csv"
)
model = None
FEATURE_NAMES = None
MODEL_METRICS = None
# Classification model
classification_model = None
CLASSIFICATION_FEATURE_NAMES = None
CLASSIFICATION_CLASSES = None
CLASSIFICATION_METRICS = None
BODY_REGION_RECOMMENDATIONS = {
'Upper Body': (
"Focus on shoulder mobility, thoracic spine extension, "
"and keeping your head neutral."),
'Lower Body': (
"Work on hip mobility, ankle dorsiflexion, "
"and knee tracking over toes.")
}
def load_champion_model():
global model, FEATURE_NAMES, MODEL_METRICS
if os.path.exists(MODEL_PATH):
print(f"Loading champion model from {MODEL_PATH}")
with open(MODEL_PATH, "rb") as f:
artifact = pickle.load(f)
model = artifact["model"]
FEATURE_NAMES = artifact["feature_columns"]
MODEL_METRICS = artifact.get("test_metrics", {})
print(f"Model loaded: {len(FEATURE_NAMES)} features")
print(f"Test R2: {MODEL_METRICS.get('r2', 'N/A')}")
return True
print(f"Champion model not found at {MODEL_PATH}")
return False
def load_classification_model():
global classification_model
global CLASSIFICATION_FEATURE_NAMES
global CLASSIFICATION_CLASSES
global CLASSIFICATION_METRICS
if os.path.exists(CLASSIFICATION_MODEL_PATH):
print(f"Loading classification model from {CLASSIFICATION_MODEL_PATH}")
with open(CLASSIFICATION_MODEL_PATH, "rb") as f:
artifact = pickle.load(f)
classification_model = artifact["model"]
CLASSIFICATION_FEATURE_NAMES = artifact["feature_columns"]
CLASSIFICATION_CLASSES = artifact["classes"]
CLASSIFICATION_METRICS = artifact.get("test_metrics", {})
len_features = len(CLASSIFICATION_FEATURE_NAMES)
print(
f"Classification model loaded: {len_features} features")
print(f"Classes: {CLASSIFICATION_CLASSES}")
return True
print(f"Classification model not found at {CLASSIFICATION_MODEL_PATH}")
return False
def predict_score(*feature_values):
if model is None:
return "Error", "Model not loaded", ""
features_df = pd.DataFrame([feature_values], columns=FEATURE_NAMES)
raw_score = model.predict(features_df)[0]
score = max(0, min(1, raw_score)) * 100
if score >= 80:
interpretation = "Excellent, great squat form"
elif score >= 60:
interpretation = "Good, minor improvements needed"
elif score >= 40:
interpretation = "Average, a lot of areas to work on"
else:
interpretation = "Needs work, focus on proper form"
r2 = MODEL_METRICS.get('r2', 'N/A')
correlation = MODEL_METRICS.get('correlation', 'N/A')
r2_str = f"{r2:.4f}" if isinstance(r2, (int, float)) else str(r2)
corr_str = f"{correlation:.4f}" if isinstance(
correlation, (int, float)) else str(correlation)
details = f"""
### Prediction Details
- **Raw Model Output:** {raw_score:.4f}
- **Normalized Score:** {score:.1f}%
- **Assessment:** {interpretation}
### Model Performance
- **Test R-squared:** {r2_str}
- **Test Correlation:** {corr_str}
*Lower deviation values = better form*
"""
return f"{score:.1f}%", interpretation, details
def predict_weakest_link(*feature_values):
if classification_model is None:
return "Error", "Model not loaded", ""
features_df = pd.DataFrame(
[feature_values], columns=CLASSIFICATION_FEATURE_NAMES)
prediction = classification_model.predict(features_df)[0]
probabilities = classification_model.predict_proba(features_df)[0]
class_probs = list(zip(CLASSIFICATION_CLASSES, probabilities))
class_probs.sort(key=lambda x: x[1], reverse=True)
confidence = max(probabilities) * 100
recommendation = BODY_REGION_RECOMMENDATIONS.get(
prediction, "Focus on exercises that strengthen this region.")
accuracy = CLASSIFICATION_METRICS.get('accuracy', 'N/A')
f1_weighted = CLASSIFICATION_METRICS.get('f1_weighted', 'N/A')
acc_str = f"{accuracy:.2%}" if isinstance(
accuracy, (int, float)) else str(accuracy)
f1_str = f"{f1_weighted:.2%}" if isinstance(
f1_weighted, (int, float)) else str(f1_weighted)
predictions_list = "\n".join(
[f"{i+1}. **{cp[0]}** - {cp[1]*100:.1f}%" for i, cp in enumerate(class_probs)]
)
details = f"""
### Prediction Details
- **Predicted Body Region:** {prediction}
- **Confidence:** {confidence:.1f}%
### Probability Distribution
{predictions_list}
### Recommendation
{recommendation}
### Model Performance
- **Test Accuracy:** {acc_str}
- **Test F1 (weighted):** {f1_str}
"""
return prediction, f"Confidence: {confidence:.1f}%", details
def load_example():
if FEATURE_NAMES is None:
return [0.5] * 35
try:
df = pd.read_csv(DATA_PATH, sep=';', decimal=',')
sample_row = df.sample(1)
# Return value for each feature
result = []
for f in FEATURE_NAMES:
if f in df.columns:
val = float(sample_row[f].values[0])
# Clamp to valid slider range [0, 1]
val = max(0.0, min(1.0, val))
result.append(val)
# using 0.5 as default if feature not in dataset
else:
result.append(0.5)
return result
except Exception as e:
print(f"Error loading example: {e}")
return [0.5] * len(FEATURE_NAMES)
def load_classification_example():
if CLASSIFICATION_FEATURE_NAMES is None:
return [0.5] * 40
try:
df = pd.read_csv(DATA_PATH, sep=';', decimal=',')
sample_row = df.sample(1)
# Return value for each feature
result = []
for f in CLASSIFICATION_FEATURE_NAMES:
if f in df.columns:
val = float(sample_row[f].values[0])
# Clamp to valid slider range [0, 1]
val = max(0.0, min(1.0, val))
result.append(val)
# using 0.5 as default if feature not in dataset
else:
result.append(0.5)
return result
except Exception as e:
print(f"Error loading classification example: {e}")
return [0.5] * len(CLASSIFICATION_FEATURE_NAMES)
def create_interface():
if FEATURE_NAMES is None:
return gr.Interface(
fn=lambda: "Model not loaded",
inputs=[],
outputs="text",
title="Error: Model not loaded"
)
inputs = []
for name in FEATURE_NAMES:
slider = gr.Slider(minimum=0, maximum=1, value=0.5,
step=0.01, label=name.replace("_", " "))
inputs.append(slider)
classification_inputs = []
if CLASSIFICATION_FEATURE_NAMES is not None:
for name in CLASSIFICATION_FEATURE_NAMES:
slider = gr.Slider(minimum=0, maximum=1, value=0.5,
step=0.01, label=name.replace("_", " "))
classification_inputs.append(slider)
description = """
## Deep Squat Movement Assessment
**How to use:**
1. Adjust the sliders to input deviation values
(0 = no deviation, 1 = maximum deviation)
2. Click "Submit" to get your predicted score
3. Or click "Load Sample" to test with real data
**Score Interpretation:**
- 80-100%: Excellent form
- 60-79%: Good form
- 40-59%: Average form
- 0-39%: Needs improvement
"""
classification_description = """
## Body Region Classification
**How to use:**
1. Adjust the sliders to input deviation values (0 = no deviation, 1 = maximum deviation)
2. Click "Predict Body Region" to identify where to focus improvements
3. Or click "Load Sample" to test with real data
**Body Regions:** Upper Body, Lower Body
"""
angle_features = [n for n in FEATURE_NAMES if "Angle" in n]
nasm_features = [n for n in FEATURE_NAMES if "NASM" in n]
time_features = [n for n in FEATURE_NAMES if "Time" in n]
other_features = [
n for n in FEATURE_NAMES if "Angle" not in n and "NASM" not in n and "Time" not in n]
angle_indices = [FEATURE_NAMES.index(f) for f in angle_features]
nasm_indices = [FEATURE_NAMES.index(f) for f in nasm_features]
time_indices = [FEATURE_NAMES.index(f) for f in time_features]
other_indices = [FEATURE_NAMES.index(f) for f in other_features]
if CLASSIFICATION_FEATURE_NAMES is not None:
class_angle_features = [
n for n in CLASSIFICATION_FEATURE_NAMES if "Angle" in n]
class_nasm_features = [
n for n in CLASSIFICATION_FEATURE_NAMES if "NASM" in n]
class_time_features = [
n for n in CLASSIFICATION_FEATURE_NAMES if "Time" in n]
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]
class_angle_indices = [CLASSIFICATION_FEATURE_NAMES.index(
f) for f in class_angle_features]
class_nasm_indices = [CLASSIFICATION_FEATURE_NAMES.index(
f) for f in class_nasm_features]
class_time_indices = [CLASSIFICATION_FEATURE_NAMES.index(
f) for f in class_time_features]
class_other_indices = [CLASSIFICATION_FEATURE_NAMES.index(
f) for f in class_other_features]
with gr.Blocks(title="Deep Squat Assessment") as demo:
gr.Markdown("# Deep Squat Movement Assessment")
with gr.Tabs():
with gr.TabItem("Movement Scoring"):
gr.Markdown(description)
with gr.Row():
with gr.Column(scale=2):
gr.Markdown("### Input Features")
gr.Markdown(
f"*{len(FEATURE_NAMES)} features loaded from champion model*")
gr.Markdown(
"*Deviation values: 0 = perfect, 1 = maximum deviation*")
with gr.Tabs():
with gr.TabItem(f"Angle Deviations ({len(angle_indices)})"):
for idx in angle_indices:
inputs[idx].render()
with gr.TabItem(f"NASM Deviations ({len(nasm_indices)})"):
for idx in nasm_indices:
inputs[idx].render()
with gr.TabItem(f"Time Deviations ({len(time_indices)})"):
for idx in time_indices:
inputs[idx].render()
if other_indices:
with gr.TabItem(f"Other ({len(other_indices)})"):
for idx in other_indices:
inputs[idx].render()
with gr.Column(scale=1):
gr.Markdown("### Results")
score_output = gr.Textbox(label="Predicted Score")
interp_output = gr.Textbox(label="Assessment")
details_output = gr.Markdown(label="Details")
with gr.Row():
submit_btn = gr.Button("Submit", variant="primary")
example_btn = gr.Button("Load Sample")
clear_btn = gr.Button("Clear")
submit_btn.click(fn=predict_score, inputs=inputs, outputs=[
score_output, interp_output, details_output])
example_btn.click(fn=load_example, inputs=[], outputs=inputs)
clear_btn.click(
fn=lambda: [0.5] * len(FEATURE_NAMES) + ["", "", ""],
inputs=[],
outputs=inputs + [score_output,
interp_output, details_output],
)
if CLASSIFICATION_FEATURE_NAMES is not None:
with gr.TabItem("Body Region Classification"):
gr.Markdown(classification_description)
with gr.Row():
with gr.Column(scale=2):
gr.Markdown("### Input Features")
gr.Markdown(
f"*{len(CLASSIFICATION_FEATURE_NAMES)} features for classification*")
gr.Markdown(
"*Deviation values: 0 = perfect, 1 = maximum deviation*")
with gr.Tabs():
with gr.TabItem(f"Angle Deviations ({len(class_angle_indices)})"):
for idx in class_angle_indices:
classification_inputs[idx].render()
with gr.TabItem(f"NASM Deviations ({len(class_nasm_indices)})"):
for idx in class_nasm_indices:
classification_inputs[idx].render()
with gr.TabItem(f"Time Deviations ({len(class_time_indices)})"):
for idx in class_time_indices:
classification_inputs[idx].render()
if class_other_indices:
with gr.TabItem(f"Other ({len(class_other_indices)})"):
for idx in class_other_indices:
classification_inputs[idx].render()
with gr.Column(scale=1):
gr.Markdown("### Results")
class_output = gr.Textbox(
label="Predicted Body Region")
class_interp_output = gr.Textbox(
label="Confidence")
class_details_output = gr.Markdown(label="Details")
with gr.Row():
class_submit_btn = gr.Button(
"Predict Body Region", variant="primary")
class_example_btn = gr.Button("Load Sample")
class_clear_btn = gr.Button("Clear")
class_submit_btn.click(fn=predict_weakest_link, inputs=classification_inputs, outputs=[
class_output, class_interp_output, class_details_output])
class_example_btn.click(fn=load_classification_example, inputs=[
], outputs=classification_inputs)
output_list = [class_output, class_interp_output, class_details_output]
class_clear_btn.click(
fn=lambda: [
0.5] * len(CLASSIFICATION_FEATURE_NAMES) + ["", "", ""],
inputs=[],
outputs=classification_inputs + output_list,
)
return demo
if __name__ == "__main__":
# load the pickled models
load_champion_model()
load_classification_model()
# create the interface
demo = create_interface()
demo.launch(share=False, server_name="0.0.0.0", server_port=7860)