few changes to the conversion to c3d

This commit is contained in:
davidpagnon 2024-04-16 17:28:37 +02:00
parent 46652a8eaa
commit db8014c3f5
16 changed files with 240 additions and 168 deletions

1
.gitignore vendored
View File

@ -8,6 +8,7 @@ dist/
# **/*.mp4 # **/*.mp4
**/*.trc **/*.trc
**/*.sto **/*.sto
**/*.c3d
**/Calib_qualisys.toml **/Calib_qualisys.toml
**/pose-3d/ **/pose-3d/

View File

@ -164,6 +164,7 @@ make_c3d = false # also save triangulated data in c3d format
## Only works on BODY_25 and BODY_25B models ## 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_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 participant_mass = 70.0 # kg
make_c3d = false # save triangulated data in c3d format in addition to trc
[opensim] [opensim]

View File

@ -163,10 +163,11 @@
## Only works on BODY_25 and BODY_25B models ## 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_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 # participant_mass = 70.0 # kg
# make_c3d = false # save triangulated data in c3d format in addition to trc
# [opensim] # [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); # # 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 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'] # # At the Session level, add participant subdirectory, e.g. ['S00_P00_Participant/S00_P00_T00_StaticTrial', 'S00_P01_Participant/S00_P00_T00_StaticTrial']

View File

@ -163,10 +163,11 @@
## Only works on BODY_25 and BODY_25B models ## 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_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 # participant_mass = 70.0 # kg
# make_c3d = false # save triangulated data in c3d format in addition to trc
# [opensim] # [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); # # 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 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'] # # At the Session level, add participant subdirectory, e.g. ['S00_P00_Participant/S00_P00_T00_StaticTrial', 'S00_P01_Participant/S00_P00_T00_StaticTrial']

View File

@ -163,10 +163,11 @@ display_figures = false # true or false (lowercase)
## Only works on BODY_25 and BODY_25B models ## 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_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 # participant_mass = 70.0 # kg
# make_c3d = false # save triangulated data in c3d format in addition to trc
# [opensim] # [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); # # 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 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'] # # At the Session level, add participant subdirectory, e.g. ['S00_P00_Participant/S00_P00_T00_StaticTrial', 'S00_P01_Participant/S00_P00_T00_StaticTrial']

View File

@ -163,10 +163,12 @@
## Only works on BODY_25 and BODY_25B models ## 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_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 # 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] # [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); # # 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 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'] # # At the Session level, add participant subdirectory, e.g. ['S00_P00_Participant/S00_P00_T00_StaticTrial', 'S00_P01_Participant/S00_P00_T00_StaticTrial']

View File

@ -163,10 +163,11 @@
## Only works on BODY_25 and BODY_25B models ## 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_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 participant_mass = 25.0 # kg
# make_c3d = false # save triangulated data in c3d format in addition to trc
# [opensim] # [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); # # 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 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'] # # At the Session level, add participant subdirectory, e.g. ['S00_P00_Participant/S00_P00_T00_StaticTrial', 'S00_P01_Participant/S00_P00_T00_StaticTrial']

View File

@ -162,10 +162,11 @@
## Only works on BODY_25 and BODY_25B models ## 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_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 participant_mass = 70.0 # kg
# make_c3d = false # save triangulated data in c3d format in addition to trc
# [opensim] # [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); # # 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 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'] # # At the Session level, add participant subdirectory, e.g. ['S00_P00_Participant/S00_P00_T00_StaticTrial', 'S00_P01_Participant/S00_P00_T00_StaticTrial']

View File

@ -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 # 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 # 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 # 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 # type = 'butterworth' # butterworth, kalman, gaussian, LOESS, median, butterworth_on_speed
# display_figures = false # true or false (lowercase) # 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] # [filtering.butterworth]
# order = 4 # order = 4
@ -159,10 +159,11 @@ reorder_trc = true # only checked if multi_person analysis
## Only works on BODY_25 and BODY_25B models ## 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_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 participant_mass = [25.0, 70.0] # kg
make_c3d = true # save triangulated data in c3d format in addition to trc
# [opensim] # [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); # # 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 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'] # # At the Session level, add participant subdirectory, e.g. ['S00_P00_Participant/S00_P00_T00_StaticTrial', 'S00_P01_Participant/S00_P00_T00_StaticTrial']

View File

