fixed batch processing
Before Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 9.1 KiB |
BIN
Content/P2S_calibration.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
Content/P2S_filtering.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
Content/P2S_markeraugmentation.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
Content/P2S_personassociation.png
Normal file
After Width: | Height: | Size: 23 KiB |
BIN
Content/P2S_poseestimation.png
Normal file
After Width: | Height: | Size: 50 KiB |
BIN
Content/P2S_synchronization.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
Content/P2S_triangulation.png
Normal file
After Width: | Height: | Size: 91 KiB |
Before Width: | Height: | Size: 3.3 MiB |
Before Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 576 KiB |
BIN
Content/synchro.jpg
Normal file
After Width: | Height: | Size: 49 KiB |
@ -52,7 +52,7 @@ output_format = 'openpose' # 'openpose', 'mmpose', 'deeplabcut', 'none' or a lis
|
||||
|
||||
|
||||
[synchronization]
|
||||
display_sync_plots = true # true or false (lowercase)
|
||||
display_sync_plots = false # true or false (lowercase)
|
||||
keypoints_to_consider = ['RWrist'] # 'all' if all points should be considered, for example if the participant did not perform any particicular sharp movement. In this case, the capture needs to be 5-10 seconds long at least
|
||||
# ['RWrist', 'RElbow'] list of keypoint names if you want to specify the keypoints to consider.
|
||||
approx_time_maxspeed = 'auto' # 'auto' if you want to consider the whole capture (default, slower if long sequences)
|
||||
|
@ -104,7 +104,7 @@ def determine_level(config_dir):
|
||||
|
||||
len_paths = [len(root.split(os.sep)) for root,dirs,files in os.walk(config_dir) if 'Config.toml' in files]
|
||||
if len_paths == []:
|
||||
raise FileNotFoundError('Please run Pose2Sim from a Session, Participant, or Trial directory.')
|
||||
raise FileNotFoundError('You need a Config.toml file in each trial or root folder.')
|
||||
level = max(len_paths) - min(len_paths) + 1
|
||||
return level
|
||||
|
||||
@ -172,7 +172,7 @@ def calibration(config=None):
|
||||
config_dict = config_dicts[0]
|
||||
try:
|
||||
session_dir = os.path.realpath([os.getcwd() if level==2 else os.path.join(os.getcwd(), '..')][0])
|
||||
[os.path.join(session_dir, c) for c in os.listdir(session_dir) if 'calib' in c.lower() ][0]
|
||||
[os.path.join(session_dir, c) for c in os.listdir(session_dir) if 'calib' in c.lower() and not c.lower().endswith('.py')][0]
|
||||
except:
|
||||
session_dir = os.path.realpath(os.getcwd())
|
||||
config_dict.get("project").update({"project_dir":session_dir})
|
||||
@ -183,17 +183,17 @@ def calibration(config=None):
|
||||
|
||||
# Run calibration
|
||||
calib_dir = [os.path.join(session_dir, c) for c in os.listdir(session_dir) if os.path.isdir(os.path.join(session_dir, c)) and 'calib' in c.lower()][0]
|
||||
logging.info("\n\n---------------------------------------------------------------------")
|
||||
logging.info("\n---------------------------------------------------------------------")
|
||||
logging.info("Camera calibration")
|
||||
logging.info(f"On {currentDateAndTime.strftime('%A %d. %B %Y, %H:%M:%S')}")
|
||||
logging.info("---------------------------------------------------------------------")
|
||||
logging.info(f"\nCalibration directory: {calib_dir}")
|
||||
logging.info(f"Calibration directory: {calib_dir}")
|
||||
logging.info("---------------------------------------------------------------------\n")
|
||||
start = time.time()
|
||||
|
||||
calibrate_cams_all(config_dict)
|
||||
|
||||
end = time.time()
|
||||
logging.info(f'\nCalibration took {end-start:.2f} s.')
|
||||
logging.info(f'\nCalibration took {end-start:.2f} s.\n')
|
||||
|
||||
|
||||
def poseEstimation(config=None):
|
||||
@ -228,17 +228,17 @@ def poseEstimation(config=None):
|
||||
frame_range = config_dict.get('project').get('frame_range')
|
||||
frames = ["all frames" if not frame_range else f"frames {frame_range[0]} to {frame_range[1]}"][0]
|
||||
|
||||
logging.info("\n\n---------------------------------------------------------------------")
|
||||
logging.info("\n---------------------------------------------------------------------")
|
||||
logging.info(f"Pose estimation for {seq_name}, for {frames}.")
|
||||
logging.info(f"On {currentDateAndTime.strftime('%A %d. %B %Y, %H:%M:%S')}")
|
||||
logging.info("---------------------------------------------------------------------")
|
||||
logging.info(f"\nProject directory: {project_dir}")
|
||||
logging.info(f"Project directory: {project_dir}")
|
||||
logging.info("---------------------------------------------------------------------\n")
|
||||
|
||||
rtm_estimator(config_dict)
|
||||
|
||||
end = time.time()
|
||||
elapsed = end - start
|
||||
logging.info(f'\nPose estimation took {time.strftime("%Hh%Mm%Ss", time.gmtime(elapsed))}.')
|
||||
logging.info(f'\nPose estimation took {time.strftime("%Hh%Mm%Ss", time.gmtime(elapsed))}.\n')
|
||||
|
||||
|
||||
def synchronization(config=None):
|
||||
@ -253,7 +253,7 @@ def synchronization(config=None):
|
||||
# Import the function
|
||||
from Pose2Sim.synchronization import synchronize_cams_all
|
||||
|
||||
# Determine the level at which the function is called (session:3, participant:2, trial:1)
|
||||
# Determine the level at which the function is called (root:2, trial:1)
|
||||
level, config_dicts = read_config_files(config)
|
||||
|
||||
if type(config)==dict:
|
||||
@ -272,17 +272,17 @@ def synchronization(config=None):
|
||||
currentDateAndTime = datetime.now()
|
||||
project_dir = os.path.realpath(config_dict.get('project').get('project_dir'))
|
||||
|
||||
logging.info("\n\n---------------------------------------------------------------------")
|
||||
logging.info("\n---------------------------------------------------------------------")
|
||||
logging.info("Camera synchronization")
|
||||
logging.info(f"On {currentDateAndTime.strftime('%A %d. %B %Y, %H:%M:%S')}")
|
||||
logging.info("---------------------------------------------------------------------")
|
||||
logging.info(f"\nProject directory: {project_dir}")
|
||||
logging.info(f"Project directory: {project_dir}")
|
||||
logging.info("---------------------------------------------------------------------\n")
|
||||
|
||||
synchronize_cams_all(config_dict)
|
||||
|
||||
end = time.time()
|
||||
elapsed = end-start
|
||||
logging.info(f'\nSynchronization took {time.strftime("%Hh%Mm%Ss", time.gmtime(elapsed))}.')
|
||||
logging.info(f'\nSynchronization took {time.strftime("%Hh%Mm%Ss", time.gmtime(elapsed))}.\n')
|
||||
|
||||
|
||||
def personAssociation(config=None):
|
||||
@ -297,7 +297,7 @@ def personAssociation(config=None):
|
||||
|
||||
from Pose2Sim.personAssociation import track_2d_all
|
||||
|
||||
# Determine the level at which the function is called (session:3, participant:2, trial:1)
|
||||
# Determine the level at which the function is called (root:2, trial:1)
|
||||
level, config_dicts = read_config_files(config)
|
||||
|
||||
if type(config)==dict:
|
||||
@ -319,17 +319,17 @@ def personAssociation(config=None):
|
||||
frame_range = config_dict.get('project').get('frame_range')
|
||||
frames = ["all frames" if frame_range == [] else f"frames {frame_range[0]} to {frame_range[1]}"][0]
|
||||
|
||||
logging.info("\n\n---------------------------------------------------------------------")
|
||||
logging.info("\n---------------------------------------------------------------------")
|
||||
logging.info(f"Associating persons for {seq_name}, for {frames}.")
|
||||
logging.info(f"On {currentDateAndTime.strftime('%A %d. %B %Y, %H:%M:%S')}")
|
||||
logging.info("---------------------------------------------------------------------")
|
||||
logging.info(f"\nProject directory: {project_dir}")
|
||||
logging.info(f"Project directory: {project_dir}")
|
||||
logging.info("---------------------------------------------------------------------\n")
|
||||
|
||||
track_2d_all(config_dict)
|
||||
|
||||
end = time.time()
|
||||
elapsed = end-start
|
||||
logging.info(f'\nAssociating persons took {time.strftime("%Hh%Mm%Ss", time.gmtime(elapsed))}.')
|
||||
logging.info(f'\nAssociating persons took {time.strftime("%Hh%Mm%Ss", time.gmtime(elapsed))}.\n')
|
||||
|
||||
|
||||
def triangulation(config=None):
|
||||
@ -343,7 +343,7 @@ def triangulation(config=None):
|
||||
|
||||
from Pose2Sim.triangulation import triangulate_all
|
||||
|
||||
# Determine the level at which the function is called (session:3, participant:2, trial:1)
|
||||
# Determine the level at which the function is called (root:2, trial:1)
|
||||
level, config_dicts = read_config_files(config)
|
||||
|
||||
if type(config)==dict:
|
||||
@ -365,17 +365,17 @@ def triangulation(config=None):
|
||||
frame_range = config_dict.get('project').get('frame_range')
|
||||
frames = ["all frames" if frame_range == [] else f"frames {frame_range[0]} to {frame_range[1]}"][0]
|
||||
|
||||
logging.info("\n\n---------------------------------------------------------------------")
|
||||
logging.info("\n---------------------------------------------------------------------")
|
||||
logging.info(f"Triangulation of 2D points for {seq_name}, for {frames}.")
|
||||
logging.info(f"On {currentDateAndTime.strftime('%A %d. %B %Y, %H:%M:%S')}")
|
||||
logging.info("---------------------------------------------------------------------")
|
||||
logging.info(f"\nProject directory: {project_dir}")
|
||||
logging.info(f"Project directory: {project_dir}")
|
||||
logging.info("---------------------------------------------------------------------\n")
|
||||
|
||||
triangulate_all(config_dict)
|
||||
|
||||
end = time.time()
|
||||
elapsed = end-start
|
||||
logging.info(f'\nTriangulation took {time.strftime("%Hh%Mm%Ss", time.gmtime(elapsed))}.')
|
||||
logging.info(f'\nTriangulation took {time.strftime("%Hh%Mm%Ss", time.gmtime(elapsed))}.\n')
|
||||
|
||||
|
||||
def filtering(config=None):
|
||||
@ -389,7 +389,7 @@ def filtering(config=None):
|
||||
|
||||
from Pose2Sim.filtering import filter_all
|
||||
|
||||
# Determine the level at which the function is called (session:3, participant:2, trial:1)
|
||||
# Determine the level at which the function is called (root:2, trial:1)
|
||||
level, config_dicts = read_config_files(config)
|
||||
|
||||
if type(config)==dict:
|
||||
@ -410,14 +410,16 @@ def filtering(config=None):
|
||||
frame_range = config_dict.get('project').get('frame_range')
|
||||
frames = ["all frames" if frame_range == [] else f"frames {frame_range[0]} to {frame_range[1]}"][0]
|
||||
|
||||
logging.info("\n\n---------------------------------------------------------------------")
|
||||
logging.info("\n---------------------------------------------------------------------")
|
||||
logging.info(f"Filtering 3D coordinates for {seq_name}, for {frames}.")
|
||||
logging.info(f"On {currentDateAndTime.strftime('%A %d. %B %Y, %H:%M:%S')}")
|
||||
logging.info("---------------------------------------------------------------------")
|
||||
logging.info(f"\nProject directory: {project_dir}\n")
|
||||
logging.info(f"Project directory: {project_dir}\n")
|
||||
logging.info("---------------------------------------------------------------------\n")
|
||||
|
||||
filter_all(config_dict)
|
||||
|
||||
logging.info('\n')
|
||||
|
||||
|
||||
def markerAugmentation(config=None):
|
||||
'''
|
||||
@ -449,17 +451,17 @@ def markerAugmentation(config=None):
|
||||
frame_range = config_dict.get('project').get('frame_range')
|
||||
frames = ["all frames" if frame_range == [] else f"frames {frame_range[0]} to {frame_range[1]}"][0]
|
||||
|
||||
logging.info("\n\n---------------------------------------------------------------------")
|
||||
logging.info("\n---------------------------------------------------------------------")
|
||||
logging.info(f"Augmentation process for {seq_name}, for {frames}.")
|
||||
logging.info(f"On {currentDateAndTime.strftime('%A %d. %B %Y, %H:%M:%S')}")
|
||||
logging.info("---------------------------------------------------------------------")
|
||||
logging.info(f"\nProject directory: {project_dir}\n")
|
||||
logging.info(f"Project directory: {project_dir}")
|
||||
logging.info("---------------------------------------------------------------------\n")
|
||||
|
||||
augmentTRC(config_dict)
|
||||
|
||||
end = time.time()
|
||||
elapsed = end-start
|
||||
logging.info(f'\nMarker augmentation took {time.strftime("%Hh%Mm%Ss", time.gmtime(elapsed))}.')
|
||||
logging.info(f'\nMarker augmentation took {time.strftime("%Hh%Mm%Ss", time.gmtime(elapsed))}.\n')
|
||||
|
||||
|
||||
def opensimProcessing(config=None):
|
||||
@ -477,7 +479,7 @@ def opensimProcessing(config=None):
|
||||
# # TODO
|
||||
# from Pose2Sim.opensimProcessing import opensim_processing_all
|
||||
|
||||
# # Determine the level at which the function is called (session:3, participant:2, trial:1)
|
||||
# # Determine the level at which the function is called (root:2, trial:1)
|
||||
# level, config_dicts = read_config_files(config)
|
||||
|
||||
# if type(config)==dict:
|
||||
@ -499,23 +501,23 @@ def opensimProcessing(config=None):
|
||||
# frame_range = config_dict.get('project').get('frame_range')
|
||||
# frames = ["all frames" if frame_range == [] else f"frames {frame_range[0]} to {frame_range[1]}"][0]
|
||||
|
||||
# logging.info("\n\n---------------------------------------------------------------------")
|
||||
# logging.info("\n---------------------------------------------------------------------")
|
||||
# # if static_file in project_dir:
|
||||
# # logging.info(f"Scaling model with <STATIC TRC FILE>.")
|
||||
# # else:
|
||||
# # logging.info(f"Running inverse kinematics <MOTION TRC FILE>.")
|
||||
# logging.info(f"On {currentDateAndTime.strftime('%A %d. %B %Y, %H:%M:%S')}")
|
||||
# logging.info("---------------------------------------------------------------------")
|
||||
# logging.info(f"\nOpenSim output directory: {project_dir}")
|
||||
# logging.info(f"OpenSim output directory: {project_dir}")
|
||||
# logging.info("---------------------------------------------------------------------\n")
|
||||
|
||||
# opensim_processing_all(config_dict)
|
||||
|
||||
# end = time.time()
|
||||
# elapsed = end-start
|
||||
# # if static_file in project_dir:
|
||||
# # logging.info(f'Model scaling took {time.strftime("%Hh%Mm%Ss", time.gmtime(elapsed))}.')
|
||||
# # logging.info(f'Model scaling took {time.strftime("%Hh%Mm%Ss", time.gmtime(elapsed))}.\n')
|
||||
# # else:
|
||||
# # logging.info(f'Inverse kinematics took {time.strftime("%Hh%Mm%Ss", time.gmtime(elapsed))}.')
|
||||
# # logging.info(f'Inverse kinematics took {time.strftime("%Hh%Mm%Ss", time.gmtime(elapsed))}.\n')
|
||||
|
||||
|
||||
def runAll(config=None, do_calibration=True, do_poseEstimation=True, do_synchronization=True, do_personAssociation=True, do_triangulation=True, do_filtering=True, do_markerAugmentation=True, do_opensimProcessing=True):
|
||||
@ -524,82 +526,101 @@ def runAll(config=None, do_calibration=True, do_poseEstimation=True, do_synchron
|
||||
and may even lead to worse results. Think carefully before running all.
|
||||
'''
|
||||
|
||||
# Determine the level at which the function is called (session:3, participant:2, trial:1)
|
||||
level, config_dicts = read_config_files(config)
|
||||
|
||||
if type(config)==dict:
|
||||
config_dict = config_dicts[0]
|
||||
if config_dict.get('project').get('project_dir') == None:
|
||||
raise ValueError('Please specify the project directory in config_dict:\n \
|
||||
config_dict.get("project").update({"project_dir":"<YOUR_TRIAL_DIRECTORY>"})')
|
||||
|
||||
# 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'), '..'))
|
||||
setup_logging(session_dir)
|
||||
|
||||
# Batch process all trials
|
||||
for config_dict in config_dicts:
|
||||
start = time.time()
|
||||
currentDateAndTime = datetime.now()
|
||||
project_dir = os.path.realpath(config_dict.get('project').get('project_dir'))
|
||||
seq_name = os.path.basename(project_dir)
|
||||
frame_range = config_dict.get('project').get('frame_range')
|
||||
frames = ["all frames" if frame_range == [] else f"frames {frame_range[0]} to {frame_range[1]}"][0]
|
||||
currentDateAndTime = datetime.now()
|
||||
start = time.time()
|
||||
|
||||
logging.info("\n\n=====================================================================")
|
||||
logging.info(f"RUNNING ALL.")
|
||||
logging.info(f"On {currentDateAndTime.strftime('%A %d. %B %Y, %H:%M:%S')}")
|
||||
logging.info(f"Project directory: {session_dir}\n")
|
||||
logging.info("=====================================================================")
|
||||
|
||||
if do_calibration:
|
||||
logging.info("\n\n=====================================================================")
|
||||
logging.info(f"RUNNING ALL FOR {seq_name}, FOR {frames}.")
|
||||
logging.info(f"On {currentDateAndTime.strftime('%A %d. %B %Y, %H:%M:%S')}")
|
||||
logging.info('Running calibration...')
|
||||
logging.info("=====================================================================")
|
||||
calibration(config)
|
||||
else:
|
||||
logging.info("\n\n=====================================================================")
|
||||
logging.info('Skipping calibration.')
|
||||
logging.info("=====================================================================")
|
||||
logging.info(f"\nProject directory: {project_dir}\n")
|
||||
|
||||
if do_calibration:
|
||||
logging.info('\nRUNNING CALIBRATION...')
|
||||
calibration(config)
|
||||
else:
|
||||
logging.info('\nSKIPPING CALIBRATION.')
|
||||
if do_poseEstimation:
|
||||
logging.info("\n\n=====================================================================")
|
||||
logging.info('Running pose estimation...')
|
||||
logging.info("=====================================================================")
|
||||
poseEstimation(config)
|
||||
else:
|
||||
logging.info("\n\n=====================================================================")
|
||||
logging.info('Skipping pose estimation.')
|
||||
logging.info("=====================================================================")
|
||||
|
||||
if do_poseEstimation:
|
||||
logging.info('\nRUNNING POSE ESTIMATION...')
|
||||
poseEstimation(config)
|
||||
else:
|
||||
logging.info('\nSKIPPING POSE ESTIMATION.')
|
||||
if do_synchronization:
|
||||
logging.info("\n\n=====================================================================")
|
||||
logging.info('Running synchronization...')
|
||||
logging.info("=====================================================================")
|
||||
synchronization(config)
|
||||
else:
|
||||
logging.info("\n\n=====================================================================")
|
||||
logging.info('Skipping synchronization.')
|
||||
logging.info("=====================================================================")
|
||||
|
||||
if do_synchronization:
|
||||
logging.info('\nRUNNING SYNCHRONIZATION...')
|
||||
synchronization(config)
|
||||
else:
|
||||
logging.info('\nSKIPPING SYNCHRONIZATION.')
|
||||
if do_personAssociation:
|
||||
logging.info("\n\n=====================================================================")
|
||||
logging.info('Running person association...')
|
||||
logging.info("=====================================================================")
|
||||
personAssociation(config)
|
||||
else:
|
||||
logging.info("\n\n=====================================================================")
|
||||
logging.info('Skipping person association.')
|
||||
logging.info("=====================================================================")
|
||||
|
||||
if do_personAssociation:
|
||||
logging.info('\nRUNNING PERSON ASSOCIATION...')
|
||||
personAssociation(config)
|
||||
else:
|
||||
logging.info('\nSKIPPING PERSON ASSOCIATION.')
|
||||
if do_triangulation:
|
||||
logging.info("\n\n=====================================================================")
|
||||
logging.info('Running triangulation...')
|
||||
logging.info("=====================================================================")
|
||||
triangulation(config)
|
||||
else:
|
||||
logging.info("\n\n=====================================================================")
|
||||
logging.info('Skipping triangulation.')
|
||||
logging.info("=====================================================================")
|
||||
|
||||
if do_triangulation:
|
||||
logging.info('\nRUNNING TRIANGULATION...')
|
||||
triangulation(config)
|
||||
else:
|
||||
logging.info('\nSKIPPING TRIANGULATION.')
|
||||
if do_filtering:
|
||||
logging.info("\n\n=====================================================================")
|
||||
logging.info('Running filtering...')
|
||||
logging.info("=====================================================================")
|
||||
filtering(config)
|
||||
else:
|
||||
logging.info("\n\n=====================================================================")
|
||||
logging.info('Skipping filtering.')
|
||||
logging.info("=====================================================================")
|
||||
|
||||
if do_filtering:
|
||||
logging.info('\nRUNNING FILTERING...')
|
||||
filtering(config)
|
||||
else:
|
||||
logging.info('\nSKIPPING FILTERING.')
|
||||
if do_markerAugmentation:
|
||||
logging.info("\n\n=====================================================================")
|
||||
logging.info('Running marker augmentation.')
|
||||
logging.info("=====================================================================")
|
||||
markerAugmentation(config)
|
||||
else:
|
||||
logging.info("\n\n=====================================================================")
|
||||
logging.info('Skipping marker augmentation.')
|
||||
logging.info("\n\n=====================================================================")
|
||||
|
||||
if do_markerAugmentation:
|
||||
logging.info('\nRUNNING MARKER AUGMENTATION.')
|
||||
markerAugmentation(config)
|
||||
else:
|
||||
logging.info('\nSKIPPING MARKER AUGMENTATION.')
|
||||
# if do_opensimProcessing:
|
||||
# logging.info("\n\n=====================================================================")
|
||||
# logging.info('Running opensim processing.')
|
||||
# logging.info("=====================================================================")
|
||||
# opensimProcessing(config)
|
||||
# else:
|
||||
# logging.info("\n\n=====================================================================")
|
||||
# logging.info('Skipping opensim processing.')
|
||||
# logging.info("=====================================================================")
|
||||
|
||||
# if do_opensimProcessing:
|
||||
# logging.info('\nRUNNING OPENSIM PROCESSING.')
|
||||
# opensimProcessing(config)
|
||||
# else:
|
||||
# logging.info('\nSKIPPING OPENSIM PROCESSING.')
|
||||
|
||||
end = time.time()
|
||||
elapsed = end-start
|
||||
logging.info(f'\nRUNNING ALL FUNCTIONS TOOK {time.strftime("%Hh%Mm%Ss", time.gmtime(elapsed))}.')
|
||||
end = time.time()
|
||||
elapsed = end-start
|
||||
logging.info(f'\nRUNNING ALL FUNCTIONS TOOK {time.strftime("%Hh%Mm%Ss", time.gmtime(elapsed))}.\n')
|
@ -66,7 +66,7 @@ class TestWorkflow(unittest.TestCase):
|
||||
@patch('builtins.input', return_value='no') # Mock input() to return 'no'
|
||||
def test_workflow(self, mock_input):
|
||||
'''
|
||||
SINGLE-PERSON and MULTI-PERSON:
|
||||
SINGLE-PERSON, MULTI-PERSON, BATCH PROCESSING:
|
||||
- calibration
|
||||
- pose estimation
|
||||
- synchronization
|
||||
@ -74,6 +74,7 @@ class TestWorkflow(unittest.TestCase):
|
||||
- triangulation
|
||||
- filtering
|
||||
- marker augmentation
|
||||
- run all
|
||||
|
||||
N.B.: Calibration from scene dimensions is not tested, as it requires the
|
||||
user to click points on the image.
|
||||
@ -86,9 +87,9 @@ class TestWorkflow(unittest.TestCase):
|
||||
'''
|
||||
|
||||
|
||||
##################
|
||||
# SINGLE-PERSON #
|
||||
##################
|
||||
###################
|
||||
# SINGLE-PERSON #
|
||||
###################
|
||||
|
||||
project_dir = '../Demo_SinglePerson'
|
||||
config_dict = toml.load(os.path.join(project_dir, 'Config.toml'))
|
||||
@ -110,10 +111,13 @@ class TestWorkflow(unittest.TestCase):
|
||||
Pose2Sim.markerAugmentation(config_dict)
|
||||
# Pose2Sim.kinematics(config_dict)
|
||||
|
||||
config_dict.get("pose").update({"overwrite_pose":False})
|
||||
Pose2Sim.runAll(config_dict)
|
||||
|
||||
##################
|
||||
# MULTI-PERSON #
|
||||
##################
|
||||
|
||||
####################
|
||||
# MULTI-PERSON #
|
||||
####################
|
||||
|
||||
project_dir = '../Demo_MultiPerson'
|
||||
config_dict = toml.load(os.path.join(project_dir, 'Config.toml'))
|
||||
@ -134,6 +138,19 @@ class TestWorkflow(unittest.TestCase):
|
||||
Pose2Sim.markerAugmentation(config_dict)
|
||||
# Pose2Sim.kinematics(config_dict)
|
||||
|
||||
config_dict.get("pose").update({"overwrite_pose":False})
|
||||
Pose2Sim.runAll(config_dict)
|
||||
|
||||
|
||||
####################
|
||||
# BATCH PROCESSING #
|
||||
####################
|
||||
|
||||
project_dir = '../Demo_Batch'
|
||||
os.chdir(project_dir)
|
||||
|
||||
Pose2Sim.runAll()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
@ -266,7 +266,7 @@ def synchronize_cams_all(config_dict):
|
||||
|
||||
# Warning if multi_person
|
||||
if multi_person:
|
||||
logging.warning('\nYou set your project as a multi-person one: make sure you set `approx_time_maxspeed` and `time_range_around_maxspeed` at times where one single persons are in the scene, you you may get inaccurate results.')
|
||||
logging.warning('\nYou set your project as a multi-person one: make sure you set `approx_time_maxspeed` and `time_range_around_maxspeed` at times where one single person is in the scene, or you may get inaccurate results.')
|
||||
do_synchro = input('Do you want to continue? (y/n)')
|
||||
if do_synchro.lower() not in ["y","yes"]:
|
||||
logging.warning('Synchronization cancelled.')
|
||||
|
151
README.md
@ -18,7 +18,7 @@
|
||||
|
||||
> **_News_: Version 0.9:**\
|
||||
> **Pose estimation with RTMPose is now included in Pose2Sim!**\
|
||||
> **Other recently added features**: Automatic camera synchronization, multi-person analysis, Blender visualization, Marker augmentation.
|
||||
> **Other recently added features**: Automatic camera synchronization, multi-person analysis, Blender visualization, Marker augmentation, Batch processing.
|
||||
<!-- Incidentally, right/left limb swapping is now handled, which is useful if few cameras are used;\
|
||||
and lens distortions are better taken into account.\ -->
|
||||
> To upgrade, type `pip install pose2sim --upgrade` (note that you need Python 3.9 or higher).
|
||||
@ -65,9 +65,7 @@ If you can only use one single camera and don't mind losing some accuracy, pleas
|
||||
5. [Demonstration Part-4 (optional): Try multi-person analysis](#demonstration-part-4-optional-try-multi-person-analysis)
|
||||
6. [Demonstration Part-5 (optional): Try batch processing](#demonstration-part-5-optional-try-batch-processing)
|
||||
2. [Use on your own data](#use-on-your-own-data)
|
||||
1. [Setting your project up](#setting-your-project-up)
|
||||
1. [Retrieve the folder structure](#retrieve-the-folder-structure)
|
||||
2. [Single Trial vs. Batch processing](#single-trial-vs-batch-processing)
|
||||
1. [Setting up your project](#setting-up-your-project)
|
||||
2. [2D pose estimation](#2d-pose-estimation)
|
||||
1. [With RTMPose (default)](#with-rtmpose-default)
|
||||
2. [With MMPose (coming soon)](#with-mmpose-coming-soon)
|
||||
@ -167,12 +165,19 @@ Pose2Sim.markerAugmentation()
|
||||
```
|
||||
3D results are stored as .trc files in each trial folder in the `pose-3d` directory.
|
||||
|
||||
*N.B.:* Default parameters have been provided in [Config.toml](https://github.com/perfanalytics/pose2sim/blob/main/Pose2Sim/Demo_SinglePerson/Config.toml) but can be edited.
|
||||
|
||||
</br>
|
||||
|
||||
__*GO FURTHER:*__\
|
||||
Try the calibration tool by changing `calibration_type` to `calculate` instead of `convert` in [Config.toml](https://github.com/perfanalytics/pose2sim/blob/main/Pose2Sim/Demo_SinglePerson/Config.toml) (more info [there](#calculate-from-scratch)).
|
||||
**Note:**
|
||||
- Default parameters have been provided in [Config.toml](https://github.com/perfanalytics/pose2sim/blob/main/Pose2Sim/Demo_SinglePerson/Config.toml) but can be edited.
|
||||
- You can run all stages at once:
|
||||
``` python
|
||||
from Pose2Sim import Pose2Sim
|
||||
Pose2Sim.runAll(do_calibration=True, do_poseEstimation=True, do_synchronization=True, do_personAssociation=True, do_triangulation=True, do_filtering=True, do_markerAugmentation=True, do_opensimProcessing=True)
|
||||
```
|
||||
- Try the calibration tool by changing `calibration_type` to `calculate` instead of `convert` in [Config.toml](https://github.com/perfanalytics/pose2sim/blob/main/Pose2Sim/Demo_SinglePerson/Config.toml) (more info [there](#calculate-from-scratch)).
|
||||
</br>
|
||||
|
||||
|
||||
|
||||
<br/>
|
||||
|
||||
@ -225,14 +230,13 @@ https://github.com/perfanalytics/pose2sim/assets/54667644/5d7c858f-7e46-40c1-928
|
||||
|
||||
Go to the Multi-participant Demo folder: `cd <path>\Pose2Sim\Demo_MultiPerson`. \
|
||||
Type `ipython`, and try the following code:
|
||||
|
||||
``` python
|
||||
from Pose2Sim import Pose2Sim
|
||||
Pose2Sim.personAssociation()
|
||||
Pose2Sim.triangulation()
|
||||
Pose2Sim.filtering()
|
||||
Pose2Sim.markerAugmentation()
|
||||
Pose2Sim.runAll(do_synchronization=False) # Synchronization possible, but tricky with multiple persons
|
||||
```
|
||||
|
||||
|
||||
One .trc file per participant will be generated and stored in the `pose-3d` directory.\
|
||||
You can then run OpenSim scaling and inverse kinematics for each resulting .trc file as in [Demonstration Part-2](#demonstration-part-2-obtain-3d-joint-angles-with-opensim).\
|
||||
You can also visualize your results with Blender as in [Demonstration Part-3](#demonstration-part-3-optional-visualize-your-results-with-blender).
|
||||
@ -241,67 +245,48 @@ You can also visualize your results with Blender as in [Demonstration Part-3](#d
|
||||
Set *[triangulation]* `reorder_trc = true` if you need to run OpenSim and to match the generated .trc files with the static trials.\
|
||||
Make sure that the order of *[markerAugmentation]* `participant_height` and `participant_mass` matches the order of the static trials.
|
||||
|
||||
*N.B.:* Note that in the case of our floating ghost participant, marker augmentation may worsen the results. See [Marker augmentation](#marker-augmentation) for instruction on when and when not to use it.
|
||||
<br/>
|
||||
|
||||
## Demonstration Part-5 (optional): Try batch processing
|
||||
> _**Run numerous analysis with different parameters and minimal friction.**_
|
||||
|
||||
Go to the Batch Demo folder: `cd <path>\Pose2Sim\Demo_Batch`. \
|
||||
Type `ipython`, and try the following code:
|
||||
|
||||
``` python
|
||||
from Pose2Sim import Pose2Sim
|
||||
Pose2Sim.runAll()
|
||||
```
|
||||
|
||||
The batch processing structure requires a `Config.toml` file in each of the trial directories. Global parameters are given in the `Config.toml` file of the `BatchSession` folder. They can be altered for specific or `Trials` by uncommenting keys and their values in their respective `Config.toml` files.
|
||||
|
||||
Run Pose2Sim from the `BatchSession` folder if you want to batch process the whole session, or from a `Trial` folder if you want to process only a specific trial.
|
||||
|
||||
|
||||
| SingleTrial | BatchSession |
|
||||
|-----------------|--------------------|
|
||||
| <pre><b>SingleTrial</b> <br>├── <b>calibration</b><br>├── <b>videos</b><br>└── <i><b>Config.toml</i></b></pre> | <pre><b>BatchSession</b> <br>├── <b>calibration</b> <br>├── Trial_1 <br>│ ├── <b>videos</b> <br>│ └── <i><b>Config.toml</i></b><br>├── Trial_2 <br>│ ├── <b>videos</b> <br>│ └── <i><b>Config.toml</i></b><br>└── <i><b>Config.toml</i></b></pre> |
|
||||
|
||||
|
||||
For example, try uncommenting `[project]` and set `frame_range = [10,99]`, or uncomment `[pose]` and set `mode = 'lightweight'` in the `Config.toml` file of `Trial_1`.
|
||||
|
||||
|
||||
</br></br>
|
||||
|
||||
# Use on your own data
|
||||
|
||||
> _**Deeper explanations and instructions are given below.**_ \
|
||||
> N.B.: If a step is not relevant for your use case (synchronization, person association, marker augmentation...), you can skip it.
|
||||
> **N.B.: If a step is not relevant for your use case (synchronization, person association, marker augmentation...), you can skip it.**
|
||||
|
||||
</br>
|
||||
|
||||
## Setting your project up
|
||||
## Setting up your project
|
||||
> _**Get ready for automatic batch processing.**_
|
||||
|
||||
### Retrieve the folder structure
|
||||
1. Open a terminal, enter `pip show pose2sim`, report package location. \
|
||||
Copy this path and do `cd <path>\pose2sim`.
|
||||
2. Copy the *Demo_SinglePerson* or *Demo_MultiPerson* folder wherever you like, and rename it as you wish.
|
||||
2. Copy-paste the *Demo_SinglePerson*, *Demo_MultiPerson*, or *Demo_Batch* folder wherever you like, and rename it as you wish.
|
||||
3. The rest of the tutorial will explain to you how to populate the `Calibration` and `videos` folders, edit the [Config.toml](https://github.com/perfanalytics/pose2sim/blob/main/Pose2Sim/Demo_SinglePerson/Config.toml) files, and run each Pose2Sim step.
|
||||
|
||||
</br>
|
||||
|
||||
### Single Trial vs. Batch processing
|
||||
|
||||
> _**Copy and edit either the [Demo_SinglePerson](https://github.com/perfanalytics/pose2sim/blob/main/Pose2Sim/Demo_SinglePerson) folder or the [S00_Demo_BatchSession](https://github.com/perfanalytics/pose2sim/blob/main/Pose2Sim/S00_Demo_BatchSession) one.**_
|
||||
> - Single trial is more straight-forward to set up for isolated experiments
|
||||
> - Batch processing allows you to run numerous analysis with different parameters and minimal friction
|
||||
|
||||
|
||||
|
||||
#### Single trial
|
||||
|
||||
The single trial folder should contain a `Config.toml` file, a `calibration` folder, and a `pose` folder, the latter including one subfolder for each camera.
|
||||
|
||||
<pre>
|
||||
SingleTrial \
|
||||
├── calibration \
|
||||
├── pose \
|
||||
└── <i><b>Config.toml</i></b>
|
||||
</pre>
|
||||
|
||||
#### Batch processing
|
||||
|
||||
For batch processing, each session directory should follow a `Session -> Participant -> Trial` structure, with a `Config.toml` file in each of the directory levels.
|
||||
|
||||
<pre>
|
||||
Session_s1 \ <i><b>Config.toml</i></b>
|
||||
├── Calibration\
|
||||
└── Participant_p1 \ <i><b>Config.toml</i></b>
|
||||
└── Trial_t1 \ <i><b>Config.toml</i></b>
|
||||
└── pose \
|
||||
</pre>
|
||||
|
||||
Run Pose2Sim from the `Session` folder if you want to batch process the whole session, from the `Participant` folder if you want to batch process all the trials of a participant, or from the `Trial` folder if you want to process a single trial. There should be one `Calibration` folder per session.
|
||||
|
||||
Global parameters are given in the `Config.toml` file of the `Session` folder, and can be altered for specific `Participants` or `Trials` by uncommenting keys and their values in their respective Config.toml files.\
|
||||
Try uncommenting `[project]` and set `frame_range = [10,300]` for a Participant for example, or uncomment `[filtering.butterworth]` and set `cut_off_frequency = 10` for a Trial.
|
||||
|
||||
</br>
|
||||
|
||||
## 2D pose estimation
|
||||
> _**Estimate 2D pose from images with RTMPose or another pose estimation solution.**_
|
||||
|
||||
@ -318,6 +303,10 @@ from Pose2Sim import Pose2Sim
|
||||
Pose2Sim.poseEstimation()
|
||||
```
|
||||
|
||||
<img src="Content/P2S_poseestimation.png" width="760">
|
||||
|
||||
</br>
|
||||
|
||||
*N.B.:* The `GPU` will be used with ONNX backend if a valid CUDA installation is found (or MPS with MacOS), otherwise the `CPU` will be used with OpenVINO backend.\
|
||||
*N.B.:* Pose estimation can be run in `lightweight`, `balanced`, or `performance` mode.\
|
||||
*N.B.:* Pose estimation can be dramatically sped up by increasing the value of `det_frequency`. In that case, the detection is only done every `det_frequency` frames, and bounding boxes are tracked inbetween (keypoint detection is still performed on all frames).\
|
||||
@ -407,8 +396,12 @@ from Pose2Sim import Pose2Sim
|
||||
Pose2Sim.calibration()
|
||||
```
|
||||
|
||||
Output:\
|
||||
<img src="Content/Calib2D.png" width="760">
|
||||
|
||||
<img src="Content/P2S_calibration.png" width="760">
|
||||
|
||||
</br>
|
||||
Output file:
|
||||
|
||||
<img src="Content/CalibFile.png" width="760">
|
||||
|
||||
|
||||
@ -508,10 +501,19 @@ from Pose2Sim import Pose2Sim
|
||||
Pose2Sim.synchronization()
|
||||
```
|
||||
|
||||
<img src="Content/P2S_synchronization.png" width="760">
|
||||
|
||||
</br>
|
||||
|
||||
For each camera, this computes mean vertical speed for the chosen keypoints, and finds the time offset for which their correlation is highest.\
|
||||
All keypoints can be taken into account, or a subset of them. The user can also specify a time for each camera when only one participant is in the scene, preferably performing a clear vertical motion.
|
||||
|
||||
*N.B.:* Works best when only one participant is in the scene, at a roughly equal distance from all cameras and when the capture is at least 5-10 seconds long.
|
||||
<img src="Content/synchro.jpg" width="760">
|
||||
|
||||
*N.B.:* Works best when:
|
||||
- only one participant is in the scene (set `approx_time_maxspeed` and `time_range_around_maxspeed` accordingly)
|
||||
- the participant is at a roughly equal distance from all cameras
|
||||
- when the capture is at least 5 seconds long
|
||||
|
||||
*N.B.:* Alternatively, use a flashlight, a clap, or a clear event to synchronize cameras. GoPro cameras can also be synchronized with a timecode, by GPS (outdoors) or with their app (slightly less reliable).
|
||||
|
||||
@ -521,8 +523,9 @@ All keypoints can be taken into account, or a subset of them. The user can also
|
||||
### Associate persons across cameras
|
||||
|
||||
> _**If `multi_person` is set to `false`, the algorithm chooses the person for whom the reprojection error is smallest.\
|
||||
If `multi_person` is set to `true`, it associates across views the people for whom the distances between epipolar lines are the smallest. People are then associated across frames according to their displacement speed.**_ \
|
||||
***N.B.:** Skip this step if only one person is in the field of view.*
|
||||
If `multi_person` is set to `true`, it associates across views the people for whom the distances between epipolar lines are the smallest. People are then associated across frames according to their displacement speed.**_
|
||||
|
||||
> ***N.B.:** Skip this step if only one person is in the field of view.*
|
||||
|
||||
Open an Anaconda prompt or a terminal in a `Session`, `Participant`, or `Trial` folder.\
|
||||
Type `ipython`.
|
||||
@ -531,10 +534,11 @@ from Pose2Sim import Pose2Sim
|
||||
Pose2Sim.personAssociation()
|
||||
```
|
||||
|
||||
Check printed output. If results are not satisfying, try and release the constraints in the [Config.toml](https://github.com/perfanalytics/pose2sim/blob/main/Pose2Sim/S00_Demo_Session/Config.toml) file.
|
||||
<img src="Content/P2S_personassociation.png" width="760">
|
||||
|
||||
Output:\
|
||||
<img src="Content/Track2D.png" width="760">
|
||||
</br>
|
||||
|
||||
Check printed output. If results are not satisfying, try and release the constraints in the [Config.toml](https://github.com/perfanalytics/pose2sim/blob/main/Pose2Sim/S00_Demo_Session/Config.toml) file.
|
||||
|
||||
</br>
|
||||
|
||||
@ -551,12 +555,13 @@ from Pose2Sim import Pose2Sim
|
||||
Pose2Sim.triangulation()
|
||||
```
|
||||
|
||||
<img src="Content/P2S_triangulation.png" width="760">
|
||||
|
||||
</br>
|
||||
|
||||
Check printed output, and visualize your trc in OpenSim: `File -> Preview experimental data`.\
|
||||
If your triangulation is not satisfying, try and release the constraints in the `Config.toml` file.
|
||||
|
||||
Output:\
|
||||
<img src="Content/Triangulate3D.png" width="760">
|
||||
|
||||
</br>
|
||||
|
||||
### Filtering 3D coordinates
|
||||
@ -571,13 +576,15 @@ from Pose2Sim import Pose2Sim
|
||||
Pose2Sim.filtering()
|
||||
```
|
||||
|
||||
<img src="Content/P2S_filtering.png" width="760">
|
||||
|
||||
</br>
|
||||
|
||||
Check your filtration with the displayed figures, and visualize your .trc file in OpenSim. If your filtering is not satisfying, try and change the parameters in the `Config.toml` file.
|
||||
|
||||
Output:\
|
||||
<img src="Content/FilterPlot.png" width="760">
|
||||
|
||||
<img src="Content/Filter3D.png" width="760">
|
||||
|
||||
</br>
|
||||
|
||||
### Marker Augmentation
|
||||
@ -604,6 +611,8 @@ from Pose2Sim import Pose2Sim
|
||||
Pose2Sim.markerAugmentation()
|
||||
```
|
||||
|
||||
<img src="Content/P2S_markeraugmentation.png" width="760">
|
||||
|
||||
</br>
|
||||
|
||||
## OpenSim kinematics
|
||||
|