sports2d 0.8.19__tar.gz → 0.8.20__tar.gz

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.
Files changed (35) hide show
  1. {sports2d-0.8.19 → sports2d-0.8.20}/PKG-INFO +2 -2
  2. {sports2d-0.8.19 → sports2d-0.8.20}/Sports2D/Demo/Config_demo.toml +3 -2
  3. {sports2d-0.8.19 → sports2d-0.8.20}/Sports2D/Sports2D.py +5 -3
  4. {sports2d-0.8.19 → sports2d-0.8.20}/Sports2D/process.py +58 -17
  5. {sports2d-0.8.19 → sports2d-0.8.20}/pyproject.toml +2 -2
  6. {sports2d-0.8.19 → sports2d-0.8.20}/sports2d.egg-info/PKG-INFO +2 -2
  7. {sports2d-0.8.19 → sports2d-0.8.20}/sports2d.egg-info/requires.txt +1 -1
  8. {sports2d-0.8.19 → sports2d-0.8.20}/.github/workflows/continuous-integration.yml +0 -0
  9. {sports2d-0.8.19 → sports2d-0.8.20}/.github/workflows/joss_pdf.yml +0 -0
  10. {sports2d-0.8.19 → sports2d-0.8.20}/.github/workflows/publish-on-release.yml +0 -0
  11. {sports2d-0.8.19 → sports2d-0.8.20}/.gitignore +0 -0
  12. {sports2d-0.8.19 → sports2d-0.8.20}/CITATION.cff +0 -0
  13. {sports2d-0.8.19 → sports2d-0.8.20}/Content/Demo_plots.png +0 -0
  14. {sports2d-0.8.19 → sports2d-0.8.20}/Content/Demo_results.png +0 -0
  15. {sports2d-0.8.19 → sports2d-0.8.20}/Content/Demo_terminal.png +0 -0
  16. {sports2d-0.8.19 → sports2d-0.8.20}/Content/Person_selection.png +0 -0
  17. {sports2d-0.8.19 → sports2d-0.8.20}/Content/Video_tuto_Sports2D_Colab.png +0 -0
  18. {sports2d-0.8.19 → sports2d-0.8.20}/Content/joint_convention.png +0 -0
  19. {sports2d-0.8.19 → sports2d-0.8.20}/Content/paper.bib +0 -0
  20. {sports2d-0.8.19 → sports2d-0.8.20}/Content/paper.md +0 -0
  21. {sports2d-0.8.19 → sports2d-0.8.20}/Content/sports2d_blender.gif +0 -0
  22. {sports2d-0.8.19 → sports2d-0.8.20}/Content/sports2d_opensim.gif +0 -0
  23. {sports2d-0.8.19 → sports2d-0.8.20}/LICENSE +0 -0
  24. {sports2d-0.8.19 → sports2d-0.8.20}/README.md +0 -0
  25. {sports2d-0.8.19 → sports2d-0.8.20}/Sports2D/Demo/demo.mp4 +0 -0
  26. {sports2d-0.8.19 → sports2d-0.8.20}/Sports2D/Sports2D.ipynb +0 -0
  27. {sports2d-0.8.19 → sports2d-0.8.20}/Sports2D/Utilities/__init__.py +0 -0
  28. {sports2d-0.8.19 → sports2d-0.8.20}/Sports2D/Utilities/common.py +0 -0
  29. {sports2d-0.8.19 → sports2d-0.8.20}/Sports2D/Utilities/tests.py +0 -0
  30. {sports2d-0.8.19 → sports2d-0.8.20}/Sports2D/__init__.py +0 -0
  31. {sports2d-0.8.19 → sports2d-0.8.20}/setup.cfg +0 -0
  32. {sports2d-0.8.19 → sports2d-0.8.20}/sports2d.egg-info/SOURCES.txt +0 -0
  33. {sports2d-0.8.19 → sports2d-0.8.20}/sports2d.egg-info/dependency_links.txt +0 -0
  34. {sports2d-0.8.19 → sports2d-0.8.20}/sports2d.egg-info/entry_points.txt +0 -0
  35. {sports2d-0.8.19 → sports2d-0.8.20}/sports2d.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sports2d
3
- Version: 0.8.19
3
+ Version: 0.8.20
4
4
  Summary: Compute 2D human pose and angles from a video or a webcam.
5
5
  Author-email: David Pagnon <contact@david-pagnon.com>
6
6
  Maintainer-email: David Pagnon <contact@david-pagnon.com>
@@ -38,7 +38,7 @@ Requires-Dist: openvino
38
38
  Requires-Dist: opencv-python<4.12
39
39
  Requires-Dist: imageio_ffmpeg
40
40
  Requires-Dist: deep-sort-realtime
41
- Requires-Dist: Pose2Sim>=0.10.33
41
+ Requires-Dist: Pose2Sim>=0.10.36
42
42
  Dynamic: license-file
43
43
 
44
44
 
@@ -60,8 +60,8 @@ pose_model = 'Body_with_feet' #With RTMLib:
60
60
  # - Hand (HAND_21, only lightweight mode. Potentially better results with Whole_body),
61
61
  # - Face (FACE_106),
62
62
  # - Animal (ANIMAL2D_17)
63
- # /!\ 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
64
- # /!\ For Face and Animal, use mode="""{dictionary}""", and find the corresponding .onnx model there https://github.com/open-mmlab/mmpose/tree/main/projects/rtmpose
63
+ # 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
64
+ # For Face and Animal, use mode="""{dictionary}""", and find the corresponding .onnx model there https://github.com/open-mmlab/mmpose/tree/main/projects/rtmpose
65
65
  mode = 'balanced' # 'lightweight', 'balanced', 'performance', or """{dictionary}""" (see below)
66
66
 
67
67
  # 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).
@@ -139,6 +139,7 @@ reject_outliers = true # Hampel filter for outlier rejection before other f
139
139
 
140
140
  filter = true
141
141
  show_graphs = true # Show plots of raw and processed results
142
+ save_graphs = false # Save position and angle plots of raw and processed results
142
143
  filter_type = 'butterworth' # butterworth, kalman, gcv_spline, gaussian, loess, median, butterworth_on_speed
143
144
 
144
145
  # Most intuitive and standard filter in biomechanics
@@ -28,7 +28,7 @@
28
28
  - Run on webcam with default parameters:
29
29
  sports2d --video_input webcam
30
30
  - Run with custom parameters (all non specified are set to default):
31
- sports2d --show_plots False --time_range 0 2.1 --result_dir path_to_result_dir
31
+ sports2d --show_graphs False --time_range 0 2.1 --result_dir path_to_result_dir
32
32
  sports2d --person_detection_method highest_likelihood --mode lightweight --det_frequency 50
33
33
  - Run with a toml configuration file:
34
34
  sports2d --config path_to_config.toml
@@ -44,7 +44,7 @@
44
44
  pip install .
45
45
 
46
46
  -----
47
- /!\ Warning /!\
47
+ Warning
48
48
  -----
49
49
  - The angle estimation is only as good as the pose estimation algorithm, i.e., it is not perfect.
50
50
  - It will only lead to acceptable results if the persons move in the 2D plane (sagittal plane).
@@ -236,6 +236,7 @@ DEFAULT_CONFIG = {'base': {'video_input': ['demo.mp4'],
236
236
  'reject_outliers': True,
237
237
  'filter': True,
238
238
  'show_graphs': True,
239
+ 'save_graphs': False,
239
240
  'filter_type': 'butterworth',
240
241
  'butterworth': {'order': 4, 'cut_off_frequency': 6.0},
241
242
  'kalman': {'trust_ratio': 500.0, 'smooth':True},
@@ -279,6 +280,7 @@ CONFIG_HELP = {'config': ["C", "path to a toml configuration file"],
279
280
  'show_realtime_results': ["R", "show results in real-time. true if not specified"],
280
281
  'display_angle_values_on': ["a", '"body", "list", "body" "list", or "none". body list if not specified'],
281
282
  'show_graphs': ["G", "show plots of raw and processed results. true if not specified"],
283
+ 'save_graphs': ["", "save position and angle plots of raw and processed results. false if not specified"],
282
284
  'joint_angles': ["j", '"Right ankle" "Left ankle" "Right knee" "Left knee" "Right hip" "Left hip" "Right shoulder" "Left shoulder" "Right elbow" "Left elbow" if not specified'],
283
285
  'segment_angles': ["s", '"Right foot" "Left foot" "Right shank" "Left shank" "Right thigh" "Left thigh" "Pelvis" "Trunk" "Shoulders" "Head" "Right arm" "Left arm" "Right forearm" "Left forearm" if not specified'],
284
286
  'save_vid': ["V", "save processed video. true if not specified"],
@@ -546,7 +548,7 @@ def main():
546
548
  - Run on webcam with default parameters:
547
549
  sports2d --video_input webcam
548
550
  - Run with custom parameters (all non specified are set to default):
549
- sports2d --show_plots False --time_range 0 2.1 --result_dir path_to_result_dir
551
+ sports2d --show_graphs False --time_range 0 2.1 --result_dir path_to_result_dir
550
552
  sports2d --mode lightweight --det_frequency 50
551
553
  - Run with a toml configuration file:
552
554
  sports2d --config path_to_config.toml
@@ -29,7 +29,7 @@
29
29
  - optionally plots pose and angle data before and after processing for comparison
30
30
  - optionally saves poses for each person as a trc file, and angles as a mot file
31
31
 
32
- /!\ Warning /!\
32
+ Warning
33
33
  - The pose detection is only as good as the pose estimation algorithm, i.e., it is not perfect.
34
34
  - It will lead to reliable results only if the persons move in the 2D plane (sagittal or frontal plane).
35
35
  - The persons need to be filmed as perpendicularly as possible from their side.
@@ -77,6 +77,7 @@ from matplotlib.widgets import Slider, Button
77
77
  from matplotlib import patheffects
78
78
 
79
79
  from rtmlib import PoseTracker, BodyWithFeet, Wholebody, Body, Hand, Custom
80
+ from rtmlib.tools.object_detection.post_processings import nms
80
81
  from deep_sort_realtime.deepsort_tracker import DeepSort
81
82
 
82
83
  from Sports2D.Utilities.common import *
@@ -789,10 +790,10 @@ def make_mot_with_angles(angles, time, mot_path):
789
790
  return angles
790
791
 
791
792
 
792
- def pose_plots(trc_data_unfiltered, trc_data, person_id):
793
+ def pose_plots(trc_data_unfiltered, trc_data, person_id, show=True):
793
794
  '''
794
795
  Displays trc filtered and unfiltered data for comparison
795
- /!\ Often crashes on the third window...
796
+ Often crashes on the third window...
796
797
 
797
798
  INPUTS:
798
799
  - trc_data_unfiltered: pd.DataFrame. The unfiltered trc data
@@ -835,13 +836,16 @@ def pose_plots(trc_data_unfiltered, trc_data, person_id):
835
836
 
836
837
  pw.addPlot(keypoint, f)
837
838
 
838
- pw.show()
839
+ if show:
840
+ pw.show()
839
841
 
842
+ return pw
840
843
 
841
- def angle_plots(angle_data_unfiltered, angle_data, person_id):
844
+
845
+ def angle_plots(angle_data_unfiltered, angle_data, person_id, show=True):
842
846
  '''
843
847
  Displays angle filtered and unfiltered data for comparison
844
- /!\ Often crashes on the third window...
848
+ Often crashes on the third window...
845
849
 
846
850
  INPUTS:
847
851
  - angle_data_unfiltered: pd.DataFrame. The unfiltered angle data
@@ -878,7 +882,10 @@ def angle_plots(angle_data_unfiltered, angle_data, person_id):
878
882
 
879
883
  pw.addPlot(angle, f)
880
884
 
881
- pw.show()
885
+ if show:
886
+ pw.show()
887
+
888
+ return pw
882
889
 
883
890
 
884
891
  def get_personIDs_with_highest_scores(all_frames_scores, nb_persons_to_detect):
@@ -1374,7 +1381,7 @@ def process_fun(config_dict, video_file, time_range, frame_rate, result_dir):
1374
1381
  - optionally plots pose and angle data before and after processing for comparison
1375
1382
  - optionally saves poses for each person as a trc file, and angles as a mot file
1376
1383
 
1377
- /!\ Warning /!\d
1384
+ Warning
1378
1385
  - The pose detection is only as good as the pose estimation algorithm, i.e., it is not perfect.
1379
1386
  - It will lead to reliable results only if the persons move in the 2D plane (sagittal or frontal plane).
1380
1387
  - The persons need to be filmed as perpendicularly as possible from their side.
@@ -1490,6 +1497,7 @@ def process_fun(config_dict, video_file, time_range, frame_rate, result_dir):
1490
1497
  handle_LR_swap = config_dict.get('post-processing').get('handle_LR_swap', False)
1491
1498
  reject_outliers = config_dict.get('post-processing').get('reject_outliers', False)
1492
1499
  show_plots = config_dict.get('post-processing').get('show_graphs')
1500
+ save_plots = config_dict.get('post-processing').get('save_graphs')
1493
1501
  filter_type = config_dict.get('post-processing').get('filter_type')
1494
1502
  butterworth_filter_order = config_dict.get('post-processing').get('butterworth', {}).get('order')
1495
1503
  butterworth_filter_cutoff = config_dict.get('post-processing').get('butterworth', {}).get('cut_off_frequency')
@@ -1513,6 +1521,7 @@ def process_fun(config_dict, video_file, time_range, frame_rate, result_dir):
1513
1521
  output_dir_name = f'{video_file_stem}_Sports2D'
1514
1522
  video_file_path = video_dir / video_file
1515
1523
  output_dir = result_dir / output_dir_name
1524
+ plots_output_dir = output_dir / f'{output_dir_name}_graphs'
1516
1525
  img_output_dir = output_dir / f'{output_dir_name}_img'
1517
1526
  vid_output_path = output_dir / f'{output_dir_name}.mp4'
1518
1527
  pose_output_path = output_dir / f'{output_dir_name}_px.trc'
@@ -1521,6 +1530,8 @@ def process_fun(config_dict, video_file, time_range, frame_rate, result_dir):
1521
1530
  output_dir.mkdir(parents=True, exist_ok=True)
1522
1531
  if save_img:
1523
1532
  img_output_dir.mkdir(parents=True, exist_ok=True)
1533
+ if save_plots:
1534
+ plots_output_dir.mkdir(parents=True, exist_ok=True)
1524
1535
 
1525
1536
  # Inverse kinematics settings
1526
1537
  do_ik = config_dict.get('kinematics').get('do_ik')
@@ -1721,6 +1732,13 @@ def process_fun(config_dict, video_file, time_range, frame_rate, result_dir):
1721
1732
  # Detect poses
1722
1733
  keypoints, scores = pose_tracker(frame)
1723
1734
 
1735
+ # Non maximum suppression (at pose level, not detection)
1736
+ frame_shape = frame.shape
1737
+ bboxes = bbox_xyxy_compute(frame_shape, keypoints, padding=0)
1738
+ score_bboxes = np.array([np.mean(s) for s in scores])
1739
+ keep = nms(bboxes, score_bboxes, nms_thr=0.45)
1740
+ keypoints, scores = keypoints[keep], scores[keep]
1741
+
1724
1742
  # Track poses across frames
1725
1743
  if tracking_mode == 'deepsort':
1726
1744
  keypoints, scores = sort_people_deepsort(keypoints, scores, deepsort_tracker, frame, frame_count)
@@ -1999,9 +2017,17 @@ def process_fun(config_dict, video_file, time_range, frame_rate, result_dir):
1999
2017
  columns_to_concat.extend([all_frames_X_person.iloc[:,kpt], all_frames_Y_person.iloc[:,kpt], all_frames_Z_homog.iloc[:,kpt]])
2000
2018
  trc_data_unfiltered_i = pd.concat([all_frames_time] + columns_to_concat, axis=1)
2001
2019
  trc_data_unfiltered.append(trc_data_unfiltered_i)
2002
- if show_plots and not to_meters:
2003
- pose_plots(trc_data_unfiltered_i, trc_data_i, i)
2004
-
2020
+ if not to_meters and (show_plots or save_plots):
2021
+ pw = pose_plots(trc_data_unfiltered_i, trc_data_i, i, show=show_plots)
2022
+ if save_plots:
2023
+ for n, f in enumerate(pw.figure_handles):
2024
+ dpi = pw.canvases[i].figure.dpi
2025
+ f.set_size_inches(1280/dpi, 720/dpi)
2026
+ title = pw.tabs.tabText(n)
2027
+ plot_path = plots_output_dir / (pose_output_path.stem + f'_person{i:02d}_px_{title.replace(" ","_").replace("/","_")}.png')
2028
+ f.savefig(plot_path, dpi=dpi, bbox_inches='tight')
2029
+ logging.info(f'Pose plots (px) saved in {plots_output_dir}.')
2030
+
2005
2031
  all_frames_X_processed[:,idx_person,:], all_frames_Y_processed[:,idx_person,:] = all_frames_X_person_filt, all_frames_Y_person_filt
2006
2032
  if calculate_angles or save_angles:
2007
2033
  all_frames_X_flipped_processed[:,idx_person,:] = all_frames_X_flipped_person
@@ -2087,9 +2113,17 @@ def process_fun(config_dict, video_file, time_range, frame_rate, result_dir):
2087
2113
  px_to_m_unfiltered_i = [convert_px_to_meters(trc_data_unfiltered[i][kpt_name], first_person_height, height_px, cx, cy, -floor_angle_estim) for kpt_name in new_keypoints_names]
2088
2114
  trc_data_unfiltered_m_i = pd.concat([all_frames_time.rename('time')]+px_to_m_unfiltered_i, axis=1)
2089
2115
 
2090
- if to_meters and show_plots:
2091
- pose_plots(trc_data_unfiltered_m_i, trc_data_m_i, i)
2092
-
2116
+ if to_meters and (show_plots or save_plots):
2117
+ pw = pose_plots(trc_data_unfiltered_m_i, trc_data_m_i, i, show=show_plots)
2118
+ if save_plots:
2119
+ for n, f in enumerate(pw.figure_handles):
2120
+ dpi = pw.canvases[i].figure.dpi
2121
+ f.set_size_inches(1280/dpi, 720/dpi)
2122
+ title = pw.tabs.tabText(n)
2123
+ plot_path = plots_output_dir / (pose_output_path_m.stem + f'_person{i:02d}_m_{title.replace(" ","_").replace("/","_")}.png')
2124
+ f.savefig(plot_path, dpi=dpi, bbox_inches='tight')
2125
+ logging.info(f'Pose plots (m) saved in {plots_output_dir}.')
2126
+
2093
2127
  # Write to trc file
2094
2128
  trc_data_m.append(trc_data_m_i)
2095
2129
  pose_path_person_m_i = (pose_output_path.parent / (pose_output_path_m.stem + f'_person{i:02d}.trc'))
@@ -2248,9 +2282,16 @@ def process_fun(config_dict, video_file, time_range, frame_rate, result_dir):
2248
2282
  logging.info(f'Angles saved to {angles_path_person.resolve()}.')
2249
2283
 
2250
2284
  # Plotting angles before and after interpolation and filtering
2251
- if show_plots:
2252
- all_frames_angles_person.insert(0, 'time', all_frames_time)
2253
- angle_plots(all_frames_angles_person, angle_data, i) # i = current person
2285
+ all_frames_angles_person.insert(0, 'time', all_frames_time)
2286
+ if save_plots and (show_plots or save_plots):
2287
+ pw = angle_plots(all_frames_angles_person, angle_data, i, show=show_plots) # i = current person
2288
+ for n, f in enumerate(pw.figure_handles):
2289
+ dpi = pw.canvases[i].figure.dpi
2290
+ f.set_size_inches(1280/dpi, 720/dpi)
2291
+ title = pw.tabs.tabText(n)
2292
+ plot_path = plots_output_dir / (pose_output_path_m.stem + f'_person{i:02d}_ang_{title.replace(" ","_").replace("/","_")}.png')
2293
+ f.savefig(plot_path, dpi=dpi, bbox_inches='tight')
2294
+ logging.info(f'Pose plots (m) saved in {plots_output_dir}.')
2254
2295
 
2255
2296
 
2256
2297
  #%% ==================================================
@@ -46,10 +46,10 @@ dependencies = [
46
46
  "c3d",
47
47
  "rtmlib",
48
48
  "openvino",
49
- "opencv-python<4.12", # or forces numpy>=2.0, which is incompatible with some opensim/python combinations
49
+ "opencv-python<4.12", # otherwise forces numpy>=2.0, which is incompatible with some opensim/python combinations
50
50
  "imageio_ffmpeg",
51
51
  "deep-sort-realtime",
52
- "Pose2Sim>=0.10.33"
52
+ "Pose2Sim>=0.10.36"
53
53
  ]
54
54
 
55
55
  [tool.setuptools_scm]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sports2d
3
- Version: 0.8.19
3
+ Version: 0.8.20
4
4
  Summary: Compute 2D human pose and angles from a video or a webcam.
5
5
  Author-email: David Pagnon <contact@david-pagnon.com>
6
6
  Maintainer-email: David Pagnon <contact@david-pagnon.com>
@@ -38,7 +38,7 @@ Requires-Dist: openvino
38
38
  Requires-Dist: opencv-python<4.12
39
39
  Requires-Dist: imageio_ffmpeg
40
40
  Requires-Dist: deep-sort-realtime
41
- Requires-Dist: Pose2Sim>=0.10.33
41
+ Requires-Dist: Pose2Sim>=0.10.36
42
42
  Dynamic: license-file
43
43
 
44
44
 
@@ -14,4 +14,4 @@ openvino
14
14
  opencv-python<4.12
15
15
  imageio_ffmpeg
16
16
  deep-sort-realtime
17
- Pose2Sim>=0.10.33
17
+ Pose2Sim>=0.10.36
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes