From 6f7e883cd3b30cb93c93e9afb406c9100c6a9b7a Mon Sep 17 00:00:00 2001 From: davidpagnon Date: Fri, 20 Sep 2024 14:42:50 +0200 Subject: [PATCH] fixed multi_person + differenciates X,Y,Z in scaling --- Pose2Sim/Demo_Batch/Config.toml | 2 +- Pose2Sim/Demo_Batch/Trial_1/Config.toml | 2 +- Pose2Sim/Demo_Batch/Trial_2/Config.toml | 2 +- Pose2Sim/Demo_MultiPerson/Config.toml | 2 +- Pose2Sim/Pose2Sim.py | 20 +++++----- Pose2Sim/common.py | 2 +- Pose2Sim/kinematics.py | 53 +++++++++++++++---------- Pose2Sim/personAssociation.py | 2 +- Pose2Sim/triangulation.py | 1 + 9 files changed, 48 insertions(+), 38 deletions(-) diff --git a/Pose2Sim/Demo_Batch/Config.toml b/Pose2Sim/Demo_Batch/Config.toml index f25e3df..1ae16fe 100644 --- a/Pose2Sim/Demo_Batch/Config.toml +++ b/Pose2Sim/Demo_Batch/Config.toml @@ -179,7 +179,7 @@ make_c3d = true # also save triangulated data in c3d format make_c3d = true # save triangulated data in c3d format in addition to trc -[opensim] +[kinematics] use_augmentation = true # true or false (lowercase) # Set to true if you want to use the model with augmented markers 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) remove_individual_scaling_setup = true # true or false (lowercase) # If true, the individual scaling setup files are removed to avoid cluttering diff --git a/Pose2Sim/Demo_Batch/Trial_1/Config.toml b/Pose2Sim/Demo_Batch/Trial_1/Config.toml index d18a7d5..a75e97e 100644 --- a/Pose2Sim/Demo_Batch/Trial_1/Config.toml +++ b/Pose2Sim/Demo_Batch/Trial_1/Config.toml @@ -179,7 +179,7 @@ # make_c3d = false # save triangulated data in c3d format in addition to trc -# [opensim] +# [kinematics] # use_augmentation = true # true or false (lowercase) # Set to true if you want to use the model with augmented markers # 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) # remove_individual_scaling_setup = true # true or false (lowercase) # If true, the individual scaling setup files are removed to avoid cluttering diff --git a/Pose2Sim/Demo_Batch/Trial_2/Config.toml b/Pose2Sim/Demo_Batch/Trial_2/Config.toml index 7dc68ee..d94eaa5 100644 --- a/Pose2Sim/Demo_Batch/Trial_2/Config.toml +++ b/Pose2Sim/Demo_Batch/Trial_2/Config.toml @@ -179,7 +179,7 @@ keypoints_to_consider = 'all' # 'all' if all points should be considered, for ex # make_c3d = false # save triangulated data in c3d format in addition to trc -# [opensim] +# [kinematics] # use_augmentation = true # true or false (lowercase) # Set to true if you want to use the model with augmented markers # 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) # remove_individual_scaling_setup = true # true or false (lowercase) # If true, the individual scaling setup files are removed to avoid cluttering diff --git a/Pose2Sim/Demo_MultiPerson/Config.toml b/Pose2Sim/Demo_MultiPerson/Config.toml index 64c3039..2ff8842 100644 --- a/Pose2Sim/Demo_MultiPerson/Config.toml +++ b/Pose2Sim/Demo_MultiPerson/Config.toml @@ -179,7 +179,7 @@ make_c3d = false # also save triangulated data in c3d format make_c3d = true # save triangulated data in c3d format in addition to trc -[opensim] +[kinematics] use_augmentation = true # true or false (lowercase) # Set to true if you want to use the model with augmented markers 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) remove_individual_scaling_setup = true # true or false (lowercase) # If true, the individual scaling setup files are removed to avoid cluttering diff --git a/Pose2Sim/Pose2Sim.py b/Pose2Sim/Pose2Sim.py index 972ef2d..9234649 100644 --- a/Pose2Sim/Pose2Sim.py +++ b/Pose2Sim/Pose2Sim.py @@ -217,7 +217,7 @@ def poseEstimation(config=None): config_dict.get("project").update({"project_dir":""})') # Set up logging - session_dir = os.path.realpath(os.path.join(config_dicts[0].get('project').get('project_dir'), '..')) + session_dir = os.path.realpath(os.path.join(config_dicts[0].get('project').get('project_dir'), '.')) setup_logging(session_dir) # Batch process all trials @@ -264,7 +264,7 @@ def synchronization(config=None): config_dict.get("project").update({"project_dir":""})') # Set up logging - session_dir = os.path.realpath(os.path.join(config_dicts[0].get('project').get('project_dir'), '..')) + session_dir = os.path.realpath(os.path.join(config_dicts[0].get('project').get('project_dir'), '.')) setup_logging(session_dir) # Batch process all trials @@ -296,7 +296,7 @@ def personAssociation(config=None): or the function can be called without an argument, in which case it the config directory is the current one. ''' - from Pose2Sim.personAssociation import track_2d_all + from Pose2Sim.personAssociation import associate_all # Determine the level at which the function is called (root:2, trial:1) level, config_dicts = read_config_files(config) @@ -308,7 +308,7 @@ def personAssociation(config=None): config_dict.get("project").update({"project_dir":""})') # Set up logging - session_dir = os.path.realpath(os.path.join(config_dicts[0].get('project').get('project_dir'), '..')) + session_dir = os.path.realpath(os.path.join(config_dicts[0].get('project').get('project_dir'), '.')) setup_logging(session_dir) # Batch process all trials @@ -326,7 +326,7 @@ def personAssociation(config=None): logging.info(f"Project directory: {project_dir}") logging.info("---------------------------------------------------------------------\n") - track_2d_all(config_dict) + associate_all(config_dict) end = time.time() elapsed = end-start @@ -354,7 +354,7 @@ def triangulation(config=None): config_dict.get("project").update({"project_dir":""})') # Set up logging - session_dir = os.path.realpath(os.path.join(config_dicts[0].get('project').get('project_dir'), '..')) + session_dir = os.path.realpath(os.path.join(config_dicts[0].get('project').get('project_dir'), '.')) setup_logging(session_dir) # Batch process all trials @@ -400,7 +400,7 @@ def filtering(config=None): config_dict.get("project").update({"project_dir":""})') # Set up logging - session_dir = os.path.realpath(os.path.join(config_dicts[0].get('project').get('project_dir'), '..')) + session_dir = os.path.realpath(os.path.join(config_dicts[0].get('project').get('project_dir'), '.')) setup_logging(session_dir) # Batch process all trials @@ -441,7 +441,7 @@ def markerAugmentation(config=None): raise ValueError('Please specify the project directory in config_dict:\n \ config_dict.get("project").update({"project_dir":""})') - session_dir = os.path.realpath(os.path.join(config_dicts[0].get('project').get('project_dir'), '..')) + session_dir = os.path.realpath(os.path.join(config_dicts[0].get('project').get('project_dir'), '.')) setup_logging(session_dir) for config_dict in config_dicts: @@ -485,7 +485,7 @@ def kinematics(config=None): raise ValueError('Please specify the project directory in config_dict:\n \ config_dict.get("project").update({"project_dir":""})') - session_dir = os.path.realpath(os.path.join(config_dicts[0].get('project').get('project_dir'), '..')) + session_dir = os.path.realpath(os.path.join(config_dicts[0].get('project').get('project_dir'), '.')) setup_logging(session_dir) # Process each configuration dictionary @@ -519,7 +519,7 @@ def runAll(config=None, do_calibration=True, do_poseEstimation=True, do_synchron # Set up logging level, config_dicts = read_config_files(config) - session_dir = os.path.realpath(os.path.join(config_dicts[0].get('project').get('project_dir'), '..')) + session_dir = os.path.realpath(os.path.join(config_dicts[0].get('project').get('project_dir'), '.')) setup_logging(session_dir) currentDateAndTime = datetime.now() diff --git a/Pose2Sim/common.py b/Pose2Sim/common.py index 2c1d085..598ab62 100644 --- a/Pose2Sim/common.py +++ b/Pose2Sim/common.py @@ -211,7 +211,7 @@ def reprojection(P_all, Q): y_calc.append(P_cam[1] @ Q / (P_cam[2] @ Q)) return x_calc, y_calc - + def euclidean_distance(q1, q2): ''' diff --git a/Pose2Sim/kinematics.py b/Pose2Sim/kinematics.py index d7c574a..b5e6aa4 100644 --- a/Pose2Sim/kinematics.py +++ b/Pose2Sim/kinematics.py @@ -275,7 +275,7 @@ def dict_segment_marker_pairs(scaling_root, right_left_symmetry=True): ''' - measurement_dict = {} + segment_markers_dict = {} for measurement in scaling_root.findall(".//Measurement"): # Collect all marker pairs for this measurement marker_pairs = [pair.find('markers').text.strip().split() for pair in measurement.findall(".//MarkerPair")] @@ -283,17 +283,22 @@ def dict_segment_marker_pairs(scaling_root, right_left_symmetry=True): # Collect all body scales for this measurement for body_scale in measurement.findall(".//BodyScale"): body_name = body_scale.get('name') - if right_left_symmetry: - measurement_dict[body_name] = marker_pairs - else: - if body_name.endswith('_r'): - marker_pairs_r = [pair for pair in marker_pairs if any([pair[0].startswith('R'), pair[1].startswith('R')])] - measurement_dict[body_name] = marker_pairs_r - elif body_name.endswith('_l'): - marker_pairs_l = [pair for pair in marker_pairs if any([pair[0].startswith('L'), pair[1].startswith('L')])] - measurement_dict[body_name] = marker_pairs_l + axes = body_scale.find('axes').text.strip().split() + for axis in axes: + body_name_axis = f"{body_name}_{axis}" + if right_left_symmetry: + segment_markers_dict.setdefault(body_name_axis, []).extend(marker_pairs) + else: + if body_name.endswith('_r'): + marker_pairs_r = [pair for pair in marker_pairs if any([pair[0].upper().startswith('R'), pair[1].upper().startswith('R')])] + segment_markers_dict.setdefault(body_name_axis, []).extend(marker_pairs_r) + elif body_name.endswith('_l'): + marker_pairs_l = [pair for pair in marker_pairs if any([pair[0].upper().startswith('L'), pair[1].upper().startswith('L')])] + segment_markers_dict.setdefault(body_name_axis, []).extend(marker_pairs_l) + else: + segment_markers_dict.setdefault(body_name_axis, []).extend(marker_pairs) - return measurement_dict + return segment_markers_dict def dict_segment_ratio(scaling_root, unscaled_model, Q_coords_scaling, markers, right_left_symmetry=True): @@ -321,10 +326,15 @@ def dict_segment_ratio(scaling_root, unscaled_model, Q_coords_scaling, markers, # Calculate ratio for each segment segment_ratios = trc_segment_lengths / model_segment_lengths segment_markers_dict = dict_segment_marker_pairs(scaling_root, right_left_symmetry=right_left_symmetry) - segment_ratio_dict = segment_markers_dict.copy() - segment_ratio_dict.update({key: np.mean([segment_ratios[segment_pairs.index(k)] + segment_ratio_dict_temp = segment_markers_dict.copy() + segment_ratio_dict_temp.update({key: np.mean([segment_ratios[segment_pairs.index(k)] for k in segment_markers_dict[key]]) for key in segment_markers_dict.keys()}) + # Merge X, Y, Z ratios into single key + segment_ratio_dict={} + xyz_keys = list(set([key[:-2] for key in segment_ratio_dict_temp.keys()])) + for key in xyz_keys: + segment_ratio_dict[key] = [segment_ratio_dict_temp[key+'_X'], segment_ratio_dict_temp[key+'_Y'], segment_ratio_dict_temp[key+'_Z']] return segment_ratio_dict @@ -351,11 +361,11 @@ def update_scale_values(scaling_root, segment_ratio_dict): scale_set.remove(scale) # Add new Scale elements based on scale_dict - for segment, scale in segment_ratio_dict.items(): + for segment, scales in segment_ratio_dict.items(): new_scale = etree.Element('Scale') # scales scales_elem = etree.SubElement(new_scale, 'scales') - scales_elem.text = ' '.join([str(scale)]*3) + scales_elem.text = ' '.join(map(str, scales)) # segment name segment_elem = etree.SubElement(new_scale, 'segment') segment_elem.text = segment @@ -394,8 +404,8 @@ def perform_scaling(trc_file, kinematics_dir, osim_setup_dir, model_name, right_ scaling_path = get_scaling_setup(model_name, osim_setup_dir) scaling_tree = etree.parse(scaling_path) scaling_root = scaling_tree.getroot() - scaling_path_temp = str(kinematics_dir / Path(scaling_path).name) - + scaling_path_temp = str(kinematics_dir / (trc_file.stem + '_scaling_setup.xml')) + # Read trc file Q_coords, _, _, markers, _ = read_trc(trc_file) @@ -415,8 +425,7 @@ def perform_scaling(trc_file, kinematics_dir, osim_setup_dir, model_name, right_ scaling_root[0].find(".//scaling_order").text = ' manualScale measurements' deactivate_measurements(scaling_root) update_scale_values(scaling_root, segment_ratio_dict) - for mk_f in scaling_root[0].findall(".//marker_file"): - mk_f.text = "Unassigned" + for mk_f in scaling_root[0].findall(".//marker_file"): mk_f.text = "Unassigned" scaling_root[0].find('ModelScaler').find('output_model_file').text = str(scaled_model_path) etree.indent(scaling_tree, space='\t', level=0) @@ -448,7 +457,7 @@ def perform_IK(trc_file, kinematics_dir, osim_setup_dir, model_name, remove_IK_s try: # Retrieve data ik_path = get_IK_Setup(model_name, osim_setup_dir) - ik_path_temp = str(kinematics_dir / Path(ik_path).name) + ik_path_temp = str(kinematics_dir / (trc_file.stem + '_ik_setup.xml')) scaled_model_path = (kinematics_dir / (trc_file.stem + '.osim')).resolve() output_motion_file = Path(kinematics_dir, trc_file.stem + '.mot').resolve() if not trc_file.exists(): @@ -546,7 +555,7 @@ def kinematics(config_dict): elif not type(subject_mass) == list: subject_mass = [subject_mass] elif len(subject_mass) < len(trc_files): - logging.warning("Number of subject masses does not match number of TRC files. Missing masses are set to 70kg.") + logging.warning("Number of subject masses does not match number of TRC files. Missing masses are set to 70kg.\n") subject_mass += [70] * (len(trc_files) - len(subject_mass)) # Perform scaling and IK for each trc file @@ -558,7 +567,7 @@ def kinematics(config_dict): logging.info(f"\tDone. OpenSim logs saved to {opensim_logs_file.resolve()}.") logging.info(f"\tScaled model saved to {(kinematics_dir / (trc_file.stem + '_scaled.osim')).resolve()}") - logging.info("\nInverse Kinematics...") + logging.info("Inverse Kinematics...") perform_IK(trc_file, kinematics_dir, osim_setup_dir, model_name, remove_IK_setup=remove_IK_setup) logging.info(f"\tDone. OpenSim logs saved to {opensim_logs_file.resolve()}.") logging.info(f"\tJoint angle data saved to {(kinematics_dir / (trc_file.stem + '.mot')).resolve()}\n") diff --git a/Pose2Sim/personAssociation.py b/Pose2Sim/personAssociation.py index 6491457..cf2d3a7 100644 --- a/Pose2Sim/personAssociation.py +++ b/Pose2Sim/personAssociation.py @@ -604,7 +604,7 @@ def recap_tracking(config_dict, error=0, nb_cams_excluded=0): logging.info(f'\nTracked json files are stored in {os.path.realpath(poseTracked_dir)}.') -def track_2d_all(config_dict): +def associate_all(config_dict): ''' For each frame, - Find all possible combinations of detected persons diff --git a/Pose2Sim/triangulation.py b/Pose2Sim/triangulation.py index 43b9e3a..3a91684 100644 --- a/Pose2Sim/triangulation.py +++ b/Pose2Sim/triangulation.py @@ -191,6 +191,7 @@ def sort_people(Q_kpt_old, Q_kpt): frame_by_frame_dist = [] for comb in personsIDs_comb: frame_by_frame_dist += [euclidean_distance(Q_kpt_old[comb[0]],Q_kpt[comb[1]])] + frame_by_frame_dist = np.mean(frame_by_frame_dist, axis=1) # sort correspondences by distance minL, _, associated_tuples = min_with_single_indices(frame_by_frame_dist, personsIDs_comb)