@ -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 ## 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_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 participant_mass = 70.0 # kg
make_c3d = false # save triangulated data in c3d format in addition to trc
[opensim] [opensim]

View File

@ -10,8 +10,6 @@
Converts c3d files to trc files. 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. 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: Usage:
from Pose2Sim.Utilities import c3d_to_trc; c3d_to_trc.c3d_to_trc_func(r'<input_c3d_file>') from Pose2Sim.Utilities import c3d_to_trc; c3d_to_trc.c3d_to_trc_func(r'<input_c3d_file>')
python -m c3d_to_trc -i input_c3d_file python -m c3d_to_trc -i input_c3d_file

View File

@ -1,73 +1,126 @@
""" #! /usr/bin/env python
Extracts marker data from a TRC file and creates a corresponding C3D file. # -*- coding: utf-8 -*-
Usage:
python trc_to_c3d.py --trc_path <path_to_trc_file> --f <frame_rate>
--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.
import os Usage:
from Pose2Sim.Utilities import trc_to_c3d; trc_to_c3d.trc_to_c3d_func(r'<input_trc_file>')
python -m trc_to_c3d -t <path_to_trc_path>
python -m trc_to_c3d --trc_path <path_to_trc_path> --c3d_path <output_c3d_file>
'''
## INIT
import argparse import argparse
import numpy as np import numpy as np
import pandas as pd
import c3d 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() lines = file.readlines()
marker_names_line = lines[3] marker_names_line = lines[3]
marker_names = marker_names_line.strip().split('\t')[2::3] marker_names = marker_names_line.strip().split('\t')[2::3]
trc_data = pd.read_csv(trc_file, sep='\t', skiprows=5) # time and marker coordinates
marker_coords = trc_data.iloc[:, 2:].to_numpy().reshape(-1, len(marker_names), 3) trc_data_np = np.genfromtxt(trc_path, skip_header=5, delimiter = '\t')[:,1:]
# marker_coords = np.nan_to_num(marker_coords, nan=0.0)
marker_coords *= 1000 # Convert from meters to millimeters
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 = 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) writer.set_point_labels(marker_names)
markers_group = writer.point_group for frame in trc_data_np:
residuals = np.full((len(marker_names), 1), 0.0)
for frame in marker_coords: cameras = np.zeros((len(marker_names), 1))
residuals = np.full((frame.shape[0], 1), 0.0) coords = frame[1:].reshape(-1,3)*1000
cameras = np.zeros((frame.shape[0], 1)) points = np.hstack((coords, residuals, cameras))
points = np.hstack((frame, residuals, cameras))
writer.add_frames([(points, np.array([]))]) writer.add_frames([(points, np.array([]))])
writer.set_start_frame(1) writer.set_start_frame(0)
writer._set_last_frame(len(marker_coords)) writer._set_last_frame(len(trc_data_np)-1)
c3d_file_path = trc_file.replace('.trc', '.c3d') with open(c3d_path, 'wb') as handle:
with open(c3d_file_path, 'wb') as handle:
writer.write(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(): def trc_to_c3d_func(*args):
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') Converts trc files to c3d files.
parser.add_argument('--f', type=int, required=True, help='Frame rate')
args = parser.parse_args() Usage:
from Pose2Sim.Utilities import trc_to_c3d; trc_to_c3d.trc_to_c3d_func(r'<input_trc_file>')
python -m trc_to_c3d -t <path_to_trc_path>
python -m trc_to_c3d --trc_path <path_to_trc_path> --c3d_path <output_c3d_file>
'''
trc_file = args.trc_path try:
frame_rate = args.f 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')
if not os.path.isfile(trc_file): marker_names, trc_data_np = extract_marker_data(trc_path)
print(f"Error: {trc_file} does not exist.") create_c3d_file(c3d_path, marker_names, trc_data_np)
return
trc_to_c3d(trc_file, frame_rate)
if __name__ == '__main__': 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)

View File

@ -16,10 +16,8 @@ import json
import numpy as np import numpy as np
import re import re
import cv2 import cv2
import os
import pandas as pd
import c3d import c3d
import glob import sys
import matplotlib as mpl import matplotlib as mpl
mpl.use('qt5agg') 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 FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
from PyQt5.QtWidgets import QMainWindow, QApplication, QWidget, QTabWidget, QVBoxLayout from PyQt5.QtWidgets import QMainWindow, QApplication, QWidget, QTabWidget, QVBoxLayout
import sys import warnings
warnings.filterwarnings("ignore", category=UserWarning, module="c3d")
## AUTHORSHIP INFORMATION ## AUTHORSHIP INFORMATION
@ -385,6 +384,83 @@ def zup2yup(Q):
return 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 ## CLASSES
class plotWindow(): class plotWindow():
''' '''
@ -436,85 +512,3 @@ class plotWindow():
def show(self): def show(self):
self.app.exec_() 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('<H', data_start))
# Create a C3D group for markers
markers_group = writer.point_group
# Add frame data
for frame in marker_coords:
# Add residual and camera columns
residuals = np.full((frame.shape[0], 1), 0.0) # Set residuals to 0.0
cameras = np.zeros((frame.shape[0], 1)) # Set cameras to 0
points = np.hstack((frame, residuals, cameras))
writer.add_frames([(points, np.array([]))])
# Set the trial start and end frames
writer.set_start_frame(1)
writer._set_last_frame(len(marker_coords))
# Write the C3D file
c3d_file_path = trc_file.replace('.trc', '.c3d')
with open(os.path.join(pose3d_dir, c3d_file_path), 'wb') as handle:
writer.write(handle)
print(f"-->c3d file saved: {c3d_file_path}")

View File

@ -37,7 +37,7 @@ from filterpy.kalman import KalmanFilter, rts_smoother
from filterpy.common import Q_discrete_white_noise from filterpy.common import Q_discrete_white_noise
from Pose2Sim.common import plotWindow from Pose2Sim.common import plotWindow
from Pose2Sim.common import trc_to_c3d from Pose2Sim.common import convert_to_c3d
## AUTHORSHIP INFORMATION ## AUTHORSHIP INFORMATION
__author__ = "David Pagnon" __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')) 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') 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') median_filter_kernel_size = config.get('filtering').get('median').get('kernel_size')
make_c3d = config.get('filtering').get('make_c3d')
# Recap # Recap
filter_mapping_recap = { filter_mapping_recap = {
@ -428,6 +429,8 @@ def recap_filter3d(config, trc_path):
} }
logging.info(filter_mapping_recap[filter_type]) logging.info(filter_mapping_recap[filter_type])
logging.info(f'Filtered 3D coordinates are stored at {trc_path}.\n') 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): def filter_all(config):
@ -497,10 +500,11 @@ def filter_all(config):
# Q_filt = Q_filt.fillna(' ') # Q_filt = Q_filt.fillna(' ')
Q_filt.to_csv(trc_o, sep='\t', index=False, header=None, lineterminator='\n') 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
recap_filter3d(config, t_out) recap_filter3d(config, t_out)
# Save c3d
if make_c3d == True:
trc_to_c3d(project_dir, frame_rate, called_from='filtering')

View File

@ -23,14 +23,15 @@ OUTPUT:
## INIT ## INIT
import os import os
import numpy as np import numpy as np
from Pose2Sim.MarkerAugmenter import utilsDataman
import copy import copy
import tensorflow as tf import tensorflow as tf
from Pose2Sim.MarkerAugmenter.utils import TRC2numpy
import json
import glob import glob
import logging import logging
from Pose2Sim.MarkerAugmenter import utilsDataman
from Pose2Sim.MarkerAugmenter.utils import TRC2numpy
from Pose2Sim.common import convert_to_c3d
## AUTHORSHIP INFORMATION ## AUTHORSHIP INFORMATION
__author__ = "Antoine Falisse, adapted by HunMin Kim" __author__ = "Antoine Falisse, adapted by HunMin Kim"
@ -65,7 +66,6 @@ def augmentTRC(config_dict):
project_dir = config_dict.get('project').get('project_dir') project_dir = config_dict.get('project').get('project_dir')
pathInputTRCFile = os.path.realpath(os.path.join(project_dir, 'pose-3d')) pathInputTRCFile = os.path.realpath(os.path.join(project_dir, 'pose-3d'))
pathOutputTRCFile = 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') subject_height = config_dict.get('markerAugmentation').get('participant_height')
if subject_height is None or subject_height == 0 or subject_height==0: 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.") 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: if not type(subject_height) == list:
subject_height = [subject_height] subject_height = [subject_height]
subject_mass = [subject_mass] subject_mass = [subject_mass]
make_c3d = config_dict.get('markerAugmentation').get('make_c3d')
augmenterDir = os.path.dirname(utilsDataman.__file__) augmenterDir = os.path.dirname(utilsDataman.__file__)
augmenterModelName = 'LSTM' augmenterModelName = 'LSTM'
augmenter_model = 'v0.3' augmenter_model = 'v0.3'
@ -151,8 +152,6 @@ def augmentTRC(config_dict):
trc_data_data = trc_data[:,1:] trc_data_data = trc_data[:,1:]
# Step 2: Normalize with reference marker position. # 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 referenceMarker_data = midhip_data # instead of trc_file.marker(referenceMarker) # change by HunMin
norm_trc_data_data = np.zeros((trc_data_data.shape[0], norm_trc_data_data = np.zeros((trc_data_data.shape[0],
trc_data_data.shape[1])) trc_data_data.shape[1]))
@ -245,7 +244,15 @@ def augmentTRC(config_dict):
# %% Return augmented .trc file # %% Return augmented .trc file
trc_file.write(pathOutputTRCFile) 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 return min_y_pos

View File

@ -50,8 +50,7 @@ from anytree.importer import DictImporter
import logging import logging
from Pose2Sim.common import retrieve_calib_params, computeP, weighted_triangulation, \ from Pose2Sim.common import retrieve_calib_params, computeP, weighted_triangulation, \
reprojection, euclidean_distance, sort_stringlist_by_last_number, zup2yup reprojection, euclidean_distance, sort_stringlist_by_last_number, zup2yup, convert_to_c3d
from Pose2Sim.common import trc_to_c3d
from Pose2Sim.skeletons import * from Pose2Sim.skeletons import *
@ -312,7 +311,7 @@ def recap_triangulate(config, error, nb_cams_excluded, keypoints_names, cam_excl
# if batch # if batch
session_dir = os.path.realpath(os.path.join(project_dir, '..', '..')) session_dir = os.path.realpath(os.path.join(project_dir, '..', '..'))
# if single trial # 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_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_file = glob.glob(os.path.join(calib_dir, '*.toml'))[0] # lastly created calibration file
calib = toml.load(calib_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') likelihood_threshold = config.get('triangulation').get('likelihood_threshold_triangulation')
show_interp_indices = config.get('triangulation').get('show_interp_indices') show_interp_indices = config.get('triangulation').get('show_interp_indices')
interpolation_kind = config.get('triangulation').get('interpolation') 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') handle_LR_swap = config.get('triangulation').get('handle_LR_swap')
undistort_points = config.get('triangulation').get('undistort_points') undistort_points = config.get('triangulation').get('undistort_points')
@ -375,7 +375,10 @@ def recap_triangulate(config, error, nb_cams_excluded, keypoints_names, cam_excl
logging.info(str_cam_excluded_count) logging.info(str_cam_excluded_count)
logging.info(f'\n3D coordinates are stored at {trc_path[n]}.') 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"}.') 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 # if batch
session_dir = os.path.realpath(os.path.join(project_dir, '..', '..')) session_dir = os.path.realpath(os.path.join(project_dir, '..', '..'))
# if single trial # 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') multi_person = config.get('project').get('multi_person')
pose_model = config.get('pose').get('pose_model') pose_model = config.get('pose').get('pose_model')
frame_range = config.get('project').get('frame_range') frame_range = config.get('project').get('frame_range')
@ -909,23 +912,25 @@ def triangulate_all(config):
# Create TRC file # 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))] 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 # Reorder TRC files
if multi_person and reorder_trc and len(trc_paths)>1: if multi_person and reorder_trc and len(trc_paths)>1:
trc_id = retrieve_right_trc_order(trc_paths) trc_id = retrieve_right_trc_order(trc_paths)
[os.rename(t, t+'.old') for t in 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)] [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] error_tot = [error_tot[i] for i in trc_id]
nb_cams_excluded_tot = [nb_cams_excluded_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] cam_excluded_count = [cam_excluded_count[i] for i in trc_id]
interp_frames = [interp_frames[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] 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 message
recap_triangulate(config, error_tot, nb_cams_excluded_tot, keypoints_names, cam_excluded_count, interp_frames, non_interp_frames, trc_paths) 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')