sports2d 0.8.16__tar.gz → 0.8.17__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.17}/PKG-INFO +12 -4
  2. {sports2d-0.8.16 → sports2d-0.8.17}/README.md +10 -2
  3. {sports2d-0.8.16 → sports2d-0.8.17}/Sports2D/Demo/Config_demo.toml +25 -7
  4. {sports2d-0.8.16 → sports2d-0.8.17}/Sports2D/Sports2D.py +20 -9
  5. {sports2d-0.8.16 → sports2d-0.8.17}/Sports2D/process.py +93 -40
  6. {sports2d-0.8.16 → sports2d-0.8.17}/pyproject.toml +1 -1
  7. {sports2d-0.8.16 → sports2d-0.8.17}/sports2d.egg-info/PKG-INFO +12 -4
  8. {sports2d-0.8.16 → sports2d-0.8.17}/sports2d.egg-info/SOURCES.txt +0 -1
  9. {sports2d-0.8.16 → sports2d-0.8.17}/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.17}/.github/workflows/continuous-integration.yml +0 -0
  12. {sports2d-0.8.16 → sports2d-0.8.17}/.github/workflows/joss_pdf.yml +0 -0
  13. {sports2d-0.8.16 → sports2d-0.8.17}/.github/workflows/publish-on-release.yml +0 -0
  14. {sports2d-0.8.16 → sports2d-0.8.17}/.gitignore +0 -0
  15. {sports2d-0.8.16 → sports2d-0.8.17}/CITATION.cff +0 -0
  16. {sports2d-0.8.16 → sports2d-0.8.17}/Content/Demo_plots.png +0 -0
  17. {sports2d-0.8.16 → sports2d-0.8.17}/Content/Demo_results.png +0 -0
  18. {sports2d-0.8.16 → sports2d-0.8.17}/Content/Demo_terminal.png +0 -0
  19. {sports2d-0.8.16 → sports2d-0.8.17}/Content/Person_selection.png +0 -0
  20. {sports2d-0.8.16 → sports2d-0.8.17}/Content/Video_tuto_Sports2D_Colab.png +0 -0
  21. {sports2d-0.8.16 → sports2d-0.8.17}/Content/joint_convention.png +0 -0
  22. {sports2d-0.8.16 → sports2d-0.8.17}/Content/paper.bib +0 -0
  23. {sports2d-0.8.16 → sports2d-0.8.17}/Content/paper.md +0 -0
  24. {sports2d-0.8.16 → sports2d-0.8.17}/Content/sports2d_blender.gif +0 -0
  25. {sports2d-0.8.16 → sports2d-0.8.17}/Content/sports2d_opensim.gif +0 -0
  26. {sports2d-0.8.16 → sports2d-0.8.17}/LICENSE +0 -0
  27. {sports2d-0.8.16 → sports2d-0.8.17}/Sports2D/Demo/demo.mp4 +0 -0
  28. {sports2d-0.8.16 → sports2d-0.8.17}/Sports2D/Sports2D.ipynb +0 -0
  29. {sports2d-0.8.16 → sports2d-0.8.17}/Sports2D/Utilities/__init__.py +0 -0
  30. {sports2d-0.8.16 → sports2d-0.8.17}/Sports2D/Utilities/common.py +0 -0
  31. {sports2d-0.8.16 → sports2d-0.8.17}/Sports2D/Utilities/tests.py +0 -0
  32. {sports2d-0.8.16 → sports2d-0.8.17}/Sports2D/__init__.py +0 -0
  33. {sports2d-0.8.16 → sports2d-0.8.17}/setup.cfg +0 -0
  34. {sports2d-0.8.16 → sports2d-0.8.17}/sports2d.egg-info/dependency_links.txt +0 -0
  35. {sports2d-0.8.16 → sports2d-0.8.17}/sports2d.egg-info/entry_points.txt +0 -0
  36. {sports2d-0.8.16 → sports2d-0.8.17}/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.17
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. You can reject outliers with a Hampel filter): `--reject_outliers True`. Results are filtered according to the selected filter (among `Butterworth`, `Kalman`, `GCV_spline`, `Gaussian`, `LOESS`, `Median`, or `Butterworth_on_speed`) and their parameters, or not filtered at all if `--filter False`.\
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. You can reject outliers with a Hampel filter): `--reject_outliers True`. Results are filtered according to the selected filter (among `Butterworth`, `Kalman`, `GCV_spline`, `Gaussian`, `LOESS`, `Median`, or `Butterworth_on_speed`) and their parameters, or not filtered at all if `--filter False`.\
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,43 @@ 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, gcv_spline, kalman, gaussian, loess, median, butterworth_on_speed
143
+ # Most intuitive and standard filter in biomechanics
141
144
  [post-processing.butterworth]
