Appendix B
Pseudocode for algorithmic implementation of EVGS
Obtain video from user and load it into the program
Apply OpenPose-BODY25 model to extract joint coordinates as keypoints for each video frame and store in variable KP_raw1,KP_raw2,…..KP_raw24
Filter the keypoint data using a zero-phase, dual-pass, second-order 12 Hz Butterworth filter (filtered_keypoints: KP_filter1,KP_filter2,…..KP_filter24)
Remove keypoints with scores below a 10% threshold (threshold = 0.1)
For each keypoint in filtered_keypoints:
If keypoint score < threshold, remove keypoint.
- 5.
Fill gaps of five frames or fewer using cubic spline interpolation (interpolated_keypoints: KP1,KP2,…..KP24)
- 6.
Determine the orientation of the video:
trunk_length_diff = abs(euclidean_distance(KP0, KP8)[0] − euclidean_distance(KP0, KP8)[Last frame (L) − 1])
If trunk_length_diff < threshold (99 frames): video_orientation = “sagittal”
Else: video_orientation = “coronal”
- 7.
Detect the direction of motion
If orientation is coronal: trunk_length_diff = abs(euclidean_distance(KP0, KP8)[0] − euclidean_distance(KP0, KP8)[L − 1])
If trunk_length_diff < 0: direction = frontal
Else: direction = rear
If orientation is sagittal: nose_x_diff = (KP0)[0][0] − (KP0)[0][L − 1]
If nose_x_diff < 0: direction = left-to-right
Else: direction = right-to-left
- 8.
Identify the four distinct gait events
Determine foot strike and foot off events using the Zeni et al. method [
33]
left_foot_strike_raw = euclidean_distance (KP21[0], KP8[0])
left_foot_off_raw = euclidean_distance ((KP19[0] + KP20[0])/2, KP8[0])
right_foot_strike_raw = euclidean_distance (KP24[0], KP8[0])
right_foot_off_raw = euclidean_distance ((KP22[0] + KP23[0])/2, KP8[0])
left_foot_strike = search_for_peaks (left_foot_strike_raw, flip = TRUE if direction = right-to-left)
right_foot_strike = search_for_peaks (right_foot_strike_raw, flip = TRUE if direction = right-to-left)
left_foot_off = search_for_peaks (left_foot_off_raw, flip = FALSE if direction = right-to-left)
right_foot_off = search_for_peaks (right_foot_off_raw, flip = FALSE if direction = right-to-left)
Determine mid-midstance and mid-midswing events using the distance between the toes
toe_distances = sqrt(square(KP19_x − KP22_x) + square(KP19_y − KP22_y))
mid_midswing = toe_distances.idxmin ()
mid_midstance = toe_distances.idxmax ()
- 9.
Allocate gait events to specific strides (strides)
right_first = left_foot_strike [0] > right_foot_strike [0]
if right_first: start = right_foot_strike, other_foot_strike = left_foot_strike
else: starts = left_foot_strike, other_foot_strike = right_foot_strike
if len(starts) >= 2:
for i in range of length(start) − 1): current_foot_strike = start[i], next_foot_strike = start [i + 1], end = next_foot_strike − 1
possible_right_foot_off = right_foot_off [current_foot_strike < right_foot_off < next_foot_strike]
possible_other_foot_strike = other_foot_strike[current_foot_strike < other_foot_strike< next_foot_strike]
possible_left_foot_off = left_foot_off [current_foot_strike < left_foot_off < next_foot_strike]
`stride_midstance = midstances [current_foot_strike < midstances < end]
if (length(possible_right_foot_off) >= 1) and (length(possible_other_foot_strike) >= 1) and (length(possible_left_foot_off) >= 1) and (length(stride_midstance) >= 2):
‘Direction’: right-to-left or left-to-right,
‘index’: i,
‘right_first’: TRUE if right_first else FALSE
‘right_foot_strike’: current_foot_strike if right_first else possible_other_foot_strike [0],
‘right_foot_off’: possible_right_foot_off [0] if right_first else possible_right_foot_off [0]
‘left_foot_strike’: possible_other_foot_strike [0] if right_first else current_foot_strike,
‘left_foot_off’: possible_left_foot_off [0] if right_first else possible_left_foot_off [0],
‘r_midstance’: stride_midstance [0] if right_first else stride_midstance [1],
‘l_midstance’: stride_midstance [1] if right_first else stride_midstance [0],
‘start’: current_foot_strike
‘end’: next_foot_strike − 1
A. If the video is coronal and frontal view, calculate scores of the following EVGS parameters:
FUNCTION (#17) MST_Lateral_Trunk_Shift_L (Keypoints):
Trunk = unit_vector(KP1[row] − KP8[row]), labY = array([0, 1])
Trunk_L = (arctan2(Trunk [1], Trunk [0]) − arctan2(labY [1], labY [0])) ∗ 180/pi
FUNCTION trunk_score(x): If x > 20: Return 2, Else if x < 0 OR (10 < x < 20): Return 1, Else if 0 <= x <= 10: Return 0, Else: Return NaN END
For each stride: midstance_frame = stride[‘l_midstance’]
stride_scores.append (trunk_score(Trunk_L[midstance_frame])) END
FUNCTION (#17) MST_Lateral_Trunk_Shift_R (Keypoints):
Trunk = unit_vector(KP1[row] − KP8[row]), labY = array([0, 1])
Trunk_R = (arctan2(Trunk [1], Trunk [0]) - arctan2(labY [1], labY [0])) ∗ 180/pi
FUNCTION trunk_score(x): If x > 20: Return 2, Else if x < 0 OR (10 < x < 20): Return 1, Else if 0 <= x <= 10: Return 0, Else Return NaN END
For each stride: midstance_frame = stride[‘r_midstance’]
stride_scores.append(trunk_score(Trunk_R[midstance_frame])) END
FUNCTION (#14) MST_Pelvic_Obliquity_L (Keypoints):
Hip = unit_vector(KP9[row] − KP12[row]), labY = array([0, 1])
hip_L = (arctan2(Hip [1], Hip [0]) − arctan2(labY [1], labY [0])) ∗ 180/pi
FUNCTION pelvis_score(x): If −1 < x < 6: Return 0, Else if x < −10 or (x > 15): Return 2, Else if (10 <= x <= −1) or (6 <= x <= 15): Return 1, Else: Return NaN, END
For each stride: midstance_frame = stride[‘l_midstance’]
stride_scores.append(pelvis_score(hip _L[midstance_frame])) END.
FUNCTION (#14) MST_Pelvic_Obliquity_R (Keypoints):
Hip = unit_vector(KP9[row] − KP12[row]), labY = array([0, 1])
hip _R = (arctan2(Hip [1], Hip [0]) − arctan2(labY [1], labY [0])) ∗ 180/pi
FUNCTION pelvis_score(x): If −6 < x < 1: Return 0, Else if x > 10 or (x < −15): Return 2, Else if (1 <= x <= 10) or (−15 <= x <= −6): Return 1, Else: Return NaN, END
For each stride: midstance_frame = stride[‘r_midstance’]
stride_scores.append(pelvis_score(hip _R[midstance_frame])) END
FUNCTION (#8) MST_Knee_Progression_Angle_L (Keypoints):
M1 = KP13_y − KP14_y/KP13_x − KP14_x, M2 = KP19_y − KP21_y/KP19_x − KP21_x
Angle = arctan(M1 − M2/1+ (M1 ∗ M2)) ∗ (360/2π)
FUNCTION ankle_score(x): If x < −25: Return 1, Else if (−25 <= x <= 25): Return 0, Else if x > 25: Return 1, Else: Return NaN, END
For each stride: midstance_frame = stride[‘l_midstance’]
stride_scores.append(ankle_score(Angle[midstance_frame])) END
FUNCTION (#8) MST_Knee_Progression_Angle_R( Keypoints):
M1 = KP10_y − KP11_y/KP10_x − KP11_x, M2 = KP22_y − KP24_y/KP22_x − KP24_x
Angle = arctan(M1 − M2/1+ (M1 ∗ M2)) ∗ (360/2π)
FUNCTION ankle_score(x): If x < −25: Return 1, Else if (−25 <= x <= 25): Return 0 Else if x > 25: Return 1, Else: Return NaN END
For each stride: midstance_frame = stride[‘r_midstance’]
stride_scores.append(ankle_score(Angle[midstance_frame])) END
FUNCTION (#5) MST_Foot_Rotation_L (Keypoints):
M1 = KP13_y − KP14_y/KP13_x − KP14_x, M2 = KP19_y − KP21_y/KP19_x − KP21_x
Angle = arctan(M1 − M2/1 + (M1 ∗ M2)) ∗ (360/2π)
FUNCTION ankle_score(x): If x > 45: Return 2, Else if (25 <= x <= 45): Return 1, Else if (−5 <= x <= 25): Return 0, Else if (−30 <= x <= −5): Return 1, Else if x < −30: Return 2, Else: Return NaN, END
For each stride: midstance_frame = stride[‘l_midstance’]
stride_scores.append(ankle_score(Angle[midstance_frame])) END
FUNCTION (#5) MST_Foot_Rotation_R (Keypoints):
M1 = KP10_y − KP11_y/KP10_x − KP11_x, M2 = KP22_y − KP24_y/KP22_x − KP24_x
Angle = arctan(M1 − M2/1 + (M1 ∗ M2)) ∗ (360/2π)
FUNCTION ankle_score(x): If x < −45: Return 2, Else if (−45 <= x <= 25): Return 1, Else if (−25 <= x <= 5): Return 0 Else if (5 <=x <= 30): Return 1, Else if x > 30: Return 2, Else: Return NaN END
For each stride: midstance_frame = stride[‘r_midstance’]
stride_scores.append(ankle_score(Angle[midstance_frame])) END
B. If the video is coronal and rear view, calculate scores of the following EVGS parameters:
FUNCTION (#4) MST_Hindfoot_Valgus_Varus_L (Keypoints):
Hindfoot = unit_vector(KP11[row] − KP24[row]), labY = array([0, 1])
foot_L = (arctan2(Hindfoot [1], Hindfoot [0]) − arctan2(labY [1], labY [0])) ∗ 180/pi
FUNCTION foot_score(x): If x < −15: Return 2, Else if (−15 <= x <= −6): Return 1, Else if (−6 <= x <= 1): Return 0 Else if (1 <= x <= 10): Return 1, Else if (x >10): Return 2, Else: Return NaN, END
For each stride: midstance_frame = stride[‘l_midstance’]
stride_scores.append(foot_score(foot_L[midstance_frame])) END
FUNCTION (#4) MST_Hindfoot_Valgus_Varus_R (Keypoints):
Hindfoot = unit_vector(KP11[row] − KP24[row]), labY = array([0, 1])
foot_R = (arctan2(Hindfoot [1], Hindfoot [0]) − arctan2(labY [1], labY [0])) ∗ 180/pi
FUNCTION foot_score(x): If x < −15: Return 2, Else if (−15 <= x <= −6): Return 1, Else if (−6 <= x <= 1): Return 0 Else if (1 <= x <= 10): Return 1, Else if (x >10): Return 2, Else: Return NaN END.
For each stride: midstance_frame = stride[‘r_midstance’]
stride_scores.append(foot_score(foot_R[midstance_frame])) END
C. If the video is Sagittal, calculate scores of the following EVGS parameters:
FUNCTION (#13) IC_Peak_Hip_Flexion (Keypoints):
M = KP1_y − KP8_y/KP1_x − KP8_x, M1 = 1/M If left-to-right: M2 = KP9_y − KP10_y/KP9_x − KP10_x Else:
M2 = KP12_y − KP13_y/KP12_x − KP13_x
Angle = arctan(M1 − M2/1 + (M1 ∗ M2)) ∗ (360/2π)
FUNCTION hip_score(x): If (x > 60): Return 2 Else if (45 < x <= 60): Return 1 Else if (25 < x <= 45): Return 0, Else if (10 < x <= 25): Return 1 Else if (x <= 10): Return 2 Else: Return NaN END
For each stride: heelstrike_frame = stride[‘right_foot_strike’] if left-to-right else stride[‘left_foot_strike’]
stride_scores.append(hip_score(Angle[heelstrike_frame])) END
FUNCTION (#10) IC_Knee_Extension (Keypoints):
If left-to-right: M1 = KP9_y − KP10_y/KP9_x − KP10_x, M2 = KP10_y − KP11_y/KP10_x − KP11_x
Else: M1 = KP12_y − KP13_y/KP12_x − KP13_x, M2 = KP13_y − KP14_y/KP13_x − KP14_x
Angle = arctan(M1 − M2/1 + (M1 ∗ M2)) ∗ (360/2π)
FUNCTION knee_score(x): If (x > 30): Return 2, Else if (15 < x <= 30): Return 1, Else if (5 < x <= 15): Return 0, Else if (−10 < x <= 5): Return 1, Else if (x < −10): Return 2 Else: Return NaN END
For each stride: heelstrike_frame = stride[‘right_foot_strike’] if left-to-right else stride[‘left_foot_strike’]
stride_scores.append(knee_score(Angle[heelstrike_frame])) END
FUNCTION (#1) IC_Initial_Contact (Keypoints):
If left-to-right: foot = unit_vector(KP22[row] − KP24[row] Else: foot = unit_vector(KP19[row] − KP21[row]
labY = array([1, 0]) Angle = (arctan2(foot [1], foot [0]) − arctan2(labY [1], labY [0])) ∗ 180/pi
FUNCTION foot_score(x): If (x < 0): Return 2 Else if (0<= x <= 20) or (160<= x <= 180): Return 1 Else: Return 0 END
For each stride: heelstrike_frame = stride[‘right_foot_strike’] if left-to-right else stride[‘left_foot_strike’]
stride_scores.append(foot_score(Angle[heelstrike_frame])) END
FUNCTION (#16) MST_Peak_Sagittal_Trunk (Keypoints):
Trunk = unit_vector(KP1[row] − KP8[row]), labY = array([0, 1])
Angle = (arctan2(Trunk [1], Trunk [0]) − arctan2(labY [1], labY [0])) ∗ 180/pi
FUNCTION trunk_score(x): If x > 15: Return 2, Else if x < −5 OR (5 < x < 15): Return 1, Else if −5 <= x <= 5: Return 0 Else Return NaN END
For each stride: midstance_frame = stride[‘r_midstance’] if left-to-right else stride[‘l_midstance’]
stride_scores.append(trunk_score(Angle[midstance_frame])) END
FUNCTION (#15) MST_Pelvic_Rotation (Keypoints):
If left-to-right: Pelvis = unit_vector(KP9[row] − KP12[row] Else: Pelvis = unit_vector(KP12[row] − KP9[row]
labY = array([0, 1]) Angle = (arctan2(Pelvis [1], Pelvis [0]) − arctan2(labY [1], labY [0])) ∗ 180/pi
FUNCTION pelvic_score(x): If (x < −15) or (x > 21): Return 2 Else if (11 < x < 21) or (−15 < x < −6): Return 1 Else if −6 < x < 11: Return 0 Else Return NaN END
For each stride: midstance_frame = stride[‘r_midstance’] if left-to-right else stride[‘l_midstance’]
stride_scores.append(pelvic_score(Angle[midstance_frame])) END
FUNCTION (#2) MST_Heel_Lift (Keypoints):
If left-to-right: foot = unit_vector(KP22[row] − KP24[row] Else: foot = unit_vector(KP19[row] − KP21[row]
labY = array([1, 0]) Angle = (arctan2(foot [1], foot [0]) − arctan2(labY [1], labY [0])) ∗ 180/pi
FUNCTION lift_score(p, l, Cc,a): If (−10 <= a <= 5) or (−180 <= a <= −170) or ((170 <= a <= 180)): If l < p <Cc: Return 0
Else if p >= Cc: Return 1 Else if p <= l: Return 1 Else: Return 2 END
For each stride: p = stride[‘right_foot_off] if left-to-right else stride[‘left_foot_off] l= stride[‘r_midstance’] if left-to-right else stride[‘l_midstance’] Cc = stride[‘right_foot_strike] if left-to-right else stride[‘left_foot_strike] m = stride[‘r_midstance’] if left-to-right else stride[‘l_midstance’] stride_scores.append(lift_score(p,l,Cc,Angle[m])) END
FUNCTION (#12) TST_Peak_Hip_Extension (Keypoints):
M = KP1_y − KP8_y/KP1_x − KP8_x , M1 = 1/M, left-to-right: M2 = KP9_y − KP10_y/KP9_x − KP10_x Else:
M2 = KP12_y − KP13_y/KP12_x − KP13_x
Angle = arctan(M1 − M2/1 + (M1 ∗ M2)) ∗ (360/2π)
FUNCTION hip_score(x): If (x > 15): Return 2 Else if (0 < x <= 15): Return 1 Else if (−20 < x <= 0): Return 0 Else if (−35 < x <= −20): Return 1 Else if (x <= −35): Return 2 Return NaN END
For each stride: heelstrike_frame = stride[‘left_foot_strike’] if left-to-right else stride[‘right_foot_strike’]
stride_scores.append(hip_score(Angle[heelstrike_frame])) END
FUNCTION (#9) TST_Knee_Extension (Keypoints):
If left-to-right: M1 = KP9_y − KP10_y/KP9_x − KP10_x M2 = KP10_y − KP11_y/KP10_x − KP11_x
Else: M1 = KP12_y − KP13_y/KP12_x − KP13_x. M2 = KP13_y − KP14_y/KP13_x − KP14_x
Angle = arctan(M1 − M2/1 + (M1 ∗ M2)) ∗ (360/2π)
FUNCTION knee_score(x): If (x > 25): Return 2 Else if (15 < x <= 25): Return 1 Else if (0 < x <= 15): Return 0 Else if (−10 < x <= 0): Return 1 Else if (x <= −10): Return 2 Else: Return NaN END
For each stride: heelstrike_frame = stride[‘left_foot_strike’] if left-to-right else stride[‘right_foot_strike’]
stride_scores.append(knee_score(Angle[heelstrike_frame])) END
FUNCTION (#3) TST_Ankle_Dorsiflexion (Keypoints):
If left-to-right: M1 = KP10_y − KP11_y/KP10_x − KP11_x M2 = KP22_y − KP24_y/KP22_x − KP24_x
Else: M1 = KP13_y − KP14_y/KP13_x − KP14_x1 M2 = KP 9_y − KP21_y/KP19_x − KP21_x
Angle = arctan(M1 − M2/1 + (M1 ∗ M2)) ∗ (360/2π)
FUNCTION ankle_score(x): If (x > 40): Return 2 Else if (25 < x <= 40): Return 1 Else if (5 < x <= 25): Return 0 Else if (−10 < x <= 5): Return 1 Else if (x <= −10): Return 2 Else: Return NaN END
For each stride: heelstrike_frame = stride[‘left_foot_strike’] if left-to-right else stride[‘right_foot_strike’]
stride_scores.append(ankle_score(Angle[heelstrike_frame])) END
FUNCTION (#11) MSW_Knee_Flexion (Keypoints):
If left-to-right: M1 = KP9_y − KP10_y/KP9_x − KP10_x M2 = KP10_y − KP11_y/KP10_x − KP11_x
Else: M1 = KP12_y − KP13_y/KP12_x − KP13_x M2 = KP13_y − KP14_y/KP13_x − KP14_x
Angle = arctan(M1 − M2/1 + (M1 ∗ M2)) ∗ (360/2π)
FUNCTION knee_score(x): If (x > 85): Return 2 Else if (70 < x <= 85): Return 1 Else if (50 < x <= 70): Return 0 Else if (35 < x <= 50): Return 1 Else if (x <= 35): Return 2 Else: Return NaN END
For each stride: heelstrike_frame = stride[‘l_midstance] if left-to-right else stride[‘r_midstance’], stride_scores.append(knee_score(Angle[heelstrike_frame])) END
FUNCTION (#7) MSW_Ankle_Dorsiflexion (Keypoints):
If left-to-right: M1 = KP10_y − KP11_y/KP10_x − KP11_x M2 = KP22_y − KP24_y/KP22_x − KP24_x
Else: M1 = KP13_y − KP14_y/KP13_x − KP14_x M2 = KP19_y − KP21_y/KP19_x − KP21_x
Angle = arctan(M1 − M2/1 + (M1 ∗ M2)) ∗ (360/2π)
FUNCTION ankle_score(x): If (x > 30): Return 2 Else if (15 < x <= 30): Return 1 Else if (−5 < x <= 15): Return 0 Else if (−20 < x <= −5): Return 1 Else if (x <= −20): Return 2 Else: Return NaN END
For each stride: heelstrike_frame = stride[l_midstance] if left-to-right else stride[‘r_midstance’]
stride_scores.append(ankle_score(Angle[heelstrike_frame])) END
FUNCTION (#6)MSW_Foot_Clearance(Keypoints):
fh = euclidean_distance(KP24[0] if left-to-right else KP21[0], KP8[0])
sh = euclidean_distance(KP21[0] if left-to-right else KP24[0], KP8[0])
ft = euclidean_distance(KP22[0] if left-to-right else KP19[0], KP8[0])
st = euclidean_distance(KP19[0] if left-to-right else KP22[0], KP8[0])
ml = euclidean_distance((KP10[0] if left-to-right else KP13[0] + KP24[0] if left-to-right else KP21[0])/2, KP8[0])
FUNCTION foot_score(ft,st,ml,fh,sh): If ( ml >= ft): Return 1 Else if ((st >= ft) and ( sh >= fh)): Return 0 Else if ((sh < fh) and (st < ft)): Return 2 Else if ((st < ft) and (sh > fh)): Return 1 Else: Return NaN END
For each stride: heelstrike_frame = stride[l_midstance] if left-to-right else stride[‘r_midstance’]
stride_scores.append(ankle_score(ft[heelstrike_frame],st[heelstrike_frame],ml[heelstrike_frame], fh[heelstrike_frame], sh[heelstrike_frame])) END