sports2d 0.8.13__tar.gz → 0.8.15__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 (36) hide show
  1. {sports2d-0.8.13 → sports2d-0.8.15}/PKG-INFO +14 -3
  2. {sports2d-0.8.13 → sports2d-0.8.15}/README.md +13 -2
  3. {sports2d-0.8.13 → sports2d-0.8.15}/Sports2D/Demo/Config_demo.toml +2 -1
  4. {sports2d-0.8.13 → sports2d-0.8.15}/Sports2D/Sports2D.py +2 -0
  5. {sports2d-0.8.13 → sports2d-0.8.15}/Sports2D/process.py +45 -16
  6. {sports2d-0.8.13 → sports2d-0.8.15}/sports2d.egg-info/PKG-INFO +14 -3
  7. {sports2d-0.8.13 → sports2d-0.8.15}/.github/workflows/continuous-integration.yml +0 -0
  8. {sports2d-0.8.13 → sports2d-0.8.15}/.github/workflows/joss_pdf.yml +0 -0
  9. {sports2d-0.8.13 → sports2d-0.8.15}/.github/workflows/publish-on-release.yml +0 -0
  10. {sports2d-0.8.13 → sports2d-0.8.15}/.gitignore +0 -0
  11. {sports2d-0.8.13 → sports2d-0.8.15}/CITATION.cff +0 -0
  12. {sports2d-0.8.13 → sports2d-0.8.15}/Content/Demo_plots.png +0 -0
  13. {sports2d-0.8.13 → sports2d-0.8.15}/Content/Demo_results.png +0 -0
  14. {sports2d-0.8.13 → sports2d-0.8.15}/Content/Demo_terminal.png +0 -0
  15. {sports2d-0.8.13 → sports2d-0.8.15}/Content/Person_selection.png +0 -0
  16. {sports2d-0.8.13 → sports2d-0.8.15}/Content/Video_tuto_Sports2D_Colab.png +0 -0
  17. {sports2d-0.8.13 → sports2d-0.8.15}/Content/joint_convention.png +0 -0
  18. {sports2d-0.8.13 → sports2d-0.8.15}/Content/paper.bib +0 -0
  19. {sports2d-0.8.13 → sports2d-0.8.15}/Content/paper.md +0 -0
  20. {sports2d-0.8.13 → sports2d-0.8.15}/Content/sports2d_blender.gif +0 -0
  21. {sports2d-0.8.13 → sports2d-0.8.15}/Content/sports2d_opensim.gif +0 -0
  22. {sports2d-0.8.13 → sports2d-0.8.15}/LICENSE +0 -0
  23. {sports2d-0.8.13 → sports2d-0.8.15}/Sports2D/Demo/demo.mp4 +0 -0
  24. {sports2d-0.8.13 → sports2d-0.8.15}/Sports2D/Sports2D.ipynb +0 -0
  25. {sports2d-0.8.13 → sports2d-0.8.15}/Sports2D/Utilities/__init__.py +0 -0
  26. {sports2d-0.8.13 → sports2d-0.8.15}/Sports2D/Utilities/common.py +0 -0
  27. {sports2d-0.8.13 → sports2d-0.8.15}/Sports2D/Utilities/filter.py +0 -0
  28. {sports2d-0.8.13 → sports2d-0.8.15}/Sports2D/Utilities/tests.py +0 -0
  29. {sports2d-0.8.13 → sports2d-0.8.15}/Sports2D/__init__.py +0 -0
  30. {sports2d-0.8.13 → sports2d-0.8.15}/pyproject.toml +0 -0
  31. {sports2d-0.8.13 → sports2d-0.8.15}/setup.cfg +0 -0
  32. {sports2d-0.8.13 → sports2d-0.8.15}/sports2d.egg-info/SOURCES.txt +0 -0
  33. {sports2d-0.8.13 → sports2d-0.8.15}/sports2d.egg-info/dependency_links.txt +0 -0
  34. {sports2d-0.8.13 → sports2d-0.8.15}/sports2d.egg-info/entry_points.txt +0 -0
  35. {sports2d-0.8.13 → sports2d-0.8.15}/sports2d.egg-info/requires.txt +0 -0
  36. {sports2d-0.8.13 → sports2d-0.8.15}/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.13
3
+ Version: 0.8.15
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>
@@ -402,6 +402,17 @@ sports2d --video_input demo.mp4 other_video.mp4 --time_range 1.2 2.7 0 3.5
402
402
  ```cmd
403
403
  sports2d --calculate_angles false
404
404
  ```
405
+ - Flip angles when the person faces the other side.\
406
+ **N.B.:** *We consider that the person looks to the right if their toe keypoint is to the right of their heel. This is not always true when the person is sprinting, especially in the swing phase. Set it to false if you want timeseries to be continuous even when the participant switches their stance.*
407
+ ```cmd
408
+ sports2d --flip_left_right true # Default
409
+ ```
410
+ - Correct segment angles according to the estimated camera tild angle.\
411
+ **N.B.:** *The camera tilt angle is automatically estimated. Set to false if it is actually the floor which is tilted rather than the camera.*
412
+ ```cmd
413
+ sports2d --correct_segment_angles_with_floor_angle true # Default
414
+ ```
415
+
405
416
  - To run **inverse kinematics with OpenSim**, check [this section](#run-inverse-kinematics)
406
417
 
407
418
  <br>
@@ -505,8 +516,8 @@ sports2d --help
505
516
  'large_hip_knee_angles': ["", "Hip and knee angles below this value are considered as imprecise. Defaults to 45"],
506
517
  'trimmed_extrema_percent': ["", "Proportion of the most extreme segment values to remove before calculating their mean. Defaults to 50"],
507
518
  'fontSize': ["", "font size for angle values. 0.3 if not specified"],
508
- 'flip_left_right': ["", "true or false. Flips angles when the person faces the other side. The person looks to the right if their toe keypoint is to the right of their heel. Set it to false if the person is sprinting or if you want timeseries to be continuous even when the participent switches their stance. true if not specified"],
509
- '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"],
519
+ 'flip_left_right': ["", "true or false. Flips angles when the person faces the other side. The person looks to the right if their toe keypoint is to the right of their heel. Set it to false if the person is sprinting or if you want timeseries to be continuous even when the participant switches their stance. true if not specified"],
520
+ '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 if it is actually the floor which is tilted, not the camera. True if not specified"],
510
521
  'interpolate': ["", "interpolate missing data. true if not specified"],
511
522
  'interp_gap_smaller_than': ["", "interpolate sequences of missing data if they are less than N frames long. 10 if not specified"],
512
523
  'fill_large_gaps_with': ["", "last_value, nan, or zeros. last_value if not specified"],
@@ -359,6 +359,17 @@ sports2d --video_input demo.mp4 other_video.mp4 --time_range 1.2 2.7 0 3.5
359
359
  ```cmd
360
360
  sports2d --calculate_angles false
361
361
  ```
362
+ - Flip angles when the person faces the other side.\
363
+ **N.B.:** *We consider that the person looks to the right if their toe keypoint is to the right of their heel. This is not always true when the person is sprinting, especially in the swing phase. Set it to false if you want timeseries to be continuous even when the participant switches their stance.*
364
+ ```cmd
365
+ sports2d --flip_left_right true # Default
366
+ ```
367
+ - Correct segment angles according to the estimated camera tild angle.\
368
+ **N.B.:** *The camera tilt angle is automatically estimated. Set to false if it is actually the floor which is tilted rather than the camera.*
369
+ ```cmd
370
+ sports2d --correct_segment_angles_with_floor_angle true # Default
371
+ ```
372
+
362
373
  - To run **inverse kinematics with OpenSim**, check [this section](#run-inverse-kinematics)
363
374
 
364
375
  <br>
@@ -462,8 +473,8 @@ sports2d --help
462
473
  'large_hip_knee_angles': ["", "Hip and knee angles below this value are considered as imprecise. Defaults to 45"],
463
474
  'trimmed_extrema_percent': ["", "Proportion of the most extreme segment values to remove before calculating their mean. Defaults to 50"],
464
475
  'fontSize': ["", "font size for angle values. 0.3 if not specified"],
465
- 'flip_left_right': ["", "true or false. Flips angles when the person faces the other side. The person looks to the right if their toe keypoint is to the right of their heel. Set it to false if the person is sprinting or if you want timeseries to be continuous even when the participent switches their stance. true if not specified"],
466
- '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"],
476
+ 'flip_left_right': ["", "true or false. Flips angles when the person faces the other side. The person looks to the right if their toe keypoint is to the right of their heel. Set it to false if the person is sprinting or if you want timeseries to be continuous even when the participant switches their stance. true if not specified"],
477
+ '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 if it is actually the floor which is tilted, not the camera. True if not specified"],
467
478
  'interpolate': ["", "interpolate missing data. true if not specified"],
468
479
  'interp_gap_smaller_than': ["", "interpolate sequences of missing data if they are less than N frames long. 10 if not specified"],
469
480
  'fill_large_gaps_with': ["", "last_value, nan, or zeros. last_value if not specified"],
@@ -133,7 +133,8 @@ correct_segment_angles_with_floor_angle = true # If the camera is tilted, correc
133
133
  interpolate = true
134
134
  interp_gap_smaller_than = 10 # do not interpolate bigger gaps
135
135
  fill_large_gaps_with = 'last_value' # 'last_value', 'nan', or 'zeros'
136
-
136
+ sections_to_keep = 'all' # 'all', 'largest', 'first', 'last'
137
+ # keep 'all' valid sections even when they are interspersed with undetected chunks, or the 'largest' valid section, or the 'first' one, or the 'last' one
137
138
  filter = true
138
139
  show_graphs = true # Show plots of raw and processed results
139
140
  filter_type = 'butterworth' # butterworth, gaussian, LOESS, median
@@ -232,6 +232,7 @@ DEFAULT_CONFIG = {'base': {'video_input': ['demo.mp4'],
232
232
  'post-processing': {'interpolate': True,
233
233
  'interp_gap_smaller_than': 10,
234
234
  'fill_large_gaps_with': 'last_value',
235
+ 'sections_to_keep':'all',
235
236
  'filter': True,
236
237
  'show_graphs': True,
237
238
  'filter_type': 'butterworth',
@@ -318,6 +319,7 @@ CONFIG_HELP = {'config': ["C", "path to a toml configuration file"],
318
319
  'interpolate': ["", "interpolate missing data. true if not specified"],
319
320
  'interp_gap_smaller_than': ["", "interpolate sequences of missing data if they are less than N frames long. 10 if not specified"],
320
321
  'fill_large_gaps_with': ["", "last_value, nan, or zeros. last_value if not specified"],
322
+ 'sections_to_keep': ["", "all, largest, first, or last. Keep 'all' valid sections even when they are interspersed with undetected chunks, or the 'largest' valid section, or the 'first' one, or the 'last' one"],
321
323
  'filter': ["", "filter results. true if not specified"],
322
324
  'filter_type': ["", "butterworth, gaussian, median, or loess. butterworth if not specified"],
323
325
  'order': ["", "order of the Butterworth filter. 4 if not specified"],
@@ -1074,6 +1074,7 @@ def select_persons_on_vid(frames, all_pose_coords):
1074
1074
  bbox=dict(facecolor=UNSELECTED_COLOR, edgecolor=LINE_UNSELECTED_COLOR, boxstyle='square,pad=0.3', path_effects=[patheffects.withSimplePatchShadow()]), zorder=3
1075
1075
  )
1076
1076
  rects.append(rect)
1077
+ annotations.append(annotation)
1077
1078
  img_plot = ax_video.imshow(frame_rgb)
1078
1079
 
1079
1080
  # Slider
@@ -1432,6 +1433,7 @@ def process_fun(config_dict, video_file, time_range, frame_rate, result_dir):
1432
1433
  interpolate = config_dict.get('post-processing').get('interpolate')
1433
1434
  interp_gap_smaller_than = config_dict.get('post-processing').get('interp_gap_smaller_than')
1434
1435
  fill_large_gaps_with = config_dict.get('post-processing').get('fill_large_gaps_with')
1436
+ sections_to_keep = config_dict.get('post-processing').get('sections_to_keep')
1435
1437
 
1436
1438
  do_filter = config_dict.get('post-processing').get('filter')
1437
1439
  show_plots = config_dict.get('post-processing').get('show_graphs')
@@ -1511,9 +1513,13 @@ def process_fun(config_dict, video_file, time_range, frame_rate, result_dir):
1511
1513
  frame_range = [int((time_range[0]-start_time) * frame_rate), int((time_range[1]-start_time) * frame_rate)] if time_range else [0, int(cap.get(cv2.CAP_PROP_FRAME_COUNT))]
1512
1514
  frame_iterator = tqdm(range(*frame_range)) # use a progress bar
1513
1515
  if show_realtime_results:
1514
- cv2.namedWindow(f'{video_file} Sports2D', cv2.WINDOW_NORMAL + cv2.WINDOW_KEEPRATIO)
1515
- cv2.setWindowProperty(f'{video_file} Sports2D', cv2.WND_PROP_ASPECT_RATIO, cv2.WINDOW_FULLSCREEN)
1516
-
1516
+ try:
1517
+ screen_width, screen_height = get_screen_size()
1518
+ display_width, display_height = calculate_display_size(cam_width, cam_height, screen_width, screen_height, margin=50)
1519
+ cv2.namedWindow(f'{video_file} Sports2D', cv2.WINDOW_NORMAL)
1520
+ cv2.resizeWindow(f'{video_file} Sports2D', display_width, display_height)
1521
+ except: # if Pose2Sim < v0.10.29
1522
+ cv2.namedWindow(f'{video_file} Sports2D', cv2.WINDOW_NORMAL + cv2.WINDOW_KEEPRATIO)
1517
1523
 
1518
1524
  # Select the appropriate model based on the model_type
1519
1525
  logging.info('\nEstimating pose...')
@@ -1561,9 +1567,9 @@ def process_fun(config_dict, video_file, time_range, frame_rate, result_dir):
1561
1567
  logging.error('Error: Pose estimation failed. Check in Config.toml that pose_model and mode are valid.')
1562
1568
  raise ValueError('Error: Pose estimation failed. Check in Config.toml that pose_model and mode are valid.')
1563
1569
 
1564
- if tracking_mode not in ['deepsort', 'sports2d']:
1565
- logging.warning(f"Tracking mode {tracking_mode} not recognized. Using sports2d method.")
1566
- tracking_mode = 'sports2d'
1570
+ # if tracking_mode not in ['deepsort', 'sports2d']:
1571
+ # logging.warning(f"Tracking mode {tracking_mode} not recognized. Using sports2d method.")
1572
+ # tracking_mode = 'sports2d'
1567
1573
  logging.info(f'Pose tracking set up for "{pose_model_name}" model.')
1568
1574
  logging.info(f'Mode: {mode}.\n')
1569
1575
  logging.info(f'Persons are detected every {det_frequency} frames and tracked inbetween. Tracking is done with {tracking_mode}.')
@@ -1641,11 +1647,13 @@ def process_fun(config_dict, video_file, time_range, frame_rate, result_dir):
1641
1647
  if tracking_mode == 'sports2d':
1642
1648
  if 'prev_keypoints' not in locals(): prev_keypoints = keypoints
1643
1649
  prev_keypoints, keypoints, scores = sort_people_sports2d(prev_keypoints, keypoints, scores=scores)
1650
+ else:
1651
+ pass
1644
1652
 
1645
1653
 
1646
1654
  # Process coordinates and compute angles
1647
1655
  valid_X, valid_Y, valid_scores = [], [], []
1648
- valid_X_flipped, valid_angles = [], []
1656
+ valid_X_flipped, valid_angles_flipped, valid_angles = [], [], []
1649
1657
  for person_idx in range(len(keypoints)):
1650
1658
  if load_trc_px:
1651
1659
  person_X = keypoints[person_idx][:,0]
@@ -1691,7 +1699,17 @@ def process_fun(config_dict, video_file, time_range, frame_rate, result_dir):
1691
1699
  else:
1692
1700
  ang = np.nan
1693
1701
  person_angles.append(ang)
1702
+
1703
+ # flip angles on the left side if flip_left_right false
1704
+ if len(visible_side) <= person_idx:
1705
+ visible_side += ['auto'] # set to 'auto' if list too short
1706
+ if visible_side[person_idx] == 'left' and not flip_left_right:
1707
+ person_angles_flipped = list(-np.array(person_angles))
1708
+ else:
1709
+ person_angles_flipped = person_angles.copy()
1710
+
1694
1711
  valid_angles.append(person_angles)
1712
+ valid_angles_flipped.append(person_angles_flipped)
1695
1713
  valid_X_flipped.append(person_X_flipped)
1696
1714
  valid_X.append(person_X)
1697
1715
  valid_Y.append(person_Y)
@@ -1706,7 +1724,8 @@ def process_fun(config_dict, video_file, time_range, frame_rate, result_dir):
1706
1724
  img = draw_keypts(img, valid_X, valid_Y, valid_scores, cmap_str='RdYlGn')
1707
1725
  img = draw_skel(img, valid_X, valid_Y, pose_model)
1708
1726
  if calculate_angles:
1709
- img = draw_angles(img, valid_X, valid_Y, valid_angles, valid_X_flipped, new_keypoints_ids, new_keypoints_names, angle_names, display_angle_values_on=display_angle_values_on, colors=colors, fontSize=fontSize, thickness=thickness)
1727
+
1728
+ img = draw_angles(img, valid_X, valid_Y, valid_angles_flipped, valid_X_flipped, new_keypoints_ids, new_keypoints_names, angle_names, display_angle_values_on=display_angle_values_on, colors=colors, fontSize=fontSize, thickness=thickness)
1710
1729
  cv2.imshow(f'{video_file} Sports2D', img)
1711
1730
  if (cv2.waitKey(1) & 0xFF) == ord('q') or (cv2.waitKey(1) & 0xFF) == 27:
1712
1731
  break
@@ -1829,7 +1848,7 @@ def process_fun(config_dict, video_file, time_range, frame_rate, result_dir):
1829
1848
 
1830
1849
  if fill_large_gaps_with.lower() == 'last_value':
1831
1850
  for col in all_frames_X_person_interp.columns:
1832
- first_run_start, last_run_end = indices_of_first_last_non_nan_chunks(all_frames_Y_person_interp[col])
1851
+ first_run_start, last_run_end = indices_of_first_last_non_nan_chunks(all_frames_Y_person_interp[col], min_chunk_size=interp_gap_smaller_than, chunk_choice_method=sections_to_keep)
1833
1852
  for coord_df in [all_frames_X_person_interp, all_frames_Y_person_interp, all_frames_Z_homog]:
1834
1853
  coord_df.loc[:first_run_start, col] = np.nan
1835
1854
  coord_df.loc[last_run_end:, col] = np.nan
@@ -1928,6 +1947,7 @@ def process_fun(config_dict, video_file, time_range, frame_rate, result_dir):
1928
1947
  f'xy_origin: {xy_origin if not xy_origin=="auto" else f"auto (estimation: {[round(c) for c in xy_origin_estim]})"} px.')
1929
1948
 
1930
1949
  # Coordinates in m
1950
+ new_visible_side = []
1931
1951
  for i in range(len(trc_data)):
1932
1952
  if not np.array(trc_data[i].iloc[:,1:] ==0).all():
1933
1953
  # Automatically determine visible side
@@ -1957,7 +1977,7 @@ def process_fun(config_dict, video_file, time_range, frame_rate, result_dir):
1957
1977
  px_to_m_i = [convert_px_to_meters(trc_data[i][kpt_name], first_person_height, height_px, cx, cy, -floor_angle_estim, visible_side=visible_side_i) for kpt_name in new_keypoints_names]
1958
1978
  trc_data_m_i = pd.concat([all_frames_time.rename('time')]+px_to_m_i, axis=1)
1959
1979
  for c in 3*np.arange(len(trc_data_m_i.columns[3::3]))+1: # only X coordinates
1960
- first_run_start, last_run_end = indices_of_first_last_non_nan_chunks(trc_data_m_i.iloc[:,c])
1980
+ first_run_start, last_run_end = indices_of_first_last_non_nan_chunks(trc_data_m_i.iloc[:,c], min_chunk_size=interp_gap_smaller_than, chunk_choice_method=sections_to_keep)
1961
1981
  trc_data_m_i.iloc[:first_run_start,c+2] = np.nan
1962
1982
  trc_data_m_i.iloc[last_run_end:,c+2] = np.nan
1963
1983
  trc_data_m_i.iloc[first_run_start:last_run_end,c+2] = trc_data_m_i.iloc[first_run_start:last_run_end,c+2].ffill().bfill()
@@ -1976,7 +1996,9 @@ def process_fun(config_dict, video_file, time_range, frame_rate, result_dir):
1976
1996
  if make_c3d:
1977
1997
  c3d_path = convert_to_c3d(str(pose_path_person_m_i))
1978
1998
  logging.info(f'Pose in meters saved to {pose_path_person_m_i.resolve()}. {"Also saved in c3d format." if make_c3d else ""}')
1979
-
1999
+ else:
2000
+ visible_side_i = 'none'
2001
+ new_visible_side += [visible_side_i]
1980
2002
 
1981
2003
 
1982
2004
 
@@ -2032,13 +2054,20 @@ def process_fun(config_dict, video_file, time_range, frame_rate, result_dir):
2032
2054
  for i in range(all_frames_angles_homog.shape[1]): # for each person
2033
2055
  for j in range(all_frames_angles_homog.shape[2]): # for each angle
2034
2056
  valid_mask = ~np.isnan(all_frames_angles_homog[:, i, j])
2035
- all_frames_angles_homog[valid_mask, i, j] = np.unwrap(all_frames_angles_homog[valid_mask, i, j], period=180)
2036
-
2057
+ ang = np.unwrap(all_frames_angles_homog[valid_mask, i, j], period=180)
2058
+ ang = ang-360 if ang.mean()> 180 else ang
2059
+ ang = ang+360 if ang.mean()<-180 else ang
2060
+ all_frames_angles_homog[valid_mask, i, j] = ang
2061
+
2037
2062
  # Process angles for each person
2038
2063
  for i, idx_person in enumerate(selected_persons):
2039
2064
  angles_path_person = angles_output_path.parent / (angles_output_path.stem + f'_person{i:02d}.mot')
2040
2065
  all_frames_angles_person = pd.DataFrame(all_frames_angles_homog[:,idx_person,:], columns=angle_names)
2041
2066
 
2067
+ # Flip angles for left side when flip_left_right false
2068
+ if new_visible_side[i] == 'left' and not flip_left_right:
2069
+ all_frames_angles_homog[:, idx_person, :] = -all_frames_angles_homog[:, idx_person, :]
2070
+
2042
2071
  # Delete person if less than 4 valid frames
2043
2072
  angle_nan_count = len(np.where(all_frames_angles_person.sum(axis=1)==0)[0])
2044
2073
  if frame_count - frame_range[0] - angle_nan_count <= 4:
@@ -2055,7 +2084,7 @@ def process_fun(config_dict, video_file, time_range, frame_rate, result_dir):
2055
2084
  all_frames_angles_person_interp = all_frames_angles_person.apply(interpolate_zeros_nans, axis=0, args = [interp_gap_smaller_than, 'linear'])
2056
2085
  if fill_large_gaps_with == 'last_value':
2057
2086
  for col in all_frames_angles_person_interp.columns:
2058
- first_run_start, last_run_end = indices_of_first_last_non_nan_chunks(all_frames_angles_person_interp[col])
2087
+ first_run_start, last_run_end = indices_of_first_last_non_nan_chunks(all_frames_angles_person_interp[col], min_chunk_size=interp_gap_smaller_than, chunk_choice_method=sections_to_keep)
2059
2088
  all_frames_angles_person_interp.loc[:first_run_start, col] = np.nan
2060
2089
  all_frames_angles_person_interp.loc[last_run_end:, col] = np.nan
2061
2090
  all_frames_angles_person_interp.loc[first_run_start:last_run_end, col] = all_frames_angles_person_interp.loc[first_run_start:last_run_end, col].ffill().bfill()
@@ -2152,7 +2181,7 @@ def process_fun(config_dict, video_file, time_range, frame_rate, result_dir):
2152
2181
  if save_vid:
2153
2182
  out_vid.write(img)
2154
2183
  if save_img:
2155
- cv2.imwrite(str((img_output_dir / f'{output_dir_name}_{(frame_count-1):06d}.png')), img)
2184
+ cv2.imwrite(str((img_output_dir / f'{output_dir_name}_{(frame_count):06d}.png')), img)
2156
2185
 
2157
2186
  if save_vid:
2158
2187
  out_vid.release()
@@ -2204,7 +2233,7 @@ def process_fun(config_dict, video_file, time_range, frame_rate, result_dir):
2204
2233
  # masses.append(DEFAULT_MASS)
2205
2234
  logging.info(f'Less than 4 valid frames. Deleting person.')
2206
2235
  else:
2207
- if visible_side[i] == 'none':
2236
+ if new_visible_side[i] == 'none':
2208
2237
  logging.info(f'Skipping marker augmentation and inverse kinematics because visible_side is "none".')
2209
2238
  # heights_m.append(DEFAULT_HEIGHT)
2210
2239
  # masses.append(DEFAULT_MASS)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sports2d
3
- Version: 0.8.13
3
+ Version: 0.8.15
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>
@@ -402,6 +402,17 @@ sports2d --video_input demo.mp4 other_video.mp4 --time_range 1.2 2.7 0 3.5
402
402
  ```cmd
403
403
  sports2d --calculate_angles false
404
404
  ```
405
+ - Flip angles when the person faces the other side.\
406
+ **N.B.:** *We consider that the person looks to the right if their toe keypoint is to the right of their heel. This is not always true when the person is sprinting, especially in the swing phase. Set it to false if you want timeseries to be continuous even when the participant switches their stance.*
407
+ ```cmd
408
+ sports2d --flip_left_right true # Default
409
+ ```
410
+ - Correct segment angles according to the estimated camera tild angle.\
411
+ **N.B.:** *The camera tilt angle is automatically estimated. Set to false if it is actually the floor which is tilted rather than the camera.*
412
+ ```cmd
413
+ sports2d --correct_segment_angles_with_floor_angle true # Default
414
+ ```
415
+
405
416
  - To run **inverse kinematics with OpenSim**, check [this section](#run-inverse-kinematics)
406
417
 
407
418
  <br>
@@ -505,8 +516,8 @@ sports2d --help
505
516
  'large_hip_knee_angles': ["", "Hip and knee angles below this value are considered as imprecise. Defaults to 45"],
506
517
  'trimmed_extrema_percent': ["", "Proportion of the most extreme segment values to remove before calculating their mean. Defaults to 50"],
507
518
  'fontSize': ["", "font size for angle values. 0.3 if not specified"],
508
- 'flip_left_right': ["", "true or false. Flips angles when the person faces the other side. The person looks to the right if their toe keypoint is to the right of their heel. Set it to false if the person is sprinting or if you want timeseries to be continuous even when the participent switches their stance. true if not specified"],
509
- '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"],
519
+ 'flip_left_right': ["", "true or false. Flips angles when the person faces the other side. The person looks to the right if their toe keypoint is to the right of their heel. Set it to false if the person is sprinting or if you want timeseries to be continuous even when the participant switches their stance. true if not specified"],
520
+ '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 if it is actually the floor which is tilted, not the camera. True if not specified"],
510
521
  'interpolate': ["", "interpolate missing data. true if not specified"],
511
522
  'interp_gap_smaller_than': ["", "interpolate sequences of missing data if they are less than N frames long. 10 if not specified"],
512
523
  'fill_large_gaps_with': ["", "last_value, nan, or zeros. last_value if not specified"],
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes