A multi-camera and multimodal dataset for posture and gait analysis 1.0.0
(33,261 bytes)
import glob
import json
import os
import re
import time
import c3d
import cv2
import numpy as np
import pandas as pd
from scipy.spatial.transform import Rotation as scipyR
from utils import project_3d_to_2d,align_data_by_timestamp,rot_world_to_kinect_ref,\
roty90, process_depth_frame, draw_img_keypoints, extract_timestamp_from_string, \
frames_original_shape, xsens_joint_names
def get_skeleton_data_from_xsens(data_path, extrinsic_ref_transforms=None, load_c3d=True,
skeleton_norm_mode=("norm_pos_orient", "camera_ref")):
"""
Parses data from exported Xsens files(.csv / .c3d) and aligns data
spatially to desired referential.
Args:
data_path(str): path to Xsens data.
extrinsic_ref_transforms(dict): dictionary with extrinsic
calibration data.
load_c3d(bool): if joint data in .c3d files should be used. The
.c3d files contains a more complete skeleton joint set and in
this case is used to replace the joints in the feet.
skeleton_norm_mode(tuple[str]): mode to use when aligning the
skeleton data. Can use more than one mode to save data in different
ways:
-"camera_ref": aligns skeleton to be as seen from the
posture camera referential. This is the default mode
and enables projection to 2D frames.
-"norm_pos_orient": normalizes skeleton orientation and
position, skeleton is centered on root joint and always
faces forward. Might be the best option when dealing
with only 3D.
-"none": uses raw skeleton data. Skeleton moves and rotates in
space.
Returns:
xsense_frame_idx, qpos3d_data processed with selected methods
"""
# load joint data(3D joint positions, 3D root position and angles) from Xsens files
qpos3d_data = pd.read_csv(data_path + "_csv/Segment Position.csv",
sep=";", index_col=0).values
qpos3d_com = pd.read_csv(data_path + "_csv/Center of Mass.csv",
sep=";", index_col=0).values
qangle_euler_xyz = pd.read_csv(data_path + "_csv/Segment Orientation - Euler.csv",
sep=";", index_col=0).values
# reshapes data from [Nsamples, Joint * 3] to [Nsamples, Joint, 3]
qpos3d_data = qpos3d_data.reshape(len(qpos3d_data), -1, 3) # reshape to [n_samples, n_joints, pos_xyz]
qpos3d_com = qpos3d_com.reshape(len(qpos3d_com), 1, 3) # reshape to [n_samples, 1, pos_xyz]
qangle = np.deg2rad(qangle_euler_xyz.reshape(len(qangle_euler_xyz), -1, 3)) # reshape to [n_samples, n_joints, euler_angle_xyz] and convert to rad
# extract necessary data when normalizing data to kinect referential
if "camera_ref" in skeleton_norm_mode:
assert qpos3d_data.shape[1] == 24, "Prop sensor data is necessary to align skeleton" \
"to camera_ref!"
assert extrinsic_ref_transforms is not None, "Extrinsic transformation data is " \
"necessary to align skeleton to camera_ref"
# separate data from prop sensor
qpos3d_data = qpos3d_data[:, :23, :]
prop_angle = qangle[:, 23, :]
qangle = qangle[:, :23, :]
# offset to walker handles in world ref from extrinsic calibration files
# (points know to be relatively fixed as people need to grab them - walker handles)
walker_offset_pos = dict(
left=extrinsic_ref_transforms["CamPostureToLeftHandleTranslation"],
right=extrinsic_ref_transforms["CamPostureToRightHandleTranslation"])
# reads optional rotation from extrinsic calibration file
# (this was added to correct bad placement of the prop sensor on
# the camera in some tests - ex. sensor placed facing opposite direction)
xsens_camera_external_rot = extrinsic_ref_transforms["PropOrientation"]
# if skeleton z_orientation should be relative to the prop sensor placed on camera
# (its off by default as some trials exhibit drift from the prop sensor resulting
# in incorrect rotation relative to the camera. In most cases people walk facing the
# camera so orientation in the z-axis is close to 0, however this needs to be turned
# on when people dont face the camera when walking(a flag is set in the external
# calibration files in theses cases).
if ("UsePropDirection" in extrinsic_ref_transforms
and any([(f in data_path) for f in extrinsic_ref_transforms["UsePropDirection"]])):
use_direction_from_prop = True
else:
use_direction_from_prop = False
else:
# separate data from prop sensor
qpos3d_data = qpos3d_data[:, :23, :]
prop_angle = None
qangle = qangle[:, :23, :]
walker_offset_pos = None
xsens_camera_external_rot = None
use_direction_from_prop = False
if load_c3d:
# read c3d data from file
with open(data_path + ".c3d", 'rb') as fhandle:
c3d_data = []
for frame_no, points, analog in c3d.Reader(fhandle).read_frames(copy=False):
# pts_data = points[np.ix_(c3d_extra_points_selec, [0, 1, 2])] / 1000.0
pts_data = points[:, [0, 1, 2]] / 1000.0
c3d_data.append(pts_data)
# check if Xsens data was exported correctly or apply small fix to correct
if len(qpos3d_data) != len(c3d_data):
#print("Warning: len(qpos3d_data) != len(c3d_qpos3d_data) -> {}|{}. "
# "This seems due to Xsens exporting bugs. Applying small fix to correct!"
# .format(qpos3d_data.shape, np.shape(c3d_data)))
qpos3d_data = qpos3d_data[:len(c3d_data)]
qpos3d_com = qpos3d_com[:len(c3d_data)]
qangle = qangle[:len(c3d_data)]
prop_angle = prop_angle[:len(c3d_data)]
# replace keypoints of both feet(toe/heel)
c3d_qpos3d_data = np.array(c3d_data)
qpos3d_data[:, [17, 18, 21, 22]] = c3d_qpos3d_data[:, [56, 55, 62, 61]]
# aligns skeleton data
qpos3d_processed_data = normalize_skeleton(
pos3d=qpos3d_data, qangle=qangle,
com_pos=qpos3d_com, prop_angle=prop_angle,
walker_offset_pos_lr_handles=walker_offset_pos,
skeleton_external_rot=xsens_camera_external_rot,
skeleton_norm_mode=skeleton_norm_mode,
use_prop_direction=use_direction_from_prop)
xsense_frame_idx = np.arange(len(qpos3d_data))
return xsense_frame_idx, qpos3d_processed_data
def normalize_skeleton(pos3d, qangle, com_pos,
skeleton_norm_mode=("norm_pos_orient", "camera_ref"),
walker_offset_pos_lr_handles=None, prop_angle=None,
use_prop_direction=False, skeleton_external_rot=None,
root_idx=0, r_wrist_idx=10, l_wrist_idx=14):
"""
Normalizes skeleton data using the desired modes:
Args:
pos3d(np.ndarray): array with 3D joint data positions. Shape:[N, J, 3]
qangle(np.ndarray): array with segment angles. Shape:[N, J, 3]
com_pos(np.ndarray): array with center-of-mass positions. Shape:[N, 1, 3]
skeleton_norm_mode(tuple[str]): mode to use when aligning the
skeleton data. Can use more than one mode to process data in
different ways:
-"camera_ref": aligns skeleton to be as seen from the
posture camera referential. This is the default mode
and enables projection to 2D frames.
-"norm_pos_orient": normalizes skeleton orientation and
position, skeleton is centered on root joint and always
faces forward. Might be the best option when dealing
with only 3D.
-"none": uses raw skeleton data. Skeleton moves and rotates in
space.
walker_offset_pos_lr_handles(None, dict[np.ndarray]): dictionary with
translation vector from posture camera to each of the walker
handles("left"/"right"). Only necessary when using "camera_ref" mode.
prop_angle(None, np.ndarray): array with prop angles.
Shape:[N, 1, 3]. Only necessary when using "camera_ref" mode.
use_prop_direction(bool): if prop rotation should be used to normalize
skeleton z_orientation relative to camera.
skeleton_external_rot(None, np.ndarray): optional rotation to fix bad
prop sensor placement in some trials.
root_idx(int): Idx of the root joint.
r_wrist_idx(int): Idx of the right wrist joint.
l_wrist_idx(int): Idx of the left wrist joint.
Returns:
list with each set of the joint positions processed with selected methods
"""
processed_skeletons = []
# center root joint on origin before doing further transforms
root_pos = pos3d.copy()[:, root_idx, :].reshape(len(pos3d), 1, 3)
pos3d_orig = pos3d - root_pos
if "camera_ref" in skeleton_norm_mode:
pos3d_cam = pos3d_orig.copy()
# rotate skeleton referential to kinect camera referential
# (prop_idx)camera ref is prone to rotation drift in some cases
# (root_idx)(removing root_z_rotation to deal with this - not as correct)
if use_prop_direction:
xsens_prop_to_world_ref = scipyR.from_rotvec(
(prop_angle) * [0, 0, 1]).as_matrix()
else:
xsens_prop_to_world_ref = scipyR.from_rotvec(
(qangle[:, root_idx, :]) * [0, 0, 1]).as_matrix()
pos3d_cam = (pos3d_cam @ xsens_prop_to_world_ref) @ (rot_world_to_kinect_ref @ roty90)
# rotate skeleton to match kinect camera tilt angle
camera_tilt = scipyR.from_rotvec(
(prop_angle + [np.pi / 2, 0, 0]) * [1, 1, 0]).as_matrix()
pos3d_cam = (pos3d_cam @ camera_tilt)
# apply external skeleton rotation if needed(sometimes to fix some acquisition problems)
external_skeleton_rot = scipyR.from_rotvec(skeleton_external_rot).as_matrix()
pos3d_cam = pos3d_cam @ external_skeleton_rot
# transl from camera to right wrist is known as well as from r_wrist to root.
l_hand_to_root_offset = (pos3d_cam[:, root_idx, :].reshape(len(pos3d_cam), 1, 3)
- pos3d_cam[:, l_wrist_idx, :].reshape(len(pos3d_cam), 1, 3))
r_hand_to_root_offset = (pos3d_cam[:, root_idx, :].reshape(len(pos3d_cam), 1, 3)
- pos3d_cam[:, r_wrist_idx, :].reshape(len(pos3d_cam), 1, 3))
# average transformation from
pos3d_l = pos3d_cam + walker_offset_pos_lr_handles["left"] + l_hand_to_root_offset
pos3d_r = pos3d_cam + walker_offset_pos_lr_handles["right"] + r_hand_to_root_offset
pos3d_cam = (pos3d_l + pos3d_r) / 2
# fix bad wrist positions
pos3d_cam = _fix_xsens_wrist_postions(pos3d_cam, walker_offset_pos_lr_handles,
threshold_m=35e-3)
processed_skeletons.append(pos3d_cam)
# convert to center of mass centered referential
if "norm_pos_orient" in skeleton_norm_mode:
pos3d_norm = pos3d_orig.copy()
# normalize joint orientation over z-axis
qangle[:, :, [0, 1]] = 0.0
root_rot = scipyR.from_rotvec(qangle[:, root_idx, :]).as_matrix()
pos3d_norm = pos3d_norm @ root_rot
# normalize joint positions(center model center_of_mass
# on the x/y/z axis for all timesteps)
pos3d_norm = pos3d_norm + root_pos
pos3d_norm = pos3d_norm - com_pos
processed_skeletons.append(pos3d_norm)
# just use raw data
if "none" in skeleton_norm_mode:
pos3d_raw = pos3d_orig + root_pos
processed_skeletons.append(pos3d_raw)
return processed_skeletons
def _fix_xsens_wrist_postions(qpos3d, walker_offset_pos_lr_handles, threshold_m=30e-3,
r_wrist_idx=10, l_wrist_idx=14):
# replace average(still maintains deviations) wrist joints with
# default wrist positions(joint handles) if wrist positions are
# far far from handles(ex. because of bad xsens calibration)
fixed_qpos3d = qpos3d.copy()
r_wrist_data_mean = qpos3d[:, r_wrist_idx, :].mean(axis=0)
l_wrist_data_mean = qpos3d[:, l_wrist_idx, :].mean(axis=0)
r_wrist_offset = np.linalg.norm(r_wrist_data_mean - walker_offset_pos_lr_handles["right"])
l_wrist_offset = np.linalg.norm(l_wrist_data_mean - walker_offset_pos_lr_handles["left"])
# replace right wrist mean
if r_wrist_offset > threshold_m:
fixed_qpos3d[:, r_wrist_idx, :] = ((qpos3d[:, r_wrist_idx, :] - r_wrist_data_mean)
+ walker_offset_pos_lr_handles["right"])
# replace left wrist mean
if l_wrist_offset > threshold_m:
fixed_qpos3d[:, l_wrist_idx, :] = ((qpos3d[:, l_wrist_idx, :] - l_wrist_data_mean)
+ walker_offset_pos_lr_handles["left"])
return fixed_qpos3d
def cams_project_3d_to_2d(points3d, intrinsic_matrix, extrinsic_matrix=None,
mode="separate", resize_factor=(1.0, 1.0),
cam2_pixel_offset=(0, 480)):
"""
Projects 3D points to cameras' frame(2D).
Args:
points3d(np.ndarray): Points to project from 3D to 2D space.
intrinsic_matrix(dict[np.ndarray]): Dict with intrinsic matrix
for each camera for the projection. Should have shape:[4x4].
extrinsic_matrix(dict[np.ndarray]): Dict with extrinsic matrix
for each camera for the projection. Should have shape:[4x4].
mode(str): Projection mode for multiple cameras:
"separate" - returns 2D points relative to each camera,
frame in separate.
"concatenated" - assumes the gait camera frame is
concatenated bellow the posture camera frame, creating
a single frame.
resize_factor(tuple[float]): Resize factor(x, y to apply to the
projected points if the frame shape is altered.
cam2_pixel_offset(tuple[int]): position offset for second camera
frame when mode is "concatenated".
Returns:
Points projected in 2D image space
"""
extrinsic_matrix = (extrinsic_matrix if extrinsic_matrix is not None
else {c: np.eye(4) for c in intrinsic_matrix.keys()})
cam_qpos2d_posture = project_3d_to_2d(points3d=points3d,
intrinsic_matrix=intrinsic_matrix["posture_camera"],
extrinsic_matrix=extrinsic_matrix["posture_camera"])
cam_qpos2d_gait = project_3d_to_2d(points3d=points3d,
intrinsic_matrix=intrinsic_matrix["gait_camera"],
extrinsic_matrix=extrinsic_matrix["gait_camera"])
# determines in which image part is the joint located(and choose that projection position)
qpos_posture_off = cam_qpos2d_posture[:, 1] - cam2_pixel_offset[1]
qpos_gait_off = -cam_qpos2d_gait[:, 1]
if mode == "separate":
cam_qpos2d_gait = cam_qpos2d_gait * list(resize_factor)
cam_qpos2d_posture = cam_qpos2d_posture * list(resize_factor)
return cam_qpos2d_gait, cam_qpos2d_posture
elif mode == "concatenated":
cams_qpos2d = np.where((qpos_posture_off < qpos_gait_off)[:, np.newaxis],
cam_qpos2d_posture,
cam_qpos2d_gait + cam2_pixel_offset)
cams_qpos2d = cams_qpos2d * [resize_factor[0], resize_factor[1]/2]
return cams_qpos2d
else:
raise NotImplementedError
def parse_walker_data(
dataset_root_path, subj_ids_to_extract, seq_ids_to_extract, rep_ids_to_extract,
save_path=None, save_data=True, show_data=False, undersample_rate=1, ignore_error=True):
"""
Function to parse raw walker data:
-Aligns data temporally from gait/posture cameras and Xsens skeleton
-Correlates Xsens 3D joint data spatially to posture camera referential.
-Projects 3D skeleton to each of the cameras' frame.
-Example of how to parse additional data (Depth/Pointcloud) which
is not used by default as it takes longer to process and occupies
significant space in disk.
-Joint angles can also be extracted from xsens file, however are not
being parsed by default(check "Joint Angles ..." tabs).
-Optional visualization of 2D data(depth frames with projected
2D joints).
Args:
dataset_root_path(str): path to root of the dataset.
subj_ids_to_extract(Iterable[str]): selected subjects to extract data.
seq_ids_to_extract(Iterable[str]): selected sequences to extract data.
rep_ids_to_extract(Iterable[str]): selected repetitions to extract data.
save_path(str): path to save extracted data.
save_data(bool): if data should be saved to disc.
show_data(bool): if data should be visualized(projected 2D keypoints
on Depth frames).
undersample_rate(int): undersample rate to apply to data. 1 means
no undersample.
ignore_error(bool): if errors should be ignored while parsing.
"""
if save_path is None:
save_path = dataset_root_path + "/processed_data"
os.makedirs(save_path, exist_ok=True) # make sure dir exists
# go to raw trial directory
dataset_root_path = dataset_root_path + "/raw_data"
print("Extracting dataset data, this will take a while!")
n_frames = 0
for subj_id in sorted(subj_ids_to_extract):
for seq_id in sorted(seq_ids_to_extract):
for rep_id in sorted(rep_ids_to_extract):
seq_path_name = subj_id + "_" + seq_id
rep_path_name = seq_path_name + "_" + rep_id
data_tree_path = "/{}/{}/{}/" \
.format(subj_id, seq_path_name, rep_path_name)
if not os.path.isdir(dataset_root_path + data_tree_path):
# ignores data combinations which dont exist or are not available
print("Requested data does not exist: {} | {} | {}"
.format(subj_id, seq_id, rep_id))
continue
extrinsic_calib_path = dataset_root_path + "/{}/{}_extrinsic_calibration.json"\
.format(subj_id, subj_id)
intrinsic_calib_path = dataset_root_path + "/{}/{}_intrinsic_calibration.json"\
.format(subj_id, subj_id)
try:
time_start = time.perf_counter()
# load extrinsic referential transforms + intrinsic camera params
with open(extrinsic_calib_path, "r") as f:
extrinsic_ref_transforms = json.load(f)
with open(intrinsic_calib_path, "r") as f:
cam_intrinsics = json.load(f)
for k in cam_intrinsics:
cam_intrinsics[k] = np.array(cam_intrinsics[k])
# get path to files
skeleton_data_file_path = dataset_root_path + data_tree_path + "{}"\
.format(rep_path_name)
depth_g_path = dataset_root_path + data_tree_path + "gait_depth_registered"
depth_p_path = dataset_root_path + data_tree_path + "posture_depth_registered"
##### get depth data indexes and timestamps #####
depth_fnum_pattern = re.compile("[a-zA-Z]+_[a-zA-Z]+_(\d+)_\d+_\d+.png")
depth_g_frame_ids = sorted(
os.listdir(depth_g_path),
key=lambda s: int(re.search(depth_fnum_pattern, s).group(1)))
depth_p_frame_ids = sorted(
os.listdir(depth_p_path),
key=lambda s: int(re.search(depth_fnum_pattern, s).group(1)))
depth_time_pattern = re.compile("[a-zA-Z]+_[a-zA-Z]+_\d+_(\d+_\d+).png")
depth_extract_tstep_func = lambda s: extract_timestamp_from_string(
re.findall(depth_time_pattern, s)[0], split_char="_")
depth_g_timestamps = np.array([depth_extract_tstep_func(f_id)
for f_id in depth_g_frame_ids])
depth_p_timestamps = np.array([depth_extract_tstep_func(f_id)
for f_id in depth_p_frame_ids])
depth_g_frame_idx = [int(re.search(depth_fnum_pattern, s).group(1))
for s in depth_g_frame_ids]
depth_p_frame_idx = [int(re.search(depth_fnum_pattern, s).group(1))
for s in depth_p_frame_ids]
##### get xsens data indexes and timestamps #####
# get skeleton joints3D data processed by the desired methods
# and corresponding indexes. The "camera_ref" presents the data relative
# to the posture camera while the "norm_pos_orient" has centered root
# position and oriented facing forward.
xsens_frame_idx, (qpos3d_camref_data, qpos3d_norm_data) = \
get_skeleton_data_from_xsens(
data_path=skeleton_data_file_path,
extrinsic_ref_transforms=extrinsic_ref_transforms,
skeleton_norm_mode=("camera_ref", "norm_pos_orient"),
load_c3d=True)
xsens_start_time = [float(os.path.basename(f).replace("sync_", "")
.replace(".stamp", "").replace("_", "."))
for f in glob.glob(dataset_root_path + data_tree_path
+ "/sync_*.stamp")]
xsens_start_time = (xsens_start_time[0] if xsens_start_time
else depth_g_timestamps[0] - 0.65)
xsens_timestamps = np.array((xsens_frame_idx * (1/60)) + xsens_start_time)
##### align depth and xsense data temporally based on timestamps #####
(dpt_p_idx, dpt_g_idx, xsens_idx), _ = \
align_data_by_timestamp(
list_data_ids=[depth_p_frame_idx, depth_g_frame_idx,
xsens_frame_idx],
list_timestamps=[depth_p_timestamps, depth_g_timestamps,
xsens_timestamps],
frames_clip_idx=(10, 5), undersample_rate=undersample_rate,
plot_data_names=["depth_posture", "depth_gait",
"xsense"], visualize=False)
# indexes of aligned data
os.makedirs(save_path + data_tree_path, exist_ok=True) # make sure dir exists
fhandle_idx_align = pd.DataFrame(
columns=["depth_posture_idx", "depth_gait_idx",
"xsense_idx"],
data={"depth_posture_idx": dpt_p_idx,
"depth_gait_idx": dpt_g_idx,
"xsense_idx": xsens_idx})
fhandle_idx_align.to_csv(save_path + data_tree_path
+ "synchronized_data_idx.csv")
# parse and save aligned data
out_qpos3d_norm_data, out_qpos3d_camref_data, \
out_qpos2d_gait_data, out_qpos2d_posture_data = [], [], [], []
num_frames_extracted = 0
for f_i, (f_dpt_p_idx, f_dpt_g_idx, f_xsens_idx) \
in enumerate(zip(dpt_p_idx, dpt_g_idx, xsens_idx)):
# select 3D data
f_qpos3d_norm = qpos3d_norm_data[f_xsens_idx]
f_qpos3d_camref = qpos3d_camref_data[f_xsens_idx]
# obtain 2D data from projection of skeleton relative to camera
f_qpos2d_gait, f_qpos2d_posture = cams_project_3d_to_2d(
points3d=f_qpos3d_camref * [-1, -1, 1],
intrinsic_matrix=cam_intrinsics,
extrinsic_matrix=dict(
gait_camera=extrinsic_ref_transforms[
"CamGaitToPostureTransform"],
posture_camera=np.eye(4)),
mode="separate")
# save 3D and 2D data
out_qpos3d_norm_data.append(f_qpos3d_norm)
out_qpos3d_camref_data.append(f_qpos3d_camref)
out_qpos2d_gait_data.append(np.round(f_qpos2d_gait).astype(np.int32))
out_qpos2d_posture_data.append(np.round(f_qpos2d_posture).astype(np.int32))
num_frames_extracted += 1
if show_data:
# example of how to process/visualize frames with overlaid
# keypoint data.
# (not being used by default as it takes longer to process
# but can be used for visualization/debugging/additional processing
show_shape = (256, 224)
f_dpt_g = process_depth_frame(
cv2.imread(
depth_g_path + "/" + depth_g_frame_ids[f_dpt_g_idx],
cv2.IMREAD_ANYDEPTH),
save_shape=show_shape).astype(np.float32)
f_dpt_p = process_depth_frame(
cv2.imread(
depth_p_path + "/" + depth_p_frame_ids[f_dpt_p_idx],
cv2.IMREAD_ANYDEPTH),
save_shape=show_shape).astype(np.float32)
# resize keypoints 2D to match resized frames
f_qpos2d_data_gait_show = np.round(
f_qpos2d_gait * np.divide(show_shape,
frames_original_shape)
).astype(np.int32)
f_qpos2d_data_posture_show = np.round(
f_qpos2d_posture * np.divide(show_shape,
frames_original_shape)
).astype(np.int32)
# draw keypoints on depth frames
depth_gait_frame_show = draw_img_keypoints(
f_dpt_g * 0.1, f_qpos2d_data_gait_show,
color=(0, 1, 0, 1), radius=2)
depth_posture_frame_show = draw_img_keypoints(
f_dpt_p * 0.1, f_qpos2d_data_posture_show,
color=(0, 1, 0, 1), radius=2)
# show data
cv2.imshow("Depth gait frame", depth_gait_frame_show)
cv2.imshow("Depth posture frame", depth_posture_frame_show)
cv2.waitKey(1)
assert len(out_qpos3d_norm_data) == len(out_qpos3d_camref_data) == \
len(out_qpos2d_gait_data) == len(out_qpos2d_posture_data) == \
num_frames_extracted, \
"Not all data has the same lenght! Check your files: " \
"{} | {} | {}, Lens: {} | {} | {} | {} | {}".format(
subj_id, seq_id, rep_id,
len(out_qpos3d_norm_data), len(out_qpos3d_camref_data),
len(out_qpos2d_gait_data), len(out_qpos2d_posture_data),
num_frames_extracted)
if save_data:
# save 3d aligned data to original dataset
out_qpos3d_norm_data = np.round(np.array(out_qpos3d_norm_data), 4)
out_qpos3d_camref_data = np.round(np.array(out_qpos3d_camref_data), 4)
out_qpos2d_gait_data = np.array(out_qpos2d_gait_data)
out_qpos2d_posture_data = np.array(out_qpos2d_posture_data)
jnames3d, jnames2d = [], []
for n in xsens_joint_names:
jnames3d.extend([n + "_x", n + "_y", n + "_z"])
jnames2d.extend([n + "_x", n + "_y"])
# save 3D skeleton data normalized
fhandle_idx_align = pd.DataFrame(
columns=jnames3d,
data=out_qpos3d_norm_data.reshape(
(len(out_qpos3d_norm_data), -1)))
fhandle_idx_align.to_csv(save_path + data_tree_path
+ "normalized_skeleton_3d.csv")
# save 3D skeleton data aligned with posture camera referential
fhandle_idx_align = pd.DataFrame(
columns=jnames3d,
data=out_qpos3d_camref_data.reshape(
(len(out_qpos3d_camref_data), -1)))
fhandle_idx_align.to_csv(save_path + data_tree_path
+ "aligned_skeleton_3d.csv")
# save 2D skeleton data projected to gait camera frames
fhandle_idx_align = pd.DataFrame(
columns=jnames2d,
data=out_qpos2d_gait_data.reshape(
(len(out_qpos2d_gait_data), -1)))
fhandle_idx_align.to_csv(save_path + data_tree_path
+ "aligned_skeleton_2d_gait.csv")
# save 2D skeleton data projected to posture camera frames
fhandle_idx_align = pd.DataFrame(
columns=jnames2d,
data=out_qpos2d_posture_data.reshape(
(len(out_qpos2d_posture_data), -1)))
fhandle_idx_align.to_csv(save_path + data_tree_path
+ "aligned_skeleton_2d_posture.csv")
n_frames += num_frames_extracted
print("Extracted data from: {} | {} | {} -> Samples: {} | {}s"
.format(subj_id, seq_id, rep_id, num_frames_extracted,
round(time.perf_counter() - time_start, 2)))
except Exception as e:
if not ignore_error:
raise e
else:
print("Could not extract data from: {} | {} | {}"
.format(subj_id, seq_id, rep_id),
" ---------------------------------------> Error: ", e)
if show_data:
cv2.destroyAllWindows()
print("Extracted a total of {} data samples".format(n_frames))
if __name__ == "__main__":
dataset_path = "../.."
# subject data to extract
subj_ids = ["participant{0:02d}".format(i) for i in range(14)]
# sequence data to extract for each subject
seq_ids = ["left_0.3", "left_0.5", "left_0.7",
"right_0.3", "right_0.5", "right_0.7",
"straight_0.3", "straight_0.5", "straight_0.7"]
rep_ids = ["corner1", "corner2", "corner3",
"corridor1", "corridor2", "corridor3"]
parse_walker_data(
dataset_root_path=dataset_path,
subj_ids_to_extract=subj_ids,
seq_ids_to_extract=seq_ids,
rep_ids_to_extract=rep_ids,
save_path="./parsing_output/",
save_data=True,
show_data=True,
ignore_error=True,
undersample_rate=1)