142
145
  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
+
148
+ # Used in countless applications, this one is a simplified Kalman filter
149
+ [post-processing.kalman]
150
+ # How much more do you trust triangulation results (measurements), than the assumption of constant acceleration(process)?
151
+ trust_ratio = 500 # = measurement_trust/process_trust ~= process_noise/measurement_noise
152
+ smooth = true # should be true, unless you need real-time filtering
153
+
154
+ # Automatically determines optimal parameters for each point, which is good when some move faster than others (eg fingers vs hips).
155
+ [post-processing.gcv_spline]
156
+ cut_off_frequency = 'auto' # 'auto' or int # If int, behaves like a Butterworth filter. 'auto' is sometimes unstable
157
+ smoothing_factor = 0.1 # >=0, ignored if cut_off_frequency != 'auto'. Biases results towards more smoothing (>1) or more fidelity to data (<1)
158
+
146
159
  [post-processing.loess]
147
160
  nb_values_used = 5 # = fraction of data used * nb frames
161
+ [post-processing.gaussian]
162
+ sigma_kernel = 1 #px
148
163
  [post-processing.median]
149
164
  kernel_size = 3
165
+ [post-processing.butterworth_on_speed]
166
+ order = 4
167
+ cut_off_frequency = 10 # Hz
150
168
 
151
169
 
152
170
  [kinematics]
153
171
  do_ik = false # Do scaling and inverse kinematics?
154
172
  use_augmentation = false # true or false (lowercase) # Set to true if you want to use the model with augmented markers
155
173
  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)
174
+ use_simple_model = false # true or false # IK 10+ times faster, but no muscles or flexible spine, no patella
157
175
  participant_mass = [55.0, 67.0] # kg # defaults to 70 if not provided. No influence on kinematics (motion), only on kinetics (forces)
158
176
  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
177
 
@@ -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', 'smoothing_factor': 0.1},
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"],
325
330
  'order': ["", "order of the Butterworth filter. 4 if not specified"],
326
331
  'cut_off_frequency': ["", "cut-off frequency of the Butterworth filter. 3 if not specified"],
332
+ '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"],
333
+ 'smooth': ["", "dual Kalman smoothing. true if not specified"],
334
+ 'gcv_cut_off_frequency': ["", "cut-off frequency of the GCV spline filter. 'auto' if not specified"],
335
+ '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"],
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,12 @@ 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
+
87
88
 
88
89
  DEFAULT_MASS = 70
89
90
  DEFAULT_HEIGHT = 1.7
@@ -196,7 +197,7 @@ def setup_video(video_file_path, save_vid, vid_output_path):
196
197
 
197
198
  def setup_model_class_mode(pose_model, mode, config_dict={}):
198
199
  '''
199
-
200
+ Set up the pose model class and mode for the pose tracker.
200
201
  '''
201
202
 
202
203
  if pose_model.upper() in ('HALPE_26', 'BODY_WITH_FEET'):
@@ -258,6 +259,10 @@ def setup_model_class_mode(pose_model, mode, config_dict={}):
258
259
  det_class=det_class, det=det, det_input_size=det_input_size,
259
260
  pose_class=pose_class, pose=pose, pose_input_size=pose_input_size)
260
261
  logging.info(f"Using model {model_name} with the following custom parameters: {mode}.")
262
+
263
+ if pose_class == 'RTMO' and model_name != 'COCO_17':
264
+ logging.warning("RTMO currently only supports 'Body' pose_model. Switching to 'Body'.")
265
+ pose_model = eval('COCO_17')
261
266
 
262
267
  except (json.JSONDecodeError, TypeError):
263
268
  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 +1441,20 @@ def process_fun(config_dict, video_file, time_range, frame_rate, result_dir):
1436
1441
  sections_to_keep = config_dict.get('post-processing').get('sections_to_keep')
1437
1442
 
1438
1443
  do_filter = config_dict.get('post-processing').get('filter')
1444
+ reject_outliers = config_dict.get('post-processing').get('reject_outliers', False)
1439
1445
  show_plots = config_dict.get('post-processing').get('show_graphs')
1440
1446
  filter_type = config_dict.get('post-processing').get('filter_type')
1441
1447
  butterworth_filter_order = config_dict.get('post-processing').get('butterworth').get('order')
1442
1448
  butterworth_filter_cutoff = config_dict.get('post-processing').get('butterworth').get('cut_off_frequency')
1449
+ gcv_filter_cutoff = config_dict.get('post-processing').get('gcv_spline').get('cut_off_frequency')
1450
+ gcv_filter_smoothingfactor = config_dict.get('post-processing').get('gcv_spline').get('smoothing_factor')
1451
+ kalman_filter_trust_ratio = config_dict.get('post-processing').get('kalman').get('trust_ratio')
1452
+ kalman_filter_smooth = config_dict.get('post-processing').get('kalman').get('smooth')
1443
1453
  gaussian_filter_kernel = config_dict.get('post-processing').get('gaussian').get('sigma_kernel')
1444
1454
  loess_filter_kernel = config_dict.get('post-processing').get('loess').get('nb_values_used')
1445
1455
  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]
1456
+ butterworthspeed_filter_order = config_dict.get('post-processing').get('butterworth_on_speed').get('order')
1457
+ butterworthspeed_filter_cutoff = config_dict.get('post-processing').get('butterworth_on_speed').get('cut_off_frequency')
1449
1458
 
1450
1459
  # Create output directories
1451
1460
  if video_file == "webcam":
@@ -1476,12 +1485,14 @@ def process_fun(config_dict, video_file, time_range, frame_rate, result_dir):
1476
1485
  trimmed_extrema_percent = config_dict.get('kinematics').get('trimmed_extrema_percent')
1477
1486
  close_to_zero_speed_px = config_dict.get('kinematics').get('close_to_zero_speed_px')
1478
1487
  close_to_zero_speed_m = config_dict.get('kinematics').get('close_to_zero_speed_m')
1479
- if do_ik or use_augmentation:
1488
+ if do_ik or use_augmentation or do_filter:
1480
1489
  try:
1481
1490
  if use_augmentation:
1482
1491
  from Pose2Sim.markerAugmentation import augment_markers_all
1483
1492
  if do_ik:
1484
1493
  from Pose2Sim.kinematics import kinematics_all
1494
+ if do_filter:
1495
+ from Pose2Sim.filtering import filter_all
1485
1496
  except ImportError:
1486
1497
  logging.error("OpenSim package is not installed. Please install it to use inverse kinematics or marker augmentation features (see 'Full install' section of the documentation).")
1487
1498
  raise ImportError("OpenSim package is not installed. Please install it to use inverse kinematics or marker augmentation features (see 'Full install' section of the documentation).")
@@ -1498,6 +1509,23 @@ def process_fun(config_dict, video_file, time_range, frame_rate, result_dir):
1498
1509
  pose3d_dir.mkdir(parents=True, exist_ok=True)
1499
1510
  kinematics_dir = Path(output_dir) / 'kinematics'
1500
1511
  kinematics_dir.mkdir(parents=True, exist_ok=True)
1512
+
1513
+ if do_filter:
1514
+ print(filter_type)
1515
+ Pose2Sim_config_dict['filtering']['reject_outliers'] = reject_outliers
1516
+ Pose2Sim_config_dict['filtering']['filter'] = do_filter
1517
+ Pose2Sim_config_dict['filtering']['type'] = filter_type
1518
+ Pose2Sim_config_dict['filtering']['butterworth']['order'] = butterworth_filter_order
1519
+ Pose2Sim_config_dict['filtering']['butterworth']['cut_off_frequency'] = butterworth_filter_cutoff
1520
+ Pose2Sim_config_dict['filtering']['gcv_spline']['cut_off_frequency'] = gcv_filter_cutoff
1521
+ Pose2Sim_config_dict['filtering']['gcv_spline']['smoothing_factor'] = gcv_filter_smoothingfactor
1522
+ Pose2Sim_config_dict['filtering']['kalman']['trust_ratio'] = kalman_filter_trust_ratio
1523
+ Pose2Sim_config_dict['filtering']['kalman']['smooth'] = kalman_filter_smooth
1524
+ Pose2Sim_config_dict['filtering']['gaussian']['sigma_kernel'] = gaussian_filter_kernel
1525
+ Pose2Sim_config_dict['filtering']['loess']['nb_values_used'] = loess_filter_kernel
1526
+ Pose2Sim_config_dict['filtering']['median']['kernel_size'] = median_filter_kernel
1527
+ Pose2Sim_config_dict['filtering']['butterworth_on_speed']['order'] = butterworthspeed_filter_order
1528
+ Pose2Sim_config_dict['filtering']['butterworth_on_speed']['cut_off_frequency'] = butterworthspeed_filter_cutoff
1501
1529
 
1502
1530
 
1503
1531
  # Set up video capture
@@ -1570,8 +1598,8 @@ def process_fun(config_dict, video_file, time_range, frame_rate, result_dir):
1570
1598
  # if tracking_mode not in ['deepsort', 'sports2d']:
1571
1599
  # logging.warning(f"Tracking mode {tracking_mode} not recognized. Using sports2d method.")
1572
1600
  # tracking_mode = 'sports2d'
1573
- logging.info(f'Pose tracking set up for "{pose_model_name}" model.')
1574
- logging.info(f'Mode: {mode}.\n')
1601
+ # logging.info(f'Pose tracking set up for "{pose_model_name}" model.')
1602
+ # logging.info(f'Mode: {mode}.\n')
1575
1603
  logging.info(f'Persons are detected every {det_frequency} frames and tracked inbetween. Tracking is done with {tracking_mode}.')
1576
1604
  if tracking_mode == 'deepsort': logging.info(f'Deepsort parameters: {deepsort_params}.')
1577
1605
  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 +1621,7 @@ def process_fun(config_dict, video_file, time_range, frame_rate, result_dir):
1593
1621
  ang_params = angle_dict.get(ang_name)
1594
1622
  kpts = ang_params[0]
1595
1623
  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]}.")
1624
+ 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
1625
 
1598
1626
 
1599
1627
  #%% ==================================================
@@ -1724,7 +1752,6 @@ def process_fun(config_dict, video_file, time_range, frame_rate, result_dir):
1724
1752
  img = draw_keypts(img, valid_X, valid_Y, valid_scores, cmap_str='RdYlGn')
1725
1753
  img = draw_skel(img, valid_X, valid_Y, pose_model)
1726
1754
  if calculate_angles:
1727
-
1728
1755
  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
1756
  cv2.imshow(f'{video_file} Sports2D', img)
1730
1757
  if (cv2.waitKey(1) & 0xFF) == ord('q') or (cv2.waitKey(1) & 0xFF) == 27:
@@ -1859,31 +1886,45 @@ def process_fun(config_dict, video_file, time_range, frame_rate, result_dir):
1859
1886
  all_frames_Y_person_interp.replace(np.nan, 0, inplace=True)
1860
1887
 
1861
1888
  # Filter
1862
- if not filter_options[0]:
1889
+ if reject_outliers:
1890
+ logging.info('Rejecting outliers with Hampel filter.')
1891
+ all_frames_X_person_interp = all_frames_X_person_interp.apply(hampel_filter, axis=0, args = [round(7*frame_rate/30), 2])
1892
+ all_frames_Y_person_interp = all_frames_Y_person_interp.apply(hampel_filter, axis=0, args = [round(7*frame_rate/30), 2])
1893
+
1894
+ if not do_filter:
1863
1895
  logging.info(f'No filtering.')
1864
1896
  all_frames_X_person_filt = all_frames_X_person_interp
1865
1897
  all_frames_Y_person_filt = all_frames_Y_person_interp
1866
1898
  else:
1867
- filter_type = filter_options[1]
1868
- if filter_type == 'butterworth':
1869
- cutoff = filter_options[3]
1899
+ if filter_type == ('butterworth' or 'butterworth_on_speed'):
1900
+ cutoff = butterworth_filter_cutoff
1870
1901
  if video_file == 'webcam':
1871
1902
  if cutoff / (fps / 2) >= 1:
1872
1903
  cutoff_old = cutoff
1873
1904
  cutoff = fps/(2+0.001)
1874
1905
  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]}.'
1906
+ butterworth_filter_cutoff = cutoff
1907
+ filt_type = 'Butterworth' if filter_type == 'butterworth' else 'Butterworth on speed'
1908
+ args = f'{filt_type} filter, {butterworth_filter_order}th order, {butterworth_filter_cutoff} Hz.'
1909
+ frame_rate = fps
1910
+ elif filter_type == 'gcv_spline':
1911
+ args = f'GVC Spline filter, which automatically evaluates the best trade-off between smoothness and fidelity to data.'
1912
+ elif filter_type == 'kalman':
1913
+ args = f'Kalman filter, trusting measurement {kalman_filter_trust_ratio} times more than the process matrix.'
1914
+ elif filter_type == 'gaussian':
1915
+ args = f'Gaussian filter, Sigma kernel {gaussian_filter_kernel}.'
1916
+ elif filter_type == 'loess':
1917
+ args = f'LOESS filter, window size of {loess_filter_kernel} frames.'
1918
+ elif filter_type == 'median':
1919
+ args = f'Median filter, kernel of {median_filter_kernel}.'
1920
+ else:
1921
+ logging.error(f"Invalid filter_type: {filter_type}. Must be 'butterworth', 'gcv_spline', 'kalman', 'gaussian', 'loess', or 'median'.")
1922
+ raise ValueError(f"Invalid filter_type: {filter_type}. Must be 'butterworth', 'gcv_spline', 'kalman', 'gaussian', 'loess', or 'median'.")
1923
+
1884
1924
  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)
