From faec842d9e2e609261f23661b6c65245a5414f18 Mon Sep 17 00:00:00 2001 From: davidpagnon Date: Wed, 20 Sep 2023 14:39:40 +0200 Subject: [PATCH] can run with config dict rather than file --- Pose2Sim/Demo/User/Config.toml | 4 ++ Pose2Sim/Pose2Sim.py | 116 ++++++++++++++++++++++++++++++--- Pose2Sim/filtering.py | 4 +- README.md | 51 ++++++++++++++- 4 files changed, 162 insertions(+), 13 deletions(-) diff --git a/Pose2Sim/Demo/User/Config.toml b/Pose2Sim/Demo/User/Config.toml index 3979ab5..1e9dc37 100644 --- a/Pose2Sim/Demo/User/Config.toml +++ b/Pose2Sim/Demo/User/Config.toml @@ -38,6 +38,10 @@ calibration_type = 'convert' # 'convert' or 'calculate' convert_from = 'qualisys' # 'qualisys', 'optitrack', or 'vicon' [calibration.convert.qualisys] binning_factor = 1 # Usually 1, except when filming in 540p where it usually is 2 + [calibration.convert.optitrack] # See readme for instructions + [calibration.convert.vicon] # No parameters needed + [calibration.convert.opencap] # No parameters needed + [calibration.convert.biocv] # No parameters needed [calibration.calculate] diff --git a/Pose2Sim/Pose2Sim.py b/Pose2Sim/Pose2Sim.py index 73256ef..0088ac9 100644 --- a/Pose2Sim/Pose2Sim.py +++ b/Pose2Sim/Pose2Sim.py @@ -88,12 +88,20 @@ def base_params(config_dict): def poseEstimation(config=os.path.join('User', 'Config.toml')): ''' - Estimate pose using BlazePose, OpenPose, AlphaPose, or DeepLabCut + Estimate pose using BlazePose, OpenPose, AlphaPose, or DeepLabCut. + + config can either be a path or a dictionary (for batch processing) ''' - + + raise NotImplementedError('This has not been integrated yet. \nPlease read README.md for further explanation') + + # TODO from Pose2Sim.poseEstimation import pose_estimation_all - config_dict = read_config_file(config) + if type(config)==dict: + config_dict = config + else: + config_dict = read_config_file(config) project_dir, seq_name, frames = base_params(config_dict) logging.info("\n\n---------------------------------------------------------------------") @@ -111,11 +119,16 @@ def poseEstimation(config=os.path.join('User', 'Config.toml')): def calibration(config=os.path.join('User', 'Config.toml')): ''' Cameras calibration from checkerboards or from qualisys files. + + config can either be a path or a dictionary (for batch processing) ''' from Pose2Sim.calibration import calibrate_cams_all - config_dict = read_config_file(config) + if type(config)==dict: + config_dict = config + else: + config_dict = read_config_file(config) project_dir, seq_name, frames = base_params(config_dict) logging.info("\n\n---------------------------------------------------------------------") @@ -132,12 +145,20 @@ def calibration(config=os.path.join('User', 'Config.toml')): def synchronization(config=os.path.join('User', 'Config.toml')): ''' - Synchronize cameras if needed - ''' + Synchronize cameras if needed. + config can either be a path or a dictionary (for batch processing) + ''' + + raise NotImplementedError('This has not been integrated yet. \nPlease read README.md for further explanation') + + #TODO from Pose2Sim.synchronization import synchronize_cams_all - config_dict = read_config_file(config) + if type(config)==dict: + config_dict = config + else: + config_dict = read_config_file(config) project_dir, seq_name, frames = base_params(config_dict) logging.info("\n\n---------------------------------------------------------------------") @@ -156,11 +177,16 @@ def personAssociation(config=os.path.join('User', 'Config.toml')): ''' Tracking of the person of interest in case of multiple persons detection. Needs a calibration file. + + config can either be a path or a dictionary (for batch processing) ''' from Pose2Sim.personAssociation import track_2d_all - config_dict = read_config_file(config) + if type(config)==dict: + config_dict = config + else: + config_dict = read_config_file(config) project_dir, seq_name, frames = base_params(config_dict) logging.info("\n\n---------------------------------------------------------------------") @@ -178,11 +204,16 @@ def personAssociation(config=os.path.join('User', 'Config.toml')): def triangulation(config=os.path.join('User', 'Config.toml')): ''' Robust triangulation of 2D points coordinates. + + config can either be a path or a dictionary (for batch processing) ''' from Pose2Sim.triangulation import triangulate_all - config_dict = read_config_file(config) + if type(config)==dict: + config_dict = config + else: + config_dict = read_config_file(config) project_dir, seq_name, frames = base_params(config_dict) logging.info("\n\n---------------------------------------------------------------------") @@ -200,11 +231,16 @@ def triangulation(config=os.path.join('User', 'Config.toml')): def filtering(config=os.path.join('User', 'Config.toml')): ''' Filter trc 3D coordinates. + + config can either be a path or a dictionary (for batch processing) ''' from Pose2Sim.filtering import filter_all - config_dict = read_config_file(config) + if type(config)==dict: + config_dict = config + else: + config_dict = read_config_file(config) project_dir, seq_name, frames = base_params(config_dict) logging.info("\n\n---------------------------------------------------------------------") @@ -213,3 +249,63 @@ def filtering(config=os.path.join('User', 'Config.toml')): logging.info(f"\nProject directory: {project_dir}") filter_all(config_dict) + + +def scalingModel(config=os.path.join('User', 'Config.toml')): + ''' + Uses OpenSim to scale a model based on a static 3D pose. + + config can either be a path or a dictionary (for batch processing) + ''' + + raise NotImplementedError('This has not been integrated yet. \nPlease read README.md for further explanation') + + # TODO + from Pose2Sim.scalingModel import scale_model_all + + if type(config)==dict: + config_dict = config + else: + config_dict = read_config_file(config) + project_dir, seq_name, frames = base_params(config_dict) + + logging.info("\n\n---------------------------------------------------------------------") + logging.info("Scaling model") + logging.info("---------------------------------------------------------------------") + logging.info(f"\nProject directory: {project_dir}") + start = time.time() + + scale_model_all(config_dict) + + end = time.time() + logging.info(f'Model scaling took {end-start:.2f} s.') + + +def inverseKinematics(config=os.path.join('User', 'Config.toml')): + ''' + Uses OpenSim to perform inverse kinematics. + + config can either be a path or a dictionary (for batch processing) + ''' + + raise NotImplementedError('This has not been integrated yet. \nPlease read README.md for further explanation') + + # TODO + from Pose2Sim.inverseKinematics import inverse_kinematics_all + + if type(config)==dict: + config_dict = config + else: + config_dict = read_config_file(config) + project_dir, seq_name, frames = base_params(config_dict) + + logging.info("\n\n---------------------------------------------------------------------") + logging.info("Inverse kinematics") + logging.info("---------------------------------------------------------------------") + logging.info(f"\nProject directory: {project_dir}") + start = time.time() + + inverse_kinematics_all(config_dict) + + end = time.time() + logging.info(f'Inverse kinematics took {end-start:.2f} s.') \ No newline at end of file diff --git a/Pose2Sim/filtering.py b/Pose2Sim/filtering.py index 2d249ab..c550c17 100644 --- a/Pose2Sim/filtering.py +++ b/Pose2Sim/filtering.py @@ -449,7 +449,7 @@ def filter_all(config): pose_folder_name = config.get('project').get('pose_folder_name') try: pose_tracked_dir = os.path.join(project_dir, pose_tracked_folder_name) - os.path.isdir(pose_tracked_dir) + os.listdir(pose_tracked_dir) pose_dir = pose_tracked_dir except: pose_dir = os.path.join(project_dir, pose_folder_name) @@ -469,7 +469,7 @@ def filter_all(config): # Trc paths trc_f_in = f'{seq_name}_{f_range[0]}-{f_range[1]}.trc' - trc_f_out = f'{seq_name}_filt_{f_range[0]}-{f_range[1]}.trc' + trc_f_out = f'{seq_name}_filt_{filter_type}_{f_range[0]}-{f_range[1]}.trc' trc_path_in = os.path.join(pose3d_dir, trc_f_in) trc_path_out = os.path.join(pose3d_dir, trc_f_out) diff --git a/README.md b/README.md index cc9e31a..39353c4 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,7 @@ If you can only use one single camera and don't mind losing some accuracy, pleas 1. [OpenSim Scaling](#opensim-scaling) 2. [OpenSim Inverse kinematics](#opensim-inverse-kinematics) 3. [Command Line](#command-line) + 7. [Batch processing](#batch-processing) 3. [Utilities](#utilities) 4. [How to cite and how to contribute](#how-to-cite-and-how-to-contribute) 1. [How to cite](#how-to-cite) @@ -659,6 +660,38 @@ Make sure to replace `38` in `py38np120` with your Python version (3.8 in this c + +## Batch processing +If you need to batch process multiple data or with multiple different parameters, you can run any Pose2Sim function with a `config` dictionary instead of a file. For example: +``` +from Pose2Sim import Pose2Sim +import toml + +config_dict = toml.load('User/Config.toml') +config_dict['project']['pose_folder_name'] = new_project_path +Pose2Sim.triangulate(config_dict) +``` +Or into a loop: +``` +from Pose2Sim import Pose2Sim +import toml +config_dict = toml.load('User/Config.toml') + +# Change project_path +for new_project_path in new_project_paths: + config_dict['project']['project_dir'] = new_project_path + config_dict['filtering']['display_figures'] = False + + # Run any Pose2Sim function with config_dict instead of a path + Pose2Sim.triangulation(config_dict) + + # Now change filtering type + for new_filter in ['butterworth', 'kalman', 'gaussian']: + config_dict['filtering']['type'] = new_filter + Pose2Sim.filtering(config_dict) +``` + + # Utilities A list of standalone tools (see [Utilities](https://github.com/perfanalytics/pose2sim/tree/main/Pose2Sim/Utilities)), which can be either run as scripts, or imported as functions. Check usage in the docstrings of each Python file. The figure below shows how some of these toolscan be used to further extend Pose2Sim usage. @@ -793,7 +826,6 @@ If you want to contribute to Pose2Sim, please follow [this guide](https://docs.g > - [x] **Pose:** Support [BlazePose](https://developers.google.com/mediapipe/solutions/vision/pose_landmarker) for faster inference (on mobile device). > - [x] **Pose:** Support [DeepLabCut](http://www.mackenziemathislab.org/deeplabcut) for training on custom datasets. > - [x] **Pose:** Support [AlphaPose](https://github.com/MVIG-SJTU/AlphaPose) as an alternative to OpenPose. -> - [ ] **Pose:** Support [MediaPipe holistic](https://github.com/google/mediapipe/blob/master/docs/solutions/holistic.md) for pronation/supination (converter, skeleton.py, OpenSim model and setup files). > - [ ] **Pose:** Support [MMPose](https://github.com/open-mmlab/mmpose), [SLEAP](https://sleap.ai/), etc.
@@ -870,3 +902,20 @@ If you want to contribute to Pose2Sim, please follow [this guide](https://docs.g > - [ ] **** Run pose estimation and OpenSim from within Pose2Sim > - [ ] **Run from command line via click or typer** > - [ ] **Utilities**: Export other data from c3d files into .mot or .sto files (angles, powers, forces, moments, GRF, EMG...) + +
+ +> - [ ] **Bug:** common.py, class plotWindow(). Python crashes after a few runs of `Pose2Sim.filtering()` when `display_figures=true`. See [there](https://github.com/superjax/plotWindow/issues/7). +> - [ ] **Bug:** calibration.py. FFMPEG error message when calibration files are images. See [there](https://github.com/perfanalytics/pose2sim/issues/33#:~:text=In%20order%20to%20check,filter%20this%20message%20yet.). + +
+ +**Pose2Sim releases:** +> - v0.1: Published online +> - v0.2: Published associated paper +> - v0.3: Supported other pose estimation algorithms +> - v0.4: New calibration tool +