sports2d 0.6.2__py3-none-any.whl → 0.7.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- Sports2D/Demo/Config_demo.toml +49 -28
- Sports2D/Sports2D.py +40 -22
- Sports2D/Utilities/common.py +124 -3
- Sports2D/Utilities/skeletons.py +6 -8
- Sports2D/Utilities/tests.py +16 -8
- Sports2D/process.py +221 -72
- {sports2d-0.6.2.dist-info → sports2d-0.7.0.dist-info}/METADATA +315 -190
- sports2d-0.7.0.dist-info/RECORD +16 -0
- {sports2d-0.6.2.dist-info → sports2d-0.7.0.dist-info}/WHEEL +1 -1
- sports2d-0.6.2.dist-info/RECORD +0 -16
- {sports2d-0.6.2.dist-info → sports2d-0.7.0.dist-info}/LICENSE +0 -0
- {sports2d-0.6.2.dist-info → sports2d-0.7.0.dist-info}/entry_points.txt +0 -0
- {sports2d-0.6.2.dist-info → sports2d-0.7.0.dist-info}/top_level.txt +0 -0
Sports2D/Demo/Config_demo.toml
CHANGED
|
@@ -12,12 +12,16 @@
|
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
[project]
|
|
15
|
-
video_input = 'demo.mp4'
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
15
|
+
video_input = 'demo.mp4' # 'webcam' or '<video_path.ext>', or ['video1_path.mp4', 'video2_path.avi>', ...]
|
|
16
|
+
# On Windows, replace '\' with '/'
|
|
17
|
+
# Beware that images won't be saved if paths contain non ASCII characters.
|
|
18
|
+
px_to_m_from_person_id = 2 # Person to use for pixels to meters conversion (not used if a calibration file is provided)
|
|
19
|
+
px_to_m_person_height = 1.65 # Height of the reference person in meters (for pixels -> meters conversion).
|
|
20
|
+
visible_side = ['front', 'none', 'auto'] # Choose visible side among ['right', 'left', 'front', 'back', 'auto', 'none']. String or list of strings.
|
|
21
|
+
# if 'auto', will be either 'left', 'right', or 'front' depending on the direction of the motion
|
|
22
|
+
# if 'none', coordinates will be left in 2D rather than 3D
|
|
23
|
+
load_trc_px = '' # If you do not want to recalculate pose, load it from a trc file (in px, not in m)
|
|
24
|
+
compare = false # Not implemented yet
|
|
21
25
|
|
|
22
26
|
# Video parameters
|
|
23
27
|
time_range = [] # [] for the whole video, or [start_time, end_time] (in seconds), or [[start_time1, end_time1], [start_time2, end_time2], ...]
|
|
@@ -49,7 +53,16 @@ result_dir = '' # If empty, project dir is current dir
|
|
|
49
53
|
slowmo_factor = 1 # 1 for normal speed. For a video recorded at 240 fps and exported to 30 fps, it would be 240/30 = 8
|
|
50
54
|
|
|
51
55
|
# Pose detection parameters
|
|
52
|
-
pose_model = 'Body_with_feet' #With RTMLib:
|
|
56
|
+
pose_model = 'Body_with_feet' #With RTMLib:
|
|
57
|
+
# - Body_with_feet (default HALPE_26 model),
|
|
58
|
+
# - Whole_body_wrist (COCO_133_WRIST: body + feet + 2 hand_points),
|
|
59
|
+
# - Whole_body (COCO_133: body + feet + hands),
|
|
60
|
+
# - Body (COCO_17). Marker augmentation won't work, Kinematic analysis will work,
|
|
61
|
+
# - Hand (HAND_21, only lightweight mode. Potentially better results with Whole_body),
|
|
62
|
+
# - Face (FACE_106),
|
|
63
|
+
# - Animal (ANIMAL2D_17)
|
|
64
|
+
# /!\ Only RTMPose is natively embeded in Pose2Sim. For all other pose estimation methods, you will have to run them yourself, and then refer to the documentation to convert the output files if needed
|
|
65
|
+
# /!\ For Face and Animal, use mode="""{dictionary}""", and find the corresponding .onnx model there https://github.com/open-mmlab/mmpose/tree/main/projects/rtmpose
|
|
53
66
|
mode = 'balanced' # 'lightweight', 'balanced', 'performance', or """{dictionary}""" (see below)
|
|
54
67
|
|
|
55
68
|
# A dictionary (WITHIN THREE DOUBLE QUOTES) allows you to manually select the person detection (if top_down approach) and/or pose estimation models (see https://github.com/Tau-J/rtmlib).
|
|
@@ -67,15 +80,19 @@ mode = 'balanced' # 'lightweight', 'balanced', 'performance', or """{dictionary}
|
|
|
67
80
|
# mode = """{'pose_class':'RTMO',
|
|
68
81
|
# 'pose_model':'https://download.openmmlab.com/mmpose/v1/projects/rtmo/onnx_sdk/rtmo-m_16xb16-600e_body7-640x640-39e78cc4_20231211.zip',
|
|
69
82
|
# 'pose_input_size':[640, 640]}"""
|
|
83
|
+
# Example with animal pose estimation:
|
|
84
|
+
# mode = """{'pose_class':'RTMPose',
|
|
85
|
+
# 'pose_model':'https://download.openmmlab.com/mmpose/v1/projects/rtmposev1/onnx_sdk/rtmpose-m_simcc-ap10k_pt-aic-coco_210e-256x256-7a041aa1_20230206.zip',
|
|
86
|
+
# 'pose_input_size':[256,256]}"""
|
|
70
87
|
|
|
71
88
|
det_frequency = 4 # Run person detection only every N frames, and inbetween track previously detected bounding boxes (keypoint detection is still run on all frames).
|
|
72
89
|
# Equal to or greater than 1, can be as high as you want in simple uncrowded cases. Much faster, but might be less accurate.
|
|
73
90
|
device = 'auto' # 'auto', 'CPU', 'CUDA', 'MPS', 'ROCM'
|
|
74
91
|
backend = 'auto' # 'auto', 'openvino', 'onnxruntime', 'opencv'
|
|
75
92
|
tracking_mode = 'sports2d' # 'sports2d' or 'deepsort'. 'deepsort' is slower but more robust in difficult configurations
|
|
76
|
-
deepsort_params = """{'max_age':30, 'n_init':3, '
|
|
77
|
-
# More robust in crowded scenes but
|
|
78
|
-
#
|
|
93
|
+
# deepsort_params = """{'max_age':30, 'n_init':3, 'max_cosine_distance':0.3, 'max_iou_distance':0.8, 'embedder_gpu': True, embedder':'torchreid'}""" # """{dictionary between 3 double quotes}"""
|
|
94
|
+
# More robust in crowded scenes but tricky to parametrize. More information there: https://github.com/levan92/deep_sort_realtime/blob/master/deep_sort_realtime/deepsort_tracker.py#L51
|
|
95
|
+
# Requires `pip install torch torchvision torchreid gdown tensorboard`
|
|
79
96
|
|
|
80
97
|
|
|
81
98
|
# Processing parameters
|
|
@@ -87,18 +104,15 @@ keypoint_number_threshold = 0.3 # Person will be ignored if the number of go
|
|
|
87
104
|
[px_to_meters_conversion]
|
|
88
105
|
# Pixel to meters conversion
|
|
89
106
|
to_meters = true
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
107
|
+
make_c3d = true
|
|
108
|
+
save_calib = true # Coming soon!
|
|
109
|
+
|
|
110
|
+
# If conversion from px_to_m_person_height
|
|
94
111
|
floor_angle = 'auto' # 'auto' or a value in degrees, eg 2.3. If 'auto', estimated from the line formed by the toes when they are on the ground (where speed = 0)
|
|
95
112
|
xy_origin = ['auto'] # ['auto'] or [px_x,px_y]. N.B.: px_y points downwards. If ['auto'], direction estimated from the start to the end of the line formed by the toes when they are on the ground
|
|
96
|
-
save_calib = true
|
|
97
113
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
large_hip_knee_angles = 45 # Hip and knee angles below this value are considered as imprecise
|
|
101
|
-
trimmed_extrema_percent = 0.5 # Proportion of the most extreme segment values to remove before calculating their mean)
|
|
114
|
+
# If conversion from a calibration file
|
|
115
|
+
calib_file = '' # Calibration in the Pose2Sim format. 'calib_demo.toml', or '' if not available
|
|
102
116
|
|
|
103
117
|
|
|
104
118
|
[angles]
|
|
@@ -136,15 +150,22 @@ filter_type = 'butterworth' # butterworth, gaussian, LOESS, median
|
|
|
136
150
|
kernel_size = 3
|
|
137
151
|
|
|
138
152
|
|
|
139
|
-
[
|
|
140
|
-
do_ik =
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
153
|
+
[kinematics]
|
|
154
|
+
do_ik = true # Do scaling and inverse kinematics?
|
|
155
|
+
use_augmentation = true # true or false (lowercase) # Set to true if you want to use the model with augmented markers
|
|
156
|
+
use_contacts_muscles = true # true or false (lowercase) # If true, contact spheres and muscles are added to the model
|
|
157
|
+
participant_mass = [67.0, 55.0] # kg # defaults to 70 if not provided. No influence on kinematics (motion), only on kinetics (forces)
|
|
158
|
+
right_left_symmetry = true # true or false (lowercase) # Set to false only if you have good reasons to think the participant is not symmetrical (e.g. prosthetic limb)
|
|
159
|
+
|
|
160
|
+
# Choosing best frames to scale the model
|
|
161
|
+
default_height = 1.7 # meters # If automatic height calculation did not work, this value is used to scale the model
|
|
162
|
+
fastest_frames_to_remove_percent = 0.1 # Frames with high speed are considered as outliers
|
|
163
|
+
close_to_zero_speed_px = 50 # Sum for all keypoints: about 50 px/frame
|
|
164
|
+
close_to_zero_speed_m = 0.2 # Sum for all keypoints: 0.2 m/frame
|
|
165
|
+
large_hip_knee_angles = 45 # Hip and knee angles below this value are considered as imprecise
|
|
166
|
+
trimmed_extrema_percent = 0.5 # Proportion of the most extreme segment values to remove before calculating their mean)
|
|
167
|
+
remove_individual_scaling_setup = true # true or false (lowercase) # If true, the individual scaling setup files are removed to avoid cluttering
|
|
168
|
+
remove_individual_ik_setup = true # true or false (lowercase) # If true, the individual IK setup files are removed to avoid cluttering
|
|
148
169
|
|
|
149
170
|
|
|
150
171
|
[logging]
|
Sports2D/Sports2D.py
CHANGED
|
@@ -122,8 +122,10 @@ from Sports2D import Sports2D
|
|
|
122
122
|
|
|
123
123
|
## CONSTANTS
|
|
124
124
|
DEFAULT_CONFIG = {'project': {'video_input': ['demo.mp4'],
|
|
125
|
-
'
|
|
126
|
-
'
|
|
125
|
+
'px_to_m_from_person_id': 2,
|
|
126
|
+
'px_to_m_person_height': 1.65,
|
|
127
|
+
'visible_side': ['front', 'none', 'auto'],
|
|
128
|
+
'load_trc_px': '',
|
|
127
129
|
'compare': False,
|
|
128
130
|
'time_range': [],
|
|
129
131
|
'video_dir': '',
|
|
@@ -153,15 +155,11 @@ DEFAULT_CONFIG = {'project': {'video_input': ['demo.mp4'],
|
|
|
153
155
|
},
|
|
154
156
|
'px_to_meters_conversion': {
|
|
155
157
|
'to_meters': True,
|
|
158
|
+
'make_c3d': True,
|
|
156
159
|
'calib_file': '',
|
|
157
|
-
'calib_on_person_id': 0,
|
|
158
160
|
'floor_angle': 'auto',
|
|
159
161
|
'xy_origin': ['auto'],
|
|
160
|
-
'save_calib': True
|
|
161
|
-
'fastest_frames_to_remove_percent': 0.1,
|
|
162
|
-
'close_to_zero_speed_px': 50,
|
|
163
|
-
'large_hip_knee_angles': 45,
|
|
164
|
-
'trimmed_extrema_percent': 0.5
|
|
162
|
+
'save_calib': True
|
|
165
163
|
},
|
|
166
164
|
'angles': {'display_angle_values_on': ['body', 'list'],
|
|
167
165
|
'fontSize': 0.3,
|
|
@@ -205,18 +203,29 @@ DEFAULT_CONFIG = {'project': {'video_input': ['demo.mp4'],
|
|
|
205
203
|
'loess': {'nb_values_used': 5},
|
|
206
204
|
'median': {'kernel_size': 3}
|
|
207
205
|
},
|
|
208
|
-
'
|
|
209
|
-
'
|
|
210
|
-
'
|
|
211
|
-
'
|
|
206
|
+
'kinematics':{'do_ik': False,
|
|
207
|
+
'use_augmentation': False,
|
|
208
|
+
'use_contacts_muscles': True,
|
|
209
|
+
'participant_mass': [67.0, 55.0],
|
|
210
|
+
'right_left_symmetry': True,
|
|
211
|
+
'default_height': 1.70,
|
|
212
|
+
'remove_individual_scaling_setup': True,
|
|
213
|
+
'remove_individual_ik_setup': True,
|
|
214
|
+
'fastest_frames_to_remove_percent': 0.1,
|
|
215
|
+
'close_to_zero_speed_px': 50,
|
|
216
|
+
'close_to_zero_speed_m': 0.2,
|
|
217
|
+
'large_hip_knee_angles': 45,
|
|
218
|
+
'trimmed_extrema_percent': 0.5,
|
|
219
|
+
'osim_setup_path': '../OpenSim_setup'
|
|
212
220
|
},
|
|
213
221
|
'logging': {'use_custom_logging': False}
|
|
214
222
|
}
|
|
215
223
|
|
|
216
224
|
CONFIG_HELP = {'config': ["C", "path to a toml configuration file"],
|
|
217
225
|
'video_input': ["i", "webcam, or video_path.mp4, or video1_path.avi video2_path.mp4 ... Beware that images won't be saved if paths contain non ASCII characters"],
|
|
218
|
-
'
|
|
219
|
-
'
|
|
226
|
+
'px_to_m_person_height': ["H", "height of the person in meters. 1.70 if not specified"],
|
|
227
|
+
'visible_side': ["", "front, back, left, right, auto, or none. 'front none auto' if not specified. If 'auto', will be either left or right depending on the direction of the motion. If 'none', no IK for this person"],
|
|
228
|
+
'load_trc_px': ["", "load trc file to avaid running pose estimation again. false if not specified"],
|
|
220
229
|
'compare': ["", "visually compare motion with trc file. false if not specified"],
|
|
221
230
|
'webcam_id': ["w", "webcam ID. 0 if not specified"],
|
|
222
231
|
'time_range': ["t", "start_time end_time. In seconds. Whole video if not specified. start_time1 end_time1 start_time2 end_time2 ... if multiple videos with different time ranges"],
|
|
@@ -240,27 +249,26 @@ CONFIG_HELP = {'config': ["C", "path to a toml configuration file"],
|
|
|
240
249
|
'backend': ["", "Backend for pose estimation can be 'auto', 'cpu', 'cuda', 'mps' (for MacOS), or 'rocm' (for AMD GPUs)"],
|
|
241
250
|
'device': ["", "Device for pose estimatino can be 'auto', 'openvino', 'onnxruntime', 'opencv'"],
|
|
242
251
|
'to_meters': ["M", "convert pixels to meters. true if not specified"],
|
|
243
|
-
'
|
|
252
|
+
'make_c3d': ["", "Convert trc to c3d file. true if not specified"],
|
|
253
|
+
'px_to_m_from_person_id': ["", "person ID to calibrate on. 0 if not specified"],
|
|
244
254
|
'floor_angle': ["", "angle of the floor. 'auto' if not specified"],
|
|
245
255
|
'xy_origin': ["", "origin of the xy plane. 'auto' if not specified"],
|
|
246
256
|
'calib_file': ["", "path to calibration file. '' if not specified, eg no calibration file"],
|
|
247
257
|
'save_calib': ["", "save calibration file. true if not specified"],
|
|
248
258
|
'do_ik': ["", "do inverse kinematics. false if not specified"],
|
|
249
|
-
'
|
|
250
|
-
'
|
|
259
|
+
'use_augmentation': ["", "Use LSTM marker augmentation. false if not specified"],
|
|
260
|
+
'use_contacts_muscles': ["", "Use model with contact spheres and muscles. false if not specified"],
|
|
261
|
+
'participant_mass': ["", "mass of the participant in kg or none. Defaults to 70 if not provided. No influence on kinematics (motion), only on kinetics (forces)"],
|
|
251
262
|
'close_to_zero_speed_m': ["","Sum for all keypoints: about 50 px/frame or 0.2 m/frame"],
|
|
252
263
|
'multiperson': ["", "multiperson involves tracking: will be faster if set to false. true if not specified"],
|
|
253
264
|
'tracking_mode': ["", "sports2d or rtmlib. sports2d is generally much more accurate and comparable in speed. sports2d if not specified"],
|
|
254
265
|
'deepsort_params': ["", 'Deepsort tracking parameters: """{dictionary between 3 double quotes}""". \n\
|
|
255
|
-
|
|
266
|
+
Default: max_age:30, n_init:3, nms_max_overlap:0.8, max_cosine_distance:0.3, nn_budget:200, max_iou_distance:0.8, embedder_gpu: True\n\
|
|
267
|
+
More information there: https://github.com/levan92/deep_sort_realtime/blob/master/deep_sort_realtime/deepsort_tracker.py#L51'],
|
|
256
268
|
'input_size': ["", "width, height. 1280, 720 if not specified. Lower resolution will be faster but less precise"],
|
|
257
269
|
'keypoint_likelihood_threshold': ["", "detected keypoints are not retained if likelihood is below this threshold. 0.3 if not specified"],
|
|
258
270
|
'average_likelihood_threshold': ["", "detected persons are not retained if average keypoint likelihood is below this threshold. 0.5 if not specified"],
|
|
259
271
|
'keypoint_number_threshold': ["", "detected persons are not retained if number of detected keypoints is below this threshold. 0.3 if not specified, i.e., i.e., 30 percent"],
|
|
260
|
-
'fastest_frames_to_remove_percent': ["", "Frames with high speed are considered as outliers. Defaults to 0.1"],
|
|
261
|
-
'close_to_zero_speed_px': ["", "Sum for all keypoints: about 50 px/frame or 0.2 m/frame. Defaults to 50"],
|
|
262
|
-
'large_hip_knee_angles': ["", "Hip and knee angles below this value are considered as imprecise. Defaults to 45"],
|
|
263
|
-
'trimmed_extrema_percent': ["", "Proportion of the most extreme segment values to remove before calculating their mean. Defaults to 50"],
|
|
264
272
|
'fontSize': ["", "font size for angle values. 0.3 if not specified"],
|
|
265
273
|
'flip_left_right': ["", "true or false. true to get consistent angles with people facing both left and right sides. Set it to false if you want timeseries to be continuous even when the participent switches their stance. true if not specified"],
|
|
266
274
|
'correct_segment_angles_with_floor_angle': ["", "true or false. If the camera is tilted, corrects segment angles as regards to the floor angle. Set to false is the floor is tilted instead. True if not specified"],
|
|
@@ -274,6 +282,16 @@ CONFIG_HELP = {'config': ["C", "path to a toml configuration file"],
|
|
|
274
282
|
'sigma_kernel': ["", "sigma of the gaussian filter. 1 if not specified"],
|
|
275
283
|
'nb_values_used': ["", "number of values used for the loess filter. 5 if not specified"],
|
|
276
284
|
'kernel_size': ["", "kernel size of the median filter. 3 if not specified"],
|
|
285
|
+
'osim_setup_path': ["", "path to OpenSim setup. '../OpenSim_setup' if not specified"],
|
|
286
|
+
'right_left_symmetry': ["", "right left symmetry. true if not specified"],
|
|
287
|
+
'default_height': ["", "default height for scaling. 1.70 if not specified"],
|
|
288
|
+
'remove_individual_scaling_setup': ["", "remove individual scaling setup files generated during scaling. true if not specified"],
|
|
289
|
+
'remove_individual_ik_setup': ["", "remove individual IK setup files generated during IK. true if not specified"],
|
|
290
|
+
'fastest_frames_to_remove_percent': ["", "Frames with high speed are considered as outliers. Defaults to 0.1"],
|
|
291
|
+
'close_to_zero_speed_m': ["","Sum for all keypoints: about 0.2 m/frame. Defaults to 0.2"],
|
|
292
|
+
'close_to_zero_speed_px': ["", "Sum for all keypoints: about 50 px/frame. Defaults to 50"],
|
|
293
|
+
'large_hip_knee_angles': ["", "Hip and knee angles below this value are considered as imprecise and ignored. Defaults to 45"],
|
|
294
|
+
'trimmed_extrema_percent': ["", "Proportion of the most extreme segment values to remove before calculating their mean. Defaults to 50"],
|
|
277
295
|
'use_custom_logging': ["", "use custom logging. false if not specified"]
|
|
278
296
|
}
|
|
279
297
|
|
Sports2D/Utilities/common.py
CHANGED
|
@@ -22,6 +22,7 @@ import subprocess
|
|
|
22
22
|
from pathlib import Path
|
|
23
23
|
import itertools as it
|
|
24
24
|
import logging
|
|
25
|
+
from collections import defaultdict
|
|
25
26
|
from anytree import PreOrderIter
|
|
26
27
|
|
|
27
28
|
import numpy as np
|
|
@@ -29,11 +30,12 @@ import pandas as pd
|
|
|
29
30
|
from scipy import interpolate
|
|
30
31
|
import imageio_ffmpeg as ffmpeg
|
|
31
32
|
import cv2
|
|
33
|
+
import c3d
|
|
32
34
|
|
|
33
35
|
import matplotlib.pyplot as plt
|
|
34
|
-
from PyQt5.QtWidgets import QMainWindow, QApplication, QWidget, QTabWidget, QVBoxLayout
|
|
35
36
|
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
|
|
36
37
|
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
|
|
38
|
+
from PyQt5.QtWidgets import QMainWindow, QApplication, QWidget, QTabWidget, QVBoxLayout
|
|
37
39
|
|
|
38
40
|
|
|
39
41
|
## AUTHORSHIP INFORMATION
|
|
@@ -63,7 +65,7 @@ angle_dict = { # lowercase!
|
|
|
63
65
|
'right elbow': [['RWrist', 'RElbow', 'RShoulder'], 'flexion', 180, -1],
|
|
64
66
|
'left elbow': [['LWrist', 'LElbow', 'LShoulder'], 'flexion', 180, -1],
|
|
65
67
|
'right wrist': [['RElbow', 'RWrist', 'RIndex'], 'flexion', -180, 1],
|
|
66
|
-
'left wrist': [['LElbow', '
|
|
68
|
+
'left wrist': [['LElbow', 'LWrist', 'LIndex'], 'flexion', -180, 1],
|
|
67
69
|
|
|
68
70
|
# segment angles
|
|
69
71
|
'right foot': [['RBigToe', 'RHeel'], 'horizontal', 0, -1],
|
|
@@ -84,6 +86,32 @@ angle_dict = { # lowercase!
|
|
|
84
86
|
'left hand': [['LIndex', 'LWrist'], 'horizontal', 0, -1]
|
|
85
87
|
}
|
|
86
88
|
|
|
89
|
+
marker_Z_positions = {'right':
|
|
90
|
+
{"RHip": 0.105, "RKnee": 0.0886, "RAnkle": 0.0972, "RBigToe":0.0766, "RHeel":0.0883, "RSmallToe": 0.1200,
|
|
91
|
+
"RShoulder": 0.2016, "RElbow": 0.1613, "RWrist": 0.120, "RThumb": 0.1625, "RIndex": 0.1735, "RPinky": 0.1740, "REye": 0.0311,
|
|
92
|
+
"LHip": -0.105, "LKnee": -0.0886, "LAnkle": -0.0972, "LBigToe": -0.0766, "LHeel": -0.0883, "LSmallToe": -0.1200,
|
|
93
|
+
"LShoulder": -0.2016, "LElbow": -0.1613, "LWrist": -0.120, "LThumb": -0.1625, "LIndex": -0.1735, "LPinky": -0.1740, "LEye": -0.0311,
|
|
94
|
+
"Hip": 0.0, "Neck": 0.0, "Head":0.0, "Nose": 0.0},
|
|
95
|
+
'left':
|
|
96
|
+
{"RHip": -0.105, "RKnee": -0.0886, "RAnkle": -0.0972, "RBigToe": -0.0766, "RHeel": -0.0883, "RSmallToe": -0.1200,
|
|
97
|
+
"RShoulder": -0.2016, "RElbow": -0.1613, "RWrist": -0.120, "RThumb": -0.1625, "RIndex": -0.1735, "RPinky": -0.1740, "REye": -0.0311,
|
|
98
|
+
"LHip": 0.105, "LKnee": 0.0886, "LAnkle": 0.0972, "LBigToe":0.0766, "LHeel":0.0883, "LSmallToe": 0.1200,
|
|
99
|
+
"LShoulder": 0.2016, "LElbow": 0.1613, "LWrist": 0.120, "LThumb": 0.1625, "LIndex": 0.1735, "LPinky": 0.1740, "LEye": 0.0311,
|
|
100
|
+
"Hip": 0.0, "Neck": 0.0, "Head":0.0, "Nose": 0.0},
|
|
101
|
+
'front': # original knee:0.0179
|
|
102
|
+
{"RHip": 0.0301, "RKnee": 0.129, "RAnkle": 0.0230, "RBigToe": 0.2179, "RHeel": -0.0119, "RSmallToe": 0.1804,
|
|
103
|
+
"RShoulder": -0.01275, "RElbow": 0.0702, "RWrist": 0.1076, "RThumb": 0.0106, "RIndex": -0.0004, "RPinky": -0.0009, "REye": 0.0702,
|
|
104
|
+
"LHip": 0.0301, "LKnee": 0.129, "LAnkle": 0.0230, "LBigToe": 0.2179, "LHeel": -0.0119, "LSmallToe": 0.1804,
|
|
105
|
+
"LShoulder": -0.01275, "LElbow": 0.0702, "LWrist": 0.1076, "LThumb": 0.0106, "LIndex": -0.0004, "LPinky": -0.0009, "LEye": 0.0702,
|
|
106
|
+
"Hip": 0.0301, "Neck": 0.0008, "Head": 0.0655, "Nose": 0.1076},
|
|
107
|
+
'back':
|
|
108
|
+
{"RHip": -0.0301, "RKnee": -0.129, "RAnkle": -0.0230, "RBigToe": -0.2179, "RHeel": 0.0119, "RSmallToe": -0.1804,
|
|
109
|
+
"RShoulder": 0.01275, "RElbow": 0.0702, "RWrist": -1076.0002, "RThumb": -0.0106, "RIndex": 0.0004, "RPinky": 0.0009, "REye": -0.0702,
|
|
110
|
+
"LHip": -0.0301, "LKnee": -0.129, "LAnkle": -0.0230, "LBigToe": -0.2179, "LHeel": 0.0119, "LSmallToe": -0.1804,
|
|
111
|
+
"LShoulder": 0.01275, "LElbow": 0.0702, "LWrist": -0.1076, "LThumb": -0.0106, "LIndex": 0.0004, "LPinky": 0.0009, "LEye": -0.0702,
|
|
112
|
+
"Hip": -0.0301, "Neck": -0.0008, "Head": -0.0655, "Nose": -0.1076},
|
|
113
|
+
}
|
|
114
|
+
|
|
87
115
|
colors = [(255, 0, 0), (0, 0, 255), (255, 255, 0), (255, 0, 255), (0, 255, 255), (0, 0, 0), (255, 255, 255),
|
|
88
116
|
(125, 0, 0), (0, 125, 0), (0, 0, 125), (125, 125, 0), (125, 0, 125), (0, 125, 125),
|
|
89
117
|
(255, 125, 125), (125, 255, 125), (125, 125, 255), (255, 255, 125), (255, 125, 255), (125, 255, 255), (125, 125, 125),
|
|
@@ -143,6 +171,15 @@ class plotWindow():
|
|
|
143
171
|
self.app.exec_()
|
|
144
172
|
|
|
145
173
|
## FUNCTIONS
|
|
174
|
+
def to_dict(d):
|
|
175
|
+
'''
|
|
176
|
+
Convert a defaultdict to a dict.
|
|
177
|
+
'''
|
|
178
|
+
if isinstance(d, defaultdict):
|
|
179
|
+
return {k: to_dict(v) for k, v in d.items()}
|
|
180
|
+
return d
|
|
181
|
+
|
|
182
|
+
|
|
146
183
|
def read_trc(trc_path):
|
|
147
184
|
'''
|
|
148
185
|
Read a TRC file and extract its contents.
|
|
@@ -172,6 +209,85 @@ def read_trc(trc_path):
|
|
|
172
209
|
raise ValueError(f"Error reading TRC file at {trc_path}: {e}")
|
|
173
210
|
|
|
174
211
|
|
|
212
|
+
def extract_trc_data(trc_path):
|
|
213
|
+
'''
|
|
214
|
+
Extract marker names and coordinates from a trc file.
|
|
215
|
+
|
|
216
|
+
INPUTS:
|
|
217
|
+
- trc_path: Path to the trc file
|
|
218
|
+
|
|
219
|
+
OUTPUTS:
|
|
220
|
+
- marker_names: List of marker names
|
|
221
|
+
- marker_coords: Array of marker coordinates (n_frames, t+3*n_markers)
|
|
222
|
+
'''
|
|
223
|
+
|
|
224
|
+
# marker names
|
|
225
|
+
with open(trc_path, 'r') as file:
|
|
226
|
+
lines = file.readlines()
|
|
227
|
+
marker_names_line = lines[3]
|
|
228
|
+
marker_names = marker_names_line.strip().split('\t')[2::3]
|
|
229
|
+
|
|
230
|
+
# time and marker coordinates
|
|
231
|
+
trc_data_np = np.genfromtxt(trc_path, skip_header=5, delimiter = '\t')[:,1:]
|
|
232
|
+
|
|
233
|
+
return marker_names, trc_data_np
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def create_c3d_file(c3d_path, marker_names, trc_data_np):
|
|
237
|
+
'''
|
|
238
|
+
Create a c3d file from the data extracted from a trc file.
|
|
239
|
+
|
|
240
|
+
INPUTS:
|
|
241
|
+
- c3d_path: Path to the c3d file
|
|
242
|
+
- marker_names: List of marker names
|
|
243
|
+
- trc_data_np: Array of marker coordinates (n_frames, t+3*n_markers)
|
|
244
|
+
|
|
245
|
+
OUTPUTS:
|
|
246
|
+
- c3d file
|
|
247
|
+
'''
|
|
248
|
+
|
|
249
|
+
# retrieve frame rate
|
|
250
|
+
times = trc_data_np[:,0]
|
|
251
|
+
frame_rate = round((len(times)-1) / (times[-1] - times[0]))
|
|
252
|
+
|
|
253
|
+
# write c3d file
|
|
254
|
+
writer = c3d.Writer(point_rate=frame_rate, analog_rate=0, point_scale=1.0, point_units='mm', gen_scale=-1.0)
|
|
255
|
+
writer.set_point_labels(marker_names)
|
|
256
|
+
writer.set_screen_axis(X='+Z', Y='+Y')
|
|
257
|
+
|
|
258
|
+
for frame in trc_data_np:
|
|
259
|
+
residuals = np.full((len(marker_names), 1), 0.0)
|
|
260
|
+
cameras = np.zeros((len(marker_names), 1))
|
|
261
|
+
coords = frame[1:].reshape(-1,3)*1000
|
|
262
|
+
points = np.hstack((coords, residuals, cameras))
|
|
263
|
+
writer.add_frames([(points, np.array([]))])
|
|
264
|
+
|
|
265
|
+
writer.set_start_frame(0)
|
|
266
|
+
writer._set_last_frame(len(trc_data_np)-1)
|
|
267
|
+
|
|
268
|
+
with open(c3d_path, 'wb') as handle:
|
|
269
|
+
writer.write(handle)
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def convert_to_c3d(trc_path):
|
|
273
|
+
'''
|
|
274
|
+
Make Visual3D compatible c3d files from a trc path
|
|
275
|
+
|
|
276
|
+
INPUT:
|
|
277
|
+
- trc_path: string, trc file to convert
|
|
278
|
+
|
|
279
|
+
OUTPUT:
|
|
280
|
+
- c3d file
|
|
281
|
+
'''
|
|
282
|
+
|
|
283
|
+
trc_path = str(trc_path)
|
|
284
|
+
c3d_path = trc_path.replace('.trc', '.c3d')
|
|
285
|
+
marker_names, trc_data_np = extract_trc_data(trc_path)
|
|
286
|
+
create_c3d_file(c3d_path, marker_names, trc_data_np)
|
|
287
|
+
|
|
288
|
+
return c3d_path
|
|
289
|
+
|
|
290
|
+
|
|
175
291
|
def interpolate_zeros_nans(col, *args):
|
|
176
292
|
'''
|
|
177
293
|
Interpolate missing points (of value zero),
|
|
@@ -469,7 +585,7 @@ def add_neck_hip_coords(kpt_name, p_X, p_Y, p_scores, kpt_ids, kpt_names):
|
|
|
469
585
|
return p_X, p_Y, p_scores
|
|
470
586
|
|
|
471
587
|
|
|
472
|
-
def best_coords_for_measurements(Q_coords, keypoints_names, fastest_frames_to_remove_percent=0.2, close_to_zero_speed=0.2, large_hip_knee_angles=45):
|
|
588
|
+
def best_coords_for_measurements(Q_coords, keypoints_names, beginning_frames_to_remove_percent=0.2, end_frames_to_remove_percent=0.2, fastest_frames_to_remove_percent=0.2, close_to_zero_speed=0.2, large_hip_knee_angles=45):
|
|
473
589
|
'''
|
|
474
590
|
Compute the best coordinates for measurements, after removing:
|
|
475
591
|
- 20% fastest frames (may be outliers)
|
|
@@ -479,6 +595,8 @@ def best_coords_for_measurements(Q_coords, keypoints_names, fastest_frames_to_re
|
|
|
479
595
|
INPUTS:
|
|
480
596
|
- Q_coords: pd.DataFrame. The XYZ coordinates of each marker
|
|
481
597
|
- keypoints_names: list. The list of marker names
|
|
598
|
+
- beginning_frames_to_remove_percent: float
|
|
599
|
+
- end_frames_to_remove_percent: float
|
|
482
600
|
- fastest_frames_to_remove_percent: float
|
|
483
601
|
- close_to_zero_speed: float (sum for all keypoints: about 50 px/frame or 0.2 m/frame)
|
|
484
602
|
- large_hip_knee_angles: int
|
|
@@ -501,6 +619,9 @@ def best_coords_for_measurements(Q_coords, keypoints_names, fastest_frames_to_re
|
|
|
501
619
|
Q_coords = pd.concat((Q_coords.reset_index(drop=True), df_Hip), axis=1)
|
|
502
620
|
n_markers = len(keypoints_names)
|
|
503
621
|
|
|
622
|
+
# Removing first and last frames
|
|
623
|
+
# Q_coords = Q_coords.iloc[int(len(Q_coords) * beginning_frames_to_remove_percent):int(len(Q_coords) * (1-end_frames_to_remove_percent))]
|
|
624
|
+
|
|
504
625
|
# Using 80% slowest frames
|
|
505
626
|
sum_speeds = pd.Series(np.nansum([np.linalg.norm(Q_coords.iloc[:,kpt:kpt+3].diff(), axis=1) for kpt in range(n_markers)], axis=0))
|
|
506
627
|
sum_speeds = sum_speeds[sum_speeds>close_to_zero_speed] # Removing when speeds close to zero (out of frame)
|
Sports2D/Utilities/skeletons.py
CHANGED
|
@@ -85,10 +85,10 @@ HALPE_26 = Node("Hip", id=19, children=[
|
|
|
85
85
|
])
|
|
86
86
|
|
|
87
87
|
|
|
88
|
-
'''
|
|
88
|
+
'''COCO_133_WRIST (full-body with hands and face, from AlphaPose, MMPose, etc.)
|
|
89
89
|
https://github.com/MVIG-SJTU/AlphaPose/blob/master/docs/MODEL_ZOO.md
|
|
90
90
|
https://github.com/open-mmlab/mmpose/tree/main/projects/rtmpose'''
|
|
91
|
-
|
|
91
|
+
COCO_133_WRIST = Node("Hip", id=None, children=[
|
|
92
92
|
Node("RHip", id=12, children=[
|
|
93
93
|
Node("RKnee", id=14, children=[
|
|
94
94
|
Node("RAnkle", id=16, children=[
|
|
@@ -139,7 +139,7 @@ COCO_133_wrist = Node("CHip", id=None, children=[
|
|
|
139
139
|
'''COCO_133 (full-body with hands and face, from AlphaPose, MMPose, etc.)
|
|
140
140
|
https://github.com/MVIG-SJTU/AlphaPose/blob/master/docs/MODEL_ZOO.md
|
|
141
141
|
https://github.com/open-mmlab/mmpose/tree/main/projects/rtmpose'''
|
|
142
|
-
COCO_133 = Node("
|
|
142
|
+
COCO_133 = Node("Hip", id=None, children=[
|
|
143
143
|
Node("RHip", id=12, children=[
|
|
144
144
|
Node("RKnee", id=14, children=[
|
|
145
145
|
Node("RAnkle", id=16, children=[
|
|
@@ -359,9 +359,7 @@ COCO_133 = Node("CHip", id=None, children=[
|
|
|
359
359
|
Node("Mouth17", id=87, children=[
|
|
360
360
|
Node("Mouth18", id=88, children=[
|
|
361
361
|
Node("Mouth19", id=89, children=[
|
|
362
|
-
Node("Mouth20", id=90
|
|
363
|
-
Node("Mouth21", id=91)
|
|
364
|
-
]),
|
|
362
|
+
Node("Mouth20", id=90)
|
|
365
363
|
]),
|
|
366
364
|
]),
|
|
367
365
|
]),
|
|
@@ -387,7 +385,7 @@ COCO_133 = Node("CHip", id=None, children=[
|
|
|
387
385
|
|
|
388
386
|
'''COCO_17 (full-body without hands and feet, from OpenPose, AlphaPose, OpenPifPaf, YOLO-pose, MMPose, etc.)
|
|
389
387
|
https://github.com/open-mmlab/mmpose/tree/main/projects/rtmpose'''
|
|
390
|
-
COCO_17 = Node("
|
|
388
|
+
COCO_17 = Node("Hip", id=None, children=[
|
|
391
389
|
Node("RHip", id=12, children=[
|
|
392
390
|
Node("RKnee", id=14, children=[
|
|
393
391
|
Node("RAnkle", id=16),
|
|
@@ -647,7 +645,7 @@ FACE_106 = Node("root", id=None, children=[
|
|
|
647
645
|
|
|
648
646
|
'''ANIMAL2D_17 (full-body animal)
|
|
649
647
|
https://github.com/AlexTheBad/AP-10K/'''
|
|
650
|
-
ANIMAL2D_17 = Node("
|
|
648
|
+
ANIMAL2D_17 = Node("Hip", id=4, children=[
|
|
651
649
|
Node("RHip", id=14, children=[
|
|
652
650
|
Node("RKnee", id=15, children=[
|
|
653
651
|
Node("RAnkle", id=16),
|
Sports2D/Utilities/tests.py
CHANGED
|
@@ -56,15 +56,24 @@ def test_workflow():
|
|
|
56
56
|
|
|
57
57
|
# Default
|
|
58
58
|
demo_cmd = ["sports2d", "--show_realtime_results", "False", "--show_graphs", "False"]
|
|
59
|
-
subprocess.run(demo_cmd, check=True, capture_output=True, text=True)
|
|
59
|
+
subprocess.run(demo_cmd, check=True, capture_output=True, text=True, encoding='utf-8')
|
|
60
60
|
|
|
61
61
|
# With no pixels to meters conversion, no multiperson, lightweight mode, detection frequency, time range and slowmo factor
|
|
62
|
-
demo_cmd2 = ["sports2d", "--
|
|
63
|
-
|
|
62
|
+
demo_cmd2 = ["sports2d", "--show_realtime_results", "False", "--show_graphs", "False",
|
|
63
|
+
"--to_meters", "False",
|
|
64
|
+
"--multiperson", "False",
|
|
65
|
+
"--mode", "lightweight", "--det_frequency", "50",
|
|
66
|
+
"--time_range", "1.2", "2.7", "--slowmo_factor", "4"]
|
|
67
|
+
subprocess.run(demo_cmd2, check=True, capture_output=True, text=True, encoding='utf-8')
|
|
64
68
|
|
|
65
|
-
# With inverse kinematics, body pose_model and custom RTMO mode
|
|
66
|
-
|
|
67
|
-
|
|
69
|
+
# With a time range, inverse kinematics, marker augmentation, body pose_model and custom RTMO mode
|
|
70
|
+
demo_cmd3 = ["sports2d", "--show_realtime_results", "False", "--show_graphs", "False",
|
|
71
|
+
"--time_range", "1.2", "2.7",
|
|
72
|
+
"--do_ik", "True", "--use_augmentation", "True",
|
|
73
|
+
"--px_to_m_from_person_id", "1", "--px_to_m_person_height", "1.65",
|
|
74
|
+
"--visible_side", "left", "front", "--participant_mass", "55.0", "67.0",
|
|
75
|
+
"--pose_model", "body", "--mode", """{'pose_class':'RTMO', 'pose_model':'https://download.openmmlab.com/mmpose/v1/projects/rtmo/onnx_sdk/rtmo-m_16xb16-600e_body7-640x640-39e78cc4_20231211.zip', 'pose_input_size':[640, 640]}"""]
|
|
76
|
+
subprocess.run(demo_cmd3, check=True, capture_output=True, text=True)
|
|
68
77
|
|
|
69
78
|
# From config file
|
|
70
79
|
cli_config_path = Path(__file__).resolve().parent.parent / 'Demo' / 'Config_demo.toml'
|
|
@@ -72,6 +81,5 @@ def test_workflow():
|
|
|
72
81
|
cli_video_dir = Path(__file__).resolve().parent.parent / 'Demo'
|
|
73
82
|
config_dict.get("project").update({"video_dir": str(cli_video_dir)})
|
|
74
83
|
with open(cli_config_path, 'w') as f: toml.dump(config_dict, f)
|
|
75
|
-
|
|
76
84
|
demo_cmd4 = ["sports2d", "--config", str(cli_config_path), "--show_realtime_results", "False", "--show_graphs", "False"]
|
|
77
|
-
subprocess.run(demo_cmd4, check=True, capture_output=True, text=True)
|
|
85
|
+
subprocess.run(demo_cmd4, check=True, capture_output=True, text=True, encoding='utf-8')
|