1925
+ all_frames_X_person_filt = all_frames_X_person_interp.apply(filter1d, axis=0, args = [Pose2Sim_config_dict, filter_type, frame_rate])
1926
+ all_frames_Y_person_filt = all_frames_Y_person_interp.apply(filter1d, axis=0, args = [Pose2Sim_config_dict, filter_type, frame_rate])
1927
+
1887
1928
 
1888
1929
  # Build TRC file
1889
1930
  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 +2135,41 @@ def process_fun(config_dict, video_file, time_range, frame_rate, result_dir):
2094
2135
  all_frames_angles_person_interp.replace(np.nan, 0, inplace=True)
2095
2136
 
2096
2137
  # Filter
2097
- if not filter_options[0]:
2138
+ if reject_outliers:
2139
+ logging.info(f'Rejecting outliers with Hampel filter.')
2140
+ all_frames_angles_person_interp = all_frames_angles_person_interp.apply(hampel_filter, axis=0)
2141
+
2142
+ if not do_filter:
2098
2143
  logging.info(f'No filtering.')
2099
2144
  all_frames_angles_person_filt = all_frames_angles_person_interp
2100
2145
  else:
2101
- filter_type = filter_options[1]
2102
- if filter_type == 'butterworth':
2103
- cutoff = filter_options[3]
2146
+ if filter_type == ('butterworth' or 'butterworth_on_speed'):
2147
+ cutoff = butterworth_filter_cutoff
2104
2148
  if video_file == 'webcam':
2105
2149
  if cutoff / (fps / 2) >= 1:
2106
2150
  cutoff_old = cutoff
2107
2151
  cutoff = fps/(2+0.001)
2108
2152
  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)
2153
+ butterworth_filter_cutoff = cutoff
2154
+ filt_type = 'Butterworth' if filter_type == 'butterworth' else 'Butterworth on speed'
2155
+ args = f'{filt_type} filter, {butterworth_filter_order}th order, {butterworth_filter_cutoff} Hz.'
2156
+ frame_rate = fps
2157
+ elif filter_type == 'gcv_spline':
2158
+ args = f'GVC Spline filter, which automatically evaluates the best trade-off between smoothness and fidelity to data.'
2159
+ elif filter_type == 'kalman':
2160
+ args = f'Kalman filter, trusting measurement {kalman_filter_trust_ratio} times more than the process matrix.'
2161
+ elif filter_type == 'gaussian':
2162
+ args = f'Gaussian filter, Sigma kernel {gaussian_filter_kernel}.'
2163
+ elif filter_type == 'loess':
2164
+ args = f'LOESS filter, window size of {loess_filter_kernel} frames.'
2165
+ elif filter_type == 'median':
2166
+ args = f'Median filter, kernel of {median_filter_kernel}.'
2167
+ else:
2168
+ logging.error(f"Invalid filter_type: {filter_type}. Must be 'butterworth', 'gcv_spline', 'kalman', 'gaussian', 'loess', or 'median'.")
2169
+ raise ValueError(f"Invalid filter_type: {filter_type}. Must be 'butterworth', 'gcv_spline', 'kalman', 'gaussian', 'loess', or 'median'.")
2170
+
2171
+ logging.info(f'Filtering with {args}.')
2172
+ all_frames_angles_person_filt = all_frames_angles_person_interp.apply(filter1d, axis=0, args = [Pose2Sim_config_dict, filter_type, frame_rate])
2120
2173
 
2121
2174
  # Add floor_angle_estim to segment angles
2122
2175
  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.17
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. You can reject outliers with a Hampel filter): `--reject_outliers True`. Results are filtered according to the selected filter (among `Butterworth`, `Kalman`, `GCV_spline`, `Gaussian`, `LOESS`, `Median`, or `Butterworth_on_speed`) and their parameters, or not filtered at all if `--filter False`.\
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