sports2d 0.8.16__tar.gz → 0.8.18__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.16 → sports2d-0.8.18}/PKG-INFO +12 -4
  2. {sports2d-0.8.16 → sports2d-0.8.18}/README.md +10 -2
  3. {sports2d-0.8.16 → sports2d-0.8.18}/Sports2D/Demo/Config_demo.toml +27 -8
  4. {sports2d-0.8.16 → sports2d-0.8.18}/Sports2D/Sports2D.py +21 -10
  5. {sports2d-0.8.16 → sports2d-0.8.18}/Sports2D/process.py +102 -45
  6. {sports2d-0.8.16 → sports2d-0.8.18}/pyproject.toml +1 -1
  7. {sports2d-0.8.16 → sports2d-0.8.18}/sports2d.egg-info/PKG-INFO +12 -4
  8. {sports2d-0.8.16 → sports2d-0.8.18}/sports2d.egg-info/SOURCES.txt +0 -1
  9. {sports2d-0.8.16 → sports2d-0.8.18}/sports2d.egg-info/requires.txt +1 -1
  10. sports2d-0.8.16/Sports2D/Utilities/filter.py +0 -176
  11. {sports2d-0.8.16 → sports2d-0.8.18}/.github/workflows/continuous-integration.yml +0 -0
  12. {sports2d-0.8.16 → sports2d-0.8.18}/.github/workflows/joss_pdf.yml +0 -0
  13. {sports2d-0.8.16 → sports2d-0.8.18}/.github/workflows/publish-on-release.yml +0 -0
  14. {sports2d-0.8.16 → sports2d-0.8.18}/.gitignore +0 -0
  15. {sports2d-0.8.16 → sports2d-0.8.18}/CITATION.cff +0 -0
  16. {sports2d-0.8.16 → sports2d-0.8.18}/Content/Demo_plots.png +0 -0
  17. {sports2d-0.8.16 → sports2d-0.8.18}/Content/Demo_results.png +0 -0
  18. {sports2d-0.8.16 → sports2d-0.8.18}/Content/Demo_terminal.png +0 -0
  19. {sports2d-0.8.16 → sports2d-0.8.18}/Content/Person_selection.png +0 -0
  20. {sports2d-0.8.16 → sports2d-0.8.18}/Content/Video_tuto_Sports2D_Colab.png +0 -0
  21. {sports2d-0.8.16 → sports2d-0.8.18}/Content/joint_convention.png +0 -0
  22. {sports2d-0.8.16 → sports2d-0.8.18}/Content/paper.bib +0 -0
  23. {sports2d-0.8.16 → sports2d-0.8.18}/Content/paper.md +0 -0
  24. {sports2d-0.8.16 → sports2d-0.8.18}/Content/sports2d_blender.gif +0 -0
  25. {sports2d-0.8.16 → sports2d-0.8.18}/Content/sports2d_opensim.gif +0 -0
  26. {sports2d-0.8.16 → sports2d-0.8.18}/LICENSE +0 -0
  27. {sports2d-0.8.16 → sports2d-0.8.18}/Sports2D/Demo/demo.mp4 +0 -0
  28. {sports2d-0.8.16 → sports2d-0.8.18}/Sports2D/Sports2D.ipynb +0 -0
  29. {sports2d-0.8.16 → sports2d-0.8.18}/Sports2D/Utilities/__init__.py +0 -0
  30. {sports2d-0.8.16 → sports2d-0.8.18}/Sports2D/Utilities/common.py +0 -0
  31. {sports2d-0.8.16 → sports2d-0.8.18}/Sports2D/Utilities/tests.py +0 -0
  32. {sports2d-0.8.16 → sports2d-0.8.18}/Sports2D/__init__.py +0 -0
  33. {sports2d-0.8.16 → sports2d-0.8.18}/setup.cfg +0 -0
  34. {sports2d-0.8.16 → sports2d-0.8.18}/sports2d.egg-info/dependency_links.txt +0 -0
  35. {sports2d-0.8.16 → sports2d-0.8.18}/sports2d.egg-info/entry_points.txt +0 -0
  36. {sports2d-0.8.16 → sports2d-0.8.18}/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.16
3
+ Version: 0.8.18
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
39
39
  Requires-Dist: imageio_ffmpeg
40
40
  Requires-Dist: deep-sort-realtime
41
- Requires-Dist: Pose2Sim
41
+ Requires-Dist: Pose2Sim>=0.10.33
42
42
  Dynamic: license-file
43
43
 
44
44
 
@@ -521,13 +521,21 @@ sports2d --help
521
521
  'interpolate': ["", "interpolate missing data. true if not specified"],
522
522
  'interp_gap_smaller_than': ["", "interpolate sequences of missing data if they are less than N frames long. 10 if not specified"],
523
523
  'fill_large_gaps_with': ["", "last_value, nan, or zeros. last_value if not specified"],
524
+ '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"],
525
+ 'reject_outliers': ["", "reject outliers with Hampel filter before other filtering methods. true if not specified"],
524
526
  'filter': ["", "filter results. true if not specified"],
525
- 'filter_type': ["", "butterworth, gaussian, median, or loess. butterworth if not specified"],
527
+ 'filter_type': ["", "butterworth, kalman, gcv_spline, gaussian, median, or loess. butterworth if not specified"],
526
528
  'order': ["", "order of the Butterworth filter. 4 if not specified"],
527
529
  'cut_off_frequency': ["", "cut-off frequency of the Butterworth filter. 3 if not specified"],
530
+ 'trust_ratio': ["", "trust ratio of the Kalman filter: How much more do you trust triangulation results (measurements), than the assumption of constant acceleration(process)? 500 if not specified"],
531
+ 'smooth': ["", "dual Kalman smoothing. true if not specified"],
532
+ 'gcv_cut_off_frequency': ["", "cut-off frequency of the GCV spline filter. 'auto' if not specified"],
533
+ 'smoothing_factor': ["", "smoothing factor of the GCV spline filter (>=0). Ignored if cut_off_frequency != 'auto'. Biases results towards more smoothing (>1) or more fidelity to data (<1). 0.1 if not specified"],
528
534
  'sigma_kernel': ["", "sigma of the gaussian filter. 1 if not specified"],
529
535
  'nb_values_used': ["", "number of values used for the loess filter. 5 if not specified"],
530
536
  'kernel_size': ["", "kernel size of the median filter. 3 if not specified"],
537
+ 'butterspeed_order': ["", "order of the Butterworth filter on speed. 4 if not specified"],
538
+ 'butterspeed_cut_off_frequency': ["", "cut-off frequency of the Butterworth filter on speed. 6 if not specified"],
531
539
  'osim_setup_path': ["", "path to OpenSim setup. '../OpenSim_setup' if not specified"],
532
540
  'right_left_symmetry': ["", "right left symmetry. true if not specified"],
533
541
  'default_height': ["", "default height for scaling. 1.70 if not specified"],
@@ -644,7 +652,7 @@ Sports2D:
644
652
  Draws the skeleton and the keypoints, with a green to red color scale to account for their confidence\
645
653
  Draws joint and segment angles on the body, and writes the values either near the joint/segment, or on the upper-left of the image with a progress bar
646
654
 
647
- 6. **Interpolates and filters results:** Missing pose and angle sequences are interpolated unless gaps are too large. Results are filtered according to the selected filter (among `Butterworth`, `Gaussian`, `LOESS`, or `Median`) and their parameters
655
+ 6. **Interpolates and filters results:** Missing pose and angle sequences are interpolated unless gaps are too large. Outliers are rejected with a Hampel filter. Results are filtered with a 6 Hz Butterworth filter. Many other filters are available, and all of the above can be configured or deactivated (see [Config_Demo.toml](https://github.com/davidpagnon/Sports2D/blob/main/Sports2D/Demo/Config_demo.toml))
648
656
 
649
657
  7. **Optionally show** processed images, saves them, or saves them as a video\
650
658
  **Optionally plots** pose and angle data before and after processing for comparison\
@@ -478,13 +478,21 @@ sports2d --help
478
478
  'interpolate': ["", "interpolate missing data. true if not specified"],
479
479
  'interp_gap_smaller_than': ["", "interpolate sequences of missing data if they are less than N frames long. 10 if not specified"],
480
480
  'fill_large_gaps_with': ["", "last_value, nan, or zeros. last_value if not specified"],
481
+ '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"],
482
+ 'reject_outliers': ["", "reject outliers with Hampel filter before other filtering methods. true if not specified"],
481
483
  'filter': ["", "filter results. true if not specified"],
482
- 'filter_type': ["", "butterworth, gaussian, median, or loess. butterworth if not specified"],
484
+ 'filter_type': ["", "butterworth, kalman, gcv_spline, gaussian, median, or loess. butterworth if not specified"],
483
485
  'order': ["", "order of the Butterworth filter. 4 if not specified"],
484
486
  'cut_off_frequency': ["", "cut-off frequency of the Butterworth filter. 3 if not specified"],
487
+ 'trust_ratio': ["", "trust ratio of the Kalman filter: How much more do you trust triangulation results (measurements), than the assumption of constant acceleration(process)? 500 if not specified"],
488
+ 'smooth': ["", "dual Kalman smoothing. true if not specified"],
489
+ 'gcv_cut_off_frequency': ["", "cut-off frequency of the GCV spline filter. 'auto' if not specified"],
490
+ 'smoothing_factor': ["", "smoothing factor of the GCV spline filter (>=0). Ignored if cut_off_frequency != 'auto'. Biases results towards more smoothing (>1) or more fidelity to data (<1). 0.1 if not specified"],
485
491
  'sigma_kernel': ["", "sigma of the gaussian filter. 1 if not specified"],
486
492
  'nb_values_used': ["", "number of values used for the loess filter. 5 if not specified"],
487
493
  'kernel_size': ["", "kernel size of the median filter. 3 if not specified"],
494
+ 'butterspeed_order': ["", "order of the Butterworth filter on speed. 4 if not specified"],
495
+ 'butterspeed_cut_off_frequency': ["", "cut-off frequency of the Butterworth filter on speed. 6 if not specified"],
488
496
  'osim_setup_path': ["", "path to OpenSim setup. '../OpenSim_setup' if not specified"],
489
497
  'right_left_symmetry': ["", "right left symmetry. true if not specified"],
490
498
  'default_height': ["", "default height for scaling. 1.70 if not specified"],
@@ -601,7 +609,7 @@ Sports2D:
601
609
  Draws the skeleton and the keypoints, with a green to red color scale to account for their confidence\
602
610
  Draws joint and segment angles on the body, and writes the values either near the joint/segment, or on the upper-left of the image with a progress bar
603
611
 
604
- 6. **Interpolates and filters results:** Missing pose and angle sequences are interpolated unless gaps are too large. Results are filtered according to the selected filter (among `Butterworth`, `Gaussian`, `LOESS`, or `Median`) and their parameters
612
+ 6. **Interpolates and filters results:** Missing pose and angle sequences are interpolated unless gaps are too large. Outliers are rejected with a Hampel filter. Results are filtered with a 6 Hz Butterworth filter. Many other filters are available, and all of the above can be configured or deactivated (see [Config_Demo.toml](https://github.com/davidpagnon/Sports2D/blob/main/Sports2D/Demo/Config_demo.toml))
605
613
 
606
614
  7. **Optionally show** processed images, saves them, or saves them as a video\
607
615
  **Optionally plots** pose and angle data before and after processing for comparison\
@@ -125,8 +125,8 @@ joint_angles = ['Right ankle', 'Left ankle', 'Right knee', 'Left knee', 'Right h
125
125
  segment_angles = ['Right foot', 'Left foot', 'Right shank', 'Left shank', 'Right thigh', 'Left thigh', 'Pelvis', 'Trunk', 'Shoulders', 'Head', 'Right arm', 'Left arm', 'Right forearm', 'Left forearm']
126
126
 
127
127
  # Processing parameters
128
- flip_left_right = true # Same angles whether the participant faces left/right. Set it to false if you want timeseries to be continuous even when the participent switches their stance.
129
- correct_segment_angles_with_floor_angle = true # If the camera is tilted, corrects segment angles as regards to the floor angle. Set to false is the floor is tilted instead
128
+ flip_left_right = true # Same angles whether the participant faces left/right. Set it to false if you want timeseries to be continuous even when the participant switches their stance.
129
+ correct_segment_angles_with_floor_angle = true # If the camera is tilted, corrects segment angles as regards to the floor angle. Set to false if it is the floor which is actually tilted
130
130
 
131
131
 
132
132
  [post-processing]
@@ -135,25 +135,44 @@ 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
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
138
+ reject_outliers = true # Hampel filter for outlier rejection before other filtering methods. Rejects outliers that are outside of a 95% confidence interal from the median in a sliding window of size 7.
139
+
138
140
  filter = true
139
- show_graphs = true # Show plots of raw and processed results
140
- filter_type = 'butterworth' # butterworth, gaussian, LOESS, median
141
+ show_graphs = true # Show plots of raw and processed results
142
+ filter_type = 'butterworth' # butterworth, kalman, gcv_spline, gaussian, loess, median, butterworth_on_speed
143
+
144
+ # Most intuitive and standard filter in biomechanics
141
145
  [post-processing.butterworth]
142
- order = 4
143
146
  cut_off_frequency = 6 # Hz # Will be divided by slowmo_factor to be equivalent to non slowed-down video
144
- [post-processing.gaussian]
145
- sigma_kernel = 1 #px
147
+ order = 4
148
+
149
+ # Used in countless applications, this one is a simplified Kalman filter
150
+ [post-processing.kalman]
151
+ # How much more do you trust triangulation results (measurements), than the assumption of constant acceleration(process)?
152
+ trust_ratio = 500 # = measurement_trust/process_trust ~= process_noise/measurement_noise
153
+ smooth = true # should be true, unless you need real-time filtering
154
+
155
+ # Automatically determines optimal parameters for each point, which is good when some move faster than others (eg fingers vs hips).
156
+ [post-processing.gcv_spline]
157
+ gcv_cut_off_frequency = 'auto' # 'auto' or int # If int, behaves like a Butterworth filter. 'auto' is usually better, unless the signal is too short (noise can then be considered as signal -> trajectories not filtered)
158
+ gcv_smoothing_factor = 0.1 # >=0, ignored if cut_off_frequency != 'auto'. Biases results towards more smoothing (>1) or more fidelity to data (<1)
159
+
146
160
  [post-processing.loess]
147
161
  nb_values_used = 5 # = fraction of data used * nb frames
162
+ [post-processing.gaussian]
163
+ sigma_kernel = 1 #px
148
164
  [post-processing.median]
149
165
  kernel_size = 3
166
+ [post-processing.butterworth_on_speed]
167
+ order = 4
168
+ cut_off_frequency = 10 # Hz
150
169
 
151
170
 
152
171
  [kinematics]
153
172
  do_ik = false # Do scaling and inverse kinematics?
154
173
  use_augmentation = false # true or false (lowercase) # Set to true if you want to use the model with augmented markers
155
174
  feet_on_floor = false # true or false (lowercase) # Set to false if you want to use the model with feet not on the floor (e.g. running, jumping, etc.)
156
- use_simple_model = false # true or false # >10 times faster IK if true. No muscles, no constraints (eg stiff spine and shoulders, no patella)
175
+ use_simple_model = false # true or false # IK 10+ times faster, but no muscles or flexible spine, no patella
157
176
  participant_mass = [55.0, 67.0] # kg # defaults to 70 if not provided. No influence on kinematics (motion), only on kinetics (forces)
158
177
  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
178
 
@@ -141,7 +141,7 @@ DEFAULT_CONFIG = {'base': {'video_input': ['demo.mp4'],
141
141
  'save_angles': True,
142
142
  'result_dir': ''
143
143
  },
144
- 'pose': {'slowmo_factor': 1,
144
+ 'pose': {'slowmo_factor': 1.0,
145
145
  'pose_model': 'body_with_feet',
146
146
  'mode': 'balanced',
147
147
  'det_frequency': 4,
@@ -233,13 +233,17 @@ DEFAULT_CONFIG = {'base': {'video_input': ['demo.mp4'],
233
233
  'interp_gap_smaller_than': 10,
234
234
  'fill_large_gaps_with': 'last_value',
235
235
  'sections_to_keep':'all',
236
+ 'reject_outliers': True,
236
237
  'filter': True,
237
238
  'show_graphs': True,
238
239
  'filter_type': 'butterworth',
239
- 'butterworth': {'order': 4, 'cut_off_frequency': 6},
240
+ 'butterworth': {'order': 4, 'cut_off_frequency': 6.0},
241
+ 'kalman': {'trust_ratio': 500.0, 'smooth':True},
242
+ 'gcv_spline': {'gcv_cut_off_frequency': 'auto', 'gcv_smoothing_factor': 1.0},
240
243
  'gaussian': {'sigma_kernel': 1},
241
244
  'loess': {'nb_values_used': 5},
242
- 'median': {'kernel_size': 3}
245
+ 'median': {'kernel_size': 3},
246
+ 'butterworth_on_speed': {'butterspeed_order': 4, 'butterspeed_cut_off_frequency': 6.0},
243
247
  },
244
248
  'kinematics':{'do_ik': False,
245
249
  'use_augmentation': False,
@@ -251,9 +255,9 @@ DEFAULT_CONFIG = {'base': {'video_input': ['demo.mp4'],
251
255
  'remove_individual_scaling_setup': True,
252
256
  'remove_individual_ik_setup': True,
253
257
  'fastest_frames_to_remove_percent': 0.1,
254
- 'close_to_zero_speed_px': 50,
258
+ 'close_to_zero_speed_px': 50.0,
255
259
  'close_to_zero_speed_m': 0.2,
256
- 'large_hip_knee_angles': 45,
260
+ 'large_hip_knee_angles': 45.0,
257
261
  'trimmed_extrema_percent': 0.5,
258
262
  'osim_setup_path': '../OpenSim_setup'
259
263
  },
@@ -298,7 +302,7 @@ CONFIG_HELP = {'config': ["C", "path to a toml configuration file"],
298
302
  'do_ik': ["", "do inverse kinematics. false if not specified"],
299
303
  'use_augmentation': ["", "Use LSTM marker augmentation. false if not specified"],
300
304
  'feet_on_floor': ["", "offset marker augmentation results so that feet are at floor level. true if not specified"],
301
- 'use_simple_model': ["", ">10 times faster IK if true. No muscles, no constraints (eg stiff spine and shoulders, no patella). false if not specified"],
305
+ 'use_simple_model': ["", "IK 10+ times faster, but no muscles or flexible spine, no patella. false if not specified"],
302
306
  '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)"],
303
307
  'close_to_zero_speed_m': ["","Sum for all keypoints: about 50 px/frame or 0.2 m/frame"],
304
308
  'tracking_mode': ["", "'sports2d' or 'deepsort'. 'deepsort' is slower, harder to parametrize but can be more robust if correctly tuned"],
@@ -314,19 +318,26 @@ CONFIG_HELP = {'config': ["C", "path to a toml configuration file"],
314
318
  'large_hip_knee_angles': ["", "Hip and knee angles below this value are considered as imprecise. Defaults to 45"],
315
319
  'trimmed_extrema_percent': ["", "Proportion of the most extreme segment values to remove before calculating their mean. Defaults to 50"],
316
320
  'fontSize': ["", "font size for angle values. 0.3 if not specified"],
317
- '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"],
318
- '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"],
321
+ '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"],
322
+ '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"],
319
323
  'interpolate': ["", "interpolate missing data. true if not specified"],
320
324
  'interp_gap_smaller_than': ["", "interpolate sequences of missing data if they are less than N frames long. 10 if not specified"],
321
325
  'fill_large_gaps_with': ["", "last_value, nan, or zeros. last_value if not specified"],
322
326
  '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"],
327
+ 'reject_outliers': ["", "reject outliers with Hampel filter before other filtering methods. true if not specified"],
323
328
  'filter': ["", "filter results. true if not specified"],
324
- 'filter_type': ["", "butterworth, gaussian, median, or loess. butterworth if not specified"],
329
+ 'filter_type': ["", "butterworth, kalman, gcv_spline, gaussian, median, or loess. butterworth if not specified"],
330
+ 'cut_off_frequency': ["", "cut-off frequency of the Butterworth filter. 6 if not specified"],
325
331
  'order': ["", "order of the Butterworth filter. 4 if not specified"],
326
- 'cut_off_frequency': ["", "cut-off frequency of the Butterworth filter. 3 if not specified"],
332
+ 'gcv_cut_off_frequency': ["", "cut-off frequency of the GCV spline filter. 'auto' is usually better, unless the signal is too short (noise can then be considered as signal -> trajectories not filtered). 'auto' if not specified"],
333
+ 'gcv_smoothing_factor': ["", "smoothing factor of the GCV spline filter (>=0). Ignored if cut_off_frequency != 'auto'. Biases results towards more smoothing (>1) or more fidelity to data (<1). 1.0 if not specified"],
334
+ 'trust_ratio': ["", "trust ratio of the Kalman filter: How much more do you trust triangulation results (measurements), than the assumption of constant acceleration(process)? 500 if not specified"],
335
+ 'smooth': ["", "dual Kalman smoothing. true if not specified"],
327
336
  'sigma_kernel': ["", "sigma of the gaussian filter. 1 if not specified"],
328
337
  'nb_values_used': ["", "number of values used for the loess filter. 5 if not specified"],
329
338
  'kernel_size': ["", "kernel size of the median filter. 3 if not specified"],
339
+ 'butterspeed_order': ["", "order of the Butterworth filter on speed. 4 if not specified"],
340
+ 'butterspeed_cut_off_frequency': ["", "cut-off frequency of the Butterworth filter on speed. 6 if not specified"],
330
341
  'osim_setup_path': ["", "path to OpenSim setup. '../OpenSim_setup' if not specified"],
331
342
  'right_left_symmetry': ["", "right left symmetry. true if not specified"],
332
343
  'default_height': ["", "default height for scaling. 1.70 if not specified"],
@@ -79,11 +79,17 @@ from matplotlib import patheffects
79
79
  from rtmlib import PoseTracker, BodyWithFeet, Wholebody, Body, Hand, Custom
80
80
  from deep_sort_realtime.deepsort_tracker import DeepSort
81
81
 
82
- from Sports2D.Utilities import filter
83
82
  from Sports2D.Utilities.common import *
84
83
  from Pose2Sim.common import *
85
84
  from Pose2Sim.skeletons import *
86
85
  from Pose2Sim.triangulation import indices_of_first_last_non_nan_chunks
86
+ from Pose2Sim.filtering import *
87
+
88
+ # Not safe, but to be used until OpenMMLab/RTMlib's SSL certificates are updated
89
+ import ssl
90
+ ssl._create_default_https_context = ssl._create_unverified_context
91
+
92
+
87
93
 
88
94
  DEFAULT_MASS = 70
89
95
  DEFAULT_HEIGHT = 1.7
@@ -196,7 +202,7 @@ def setup_video(video_file_path, save_vid, vid_output_path):
196
202
 
197
203
  def setup_model_class_mode(pose_model, mode, config_dict={}):
198
204
  '''
199
-
205
+ Set up the pose model class and mode for the pose tracker.
200
206
  '''
201
207
 
202
208
  if pose_model.upper() in ('HALPE_26', 'BODY_WITH_FEET'):
@@ -258,6 +264,10 @@ def setup_model_class_mode(pose_model, mode, config_dict={}):
258
264
  det_class=det_class, det=det, det_input_size=det_input_size,
259
265
  pose_class=pose_class, pose=pose, pose_input_size=pose_input_size)
260
266
  logging.info(f"Using model {model_name} with the following custom parameters: {mode}.")
267
+
268
+ if pose_class == 'RTMO' and model_name != 'COCO_17':
269
+ logging.warning("RTMO currently only supports 'Body' pose_model. Switching to 'Body'.")
270
+ pose_model = eval('COCO_17')
261
271
 
262
272
  except (json.JSONDecodeError, TypeError):
263
273
  logging.warning("Invalid mode. Must be 'lightweight', 'balanced', 'performance', or '''{dictionary}''' of parameters within triple quotes. Make sure input_sizes are within square brackets.")
@@ -1436,16 +1446,21 @@ def process_fun(config_dict, video_file, time_range, frame_rate, result_dir):
1436
1446
  sections_to_keep = config_dict.get('post-processing').get('sections_to_keep')
1437
1447
 
1438
1448
  do_filter = config_dict.get('post-processing').get('filter')
1449
+ handle_LR_swap = config_dict.get('post-processing').get('handle_LR_swap', False)
1450
+ reject_outliers = config_dict.get('post-processing').get('reject_outliers', False)
1439
1451
  show_plots = config_dict.get('post-processing').get('show_graphs')
1440
1452
  filter_type = config_dict.get('post-processing').get('filter_type')
1441
- butterworth_filter_order = config_dict.get('post-processing').get('butterworth').get('order')
1442
- butterworth_filter_cutoff = config_dict.get('post-processing').get('butterworth').get('cut_off_frequency')
1443
- gaussian_filter_kernel = config_dict.get('post-processing').get('gaussian').get('sigma_kernel')
1444
- loess_filter_kernel = config_dict.get('post-processing').get('loess').get('nb_values_used')
1445
- median_filter_kernel = config_dict.get('post-processing').get('median').get('kernel_size')
1446
- filter_options = [do_filter, filter_type,
1447
- butterworth_filter_order, butterworth_filter_cutoff, frame_rate,
1448
- gaussian_filter_kernel, loess_filter_kernel, median_filter_kernel]
1453
+ butterworth_filter_order = config_dict.get('post-processing').get('butterworth', {}).get('order')
1454
+ butterworth_filter_cutoff = config_dict.get('post-processing').get('butterworth', {}).get('cut_off_frequency')
1455
+ gcv_filter_cutoff = config_dict.get('post-processing').get('gcv_spline', {}).get('gcv_cut_off_frequency')
1456
+ gcv_smoothing_factor = config_dict.get('post-processing').get('gcv_spline', {}).get('gcv_smoothing_factor')
1457
+ kalman_filter_trust_ratio = config_dict.get('post-processing').get('kalman', {}).get('trust_ratio')
1458
+ kalman_filter_smooth = config_dict.get('post-processing').get('kalman', {}).get('smooth')
1459
+ gaussian_filter_kernel = config_dict.get('post-processing').get('gaussian', {}).get('sigma_kernel')
1460
+ loess_filter_kernel = config_dict.get('post-processing').get('loess', {}).get('nb_values_used')
1461
+ median_filter_kernel = config_dict.get('post-processing').get('median', {}).get('kernel_size')
1462
+ butterworthspeed_filter_order = config_dict.get('post-processing').get('butterworth_on_speed', {}).get('order')
1463
+ butterworthspeed_filter_cutoff = config_dict.get('post-processing').get('butterworth_on_speed', {}).get('cut_off_frequency')
1449
1464
 
1450
1465
  # Create output directories
1451
1466
  if video_file == "webcam":
@@ -1476,7 +1491,7 @@ def process_fun(config_dict, video_file, time_range, frame_rate, result_dir):
1476
1491
  trimmed_extrema_percent = config_dict.get('kinematics').get('trimmed_extrema_percent')
1477
1492
  close_to_zero_speed_px = config_dict.get('kinematics').get('close_to_zero_speed_px')
1478
1493
  close_to_zero_speed_m = config_dict.get('kinematics').get('close_to_zero_speed_m')
1479
- if do_ik or use_augmentation:
1494
+ if do_ik or use_augmentation or do_filter:
1480
1495
  try:
1481
1496
  if use_augmentation:
1482
1497
  from Pose2Sim.markerAugmentation import augment_markers_all
@@ -1498,6 +1513,23 @@ def process_fun(config_dict, video_file, time_range, frame_rate, result_dir):
1498
1513
  pose3d_dir.mkdir(parents=True, exist_ok=True)
1499
1514
  kinematics_dir = Path(output_dir) / 'kinematics'
1500
1515
  kinematics_dir.mkdir(parents=True, exist_ok=True)
1516
+
1517
+ if do_filter:
1518
+ Pose2Sim_config_dict['personAssociation']['handle_LR_swap'] = handle_LR_swap
1519
+ Pose2Sim_config_dict['filtering']['reject_outliers'] = reject_outliers
1520
+ Pose2Sim_config_dict['filtering']['filter'] = do_filter
1521
+ Pose2Sim_config_dict['filtering']['type'] = filter_type
1522
+ Pose2Sim_config_dict['filtering']['gcv_spline']['cut_off_frequency'] = gcv_filter_cutoff
1523
+ Pose2Sim_config_dict['filtering']['gcv_spline']['smoothing_factor'] = gcv_smoothing_factor
1524
+ Pose2Sim_config_dict['filtering']['butterworth']['cut_off_frequency'] = butterworth_filter_cutoff
1525
+ Pose2Sim_config_dict['filtering']['butterworth']['order'] = butterworth_filter_order
1526
+ Pose2Sim_config_dict['filtering']['kalman']['trust_ratio'] = kalman_filter_trust_ratio
1527
+ Pose2Sim_config_dict['filtering']['kalman']['smooth'] = kalman_filter_smooth
1528
+ Pose2Sim_config_dict['filtering']['gaussian']['sigma_kernel'] = gaussian_filter_kernel
1529
+ Pose2Sim_config_dict['filtering']['loess']['nb_values_used'] = loess_filter_kernel
1530
+ Pose2Sim_config_dict['filtering']['median']['kernel_size'] = median_filter_kernel
1531
+ Pose2Sim_config_dict['filtering']['butterworth_on_speed']['order'] = butterworthspeed_filter_order
1532
+ Pose2Sim_config_dict['filtering']['butterworth_on_speed']['cut_off_frequency'] = butterworthspeed_filter_cutoff
1501
1533
 
1502
1534
 
1503
1535
  # Set up video capture
@@ -1570,8 +1602,8 @@ def process_fun(config_dict, video_file, time_range, frame_rate, result_dir):
1570
1602
  # if tracking_mode not in ['deepsort', 'sports2d']:
1571
1603
  # logging.warning(f"Tracking mode {tracking_mode} not recognized. Using sports2d method.")
1572
1604
  # tracking_mode = 'sports2d'
1573
- logging.info(f'Pose tracking set up for "{pose_model_name}" model.')
1574
- logging.info(f'Mode: {mode}.\n')
1605
+ # logging.info(f'Pose tracking set up for "{pose_model_name}" model.')
1606
+ # logging.info(f'Mode: {mode}.\n')
1575
1607
  logging.info(f'Persons are detected every {det_frequency} frames and tracked inbetween. Tracking is done with {tracking_mode}.')
1576
1608
  if tracking_mode == 'deepsort': logging.info(f'Deepsort parameters: {deepsort_params}.')
1577
1609
  logging.info(f'{"All persons are" if nb_persons_to_detect=="all" else f"{nb_persons_to_detect} persons are" if nb_persons_to_detect>1 else "1 person is"} analyzed. Person ordering method is {person_ordering_method}.')
@@ -1593,7 +1625,7 @@ def process_fun(config_dict, video_file, time_range, frame_rate, result_dir):
1593
1625
  ang_params = angle_dict.get(ang_name)
1594
1626
  kpts = ang_params[0]
1595
1627
  if any(item not in keypoints_names+['Neck', 'Hip'] for item in kpts):
1596
- logging.warning(f"Skipping {ang_name} angle computation because at least one of the following keypoints is not provided by the model: {ang_params[0]}.")
1628
+ logging.warning(f"Skipping {ang_name} angle computation because at least one of the following keypoints is not provided by the pose estimation model: {ang_params[0]}.")
1597
1629
 
1598
1630
 
1599
1631
  #%% ==================================================
@@ -1724,7 +1756,6 @@ def process_fun(config_dict, video_file, time_range, frame_rate, result_dir):
1724
1756
  img = draw_keypts(img, valid_X, valid_Y, valid_scores, cmap_str='RdYlGn')
1725
1757
  img = draw_skel(img, valid_X, valid_Y, pose_model)
1726
1758
  if calculate_angles:
1727
-
1728
1759
  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)
1729
1760
  cv2.imshow(f'{video_file} Sports2D', img)
1730
1761
  if (cv2.waitKey(1) & 0xFF) == ord('q') or (cv2.waitKey(1) & 0xFF) == 27:
@@ -1859,31 +1890,45 @@ def process_fun(config_dict, video_file, time_range, frame_rate, result_dir):
1859
1890
  all_frames_Y_person_interp.replace(np.nan, 0, inplace=True)
1860
1891
 
1861
1892
  # Filter
1862
- if not filter_options[0]:
1893
+ if reject_outliers:
1894
+ logging.info('Rejecting outliers with Hampel filter.')
1895
+ all_frames_X_person_interp = all_frames_X_person_interp.apply(hampel_filter, axis=0, args = [round(7*frame_rate/30), 2])
1896
+ all_frames_Y_person_interp = all_frames_Y_person_interp.apply(hampel_filter, axis=0, args = [round(7*frame_rate/30), 2])
1897
+
1898
+ if not do_filter:
1863
1899
  logging.info(f'No filtering.')
1864
1900
  all_frames_X_person_filt = all_frames_X_person_interp
1865
1901
  all_frames_Y_person_filt = all_frames_Y_person_interp
1866
1902
  else:
1867
- filter_type = filter_options[1]
1868
- if filter_type == 'butterworth':
1869
- cutoff = filter_options[3]
1903
+ if filter_type == ('butterworth' or 'butterworth_on_speed'):
1904
+ cutoff = butterworth_filter_cutoff
1870
1905
  if video_file == 'webcam':
1871
1906
  if cutoff / (fps / 2) >= 1:
1872
1907
  cutoff_old = cutoff
1873
1908
  cutoff = fps/(2+0.001)
1874
1909
  args = f'\n{cutoff_old:.1f} Hz cut-off framerate too large for a real-time framerate of {fps:.1f} Hz. Using a cut-off framerate of {cutoff:.1f} Hz instead.'
1875
- filter_options[3] = cutoff
1876
- args = f'Butterworth filter, {filter_options[2]}th order, {filter_options[3]} Hz.'
1877
- filter_options[4] = fps
1878
- if filter_type == 'gaussian':
1879
- args = f'Gaussian filter, Sigma kernel {filter_options[5]}.'
1880
- if filter_type == 'loess':
1881
- args = f'LOESS filter, window size of {filter_options[6]} frames.'
1882
- if filter_type == 'median':
1883
- args = f'Median filter, kernel of {filter_options[7]}.'
1910
+ butterworth_filter_cutoff = cutoff
1911
+ filt_type = 'Butterworth' if filter_type == 'butterworth' else 'Butterworth on speed'
1912
+ args = f'{filt_type} filter, {butterworth_filter_order}th order, {butterworth_filter_cutoff} Hz.'
1913
+ frame_rate = fps
1914
+ elif filter_type == 'gcv_spline':
1915
+ args = f'GVC Spline filter, which automatically evaluates the best trade-off between smoothness and fidelity to data.'
1916
+ elif filter_type == 'kalman':
1917
+ args = f'Kalman filter, trusting measurement {kalman_filter_trust_ratio} times more than the process matrix.'
1918
+ elif filter_type == 'gaussian':
1919
+ args = f'Gaussian filter, Sigma kernel {gaussian_filter_kernel}.'
1920
+ elif filter_type == 'loess':
1921
+ args = f'LOESS filter, window size of {loess_filter_kernel} frames.'
1922
+ elif filter_type == 'median':
1923
+ args = f'Median filter, kernel of {median_filter_kernel}.'
1924
+ else:
1925
+ logging.error(f"Invalid filter_type: {filter_type}. Must be 'butterworth', 'gcv_spline', 'kalman', 'gaussian', 'loess', or 'median'.")
1926
+ raise ValueError(f"Invalid filter_type: {filter_type}. Must be 'butterworth', 'gcv_spline', 'kalman', 'gaussian', 'loess', or 'median'.")
1927
+
1884
1928
  logging.info(f'Filtering with {args}')
1885
- all_frames_X_person_filt = all_frames_X_person_interp.apply(filter.filter1d, axis=0, args=filter_options)
1886
- all_frames_Y_person_filt = all_frames_Y_person_interp.apply(filter.filter1d, axis=0, args=filter_options)
1929
+ all_frames_X_person_filt = all_frames_X_person_interp.apply(filter1d, axis=0, args = [Pose2Sim_config_dict, filter_type, frame_rate])
1930
+ all_frames_Y_person_filt = all_frames_Y_person_interp.apply(filter1d, axis=0, args = [Pose2Sim_config_dict, filter_type, frame_rate])
1931
+
1887
1932
 
1888
1933
  # Build TRC file
1889
1934
  trc_data_i = trc_data_from_XYZtime(all_frames_X_person_filt, all_frames_Y_person_filt, all_frames_Z_homog, all_frames_time)
@@ -2094,29 +2139,41 @@ def process_fun(config_dict, video_file, time_range, frame_rate, result_dir):
2094
2139
  all_frames_angles_person_interp.replace(np.nan, 0, inplace=True)
2095
2140
 
2096
2141
  # Filter
2097
- if not filter_options[0]:
2142
+ if reject_outliers:
2143
+ logging.info(f'Rejecting outliers with Hampel filter.')
2144
+ all_frames_angles_person_interp = all_frames_angles_person_interp.apply(hampel_filter, axis=0)
2145
+
2146
+ if not do_filter:
2098
2147
  logging.info(f'No filtering.')
2099
2148
  all_frames_angles_person_filt = all_frames_angles_person_interp
2100
2149
  else:
2101
- filter_type = filter_options[1]
2102
- if filter_type == 'butterworth':
2103
- cutoff = filter_options[3]
2150
+ if filter_type == ('butterworth' or 'butterworth_on_speed'):
2151
+ cutoff = butterworth_filter_cutoff
2104
2152
  if video_file == 'webcam':
2105
2153
  if cutoff / (fps / 2) >= 1:
2106
2154
  cutoff_old = cutoff
2107
2155
  cutoff = fps/(2+0.001)
2108
2156
  args = f'\n{cutoff_old:.1f} Hz cut-off framerate too large for a real-time framerate of {fps:.1f} Hz. Using a cut-off framerate of {cutoff:.1f} Hz instead.'
2109
- filter_options[3] = cutoff
2110
- args = f'Butterworth filter, {filter_options[2]}th order, {filter_options[3]} Hz.'
2111
- filter_options[4] = fps
2112
- if filter_type == 'gaussian':
2113
- args = f'Gaussian filter, Sigma kernel {filter_options[5]}.'
2114
- if filter_type == 'loess':
2115
- args = f'LOESS filter, window size of {filter_options[6]} frames.'
2116
- if filter_type == 'median':
2117
- args = f'Median filter, kernel of {filter_options[7]}.'
2118
- logging.info(f'Filtering with {args}')
2119
- all_frames_angles_person_filt = all_frames_angles_person_interp.apply(filter.filter1d, axis=0, args=filter_options)
2157
+ butterworth_filter_cutoff = cutoff
2158
+ filt_type = 'Butterworth' if filter_type == 'butterworth' else 'Butterworth on speed'
2159
+ args = f'{filt_type} filter, {butterworth_filter_order}th order, {butterworth_filter_cutoff} Hz.'
2160
+ frame_rate = fps
2161
+ elif filter_type == 'gcv_spline':
2162
+ args = f'GVC Spline filter, which automatically evaluates the best trade-off between smoothness and fidelity to data.'
2163
+ elif filter_type == 'kalman':
2164
+ args = f'Kalman filter, trusting measurement {kalman_filter_trust_ratio} times more than the process matrix.'
2165
+ elif filter_type == 'gaussian':
2166
+ args = f'Gaussian filter, Sigma kernel {gaussian_filter_kernel}.'
2167
+ elif filter_type == 'loess':
2168
+ args = f'LOESS filter, window size of {loess_filter_kernel} frames.'
2169
+ elif filter_type == 'median':
2170
+ args = f'Median filter, kernel of {median_filter_kernel}.'
2171
+ else:
2172
+ logging.error(f"Invalid filter_type: {filter_type}. Must be 'butterworth', 'gcv_spline', 'kalman', 'gaussian', 'loess', or 'median'.")
2173
+ raise ValueError(f"Invalid filter_type: {filter_type}. Must be 'butterworth', 'gcv_spline', 'kalman', 'gaussian', 'loess', or 'median'.")
2174
+
2175
+ logging.info(f'Filtering with {args}.')
2176
+ all_frames_angles_person_filt = all_frames_angles_person_interp.apply(filter1d, axis=0, args = [Pose2Sim_config_dict, filter_type, frame_rate])
2120
2177
 
2121
2178
  # Add floor_angle_estim to segment angles
2122
2179
  if correct_segment_angles_with_floor_angle and to_meters:
@@ -49,7 +49,7 @@ dependencies = [
49
49
  "opencv-python",
50
50
  "imageio_ffmpeg",
51
51
  "deep-sort-realtime",
52
- "Pose2Sim"
52
+ "Pose2Sim>=0.10.33"
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.16
3
+ Version: 0.8.18
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
39
39
  Requires-Dist: imageio_ffmpeg
40
40
  Requires-Dist: deep-sort-realtime
41
- Requires-Dist: Pose2Sim
41
+ Requires-Dist: Pose2Sim>=0.10.33
42
42
  Dynamic: license-file
43
43
 
44
44
 
@@ -521,13 +521,21 @@ sports2d --help
521
521
  'interpolate': ["", "interpolate missing data. true if not specified"],
522
522
  'interp_gap_smaller_than': ["", "interpolate sequences of missing data if they are less than N frames long. 10 if not specified"],
523
523
  'fill_large_gaps_with': ["", "last_value, nan, or zeros. last_value if not specified"],
524
+ '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"],
525
+ 'reject_outliers': ["", "reject outliers with Hampel filter before other filtering methods. true if not specified"],
524
526
  'filter': ["", "filter results. true if not specified"],
525
- 'filter_type': ["", "butterworth, gaussian, median, or loess. butterworth if not specified"],
527
+ 'filter_type': ["", "butterworth, kalman, gcv_spline, gaussian, median, or loess. butterworth if not specified"],
526
528
  'order': ["", "order of the Butterworth filter. 4 if not specified"],
527
529
  'cut_off_frequency': ["", "cut-off frequency of the Butterworth filter. 3 if not specified"],
530
+ 'trust_ratio': ["", "trust ratio of the Kalman filter: How much more do you trust triangulation results (measurements), than the assumption of constant acceleration(process)? 500 if not specified"],
531
+ 'smooth': ["", "dual Kalman smoothing. true if not specified"],
532
+ 'gcv_cut_off_frequency': ["", "cut-off frequency of the GCV spline filter. 'auto' if not specified"],
533
+ 'smoothing_factor': ["", "smoothing factor of the GCV spline filter (>=0). Ignored if cut_off_frequency != 'auto'. Biases results towards more smoothing (>1) or more fidelity to data (<1). 0.1 if not specified"],
528
534
  'sigma_kernel': ["", "sigma of the gaussian filter. 1 if not specified"],
529
535
  'nb_values_used': ["", "number of values used for the loess filter. 5 if not specified"],
530
536
  'kernel_size': ["", "kernel size of the median filter. 3 if not specified"],
537
+ 'butterspeed_order': ["", "order of the Butterworth filter on speed. 4 if not specified"],
538
+ 'butterspeed_cut_off_frequency': ["", "cut-off frequency of the Butterworth filter on speed. 6 if not specified"],
531
539
  'osim_setup_path': ["", "path to OpenSim setup. '../OpenSim_setup' if not specified"],
532
540
  'right_left_symmetry': ["", "right left symmetry. true if not specified"],
533
541
  'default_height': ["", "default height for scaling. 1.70 if not specified"],
@@ -644,7 +652,7 @@ Sports2D:
644
652
  Draws the skeleton and the keypoints, with a green to red color scale to account for their confidence\
645
653
  Draws joint and segment angles on the body, and writes the values either near the joint/segment, or on the upper-left of the image with a progress bar
646
654
 
647
- 6. **Interpolates and filters results:** Missing pose and angle sequences are interpolated unless gaps are too large. Results are filtered according to the selected filter (among `Butterworth`, `Gaussian`, `LOESS`, or `Median`) and their parameters
655
+ 6. **Interpolates and filters results:** Missing pose and angle sequences are interpolated unless gaps are too large. Outliers are rejected with a Hampel filter. Results are filtered with a 6 Hz Butterworth filter. Many other filters are available, and all of the above can be configured or deactivated (see [Config_Demo.toml](https://github.com/davidpagnon/Sports2D/blob/main/Sports2D/Demo/Config_demo.toml))
648
656
 
649
657
  7. **Optionally show** processed images, saves them, or saves them as a video\
650
658
  **Optionally plots** pose and angle data before and after processing for comparison\
@@ -24,7 +24,6 @@ Sports2D/Demo/Config_demo.toml
24
24
  Sports2D/Demo/demo.mp4
25
25
  Sports2D/Utilities/__init__.py
26
26
  Sports2D/Utilities/common.py
27
- Sports2D/Utilities/filter.py
28
27
  Sports2D/Utilities/tests.py
29
28
  sports2d.egg-info/PKG-INFO
30
29
  sports2d.egg-info/SOURCES.txt
@@ -14,4 +14,4 @@ openvino
14
14
  opencv-python
15
15
  imageio_ffmpeg
16
16
  deep-sort-realtime
17
- Pose2Sim
17
+ Pose2Sim>=0.10.33
@@ -1,176 +0,0 @@
1
- #!/usr/bin/env python
2
- # -*- coding: utf-8 -*-
3
-
4
-
5
- '''
6
- ##################################################
7
- ## Filter TRC files ##
8
- ##################################################
9
-
10
- Filters pandans columns or numpy arrays.
11
- Available filters: Butterworth, Gaussian, LOESS, Median.
12
-
13
- Usage:
14
- col_filtered = filter1d(col, *filter_options)
15
- filter_options = (do_filter, filter_type, butterworth_filter_order, butterworth_filter_cutoff, frame_rate, gaussian_filter_kernel, loess_filter_kernel, median_filter_kernel)
16
- bool str int int int int int int
17
-
18
- '''
19
-
20
-
21
- ## INIT
22
- from importlib.metadata import version
23
- import numpy as np
24
- from scipy import signal
25
- from scipy.ndimage import gaussian_filter1d
26
- from statsmodels.nonparametric.smoothers_lowess import lowess
27
-
28
-
29
- ## AUTHORSHIP INFORMATION
30
- __author__ = "David Pagnon"
31
- __copyright__ = "Copyright 2021, Pose2Sim"
32
- __credits__ = ["David Pagnon"]
33
- __license__ = "BSD 3-Clause License"
34
- __version__ = version("sports2d")
35
- __maintainer__ = "David Pagnon"
36
- __email__ = "contact@david-pagnon.com"
37
- __status__ = "Development"
38
-
39
-
40
-
41
- ## FUNCTIONS
42
- def butterworth_filter_1d(col, args):
43
- '''
44
- 1D Zero-phase Butterworth filter (dual pass)
45
- Deals with nans
46
-
47
- INPUT:
48
- - col: numpy array
49
- - order: int
50
- - cutoff: int
51
- - framerate: int
52
-
53
- OUTPUT
54
- - col_filtered: Filtered pandas dataframe column
55
- '''
56
-
57
- order, cutoff, framerate = args
58
-
59
- # Filter
60
- b, a = signal.butter(order/2, cutoff/(framerate/2), 'low', analog = False)
61
- padlen = 3 * max(len(a), len(b))
62
-
63
- # split into sequences of not nans
64
- col_filtered = col.copy()
65
- mask = np.isnan(col_filtered) | col_filtered.eq(0)
66
- falsemask_indices = np.where(~mask)[0]
67
- gaps = np.where(np.diff(falsemask_indices) > 1)[0] + 1
68
- idx_sequences = np.split(falsemask_indices, gaps)
69
- if idx_sequences[0].size > 0:
70
- idx_sequences_to_filter = [seq for seq in idx_sequences if len(seq) > padlen]
71
-
72
- # Filter each of the selected sequences
73
- for seq_f in idx_sequences_to_filter:
74
- col_filtered[seq_f] = signal.filtfilt(b, a, col_filtered[seq_f])
75
-
76
- return col_filtered
77
-
78
-
79
- def gaussian_filter_1d(col, kernel):
80
- '''
81
- 1D Gaussian filter
82
-
83
- INPUT:
84
- - col: numpy array
85
- - kernel: Sigma kernel value (int)
86
-
87
- OUTPUT
88
- - col_filtered: Filtered pandas dataframe column
89
- '''
90
-
91
- col_filtered = gaussian_filter1d(col, kernel)
92
-
93
- return col_filtered
94
-
95
-
96
- def loess_filter_1d(col, kernel):
97
- '''
98
- 1D LOWESS filter (Locally Weighted Scatterplot Smoothing)
99
-
100
- INPUT:
101
- - col: numpy array
102
- - kernel: Kernel value: window length used for smoothing (int)
103
- NB: frac = kernel / frames_number
104
-
105
- OUTPUT
106
- - col_filtered: Filtered pandas dataframe column
107
- '''
108
-
109
- # split into sequences of not nans
110
- col_filtered = col.copy()
111
- mask = np.isnan(col_filtered)
112
- falsemask_indices = np.where(~mask)[0]
113
- gaps = np.where(np.diff(falsemask_indices) > 1)[0] + 1
114
- idx_sequences = np.split(falsemask_indices, gaps)
115
- if idx_sequences[0].size > 0:
116
- idx_sequences_to_filter = [seq for seq in idx_sequences if len(seq) > kernel]
117
-
118
- # Filter each of the selected sequences
119
- for seq_f in idx_sequences_to_filter:
120
- col_filtered[seq_f] = lowess(col_filtered[seq_f], seq_f, is_sorted=True, frac=kernel/len(seq_f), it=0)[:,1]
121
-
122
- return col_filtered
123
-
124
-
125
- def median_filter_1d(col, kernel):
126
- '''
127
- 1D median filter
128
-
129
- INPUT:
130
- - col: numpy array
131
- - kernel: window size (int)
132
-
133
- OUTPUT
134
- - col_filtered: Filtered pandas dataframe column
135
- '''
136
-
137
- col_filtered = signal.medfilt(col, kernel_size=kernel)
138
-
139
- return col_filtered
140
-
141
-
142
- def filter1d(col, *filter_options):
143
- '''
144
- Choose filter type and filter column
145
-
146
- INPUT:
147
- - col: Pandas dataframe column
148
- - filter_options = (do_filter, filter_type, butterworth_filter_order, butterworth_filter_cutoff, frame_rate, gaussian_filter_kernel, loess_filter_kernel, median_filter_kernel)
149
-
150
- OUTPUT
151
- - col_filtered: Filtered pandas dataframe column
152
- '''
153
-
154
- filter_type = filter_options[1]
155
- if filter_type == 'butterworth':
156
- args = (filter_options[2], filter_options[3], filter_options[4])
157
- if filter_type == 'gaussian':
158
- args = (filter_options[5])
159
- if filter_type == 'loess':
160
- args = (filter_options[6])
161
- if filter_type == 'median':
162
- args = (filter_options[7])
163
-
164
- # Choose filter
165
- filter_mapping = {
166
- 'butterworth': butterworth_filter_1d,
167
- 'gaussian': gaussian_filter_1d,
168
- 'loess': loess_filter_1d,
169
- 'median': median_filter_1d
170
- }
171
- filter_fun = filter_mapping[filter_type]
172
-
173
- # Filter column
174
- col_filtered = filter_fun(col, args)
175
-
176
- return col_filtered
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes