Repository navigation
Expand file tree
/
Copy pathlearning_engine.py
More file actions
110 lines (91 loc) · 4.87 KB
/
Copy pathlearning_engine.py
File metadata and controls
110 lines (91 loc) · 4.87 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
import numpy as np
class LearningEngine:
"""
K и learning_rate считаются МЕЖДУ ПОПЫТКАМИ ОДНОЙ И ТОЙ ЖЕ РУКИ,
а не просто "первый файл против второго". Если руки чередуются
(правая-левая-правая-левая), сравнение по порядку файлов сравнивало
бы разные руки друг с другом — это не адаптация, а просто разница
между левой и правой рукой, что не то же самое.
"""
@staticmethod
def compute_adaptation_coefficient(sigma1, sigma2):
if sigma1 == 0 or np.isnan(sigma1):
return 0
return ((sigma1 - sigma2) / sigma1) * 100
@staticmethod
def compute_learning_rate(mean1, mean2):
if mean1 == 0 or np.isnan(mean1):
return 0
return ((mean2 - mean1) / mean1) * 100
@staticmethod
def compute_recovery_time(metric, fps=60):
baseline_frames = int(0.5 * fps)
if len(metric) < baseline_frames + 10:
return None
baseline = np.mean(metric[:baseline_frames])
std_baseline = np.std(metric[:baseline_frames])
for i in range(baseline_frames, len(metric)):
if abs(metric[i] - baseline) < 2 * std_baseline:
return (i - baseline_frames) / fps
return None
@staticmethod
def compute_trial_to_trial_variability(sigmas, means):
if len(means) < 2:
return {'between_trial_variability': 0, 'within_trial_variability': 0, 'stability_index': 0}
return {
'between_trial_variability': float(np.std(means)),
'within_trial_variability': float(np.mean(sigmas)) if sigmas else 0,
'stability_index': float(np.std(means) / (np.mean(sigmas) + 1e-6)) if sigmas else 0
}
def analyze_session(self, trial_metrics, fps=60):
"""
trial_metrics — попытки В ХРОНОЛОГИЧЕСКОМ ПОРЯДКЕ (как поданы
video_paths). Внутри группируем по hand_side и сравниваем первую
попытку каждой руки со второй попыткой ТОЙ ЖЕ руки.
Возвращает per_hand: {0: {...}, 1: {...}} — раздельно по левой (0)
и правой (1) руке, плюс усреднённые adaptation_coefficient_K /
learning_rate / stability_index по обеим рукам для одного общего
числа, если нужно на глаз сравнить.
"""
valid_trials = [
t for t in trial_metrics
if not t.get('insufficient_hand_data')
and 'mean_velocity_all_landmarks' in t
]
if len(valid_trials) < 2:
return None
by_hand = {}
for t in valid_trials:
by_hand.setdefault(t['hand_side'], []).append(t)
per_hand_results = {}
for hand_side, trials in by_hand.items():
if len(trials) < 2:
continue
means = [t['mean_velocity_all_landmarks'] for t in trials]
sigmas = [t.get('std_velocity_all_landmarks', 0) for t in trials]
sigma1, sigma2 = sigmas[0], sigmas[1]
mean1, mean2 = means[0], means[1]
K = self.compute_adaptation_coefficient(sigma1, sigma2)
LR = self.compute_learning_rate(mean1, mean2)
variability = self.compute_trial_to_trial_variability(sigmas, means)
per_hand_results[hand_side] = {
'adaptation_coefficient_K': K,
'learning_rate': LR,
'between_trial_variability': variability['between_trial_variability'],
'within_trial_variability': variability['within_trial_variability'],
'stability_index': variability['stability_index'],
'num_trials_used': len(trials)
}
if not per_hand_results:
return None
avg_K = float(np.mean([r['adaptation_coefficient_K'] for r in per_hand_results.values()]))
avg_LR = float(np.mean([r['learning_rate'] for r in per_hand_results.values()]))
avg_stability = float(np.mean([r['stability_index'] for r in per_hand_results.values()]))
return {
'per_hand': per_hand_results,
'adaptation_coefficient_K': avg_K,
'learning_rate': avg_LR,
'between_trial_variability': float(np.mean([r['between_trial_variability'] for r in per_hand_results.values()])),
'within_trial_variability': float(np.mean([r['within_trial_variability'] for r in per_hand_results.values()])),
'stability_index': avg_stability
}