c3d-parser 1.2.2__tar.gz → 1.2.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {c3d_parser-1.2.2/src/c3d_parser.egg-info → c3d_parser-1.2.4}/PKG-INFO +2 -2
- {c3d_parser-1.2.2 → c3d_parser-1.2.4}/requirements.txt +1 -1
- c3d_parser-1.2.4/src/c3d_parser/__init__.py +2 -0
- {c3d_parser-1.2.2 → c3d_parser-1.2.4}/src/c3d_parser/core/c3d_parser.py +92 -20
- {c3d_parser-1.2.2 → c3d_parser-1.2.4}/src/c3d_parser/view/dialogs/options_dialog.py +2 -0
- {c3d_parser-1.2.2 → c3d_parser-1.2.4}/src/c3d_parser/view/main_window.py +28 -13
- {c3d_parser-1.2.2 → c3d_parser-1.2.4}/src/c3d_parser/view/ui/ui_options_dialog.py +16 -2
- {c3d_parser-1.2.2 → c3d_parser-1.2.4/src/c3d_parser.egg-info}/PKG-INFO +2 -2
- {c3d_parser-1.2.2 → c3d_parser-1.2.4}/src/c3d_parser.egg-info/requires.txt +1 -1
- c3d_parser-1.2.2/src/c3d_parser/__init__.py +0 -2
- {c3d_parser-1.2.2 → c3d_parser-1.2.4}/LICENSE +0 -0
- {c3d_parser-1.2.2 → c3d_parser-1.2.4}/README.md +0 -0
- {c3d_parser-1.2.2 → c3d_parser-1.2.4}/pyproject.toml +0 -0
- {c3d_parser-1.2.2 → c3d_parser-1.2.4}/setup.cfg +0 -0
- {c3d_parser-1.2.2 → c3d_parser-1.2.4}/src/c3d_parser/application.py +0 -0
- {c3d_parser-1.2.2 → c3d_parser-1.2.4}/src/c3d_parser/core/c3d_patch.py +0 -0
- {c3d_parser-1.2.2 → c3d_parser-1.2.4}/src/c3d_parser/core/osim.py +0 -0
- {c3d_parser-1.2.2 → c3d_parser-1.2.4}/src/c3d_parser/core/osim_resources/external_loads_template.xml +0 -0
- {c3d_parser-1.2.2 → c3d_parser-1.2.4}/src/c3d_parser/core/osim_resources/ik_task_set.xml +0 -0
- {c3d_parser-1.2.2 → c3d_parser-1.2.4}/src/c3d_parser/core/utils.py +0 -0
- {c3d_parser-1.2.2 → c3d_parser-1.2.4}/src/c3d_parser/settings/general.py +0 -0
- {c3d_parser-1.2.2 → c3d_parser-1.2.4}/src/c3d_parser/settings/logging.py +0 -0
- {c3d_parser-1.2.2 → c3d_parser-1.2.4}/src/c3d_parser/settings/marker_maps/AUC.json +0 -0
- {c3d_parser-1.2.2 → c3d_parser-1.2.4}/src/c3d_parser/settings/marker_maps/FMC.json +0 -0
- {c3d_parser-1.2.2 → c3d_parser-1.2.4}/src/c3d_parser/settings/marker_maps/MH.json +0 -0
- {c3d_parser-1.2.2 → c3d_parser-1.2.4}/src/c3d_parser/settings/marker_maps/QCMAS.json +0 -0
- {c3d_parser-1.2.2 → c3d_parser-1.2.4}/src/c3d_parser/settings/marker_maps/RBWH.json +0 -0
- {c3d_parser-1.2.2 → c3d_parser-1.2.4}/src/c3d_parser/settings/marker_maps/RCH.json +0 -0
- {c3d_parser-1.2.2 → c3d_parser-1.2.4}/src/c3d_parser/settings/marker_maps/Sydney.json +0 -0
- {c3d_parser-1.2.2 → c3d_parser-1.2.4}/src/c3d_parser/splash_rc.py +0 -0
- {c3d_parser-1.2.2 → c3d_parser-1.2.4}/src/c3d_parser/splashscreen.py +0 -0
- {c3d_parser-1.2.2 → c3d_parser-1.2.4}/src/c3d_parser/view/dialogs/about_dialog.py +0 -0
- {c3d_parser-1.2.2 → c3d_parser-1.2.4}/src/c3d_parser/view/dialogs/delete_marker_set_dialog.py +0 -0
- {c3d_parser-1.2.2 → c3d_parser-1.2.4}/src/c3d_parser/view/dialogs/marker_set_dialog.py +0 -0
- {c3d_parser-1.2.2 → c3d_parser-1.2.4}/src/c3d_parser/view/dialogs/marker_set_import_dialog.py +0 -0
- {c3d_parser-1.2.2 → c3d_parser-1.2.4}/src/c3d_parser/view/ui/resources_rc.py +0 -0
- {c3d_parser-1.2.2 → c3d_parser-1.2.4}/src/c3d_parser/view/ui/ui_delete_marker_set_dialog.py +0 -0
- {c3d_parser-1.2.2 → c3d_parser-1.2.4}/src/c3d_parser/view/ui/ui_main_window.py +0 -0
- {c3d_parser-1.2.2 → c3d_parser-1.2.4}/src/c3d_parser/view/ui/ui_marker_set_dialog.py +0 -0
- {c3d_parser-1.2.2 → c3d_parser-1.2.4}/src/c3d_parser/view/ui/ui_marker_set_import_dialog.py +0 -0
- {c3d_parser-1.2.2 → c3d_parser-1.2.4}/src/c3d_parser/view/utils.py +0 -0
- {c3d_parser-1.2.2 → c3d_parser-1.2.4}/src/c3d_parser/view/widgets.py +0 -0
- {c3d_parser-1.2.2 → c3d_parser-1.2.4}/src/c3d_parser.egg-info/SOURCES.txt +0 -0
- {c3d_parser-1.2.2 → c3d_parser-1.2.4}/src/c3d_parser.egg-info/dependency_links.txt +0 -0
- {c3d_parser-1.2.2 → c3d_parser-1.2.4}/src/c3d_parser.egg-info/entry_points.txt +0 -0
- {c3d_parser-1.2.2 → c3d_parser-1.2.4}/src/c3d_parser.egg-info/top_level.txt +0 -0
- {c3d_parser-1.2.2 → c3d_parser-1.2.4}/tests/test_parser.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: c3d-parser
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.4
|
|
4
4
|
Summary: C3D parser for gait data harmonisation.
|
|
5
5
|
Author-email: Timothy Salemink <tim.nicolas@outlook.com>, Sally Jack <sallyjaack@gmail.com>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -12,7 +12,7 @@ Requires-Dist: numpy==1.26.4
|
|
|
12
12
|
Requires-Dist: pandas
|
|
13
13
|
Requires-Dist: c3d==0.5.2
|
|
14
14
|
Requires-Dist: trc-data-reader>=0.4.1
|
|
15
|
-
Requires-Dist: opensim-model-creator==0.7.
|
|
15
|
+
Requires-Dist: opensim-model-creator==0.7.2
|
|
16
16
|
Requires-Dist: PySide6
|
|
17
17
|
Requires-Dist: matplotlib
|
|
18
18
|
Requires-Dist: mplcursors
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
|
|
2
2
|
import os
|
|
3
3
|
import re
|
|
4
|
+
import copy
|
|
4
5
|
import math
|
|
5
6
|
import json
|
|
6
7
|
import logging
|
|
@@ -38,7 +39,8 @@ required_markers = [{"LASI", "RASI"}, {"LKNE", "RKNE"}, {"LANK", "RANK"}, {"LMED
|
|
|
38
39
|
|
|
39
40
|
|
|
40
41
|
def parse_session(static_trial, dynamic_trials, input_directory, output_directory, lab, marker_diameter, static_data,
|
|
41
|
-
left_foot_flat, right_foot_flat, optimise_knee_axis, filter_trc, filter_grf, ik_task_set,
|
|
42
|
+
left_foot_flat, right_foot_flat, optimise_knee_axis, filter_trc, filter_grf, ik_task_set,
|
|
43
|
+
running_gait, progress_tracker):
|
|
42
44
|
|
|
43
45
|
clear_directory(output_directory)
|
|
44
46
|
|
|
@@ -67,7 +69,7 @@ def parse_session(static_trial, dynamic_trials, input_directory, output_director
|
|
|
67
69
|
try:
|
|
68
70
|
analog_data, events, s_t_data, trc_file_path, grf_file_path = \
|
|
69
71
|
parse_dynamic_trial(file_path, lab, output_directory, trial_index, marker_data_rate, static_data,
|
|
70
|
-
filter_trc, filter_grf)
|
|
72
|
+
filter_trc, filter_grf, running_gait)
|
|
71
73
|
except ParserError as e:
|
|
72
74
|
logger.error(e)
|
|
73
75
|
continue
|
|
@@ -167,7 +169,9 @@ def parse_static_trial(c3d_file, lab, marker_diameter, output_directory, static_
|
|
|
167
169
|
return frame, trc_file_path, height, weight
|
|
168
170
|
|
|
169
171
|
|
|
170
|
-
def parse_dynamic_trial(c3d_file, lab, output_directory, trial_index, marker_data_rate, static_data, filter_trc,
|
|
172
|
+
def parse_dynamic_trial(c3d_file, lab, output_directory, trial_index, marker_data_rate, static_data, filter_trc,
|
|
173
|
+
filter_grf, running_gait):
|
|
174
|
+
|
|
171
175
|
file_name = os.path.basename(c3d_file)
|
|
172
176
|
logger.info(f"Parsing dynamic trial: {file_name}.")
|
|
173
177
|
|
|
@@ -221,7 +225,13 @@ def parse_dynamic_trial(c3d_file, lab, output_directory, trial_index, marker_dat
|
|
|
221
225
|
set_marker_data(trc_data, frame_data, rate=marker_data_rate)
|
|
222
226
|
trc_file_path = write_trc_data(trc_data, output_file_name, output_directory)
|
|
223
227
|
|
|
224
|
-
|
|
228
|
+
# Write gait event data.
|
|
229
|
+
write_event_data(events, output_file_name, output_directory)
|
|
230
|
+
|
|
231
|
+
# Disable spatio-temporal analysis for running gait.
|
|
232
|
+
s_t_data = {}
|
|
233
|
+
if not running_gait:
|
|
234
|
+
s_t_data = calculate_spatiotemporal_data(frame_data, events, static_data)
|
|
225
235
|
|
|
226
236
|
return analog_data, events, s_t_data, trc_file_path, grf_file_path
|
|
227
237
|
|
|
@@ -246,6 +256,24 @@ def write_c3d_parser_history(input_directory, static_trial, deidentified_file_na
|
|
|
246
256
|
raise ParserError("Could not write c3d_parser_history.log")
|
|
247
257
|
|
|
248
258
|
|
|
259
|
+
def write_event_data(events, file_name, output_directory):
|
|
260
|
+
event_directory = os.path.join(output_directory, "events")
|
|
261
|
+
if not os.path.exists(event_directory):
|
|
262
|
+
os.makedirs(event_directory)
|
|
263
|
+
event_file_path = os.path.join(event_directory, f"{file_name}.json")
|
|
264
|
+
|
|
265
|
+
simplified_events = {
|
|
266
|
+
side: {
|
|
267
|
+
cycle: {time: event[0] for time, event in times.items()}
|
|
268
|
+
for cycle, times in cycles.items()
|
|
269
|
+
}
|
|
270
|
+
for side, cycles in events.items()
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
with open(event_file_path, 'w') as f:
|
|
274
|
+
json.dump(simplified_events, f, indent=2)
|
|
275
|
+
|
|
276
|
+
|
|
249
277
|
def run_ik(osim_model, trc_file_path, output_directory, ik_task_set):
|
|
250
278
|
# Perform inverse kinematics.
|
|
251
279
|
file_name = os.path.splitext(os.path.basename(trc_file_path))[0]
|
|
@@ -613,8 +641,8 @@ def extract_data(file_path, start_frame, end_frame):
|
|
|
613
641
|
times = np.arange(start, stop_analog, time_increment).tolist()
|
|
614
642
|
analog_data = {'time': times}
|
|
615
643
|
|
|
616
|
-
|
|
617
|
-
analog_data.update({label: [] for label in
|
|
644
|
+
analog_labels = get_metadata(reader, 'ANALOG:LABELS').string_array
|
|
645
|
+
analog_data.update({label: [] for label in analog_labels})
|
|
618
646
|
for i, points, analog in reader.read_frames():
|
|
619
647
|
if i < start_frame or end_frame < i:
|
|
620
648
|
continue
|
|
@@ -629,14 +657,14 @@ def extract_data(file_path, start_frame, end_frame):
|
|
|
629
657
|
event_group = get_metadata(reader, 'EVENT')
|
|
630
658
|
event_count = get_metadata(event_group, 'USED').int8_value
|
|
631
659
|
contexts = get_metadata(event_group, 'CONTEXTS').string_array
|
|
632
|
-
|
|
660
|
+
event_labels = get_metadata(event_group, 'LABELS').string_array
|
|
633
661
|
times = get_metadata(event_group, 'TIMES').float_array
|
|
634
662
|
events = {'Left': {}, 'Right': {}}
|
|
635
663
|
|
|
636
664
|
for i in range(event_count):
|
|
637
665
|
foot = contexts[i].strip()
|
|
638
666
|
if foot:
|
|
639
|
-
label =
|
|
667
|
+
label = event_labels[i].strip()
|
|
640
668
|
event_time = times[i][1]
|
|
641
669
|
event_time = round(float(event_time), 4)
|
|
642
670
|
events[foot][event_time] = label
|
|
@@ -680,15 +708,23 @@ def extract_data(file_path, start_frame, end_frame):
|
|
|
680
708
|
corners = get_metadata(reader, 'FORCE_PLATFORM:CORNERS').float_array
|
|
681
709
|
|
|
682
710
|
try:
|
|
683
|
-
units
|
|
711
|
+
# Convert analog units in V (to N).
|
|
684
712
|
channels = get_metadata(reader, 'FORCE_PLATFORM:CHANNEL').int16_array
|
|
685
|
-
|
|
713
|
+
units = get_metadata(reader, 'ANALOG:UNITS').string_array
|
|
686
714
|
if all(units[i - 1] == 'V' for i in channels.flatten()):
|
|
687
715
|
calibration_matrix = get_metadata(reader, 'FORCE_PLATFORM:CAL_MATRIX').float_array
|
|
688
716
|
apply_calibration_matrix(plate_count, calibration_matrix, channels, analog_data)
|
|
689
717
|
except ParserError as e:
|
|
690
718
|
logger.warn(e)
|
|
691
719
|
|
|
720
|
+
try:
|
|
721
|
+
# Reorder analog channels according to metadata order.
|
|
722
|
+
channels = get_metadata(reader, 'FORCE_PLATFORM:CHANNEL').int16_array
|
|
723
|
+
selected_channels = [analog_labels[i - 1] for i in channels.flatten()]
|
|
724
|
+
analog_data = analog_data[['time'] + selected_channels]
|
|
725
|
+
except ParserError as e:
|
|
726
|
+
logger.warn(e)
|
|
727
|
+
|
|
692
728
|
return analog_data, reader.analog_rate, trimmed_events, plate_count, corners
|
|
693
729
|
|
|
694
730
|
|
|
@@ -730,7 +766,7 @@ def extract_static_data(file_path):
|
|
|
730
766
|
if 'RLEGLENGTH' in processing_group:
|
|
731
767
|
right_leg_length = reader.get('PROCESSING:RLegLength').float_value
|
|
732
768
|
|
|
733
|
-
if mass > 200:
|
|
769
|
+
if mass and mass > 200:
|
|
734
770
|
mass = mass / 9.81
|
|
735
771
|
|
|
736
772
|
return (height, mass, asis_width, left_knee_width, right_knee_width,
|
|
@@ -777,14 +813,6 @@ def calculate_force_and_couple(analog_data, plate_count):
|
|
|
777
813
|
|
|
778
814
|
for i in range(plate_count):
|
|
779
815
|
start = 1 + (6 * i)
|
|
780
|
-
|
|
781
|
-
next_plate = 1 + (6 * (i + 1))
|
|
782
|
-
if next_plate < len(analog_data.columns):
|
|
783
|
-
channel = analog_data.columns[start]
|
|
784
|
-
next_channel = analog_data.columns[next_plate]
|
|
785
|
-
if channel.startswith('Fx_3') and next_channel.startswith('Fx_3_new'):
|
|
786
|
-
start = next_plate
|
|
787
|
-
|
|
788
816
|
columns = list(range(start, start + 6))
|
|
789
817
|
Fx, Fy, Fz, Mx, My, Mz = analog_data.iloc[:, columns].values.T.astype(float)
|
|
790
818
|
|
|
@@ -1259,6 +1287,10 @@ def calculate_spatiotemporal_data(frame_data, events, static_data):
|
|
|
1259
1287
|
step_widths = {"Left": {}, "Right": {}}
|
|
1260
1288
|
phases = {"Left": {}, "Right": {}}
|
|
1261
1289
|
gait_speeds = {"Left": {}, "Right": {}}
|
|
1290
|
+
cadences = {"Left": {}, "Right": {}}
|
|
1291
|
+
initial_contact_times = {"Left": {}, "Right": {}}
|
|
1292
|
+
toe_off_times = {"Left": {}, "Right": {}}
|
|
1293
|
+
terminal_contact_times = {"Left": {}, "Right": {}}
|
|
1262
1294
|
strike_count = 0
|
|
1263
1295
|
|
|
1264
1296
|
left_leg_length = static_data['Left Leg Length'] / 1000
|
|
@@ -1271,6 +1303,27 @@ def calculate_spatiotemporal_data(frame_data, events, static_data):
|
|
|
1271
1303
|
for event_time, (event_type, event_plate) in stride_events.items():
|
|
1272
1304
|
time_ordered_events[event_time][foot] = [event_type, stride_number]
|
|
1273
1305
|
|
|
1306
|
+
# Add terminal foot-strike to events for each cycle.
|
|
1307
|
+
full_cycle_events = copy.deepcopy(events)
|
|
1308
|
+
for foot, foot_events in full_cycle_events.items():
|
|
1309
|
+
for stride_number, stride_events in foot_events.items():
|
|
1310
|
+
if len(stride_events) == 2 and (stride_number + 1) in foot_events:
|
|
1311
|
+
event_list = list(stride_events.values())
|
|
1312
|
+
next_event_time, next_event = next(iter(foot_events[stride_number + 1].items()))
|
|
1313
|
+
if event_list[0][0] == "Foot Strike" and event_list[1][0] == "Foot Off":
|
|
1314
|
+
if next_event[0] == "Foot Strike":
|
|
1315
|
+
stride_events[next_event_time] = ["Terminal Foot Strike", next_event[1]]
|
|
1316
|
+
|
|
1317
|
+
# Extract event times.
|
|
1318
|
+
for foot, foot_events in full_cycle_events.items():
|
|
1319
|
+
for stride_number, stride_events in foot_events.items():
|
|
1320
|
+
if len(stride_events) == 3:
|
|
1321
|
+
(strike_time, strike), (off_time, off), (terminal_time, terminal) = stride_events.items()
|
|
1322
|
+
if strike[0] == "Foot Strike" and off[0] == "Foot Off" and terminal[0] == "Terminal Foot Strike":
|
|
1323
|
+
initial_contact_times[foot][stride_number] = strike_time
|
|
1324
|
+
toe_off_times[foot][stride_number] = off_time
|
|
1325
|
+
terminal_contact_times[foot][stride_number] = terminal_time
|
|
1326
|
+
|
|
1274
1327
|
opposite_side = {"Left": "Right", "Right": "Left"}
|
|
1275
1328
|
strike_position = {"Left": None, "Right": None}
|
|
1276
1329
|
foot_events = {"Left": None, "Right": None}
|
|
@@ -1291,6 +1344,7 @@ def calculate_spatiotemporal_data(frame_data, events, static_data):
|
|
|
1291
1344
|
stride_lengths[foot][stride_number - 1] = stride_length / 1000
|
|
1292
1345
|
total_time = event_time - strike_times[foot]
|
|
1293
1346
|
gait_speeds[foot][stride_number - 1] = (stride_length / total_time) / 1000
|
|
1347
|
+
cadences[foot][stride_number - 1] = (60 / total_time) * 2
|
|
1294
1348
|
strike_position[foot] = heel_coordinates
|
|
1295
1349
|
strike_times[foot] = event_time
|
|
1296
1350
|
|
|
@@ -1353,7 +1407,10 @@ def calculate_spatiotemporal_data(frame_data, events, static_data):
|
|
|
1353
1407
|
s_t_data["Normalised Step Length"] = normalised_step_lengths
|
|
1354
1408
|
s_t_data["Step Width (m)"] = step_widths
|
|
1355
1409
|
|
|
1356
|
-
# Determine phase percentages.
|
|
1410
|
+
# Determine phase times and percentages.
|
|
1411
|
+
stride_durations = {"Left": {}, "Right": {}}
|
|
1412
|
+
stance_durations = {"Left": {}, "Right": {}}
|
|
1413
|
+
swing_durations = {"Left": {}, "Right": {}}
|
|
1357
1414
|
stance_phases = {"Left": {}, "Right": {}}
|
|
1358
1415
|
swing_phases = {"Left": {}, "Right": {}}
|
|
1359
1416
|
single_support_phases = {"Left": {}, "Right": {}}
|
|
@@ -1365,11 +1422,20 @@ def calculate_spatiotemporal_data(frame_data, events, static_data):
|
|
|
1365
1422
|
stance_phases[side][cycle_number] = (cycle["Stance"] / total_time) * 100
|
|
1366
1423
|
swing_phases[side][cycle_number] = (cycle["Swing"] / total_time) * 100
|
|
1367
1424
|
|
|
1425
|
+
stride_durations[side][cycle_number] = total_time
|
|
1426
|
+
stance_durations[side][cycle_number] = cycle["Stance"]
|
|
1427
|
+
swing_durations[side][cycle_number] = cycle["Swing"]
|
|
1428
|
+
|
|
1368
1429
|
if "Initial-DS" in cycle and "Terminal-DS" in cycle:
|
|
1369
1430
|
ds_time = cycle["Initial-DS"] + cycle["Terminal-DS"]
|
|
1370
1431
|
single_support_phases[side][cycle_number] = ((cycle["Stance"] - ds_time) / total_time) * 100
|
|
1371
1432
|
double_support_phases[side][cycle_number] = (ds_time / total_time) * 100
|
|
1372
1433
|
|
|
1434
|
+
# Define time based measurements.
|
|
1435
|
+
s_t_data["Stride Duration (s)"] = stride_durations
|
|
1436
|
+
s_t_data["Stance Duration (s)"] = stance_durations
|
|
1437
|
+
s_t_data["Swing Duration (s)"] = swing_durations
|
|
1438
|
+
|
|
1373
1439
|
# Assign phase percentages.
|
|
1374
1440
|
s_t_data["Stance Phase %"] = stance_phases
|
|
1375
1441
|
s_t_data["Swing Phase %"] = swing_phases
|
|
@@ -1377,6 +1443,12 @@ def calculate_spatiotemporal_data(frame_data, events, static_data):
|
|
|
1377
1443
|
s_t_data["Double Support Phase %"] = double_support_phases
|
|
1378
1444
|
s_t_data["Gait Speed (m/s)"] = gait_speeds
|
|
1379
1445
|
s_t_data["Normalised Gait Speed"] = normalised_gait_speeds
|
|
1446
|
+
s_t_data["Cadence (steps/min)"] = cadences
|
|
1447
|
+
|
|
1448
|
+
# Assign event times.
|
|
1449
|
+
s_t_data["Initial Foot Contact (t)"] = initial_contact_times
|
|
1450
|
+
s_t_data["Foot Contact (t)"] = toe_off_times
|
|
1451
|
+
s_t_data["Terminal Foot Contact (t)"] = terminal_contact_times
|
|
1380
1452
|
|
|
1381
1453
|
data_frame = convert_to_data_frame(s_t_data)
|
|
1382
1454
|
|
|
@@ -90,6 +90,7 @@ class OptionsDialog(QDialog):
|
|
|
90
90
|
self._ui.checkBoxCustomTaskSet.setChecked(options['use_custom_ik_task_set'])
|
|
91
91
|
self._ui.lineEditIKTaskSet.setText(options['ik_task_set_path'])
|
|
92
92
|
self._ui.checkBoxApproximateAnthropometrics.setChecked(options['approximate_anthropometrics'])
|
|
93
|
+
self._ui.checkBoxRunningGait.setChecked(options['running_gait'])
|
|
93
94
|
|
|
94
95
|
def save(self):
|
|
95
96
|
options = {
|
|
@@ -105,6 +106,7 @@ class OptionsDialog(QDialog):
|
|
|
105
106
|
'use_custom_ik_task_set': self._ui.checkBoxCustomTaskSet.isChecked(),
|
|
106
107
|
'ik_task_set_path': self._ui.lineEditIKTaskSet.text(),
|
|
107
108
|
'approximate_anthropometrics': self._ui.checkBoxApproximateAnthropometrics.isChecked(),
|
|
109
|
+
'running_gait': self._ui.checkBoxRunningGait.isChecked(),
|
|
108
110
|
}
|
|
109
111
|
|
|
110
112
|
return options
|
|
@@ -77,6 +77,7 @@ class MainWindow(QMainWindow):
|
|
|
77
77
|
self._use_custom_ik_task_set = False
|
|
78
78
|
self._ik_task_set_path = ''
|
|
79
79
|
self._approximate_anthropometrics = False
|
|
80
|
+
self._running_gait = False
|
|
80
81
|
|
|
81
82
|
self._colour_left = '#A52A2A'
|
|
82
83
|
self._colour_right = '#0F52BA'
|
|
@@ -245,6 +246,11 @@ class MainWindow(QMainWindow):
|
|
|
245
246
|
self._spatiotemporal_tables = []
|
|
246
247
|
|
|
247
248
|
def _visualise_spatiotemporal_data(self):
|
|
249
|
+
|
|
250
|
+
# Temporarily disable spatio-temporal visualisation for running gait.
|
|
251
|
+
if self._running_gait:
|
|
252
|
+
return
|
|
253
|
+
|
|
248
254
|
layout = self._ui.scrollAreaSpatiotemporal.layout()
|
|
249
255
|
self._clear_spatiotemporal_data()
|
|
250
256
|
|
|
@@ -597,7 +603,7 @@ class MainWindow(QMainWindow):
|
|
|
597
603
|
self._worker = _ExecThread(parse_session, static_trial, dynamic_trials, input_directory,
|
|
598
604
|
self._output_directory, lab, marker_diameter, static_data,
|
|
599
605
|
left_foot_flat, right_foot_flat, optimise_knee_axis, self._filter_trc,
|
|
600
|
-
self._filter_grf, ik_task_set, self._progress_tracker)
|
|
606
|
+
self._filter_grf, ik_task_set, self._running_gait, self._progress_tracker)
|
|
601
607
|
self._worker.finished.connect(self._parse_finished)
|
|
602
608
|
self._worker.cancelled.connect(self._parse_cancelled)
|
|
603
609
|
self._worker.failed.connect(self._parse_failed)
|
|
@@ -890,6 +896,7 @@ class MainWindow(QMainWindow):
|
|
|
890
896
|
'use_custom_ik_task_set': self._use_custom_ik_task_set,
|
|
891
897
|
'ik_task_set_path': self._ik_task_set_path,
|
|
892
898
|
'approximate_anthropometrics': self._approximate_anthropometrics,
|
|
899
|
+
'running_gait': self._running_gait,
|
|
893
900
|
}
|
|
894
901
|
|
|
895
902
|
return options
|
|
@@ -907,6 +914,7 @@ class MainWindow(QMainWindow):
|
|
|
907
914
|
self._use_custom_ik_task_set = options['use_custom_ik_task_set']
|
|
908
915
|
self._ik_task_set_path = options['ik_task_set_path']
|
|
909
916
|
self._approximate_anthropometrics = options['approximate_anthropometrics']
|
|
917
|
+
self._running_gait = options['running_gait']
|
|
910
918
|
|
|
911
919
|
def _show_custom_marker_set_dialog(self):
|
|
912
920
|
static_trials = []
|
|
@@ -976,6 +984,7 @@ class MainWindow(QMainWindow):
|
|
|
976
984
|
settings.setValue('use_custom_ik_task_set', self._use_custom_ik_task_set)
|
|
977
985
|
settings.setValue('ik_task_set_path', self._ik_task_set_path)
|
|
978
986
|
settings.setValue('approximate_anthropometrics', self._approximate_anthropometrics)
|
|
987
|
+
settings.setValue('running_gait', self._running_gait)
|
|
979
988
|
settings.endGroup()
|
|
980
989
|
|
|
981
990
|
def _load_settings(self):
|
|
@@ -1026,6 +1035,8 @@ class MainWindow(QMainWindow):
|
|
|
1026
1035
|
self._ik_task_set_path = settings.value('ik_task_set_path')
|
|
1027
1036
|
if settings.contains('approximate_anthropometrics'):
|
|
1028
1037
|
self._approximate_anthropometrics = settings.value('approximate_anthropometrics') == 'true'
|
|
1038
|
+
if settings.contains('running_gait'):
|
|
1039
|
+
self._running_gait = settings.value('running_gait') == 'true'
|
|
1029
1040
|
settings.endGroup()
|
|
1030
1041
|
|
|
1031
1042
|
def _quit_application(self):
|
|
@@ -1386,27 +1397,31 @@ class SpatiotemporalTableDelegate(QStyledItemDelegate):
|
|
|
1386
1397
|
painter.drawLine(right, centre_y + whisker_width, right, centre_y - whisker_width)
|
|
1387
1398
|
painter.drawLine(left, centre_y, right, centre_y)
|
|
1388
1399
|
|
|
1389
|
-
# Calculate slightly-arbitrary max value for meter metrics.
|
|
1390
|
-
|
|
1391
|
-
|
|
1400
|
+
# Calculate slightly-arbitrary max value for meter and second metrics.
|
|
1401
|
+
max_meter_value = data.iloc[:2, :].max().max() + 0.40
|
|
1402
|
+
max_time_value = data.iloc[5:7, :].max().max() + 0.40
|
|
1403
|
+
if index.row() in [0, 1, 2, 3, 4]:
|
|
1404
|
+
max_value = max_meter_value
|
|
1405
|
+
elif index.row() in [5, 6, 7]:
|
|
1406
|
+
max_value = max_time_value
|
|
1407
|
+
elif index.row() in [8, 9, 10, 11]:
|
|
1392
1408
|
max_value = 100
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
max_value = 240
|
|
1409
|
+
elif index.row() == 12:
|
|
1410
|
+
max_value = 3.0
|
|
1411
|
+
elif index.row() == 13:
|
|
1412
|
+
max_value = 1.5
|
|
1413
|
+
elif index.row() == 14:
|
|
1414
|
+
max_value = 240
|
|
1400
1415
|
|
|
1401
1416
|
left_columns = [i for i in columns if "Left" in data.columns[i]]
|
|
1402
1417
|
right_columns = [i for i in columns if "Right" in data.columns[i]]
|
|
1403
1418
|
left_values = data.iloc[index.row(), left_columns]
|
|
1404
1419
|
right_values = data.iloc[index.row(), right_columns]
|
|
1405
1420
|
|
|
1406
|
-
if not left_values.empty:
|
|
1421
|
+
if not left_values.empty and index.row() < 15:
|
|
1407
1422
|
l_min, l_max, l_mean = left_values.min(), left_values.max(), left_values.mean()
|
|
1408
1423
|
draw_box(l_min, l_max, l_mean, max_value, side='left')
|
|
1409
|
-
if not right_values.empty:
|
|
1424
|
+
if not right_values.empty and index.row() < 15:
|
|
1410
1425
|
r_min, r_max, r_mean = right_values.min(), right_values.max(), right_values.mean()
|
|
1411
1426
|
draw_box(r_min, r_max, r_mean, max_value, side='right')
|
|
1412
1427
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
################################################################################
|
|
4
4
|
## Form generated from reading UI file 'options_dialog.ui'
|
|
5
5
|
##
|
|
6
|
-
## Created by: Qt User Interface Compiler version 6.
|
|
6
|
+
## Created by: Qt User Interface Compiler version 6.9.0
|
|
7
7
|
##
|
|
8
8
|
## WARNING! All changes made in this file will be lost when recompiling UI file!
|
|
9
9
|
################################################################################
|
|
@@ -24,7 +24,7 @@ class Ui_OptionsDialog(object):
|
|
|
24
24
|
def setupUi(self, OptionsDialog):
|
|
25
25
|
if not OptionsDialog.objectName():
|
|
26
26
|
OptionsDialog.setObjectName(u"OptionsDialog")
|
|
27
|
-
OptionsDialog.resize(450,
|
|
27
|
+
OptionsDialog.resize(450, 686)
|
|
28
28
|
self.verticalLayout = QVBoxLayout(OptionsDialog)
|
|
29
29
|
self.verticalLayout.setObjectName(u"verticalLayout")
|
|
30
30
|
self.groupBox_3 = QGroupBox(OptionsDialog)
|
|
@@ -259,6 +259,18 @@ class Ui_OptionsDialog(object):
|
|
|
259
259
|
|
|
260
260
|
self.verticalLayout.addWidget(self.groupBox_6)
|
|
261
261
|
|
|
262
|
+
self.groupBox_7 = QGroupBox(OptionsDialog)
|
|
263
|
+
self.groupBox_7.setObjectName(u"groupBox_7")
|
|
264
|
+
self.verticalLayout_4 = QVBoxLayout(self.groupBox_7)
|
|
265
|
+
self.verticalLayout_4.setObjectName(u"verticalLayout_4")
|
|
266
|
+
self.checkBoxRunningGait = QCheckBox(self.groupBox_7)
|
|
267
|
+
self.checkBoxRunningGait.setObjectName(u"checkBoxRunningGait")
|
|
268
|
+
|
|
269
|
+
self.verticalLayout_4.addWidget(self.checkBoxRunningGait)
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
self.verticalLayout.addWidget(self.groupBox_7)
|
|
273
|
+
|
|
262
274
|
self.horizontalLayout_3 = QHBoxLayout()
|
|
263
275
|
self.horizontalLayout_3.setObjectName(u"horizontalLayout_3")
|
|
264
276
|
self.horizontalSpacer = QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
|
|
@@ -315,6 +327,8 @@ class Ui_OptionsDialog(object):
|
|
|
315
327
|
self.pushButtonIKTaskSetChooser.setText(QCoreApplication.translate("OptionsDialog", u"...", None))
|
|
316
328
|
self.groupBox_6.setTitle(QCoreApplication.translate("OptionsDialog", u"Anthropometrics", None))
|
|
317
329
|
self.checkBoxApproximateAnthropometrics.setText(QCoreApplication.translate("OptionsDialog", u"Approximate missing anthropometrics", None))
|
|
330
|
+
self.groupBox_7.setTitle(QCoreApplication.translate("OptionsDialog", u"Experimental", None))
|
|
331
|
+
self.checkBoxRunningGait.setText(QCoreApplication.translate("OptionsDialog", u"Running gait", None))
|
|
318
332
|
self.pushButtonOK.setText(QCoreApplication.translate("OptionsDialog", u"OK", None))
|
|
319
333
|
self.pushButtonCancel.setText(QCoreApplication.translate("OptionsDialog", u"Cancel", None))
|
|
320
334
|
# retranslateUi
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: c3d-parser
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.4
|
|
4
4
|
Summary: C3D parser for gait data harmonisation.
|
|
5
5
|
Author-email: Timothy Salemink <tim.nicolas@outlook.com>, Sally Jack <sallyjaack@gmail.com>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -12,7 +12,7 @@ Requires-Dist: numpy==1.26.4
|
|
|
12
12
|
Requires-Dist: pandas
|
|
13
13
|
Requires-Dist: c3d==0.5.2
|
|
14
14
|
Requires-Dist: trc-data-reader>=0.4.1
|
|
15
|
-
Requires-Dist: opensim-model-creator==0.7.
|
|
15
|
+
Requires-Dist: opensim-model-creator==0.7.2
|
|
16
16
|
Requires-Dist: PySide6
|
|
17
17
|
Requires-Dist: matplotlib
|
|
18
18
|
Requires-Dist: mplcursors
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{c3d_parser-1.2.2 → c3d_parser-1.2.4}/src/c3d_parser/core/osim_resources/external_loads_template.xml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{c3d_parser-1.2.2 → c3d_parser-1.2.4}/src/c3d_parser/view/dialogs/delete_marker_set_dialog.py
RENAMED
|
File without changes
|
|
File without changes
|
{c3d_parser-1.2.2 → c3d_parser-1.2.4}/src/c3d_parser/view/dialogs/marker_set_import_dialog.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|