From db8014c3f5c127118260a606e7278bcaf3053183 Mon Sep 17 00:00:00 2001 From: davidpagnon Date: Tue, 16 Apr 2024 17:28:37 +0200 Subject: [PATCH] few changes to the conversion to c3d --- .gitignore | 1 + Pose2Sim/S00_Demo_BatchSession/Config.toml | 1 + .../S00_P00_SingleParticipant/Config.toml | 3 +- .../S00_P00_T00_StaticTrial/Config.toml | 3 +- .../S00_P00_T01_BalancingTrial/Config.toml | 3 +- .../S00_P01_MultiParticipants/Config.toml | 4 +- .../Config.toml | 3 +- .../Config.toml | 3 +- .../S00_P01_T02_Participants1-2/Config.toml | 9 +- Pose2Sim/S01_Demo_SingleTrial/Config.toml | 1 + Pose2Sim/Utilities/c3d_to_trc.py | 2 - Pose2Sim/Utilities/trc_to_c3d.py | 143 ++++++++++----- Pose2Sim/common.py | 166 +++++++++--------- Pose2Sim/filtering.py | 12 +- Pose2Sim/markerAugmentation.py | 21 ++- Pose2Sim/triangulation.py | 33 ++-- 16 files changed, 240 insertions(+), 168 deletions(-) diff --git a/.gitignore b/.gitignore index b3fc9ea..adcd451 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ dist/ # **/*.mp4 **/*.trc **/*.sto +**/*.c3d **/Calib_qualisys.toml **/pose-3d/ diff --git a/Pose2Sim/S00_Demo_BatchSession/Config.toml b/Pose2Sim/S00_Demo_BatchSession/Config.toml index aa0e334..155396c 100644 --- a/Pose2Sim/S00_Demo_BatchSession/Config.toml +++ b/Pose2Sim/S00_Demo_BatchSession/Config.toml @@ -164,6 +164,7 @@ make_c3d = false # also save triangulated data in c3d format ## Only works on BODY_25 and BODY_25B models participant_height = 1.72 # m # float if single person, list of float if multi-person (same order as the Static trials) participant_mass = 70.0 # kg +make_c3d = false # save triangulated data in c3d format in addition to trc [opensim] diff --git a/Pose2Sim/S00_Demo_BatchSession/S00_P00_SingleParticipant/Config.toml b/Pose2Sim/S00_Demo_BatchSession/S00_P00_SingleParticipant/Config.toml index c4e84e3..ac7caae 100644 --- a/Pose2Sim/S00_Demo_BatchSession/S00_P00_SingleParticipant/Config.toml +++ b/Pose2Sim/S00_Demo_BatchSession/S00_P00_SingleParticipant/Config.toml @@ -163,10 +163,11 @@ ## Only works on BODY_25 and BODY_25B models # participant_height = 1.72 # m # float if single person, list of float if multi-person (same order as the Static trials) # participant_mass = 70.0 # kg +# make_c3d = false # save triangulated data in c3d format in addition to trc # [opensim] -# static_trial = ['S00_P00_Participant/S00_P00_T00_StaticTrial'] +# static_trial = [# static_trial = ['S00_P00_Participant/S00_P00_T00_StaticTrial'] # # If this Config.toml file is at the Trial level, set to true or false (lowercase); # # At the Participant level, specify the name of the static trial folder name, e.g. ['S00_P00_T00_StaticTrial']; # # At the Session level, add participant subdirectory, e.g. ['S00_P00_Participant/S00_P00_T00_StaticTrial', 'S00_P01_Participant/S00_P00_T00_StaticTrial'] diff --git a/Pose2Sim/S00_Demo_BatchSession/S00_P00_SingleParticipant/S00_P00_T00_StaticTrial/Config.toml b/Pose2Sim/S00_Demo_BatchSession/S00_P00_SingleParticipant/S00_P00_T00_StaticTrial/Config.toml index 49095bc..db089a8 100644 --- a/Pose2Sim/S00_Demo_BatchSession/S00_P00_SingleParticipant/S00_P00_T00_StaticTrial/Config.toml +++ b/Pose2Sim/S00_Demo_BatchSession/S00_P00_SingleParticipant/S00_P00_T00_StaticTrial/Config.toml @@ -163,10 +163,11 @@ ## Only works on BODY_25 and BODY_25B models # participant_height = 1.72 # m # float if single person, list of float if multi-person (same order as the Static trials) # participant_mass = 70.0 # kg +# make_c3d = false # save triangulated data in c3d format in addition to trc # [opensim] -# static_trial = ['S00_P00_Participant/S00_P00_T00_StaticTrial'] +# static_trial = [# static_trial = ['S00_P00_Participant/S00_P00_T00_StaticTrial'] # # If this Config.toml file is at the Trial level, set to true or false (lowercase); # # At the Participant level, specify the name of the static trial folder name, e.g. ['S00_P00_T00_StaticTrial']; # # At the Session level, add participant subdirectory, e.g. ['S00_P00_Participant/S00_P00_T00_StaticTrial', 'S00_P01_Participant/S00_P00_T00_StaticTrial'] diff --git a/Pose2Sim/S00_Demo_BatchSession/S00_P00_SingleParticipant/S00_P00_T01_BalancingTrial/Config.toml b/Pose2Sim/S00_Demo_BatchSession/S00_P00_SingleParticipant/S00_P00_T01_BalancingTrial/Config.toml index ed8de55..f550189 100644 --- a/Pose2Sim/S00_Demo_BatchSession/S00_P00_SingleParticipant/S00_P00_T01_BalancingTrial/Config.toml +++ b/Pose2Sim/S00_Demo_BatchSession/S00_P00_SingleParticipant/S00_P00_T01_BalancingTrial/Config.toml @@ -163,10 +163,11 @@ display_figures = false # true or false (lowercase) ## Only works on BODY_25 and BODY_25B models # participant_height = 1.72 # m # float if single person, list of float if multi-person (same order as the Static trials) # participant_mass = 70.0 # kg +# make_c3d = false # save triangulated data in c3d format in addition to trc # [opensim] -# static_trial = ['S00_P00_Participant/S00_P00_T00_StaticTrial'] +# static_trial = [# static_trial = ['S00_P00_Participant/S00_P00_T00_StaticTrial'] # # If this Config.toml file is at the Trial level, set to true or false (lowercase); # # At the Participant level, specify the name of the static trial folder name, e.g. ['S00_P00_T00_StaticTrial']; # # At the Session level, add participant subdirectory, e.g. ['S00_P00_Participant/S00_P00_T00_StaticTrial', 'S00_P01_Participant/S00_P00_T00_StaticTrial'] diff --git a/Pose2Sim/S00_Demo_BatchSession/S00_P01_MultiParticipants/Config.toml b/Pose2Sim/S00_Demo_BatchSession/S00_P01_MultiParticipants/Config.toml index c4e84e3..7fd0223 100644 --- a/Pose2Sim/S00_Demo_BatchSession/S00_P01_MultiParticipants/Config.toml +++ b/Pose2Sim/S00_Demo_BatchSession/S00_P01_MultiParticipants/Config.toml @@ -163,10 +163,12 @@ ## Only works on BODY_25 and BODY_25B models # participant_height = 1.72 # m # float if single person, list of float if multi-person (same order as the Static trials) # participant_mass = 70.0 # kg +# make_c3d = false # save triangulated data in c3d format in addition to trc +# make_c3d = false # save triangulated data in c3d format in addition to trc # [opensim] -# static_trial = ['S00_P00_Participant/S00_P00_T00_StaticTrial'] +# static_trial = [# static_trial = ['S00_P00_Participant/S00_P00_T00_StaticTrial'] # # If this Config.toml file is at the Trial level, set to true or false (lowercase); # # At the Participant level, specify the name of the static trial folder name, e.g. ['S00_P00_T00_StaticTrial']; # # At the Session level, add participant subdirectory, e.g. ['S00_P00_Participant/S00_P00_T00_StaticTrial', 'S00_P01_Participant/S00_P00_T00_StaticTrial'] diff --git a/Pose2Sim/S00_Demo_BatchSession/S00_P01_MultiParticipants/S00_P01_T00_StaticTrialParticipant1/Config.toml b/Pose2Sim/S00_Demo_BatchSession/S00_P01_MultiParticipants/S00_P01_T00_StaticTrialParticipant1/Config.toml index 783934a..cb10a8b 100644 --- a/Pose2Sim/S00_Demo_BatchSession/S00_P01_MultiParticipants/S00_P01_T00_StaticTrialParticipant1/Config.toml +++ b/Pose2Sim/S00_Demo_BatchSession/S00_P01_MultiParticipants/S00_P01_T00_StaticTrialParticipant1/Config.toml @@ -163,10 +163,11 @@ ## Only works on BODY_25 and BODY_25B models participant_height = 1.21 # m # float if single person, list of float if multi-person (same order as the Static trials) participant_mass = 25.0 # kg +# make_c3d = false # save triangulated data in c3d format in addition to trc # [opensim] -# static_trial = ['S00_P00_Participant/S00_P00_T00_StaticTrial'] +# static_trial = [# static_trial = ['S00_P00_Participant/S00_P00_T00_StaticTrial'] # # If this Config.toml file is at the Trial level, set to true or false (lowercase); # # At the Participant level, specify the name of the static trial folder name, e.g. ['S00_P00_T00_StaticTrial']; # # At the Session level, add participant subdirectory, e.g. ['S00_P00_Participant/S00_P00_T00_StaticTrial', 'S00_P01_Participant/S00_P00_T00_StaticTrial'] diff --git a/Pose2Sim/S00_Demo_BatchSession/S00_P01_MultiParticipants/S00_P01_T01_StaticTrialParticipant2/Config.toml b/Pose2Sim/S00_Demo_BatchSession/S00_P01_MultiParticipants/S00_P01_T01_StaticTrialParticipant2/Config.toml index 3244c35..85ea0fa 100644 --- a/Pose2Sim/S00_Demo_BatchSession/S00_P01_MultiParticipants/S00_P01_T01_StaticTrialParticipant2/Config.toml +++ b/Pose2Sim/S00_Demo_BatchSession/S00_P01_MultiParticipants/S00_P01_T01_StaticTrialParticipant2/Config.toml @@ -162,10 +162,11 @@ ## Only works on BODY_25 and BODY_25B models participant_height = 1.72 # m # float if single person, list of float if multi-person (same order as the Static trials) participant_mass = 70.0 # kg +# make_c3d = false # save triangulated data in c3d format in addition to trc # [opensim] -# static_trial = ['S00_P00_Participant/S00_P00_T00_StaticTrial'] +# static_trial = [# static_trial = ['S00_P00_Participant/S00_P00_T00_StaticTrial'] # # If this Config.toml file is at the Trial level, set to true or false (lowercase); # # At the Participant level, specify the name of the static trial folder name, e.g. ['S00_P00_T00_StaticTrial']; # # At the Session level, add participant subdirectory, e.g. ['S00_P00_Participant/S00_P00_T00_StaticTrial', 'S00_P01_Participant/S00_P00_T00_StaticTrial'] diff --git a/Pose2Sim/S00_Demo_BatchSession/S00_P01_MultiParticipants/S00_P01_T02_Participants1-2/Config.toml b/Pose2Sim/S00_Demo_BatchSession/S00_P01_MultiParticipants/S00_P01_T02_Participants1-2/Config.toml index aac6721..df3dd18 100644 --- a/Pose2Sim/S00_Demo_BatchSession/S00_P01_MultiParticipants/S00_P01_T02_Participants1-2/Config.toml +++ b/Pose2Sim/S00_Demo_BatchSession/S00_P01_MultiParticipants/S00_P01_T02_Participants1-2/Config.toml @@ -129,13 +129,13 @@ reorder_trc = true # only checked if multi_person analysis # show_interp_indices = true # true or false (lowercase). For each keypoint, return the frames that need to be interpolated # handle_LR_swap = false # Better if few cameras (eg less than 4) with risk of limb swapping (eg camera facing sagittal plane), otherwise slightly less accurate and slower # undistort_points = false # Better if distorted image (parallel lines curvy on the edge or at least one param > 10^-2), but unnecessary (and slightly slower) if distortions are low -# make_c3d = false # save triangulated data in c3d format in addition to trc +make_c3d = true # save triangulated data in c3d format in addition to trc -# [filtering] +[filtering] # type = 'butterworth' # butterworth, kalman, gaussian, LOESS, median, butterworth_on_speed # display_figures = false # true or false (lowercase) -# make_c3d = false # save triangulated data in c3d format in addition to trc +make_c3d = true # save triangulated data in c3d format in addition to trc # [filtering.butterworth] # order = 4 @@ -159,10 +159,11 @@ reorder_trc = true # only checked if multi_person analysis ## Only works on BODY_25 and BODY_25B models participant_height = [1.21, 1.72] # m # float if single person, list of float if multi-person (same order as the Static trials) participant_mass = [25.0, 70.0] # kg +make_c3d = true # save triangulated data in c3d format in addition to trc # [opensim] -# static_trial = ['S00_P00_Participant/S00_P00_T00_StaticTrial'] +# static_trial = [# static_trial = ['S00_P00_Participant/S00_P00_T00_StaticTrial'] # # If this Config.toml file is at the Trial level, set to true or false (lowercase); # # At the Participant level, specify the name of the static trial folder name, e.g. ['S00_P00_T00_StaticTrial']; # # At the Session level, add participant subdirectory, e.g. ['S00_P00_Participant/S00_P00_T00_StaticTrial', 'S00_P01_Participant/S00_P00_T00_StaticTrial'] diff --git a/Pose2Sim/S01_Demo_SingleTrial/Config.toml b/Pose2Sim/S01_Demo_SingleTrial/Config.toml index 2fb12a1..c7cb654 100644 --- a/Pose2Sim/S01_Demo_SingleTrial/Config.toml +++ b/Pose2Sim/S01_Demo_SingleTrial/Config.toml @@ -164,6 +164,7 @@ make_c3d = false # save triangulated data in c3d format in addition to trc ## Only works on BODY_25 and BODY_25B models participant_height = 1.72 # m # float if single person, list of float if multi-person (same order as the Static trials) participant_mass = 70.0 # kg +make_c3d = false # save triangulated data in c3d format in addition to trc [opensim] diff --git a/Pose2Sim/Utilities/c3d_to_trc.py b/Pose2Sim/Utilities/c3d_to_trc.py index bd456b4..ea166bc 100644 --- a/Pose2Sim/Utilities/c3d_to_trc.py +++ b/Pose2Sim/Utilities/c3d_to_trc.py @@ -10,8 +10,6 @@ Converts c3d files to trc files. Beware that it only allows you to retrieve 3D points, you won't get analog data nor computed data such as angles or powers with this code. - N.B.: First install c3d: `pip install c3d` - Usage: from Pose2Sim.Utilities import c3d_to_trc; c3d_to_trc.c3d_to_trc_func(r'') python -m c3d_to_trc -i input_c3d_file diff --git a/Pose2Sim/Utilities/trc_to_c3d.py b/Pose2Sim/Utilities/trc_to_c3d.py index 7f115a6..32bedb5 100644 --- a/Pose2Sim/Utilities/trc_to_c3d.py +++ b/Pose2Sim/Utilities/trc_to_c3d.py @@ -1,73 +1,126 @@ -""" -Extracts marker data from a TRC file and creates a corresponding C3D file. +#! /usr/bin/env python +# -*- coding: utf-8 -*- -Usage: - python trc_to_c3d.py --trc_path --f ---trc_path: Path to the TRC file. ---f: Frame rate of the 3D data. -c3d file will be saved in the same directory as the TRC file with the same name. +''' + ################################################## + ## Convert trc files to c3d ## + ################################################## + + Converts trc files to c3d files. + + Usage: + from Pose2Sim.Utilities import trc_to_c3d; trc_to_c3d.trc_to_c3d_func(r'') + python -m trc_to_c3d -t + python -m trc_to_c3d --trc_path --c3d_path +''' -""" -import os +## INIT import argparse import numpy as np -import pandas as pd import c3d -def extract_marker_data(trc_file): - with open(trc_file, 'r') as file: + +## AUTHORSHIP INFORMATION +__author__ = "HunMin Kim, David Pagnon" +__copyright__ = "Copyright 2021, Pose2Sim" +__credits__ = ["HuMin Kim, David Pagnon"] +__license__ = "BSD 3-Clause License" +__version__ = '0.8' +__maintainer__ = "David Pagnon" +__email__ = "contact@david-pagnon.com" +__status__ = "Development" + + +## FUNCTIONS +def extract_marker_data(trc_path): + ''' + Extract marker names and coordinates from a trc file. + + INPUTS: + - trc_path: Path to the trc file + + OUTPUTS: + - marker_names: List of marker names + - marker_coords: Array of marker coordinates (n_frames, t+3*n_markers) + ''' + + # marker names + with open(trc_path, 'r') as file: lines = file.readlines() marker_names_line = lines[3] marker_names = marker_names_line.strip().split('\t')[2::3] - trc_data = pd.read_csv(trc_file, sep='\t', skiprows=5) - marker_coords = trc_data.iloc[:, 2:].to_numpy().reshape(-1, len(marker_names), 3) - # marker_coords = np.nan_to_num(marker_coords, nan=0.0) - marker_coords *= 1000 # Convert from meters to millimeters + # time and marker coordinates + trc_data_np = np.genfromtxt(trc_path, skip_header=5, delimiter = '\t')[:,1:] - return marker_names, marker_coords + return marker_names, trc_data_np -def create_c3d_file(trc_file, marker_names, marker_coords, frame_rate): + +def create_c3d_file(c3d_path, marker_names, trc_data_np): + ''' + Create a c3d file from the data extracted from a trc file. + + INPUTS: + - c3d_path: Path to the c3d file + - marker_names: List of marker names + - trc_data_np: Array of marker coordinates (n_frames, t+3*n_markers) + + OUTPUTS: + - c3d file + ''' + + # retrieve frame rate + times = trc_data_np[:,0] + frame_rate = round((len(times)-1) / (times[-1] - times[0])) + + # write c3d file writer = c3d.Writer(point_rate=frame_rate, analog_rate=0, point_scale=1.0, point_units='mm', gen_scale=-1.0) writer.set_point_labels(marker_names) - markers_group = writer.point_group - - for frame in marker_coords: - residuals = np.full((frame.shape[0], 1), 0.0) - cameras = np.zeros((frame.shape[0], 1)) - points = np.hstack((frame, residuals, cameras)) + for frame in trc_data_np: + residuals = np.full((len(marker_names), 1), 0.0) + cameras = np.zeros((len(marker_names), 1)) + coords = frame[1:].reshape(-1,3)*1000 + points = np.hstack((coords, residuals, cameras)) writer.add_frames([(points, np.array([]))]) - writer.set_start_frame(1) - writer._set_last_frame(len(marker_coords)) + writer.set_start_frame(0) + writer._set_last_frame(len(trc_data_np)-1) - c3d_file_path = trc_file.replace('.trc', '.c3d') - with open(c3d_file_path, 'wb') as handle: + with open(c3d_path, 'wb') as handle: writer.write(handle) - print(f"Successfully created c3d file.") -def trc_to_c3d(trc_file, frame_rate): - marker_names, marker_coords = extract_marker_data(trc_file) - create_c3d_file(trc_file, marker_names, marker_coords, frame_rate) -def main(): - parser = argparse.ArgumentParser(description='Convert TRC files to C3D files.') - parser.add_argument('--trc_path', type=str, required=True, help='Path to the TRC file') - parser.add_argument('--f', type=int, required=True, help='Frame rate') +def trc_to_c3d_func(*args): + ''' + Converts trc files to c3d files. + + Usage: + from Pose2Sim.Utilities import trc_to_c3d; trc_to_c3d.trc_to_c3d_func(r'') + python -m trc_to_c3d -t + python -m trc_to_c3d --trc_path --c3d_path + ''' - args = parser.parse_args() + try: + trc_path = args[0]['trc_path'] # invoked with argparse + if args[0]['c3d_path'] == None: + c3d_path = trc_path.replace('.trc', '.c3d') + else: + c3d_path = args[0]['c3d_path'] + except: + trc_path = args[0] # invoked as a function + c3d_path = trc_path.replace('.trc', '.c3d') - trc_file = args.trc_path - frame_rate = args.f + marker_names, trc_data_np = extract_marker_data(trc_path) + create_c3d_file(c3d_path, marker_names, trc_data_np) - if not os.path.isfile(trc_file): - print(f"Error: {trc_file} does not exist.") - return - - trc_to_c3d(trc_file, frame_rate) if __name__ == '__main__': - main() + parser = argparse.ArgumentParser(description='Convert TRC files to C3D files.') + parser.add_argument('-t', '--trc_path', type=str, required=True, help='trc input file path') + parser.add_argument('-c', '--c3d_path', type=str, required=False, help='c3d output file path') + args = vars(parser.parse_args()) + + trc_to_c3d_func(args) diff --git a/Pose2Sim/common.py b/Pose2Sim/common.py index 1a22c04..492eea7 100644 --- a/Pose2Sim/common.py +++ b/Pose2Sim/common.py @@ -16,10 +16,8 @@ import json import numpy as np import re import cv2 -import os -import pandas as pd import c3d -import glob +import sys import matplotlib as mpl mpl.use('qt5agg') @@ -27,7 +25,8 @@ mpl.rc('figure', max_open_warning=0) from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar from PyQt5.QtWidgets import QMainWindow, QApplication, QWidget, QTabWidget, QVBoxLayout -import sys +import warnings +warnings.filterwarnings("ignore", category=UserWarning, module="c3d") ## AUTHORSHIP INFORMATION @@ -385,6 +384,83 @@ def zup2yup(Q): return Q +def extract_trc_data(trc_path): + ''' + Extract marker names and coordinates from a trc file. + + INPUTS: + - trc_path: Path to the trc file + + OUTPUTS: + - marker_names: List of marker names + - marker_coords: Array of marker coordinates (n_frames, t+3*n_markers) + ''' + + # marker names + with open(trc_path, 'r') as file: + lines = file.readlines() + marker_names_line = lines[3] + marker_names = marker_names_line.strip().split('\t')[2::3] + + # time and marker coordinates + trc_data_np = np.genfromtxt(trc_path, skip_header=5, delimiter = '\t')[:,1:] + + return marker_names, trc_data_np + + +def create_c3d_file(c3d_path, marker_names, trc_data_np): + ''' + Create a c3d file from the data extracted from a trc file. + + INPUTS: + - c3d_path: Path to the c3d file + - marker_names: List of marker names + - trc_data_np: Array of marker coordinates (n_frames, t+3*n_markers) + + OUTPUTS: + - c3d file + ''' + + # retrieve frame rate + times = trc_data_np[:,0] + frame_rate = round((len(times)-1) / (times[-1] - times[0])) + + # write c3d file + writer = c3d.Writer(point_rate=frame_rate, analog_rate=0, point_scale=1.0, point_units='mm', gen_scale=-1.0) + writer.set_point_labels(marker_names) + + for frame in trc_data_np: + residuals = np.full((len(marker_names), 1), 0.0) + cameras = np.zeros((len(marker_names), 1)) + coords = frame[1:].reshape(-1,3)*1000 + points = np.hstack((coords, residuals, cameras)) + writer.add_frames([(points, np.array([]))]) + + writer.set_start_frame(0) + writer._set_last_frame(len(trc_data_np)-1) + + with open(c3d_path, 'wb') as handle: + writer.write(handle) + + +def convert_to_c3d(trc_path): + ''' + Make Visual3D compatible c3d files from a trc path + + INPUT: + - trc_path: string, trc file to convert + + OUTPUT: + - c3d file + ''' + + c3d_path = trc_path.replace('.trc', '.c3d') + marker_names, trc_data_np = extract_trc_data(trc_path) + create_c3d_file(c3d_path, marker_names, trc_data_np) + + return c3d_path + + ## CLASSES class plotWindow(): ''' @@ -436,85 +512,3 @@ class plotWindow(): def show(self): self.app.exec_() - -# Save c3d -def trc_to_c3d(project_dir, frame_rate, called_from): - ''' - Converts.trc files to.c3d files - INPUT: - - project_dir: path to project folder - - frame_rate: frame rate of the video - - called_from: string that determines which.trc files to convert. - 'triangulation' for.trc files from triangulation step - 'filtering' for.trc files from filtering step - - ''' - # Determine the 3D pose folder - pose3d_dir = os.path.join(project_dir, 'pose-3d') - - # Determine the .trc file name to read - trc_files = [] - if called_from == 'triangulation': - trc_pattern = "*.trc" - trc_files = [os.path.basename(f) for f in glob.glob(os.path.join(pose3d_dir, trc_pattern)) if 'filt' not in f] - elif called_from == 'filtering': - trc_pattern = "*_filt_*.trc" - trc_files = [os.path.basename(f) for f in glob.glob(os.path.join(pose3d_dir, trc_pattern))] - else: - print("Invalid called_from value.") - - for trc_file in trc_files: - # Extract marker names from the 4th row of the TRC file - with open(os.path.join(pose3d_dir, trc_file), 'r') as file: - lines = file.readlines() - marker_names_line = lines[3] - marker_names = marker_names_line.strip().split('\t')[2::3] - - # Read the data frame (skiprows=5) - trc_data = pd.read_csv(os.path.join(pose3d_dir, trc_file), sep='\t', skiprows=5) - - # Extract marker coordinates - marker_coords = trc_data.iloc[:, 2:].to_numpy().reshape(-1, len(marker_names), 3) - # marker_coords = np.nan_to_num(marker_coords, nan=0.0) - - scale_factor = 1000 - marker_coords = marker_coords * scale_factor - - # Create a C3D writer - writer = c3d.Writer(point_rate=frame_rate, analog_rate=0, point_scale=1.0, point_units='mm', gen_scale=-1.0) - - # Add marker parameters - writer.set_point_labels(marker_names) - - # # Add marker descriptions (optional) - # marker_descriptions = [''] * len(marker_names) - # writer.point_group.add_param('DESCRIPTIONS', desc='Marker descriptions', - # bytes_per_element=-1, dimensions=[len(marker_names)], - # bytes=np.array(marker_descriptions, dtype=object)) - - # # Set the data start parameter - # data_start = writer.header.data_block - # writer.point_group.add_param('DATA_START', desc='Data start parameter', - # bytes_per_element=2, dimensions=[], bytes=struct.pack('c3d file saved: {c3d_file_path}") \ No newline at end of file diff --git a/Pose2Sim/filtering.py b/Pose2Sim/filtering.py index 64c1eef..32873fb 100644 --- a/Pose2Sim/filtering.py +++ b/Pose2Sim/filtering.py @@ -37,7 +37,7 @@ from filterpy.kalman import KalmanFilter, rts_smoother from filterpy.common import Q_discrete_white_noise from Pose2Sim.common import plotWindow -from Pose2Sim.common import trc_to_c3d +from Pose2Sim.common import convert_to_c3d ## AUTHORSHIP INFORMATION __author__ = "David Pagnon" @@ -416,6 +416,7 @@ def recap_filter3d(config, trc_path): gaussian_filter_sigma_kernel = int(config.get('filtering').get('gaussian').get('sigma_kernel')) loess_filter_nb_values = config.get('filtering').get('LOESS').get('nb_values_used') median_filter_kernel_size = config.get('filtering').get('median').get('kernel_size') + make_c3d = config.get('filtering').get('make_c3d') # Recap filter_mapping_recap = { @@ -428,6 +429,8 @@ def recap_filter3d(config, trc_path): } logging.info(filter_mapping_recap[filter_type]) logging.info(f'Filtered 3D coordinates are stored at {trc_path}.\n') + if make_c3d: + logging.info('All filtered trc files have been converted to c3d.') def filter_all(config): @@ -497,10 +500,11 @@ def filter_all(config): # Q_filt = Q_filt.fillna(' ') Q_filt.to_csv(trc_o, sep='\t', index=False, header=None, lineterminator='\n') + # Save c3d + if make_c3d: + convert_to_c3d(t_out) + # Recap recap_filter3d(config, t_out) - # Save c3d - if make_c3d == True: - trc_to_c3d(project_dir, frame_rate, called_from='filtering') diff --git a/Pose2Sim/markerAugmentation.py b/Pose2Sim/markerAugmentation.py index 90e6ff4..3e1c4b9 100644 --- a/Pose2Sim/markerAugmentation.py +++ b/Pose2Sim/markerAugmentation.py @@ -23,14 +23,15 @@ OUTPUT: ## INIT import os import numpy as np -from Pose2Sim.MarkerAugmenter import utilsDataman import copy import tensorflow as tf -from Pose2Sim.MarkerAugmenter.utils import TRC2numpy -import json import glob import logging +from Pose2Sim.MarkerAugmenter import utilsDataman +from Pose2Sim.MarkerAugmenter.utils import TRC2numpy +from Pose2Sim.common import convert_to_c3d + ## AUTHORSHIP INFORMATION __author__ = "Antoine Falisse, adapted by HunMin Kim" @@ -65,7 +66,6 @@ def augmentTRC(config_dict): project_dir = config_dict.get('project').get('project_dir') pathInputTRCFile = os.path.realpath(os.path.join(project_dir, 'pose-3d')) pathOutputTRCFile = os.path.realpath(os.path.join(project_dir, 'pose-3d')) - pose_model = config_dict.get('pose').get('pose_model') subject_height = config_dict.get('markerAugmentation').get('participant_height') if subject_height is None or subject_height == 0 or subject_height==0: raise ValueError("Subject height is not set or invalid in the config file.") @@ -73,6 +73,7 @@ def augmentTRC(config_dict): if not type(subject_height) == list: subject_height = [subject_height] subject_mass = [subject_mass] + make_c3d = config_dict.get('markerAugmentation').get('make_c3d') augmenterDir = os.path.dirname(utilsDataman.__file__) augmenterModelName = 'LSTM' augmenter_model = 'v0.3' @@ -151,8 +152,6 @@ def augmentTRC(config_dict): trc_data_data = trc_data[:,1:] # Step 2: Normalize with reference marker position. - with open(os.path.join(augmenterModelDir, "metadata.json"), 'r') as f: - metadata = json.load(f) referenceMarker_data = midhip_data # instead of trc_file.marker(referenceMarker) # change by HunMin norm_trc_data_data = np.zeros((trc_data_data.shape[0], trc_data_data.shape[1])) @@ -245,7 +244,15 @@ def augmentTRC(config_dict): # %% Return augmented .trc file trc_file.write(pathOutputTRCFile) - logging.info(f'Augmented marker coordinates are stored at {pathOutputTRCFile}.\n') + logging.info(f'Augmented marker coordinates are stored at {pathOutputTRCFile}.') + + # Save c3d + if make_c3d: + print(pathOutputTRCFile) + convert_to_c3d(pathOutputTRCFile) + logging.info(f'Augmented trc files have been converted to c3d.') + + logging.info('\n') return min_y_pos diff --git a/Pose2Sim/triangulation.py b/Pose2Sim/triangulation.py index 62805b7..9348a30 100644 --- a/Pose2Sim/triangulation.py +++ b/Pose2Sim/triangulation.py @@ -50,8 +50,7 @@ from anytree.importer import DictImporter import logging from Pose2Sim.common import retrieve_calib_params, computeP, weighted_triangulation, \ - reprojection, euclidean_distance, sort_stringlist_by_last_number, zup2yup -from Pose2Sim.common import trc_to_c3d + reprojection, euclidean_distance, sort_stringlist_by_last_number, zup2yup, convert_to_c3d from Pose2Sim.skeletons import * @@ -312,7 +311,7 @@ def recap_triangulate(config, error, nb_cams_excluded, keypoints_names, cam_excl # if batch session_dir = os.path.realpath(os.path.join(project_dir, '..', '..')) # if single trial - session_dir = os.getcwd() if not 'Config.toml' in session_dir else session_dir + session_dir = os.getcwd() if not 'Config.toml' in os.listdir(session_dir) else session_dir calib_dir = [os.path.join(session_dir, c) for c in os.listdir(session_dir) if 'calib' in c.lower()][0] calib_file = glob.glob(os.path.join(calib_dir, '*.toml'))[0] # lastly created calibration file calib = toml.load(calib_file) @@ -322,6 +321,7 @@ def recap_triangulate(config, error, nb_cams_excluded, keypoints_names, cam_excl likelihood_threshold = config.get('triangulation').get('likelihood_threshold_triangulation') show_interp_indices = config.get('triangulation').get('show_interp_indices') interpolation_kind = config.get('triangulation').get('interpolation') + make_c3d = config.get('triangulation').get('make_c3d') handle_LR_swap = config.get('triangulation').get('handle_LR_swap') undistort_points = config.get('triangulation').get('undistort_points') @@ -374,8 +374,11 @@ def recap_triangulate(config, error, nb_cams_excluded, keypoints_names, cam_excl str_cam_excluded_count += f'Camera {k}: {int(np.round(v*100))}%, ' logging.info(str_cam_excluded_count) logging.info(f'\n3D coordinates are stored at {trc_path[n]}.') - - logging.info(f'\n\nLimb swapping was {"handled" if handle_LR_swap else "not handled"}.') + + logging.info('\n\n') + if make_c3d: + logging.info('All trc files have been converted to c3d.') + logging.info(f'Limb swapping was {"handled" if handle_LR_swap else "not handled"}.') logging.info(f'Lens distortions were {"taken into account" if undistort_points else "not taken into account"}.') @@ -693,7 +696,7 @@ def triangulate_all(config): # if batch session_dir = os.path.realpath(os.path.join(project_dir, '..', '..')) # if single trial - session_dir = os.getcwd() if not 'Config.toml' in session_dir else session_dir + session_dir = os.getcwd() if not 'Config.toml' in os.listdir(session_dir) else session_dir multi_person = config.get('project').get('multi_person') pose_model = config.get('pose').get('pose_model') frame_range = config.get('project').get('frame_range') @@ -705,7 +708,7 @@ def triangulate_all(config): undistort_points = config.get('triangulation').get('undistort_points') make_c3d = config.get('triangulation').get('make_c3d') frame_rate = config.get('project').get('frame_rate') - + calib_dir = [os.path.join(session_dir, c) for c in os.listdir(session_dir) if 'calib' in c.lower()][0] try: calib_file = glob.glob(os.path.join(calib_dir, '*.toml'))[0] # lastly created calibration file @@ -909,23 +912,25 @@ def triangulate_all(config): # Create TRC file trc_paths = [make_trc(config, Q_tot[n], keypoints_names, f_range, id_person=n) for n in range(len(Q_tot))] - + if make_c3d: + c3d_paths = [convert_to_c3d(t) for t in trc_paths] + # Reorder TRC files if multi_person and reorder_trc and len(trc_paths)>1: trc_id = retrieve_right_trc_order(trc_paths) [os.rename(t, t+'.old') for t in trc_paths] [os.rename(t+'.old', trc_paths[i]) for i, t in zip(trc_id,trc_paths)] + if make_c3d: + [os.rename(c, c+'.old') for c in c3d_paths] + [os.rename(c+'.old', c3d_paths[i]) for i, c in zip(trc_id,c3d_paths)] error_tot = [error_tot[i] for i in trc_id] nb_cams_excluded_tot = [nb_cams_excluded_tot[i] for i in trc_id] cam_excluded_count = [cam_excluded_count[i] for i in trc_id] interp_frames = [interp_frames[i] for i in trc_id] non_interp_frames = [non_interp_frames[i] for i in trc_id] - logging.info('\nThe trc files have been renamed to match the order of the static sequences.') - + logging.info('\nThe trc and c3d files have been renamed to match the order of the static sequences.') + + # Recap message recap_triangulate(config, error_tot, nb_cams_excluded_tot, keypoints_names, cam_excluded_count, interp_frames, non_interp_frames, trc_paths) - - # Save c3d - if make_c3d == True: - trc_to_c3d(project_dir, frame_rate, called_from='triangulation')