c3d-parser 1.2.4__tar.gz → 1.3.0__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.4/src/c3d_parser.egg-info → c3d_parser-1.3.0}/PKG-INFO +1 -1
- c3d_parser-1.3.0/src/c3d_parser/__init__.py +2 -0
- {c3d_parser-1.2.4 → c3d_parser-1.3.0}/src/c3d_parser/core/c3d_parser.py +56 -18
- {c3d_parser-1.2.4 → c3d_parser-1.3.0}/src/c3d_parser/view/dialogs/options_dialog.py +2 -0
- {c3d_parser-1.2.4 → c3d_parser-1.3.0}/src/c3d_parser/view/main_window.py +49 -26
- {c3d_parser-1.2.4 → c3d_parser-1.3.0}/src/c3d_parser/view/ui/ui_options_dialog.py +7 -1
- {c3d_parser-1.2.4 → c3d_parser-1.3.0/src/c3d_parser.egg-info}/PKG-INFO +1 -1
- c3d_parser-1.2.4/src/c3d_parser/__init__.py +0 -2
- {c3d_parser-1.2.4 → c3d_parser-1.3.0}/LICENSE +0 -0
- {c3d_parser-1.2.4 → c3d_parser-1.3.0}/README.md +0 -0
- {c3d_parser-1.2.4 → c3d_parser-1.3.0}/pyproject.toml +0 -0
- {c3d_parser-1.2.4 → c3d_parser-1.3.0}/requirements.txt +0 -0
- {c3d_parser-1.2.4 → c3d_parser-1.3.0}/setup.cfg +0 -0
- {c3d_parser-1.2.4 → c3d_parser-1.3.0}/src/c3d_parser/application.py +0 -0
- {c3d_parser-1.2.4 → c3d_parser-1.3.0}/src/c3d_parser/core/c3d_patch.py +0 -0
- {c3d_parser-1.2.4 → c3d_parser-1.3.0}/src/c3d_parser/core/osim.py +0 -0
- {c3d_parser-1.2.4 → c3d_parser-1.3.0}/src/c3d_parser/core/osim_resources/external_loads_template.xml +0 -0
- {c3d_parser-1.2.4 → c3d_parser-1.3.0}/src/c3d_parser/core/osim_resources/ik_task_set.xml +0 -0
- {c3d_parser-1.2.4 → c3d_parser-1.3.0}/src/c3d_parser/core/utils.py +0 -0
- {c3d_parser-1.2.4 → c3d_parser-1.3.0}/src/c3d_parser/settings/general.py +0 -0
- {c3d_parser-1.2.4 → c3d_parser-1.3.0}/src/c3d_parser/settings/logging.py +0 -0
- {c3d_parser-1.2.4 → c3d_parser-1.3.0}/src/c3d_parser/settings/marker_maps/AUC.json +0 -0
- {c3d_parser-1.2.4 → c3d_parser-1.3.0}/src/c3d_parser/settings/marker_maps/FMC.json +0 -0
- {c3d_parser-1.2.4 → c3d_parser-1.3.0}/src/c3d_parser/settings/marker_maps/MH.json +0 -0
- {c3d_parser-1.2.4 → c3d_parser-1.3.0}/src/c3d_parser/settings/marker_maps/QCMAS.json +0 -0
- {c3d_parser-1.2.4 → c3d_parser-1.3.0}/src/c3d_parser/settings/marker_maps/RBWH.json +0 -0
- {c3d_parser-1.2.4 → c3d_parser-1.3.0}/src/c3d_parser/settings/marker_maps/RCH.json +0 -0
- {c3d_parser-1.2.4 → c3d_parser-1.3.0}/src/c3d_parser/settings/marker_maps/Sydney.json +0 -0
- {c3d_parser-1.2.4 → c3d_parser-1.3.0}/src/c3d_parser/splash_rc.py +0 -0
- {c3d_parser-1.2.4 → c3d_parser-1.3.0}/src/c3d_parser/splashscreen.py +0 -0
- {c3d_parser-1.2.4 → c3d_parser-1.3.0}/src/c3d_parser/view/dialogs/about_dialog.py +0 -0
- {c3d_parser-1.2.4 → c3d_parser-1.3.0}/src/c3d_parser/view/dialogs/delete_marker_set_dialog.py +0 -0
- {c3d_parser-1.2.4 → c3d_parser-1.3.0}/src/c3d_parser/view/dialogs/marker_set_dialog.py +0 -0
- {c3d_parser-1.2.4 → c3d_parser-1.3.0}/src/c3d_parser/view/dialogs/marker_set_import_dialog.py +0 -0
- {c3d_parser-1.2.4 → c3d_parser-1.3.0}/src/c3d_parser/view/ui/resources_rc.py +0 -0
- {c3d_parser-1.2.4 → c3d_parser-1.3.0}/src/c3d_parser/view/ui/ui_delete_marker_set_dialog.py +0 -0
- {c3d_parser-1.2.4 → c3d_parser-1.3.0}/src/c3d_parser/view/ui/ui_main_window.py +0 -0
- {c3d_parser-1.2.4 → c3d_parser-1.3.0}/src/c3d_parser/view/ui/ui_marker_set_dialog.py +0 -0
- {c3d_parser-1.2.4 → c3d_parser-1.3.0}/src/c3d_parser/view/ui/ui_marker_set_import_dialog.py +0 -0
- {c3d_parser-1.2.4 → c3d_parser-1.3.0}/src/c3d_parser/view/utils.py +0 -0
- {c3d_parser-1.2.4 → c3d_parser-1.3.0}/src/c3d_parser/view/widgets.py +0 -0
- {c3d_parser-1.2.4 → c3d_parser-1.3.0}/src/c3d_parser.egg-info/SOURCES.txt +0 -0
- {c3d_parser-1.2.4 → c3d_parser-1.3.0}/src/c3d_parser.egg-info/dependency_links.txt +0 -0
- {c3d_parser-1.2.4 → c3d_parser-1.3.0}/src/c3d_parser.egg-info/entry_points.txt +0 -0
- {c3d_parser-1.2.4 → c3d_parser-1.3.0}/src/c3d_parser.egg-info/requires.txt +0 -0
- {c3d_parser-1.2.4 → c3d_parser-1.3.0}/src/c3d_parser.egg-info/top_level.txt +0 -0
- {c3d_parser-1.2.4 → c3d_parser-1.3.0}/tests/test_parser.py +0 -0
|
@@ -421,6 +421,8 @@ def harmonise_markers(frame_data, lab, required_markers):
|
|
|
421
421
|
if None in frame_data.columns:
|
|
422
422
|
frame_data.drop(columns=frame_data.columns[frame_data.columns.isna()], inplace=True)
|
|
423
423
|
|
|
424
|
+
# TODO: This error window does not indicate which trial has the issue.
|
|
425
|
+
# Update this.
|
|
424
426
|
# Ensure required markers are present.
|
|
425
427
|
available = set(frame_data.columns)
|
|
426
428
|
for item in required_markers:
|
|
@@ -476,8 +478,10 @@ def trim_frames(frame_data):
|
|
|
476
478
|
if not drop_frames.empty:
|
|
477
479
|
frame_data.drop(drop_frames, inplace=True)
|
|
478
480
|
|
|
481
|
+
# TODO: If we convert this to a dictionary we can keep the markers that are missing and check if they are required.
|
|
479
482
|
remaining_frames = [frame for frame in incomplete_frames.keys() if trim_start <= frame <= trim_end]
|
|
480
483
|
if remaining_frames:
|
|
484
|
+
# TODO: We should raise a ParserError (skip this trial) if the missing markers are required...?
|
|
481
485
|
logger.warn(f"Frames {remaining_frames} are incomplete.")
|
|
482
486
|
|
|
483
487
|
return trim_start, trim_end
|
|
@@ -739,38 +743,41 @@ def extract_static_data(file_path):
|
|
|
739
743
|
with open(file_path, 'rb') as handle:
|
|
740
744
|
reader = c3d.Reader(handle)
|
|
741
745
|
|
|
746
|
+
static_data = {}
|
|
742
747
|
if 'PROCESSING' not in reader:
|
|
743
748
|
logger.warn("No processing section found in static trial.")
|
|
744
|
-
return
|
|
745
|
-
|
|
749
|
+
return static_data
|
|
746
750
|
processing_group = reader.get('PROCESSING')
|
|
747
|
-
|
|
748
|
-
|
|
751
|
+
|
|
752
|
+
if 'SUBJECT_INFO' in reader:
|
|
753
|
+
subject_info = reader.get('SUBJECT_INFO')
|
|
754
|
+
if 'SEX' in subject_info:
|
|
755
|
+
static_data['sex'] = subject_info.get('SEX').string_value.title()
|
|
756
|
+
if 'AGE' in subject_info:
|
|
757
|
+
static_data['age'] = int(subject_info.get('AGE').float_value)
|
|
749
758
|
|
|
750
759
|
if 'HEIGHT' in processing_group:
|
|
751
|
-
height = reader.get('PROCESSING:Height').float_value
|
|
760
|
+
static_data['height'] = reader.get('PROCESSING:Height').float_value
|
|
752
761
|
if 'BODYMASS' in processing_group:
|
|
753
|
-
mass = reader.get('PROCESSING:Bodymass').float_value
|
|
762
|
+
static_data['mass'] = reader.get('PROCESSING:Bodymass').float_value
|
|
763
|
+
if static_data['mass'] > 200:
|
|
764
|
+
static_data['mass'] = static_data['mass'] / 9.81
|
|
754
765
|
if 'INTERASISDISTANCE' in processing_group:
|
|
755
|
-
asis_width = reader.get('PROCESSING:InterAsisDistance').float_value
|
|
766
|
+
static_data['asis_width'] = reader.get('PROCESSING:InterAsisDistance').float_value
|
|
756
767
|
if 'LKNEEWIDTH' in processing_group:
|
|
757
|
-
left_knee_width = reader.get('PROCESSING:LKneeWidth').float_value
|
|
768
|
+
static_data['left_knee_width'] = reader.get('PROCESSING:LKneeWidth').float_value
|
|
758
769
|
if 'RKNEEWIDTH' in processing_group:
|
|
759
|
-
right_knee_width = reader.get('PROCESSING:RKneeWidth').float_value
|
|
770
|
+
static_data['right_knee_width'] = reader.get('PROCESSING:RKneeWidth').float_value
|
|
760
771
|
if 'LANKLEWIDTH' in processing_group:
|
|
761
|
-
left_ankle_width = reader.get('PROCESSING:LAnkleWidth').float_value
|
|
772
|
+
static_data['left_ankle_width'] = reader.get('PROCESSING:LAnkleWidth').float_value
|
|
762
773
|
if 'RANKLEWIDTH' in processing_group:
|
|
763
|
-
right_ankle_width = reader.get('PROCESSING:RAnkleWidth').float_value
|
|
774
|
+
static_data['right_ankle_width'] = reader.get('PROCESSING:RAnkleWidth').float_value
|
|
764
775
|
if 'LLEGLENGTH' in processing_group:
|
|
765
|
-
left_leg_length = reader.get('PROCESSING:LLegLength').float_value
|
|
776
|
+
static_data['left_leg_length'] = reader.get('PROCESSING:LLegLength').float_value
|
|
766
777
|
if 'RLEGLENGTH' in processing_group:
|
|
767
|
-
right_leg_length = reader.get('PROCESSING:RLegLength').float_value
|
|
768
|
-
|
|
769
|
-
if mass and mass > 200:
|
|
770
|
-
mass = mass / 9.81
|
|
778
|
+
static_data['right_leg_length'] = reader.get('PROCESSING:RLegLength').float_value
|
|
771
779
|
|
|
772
|
-
return
|
|
773
|
-
left_ankle_width, right_ankle_width, left_leg_length, right_leg_length)
|
|
780
|
+
return static_data
|
|
774
781
|
|
|
775
782
|
|
|
776
783
|
def read_grf(file_path):
|
|
@@ -1038,6 +1045,8 @@ def scale_grf_data(analog_data):
|
|
|
1038
1045
|
analog_data[columns] = analog_data[columns] / 1000
|
|
1039
1046
|
|
|
1040
1047
|
|
|
1048
|
+
# TODO: This incorrectly classifies Adelaide's static trials as dynamic.
|
|
1049
|
+
# Use `calculate_distance_covered`...?
|
|
1041
1050
|
def is_dynamic(file_path):
|
|
1042
1051
|
with open(file_path, 'rb') as handle:
|
|
1043
1052
|
try:
|
|
@@ -1046,6 +1055,15 @@ def is_dynamic(file_path):
|
|
|
1046
1055
|
finally:
|
|
1047
1056
|
logging.disable(logging.NOTSET)
|
|
1048
1057
|
|
|
1058
|
+
if 'PROCESSING' in reader:
|
|
1059
|
+
processing_group = reader.get('PROCESSING')
|
|
1060
|
+
if 'MEASUREMENT TYPE' in processing_group:
|
|
1061
|
+
measurement_type = reader.get('PROCESSING:Measurement type').string_value.strip().capitalize()
|
|
1062
|
+
if measurement_type == 'Dynamic':
|
|
1063
|
+
return True
|
|
1064
|
+
elif measurement_type == 'Static':
|
|
1065
|
+
return False
|
|
1066
|
+
|
|
1049
1067
|
if reader.analog_used > 0 and reader.frame_count > 100:
|
|
1050
1068
|
return True
|
|
1051
1069
|
else:
|
|
@@ -1204,6 +1222,11 @@ def normalise_kinetics(kinetic_data, events):
|
|
|
1204
1222
|
normalised_data[foot][file_name] = {}
|
|
1205
1223
|
data_segment = data.iloc[start:frame, 1:]
|
|
1206
1224
|
|
|
1225
|
+
# TODO: If the model +tve and -tve are wrong, then we can't just flip these.
|
|
1226
|
+
# Because the non-GRF parts of the moments would also be flipped.
|
|
1227
|
+
# Just visually check the results against some clinical kinetics.
|
|
1228
|
+
# ...
|
|
1229
|
+
# Looks pretty good so far.
|
|
1207
1230
|
# Perform sign transformations.
|
|
1208
1231
|
data_segment[f"hip_flexion_{side}_moment"] = -data_segment[f"hip_flexion_{side}_moment"]
|
|
1209
1232
|
data_segment[f"hip_adduction_{side}_moment"] = -data_segment[f"hip_adduction_{side}_moment"]
|
|
@@ -1227,6 +1250,16 @@ def normalise_kinetics(kinetic_data, events):
|
|
|
1227
1250
|
return normalised_data
|
|
1228
1251
|
|
|
1229
1252
|
|
|
1253
|
+
def write_normalised_grfs(grf_data, selected_trials, output_directory):
|
|
1254
|
+
normalised_directory = os.path.join(output_directory, 'normalised')
|
|
1255
|
+
if not os.path.exists(normalised_directory):
|
|
1256
|
+
os.makedirs(normalised_directory)
|
|
1257
|
+
output_file = os.path.join(normalised_directory, f"combined_grf.csv")
|
|
1258
|
+
columns = ['anterior_posterior', 'medial_lateral', 'vertical']
|
|
1259
|
+
excluded_cycles = set()
|
|
1260
|
+
write_normalised_data(grf_data, columns, selected_trials, excluded_cycles, output_file)
|
|
1261
|
+
|
|
1262
|
+
|
|
1230
1263
|
def write_normalised_kinematics(kinematic_data, selected_trials, excluded_cycles, output_directory):
|
|
1231
1264
|
normalised_directory = os.path.join(output_directory, 'normalised')
|
|
1232
1265
|
if not os.path.exists(normalised_directory):
|
|
@@ -1281,6 +1314,9 @@ def write_normalised_data(data, column_names, selected_trials, excluded_cycles,
|
|
|
1281
1314
|
file.write('\n')
|
|
1282
1315
|
|
|
1283
1316
|
|
|
1317
|
+
# TODO: Rework this function to be less sequential.
|
|
1318
|
+
# It should be able to calculate metrics for walking and running gait.
|
|
1319
|
+
# Put this at the start of the sequential bit.
|
|
1284
1320
|
def calculate_spatiotemporal_data(frame_data, events, static_data):
|
|
1285
1321
|
stride_lengths = {"Left": {}, "Right": {}}
|
|
1286
1322
|
step_lengths = {"Left": {}, "Right": {}}
|
|
@@ -1303,6 +1339,8 @@ def calculate_spatiotemporal_data(frame_data, events, static_data):
|
|
|
1303
1339
|
for event_time, (event_type, event_plate) in stride_events.items():
|
|
1304
1340
|
time_ordered_events[event_time][foot] = [event_type, stride_number]
|
|
1305
1341
|
|
|
1342
|
+
# TODO: We should be able to use this to calculate ALL measurements non-sequentially.
|
|
1343
|
+
# TODO: Double check that we don't have situations where the events are not ordered in increasing stride number.
|
|
1306
1344
|
# Add terminal foot-strike to events for each cycle.
|
|
1307
1345
|
full_cycle_events = copy.deepcopy(events)
|
|
1308
1346
|
for foot, foot_events in full_cycle_events.items():
|
|
@@ -91,6 +91,7 @@ class OptionsDialog(QDialog):
|
|
|
91
91
|
self._ui.lineEditIKTaskSet.setText(options['ik_task_set_path'])
|
|
92
92
|
self._ui.checkBoxApproximateAnthropometrics.setChecked(options['approximate_anthropometrics'])
|
|
93
93
|
self._ui.checkBoxRunningGait.setChecked(options['running_gait'])
|
|
94
|
+
self._ui.checkBoxOutputGRFs.setChecked(options['output_grf'])
|
|
94
95
|
|
|
95
96
|
def save(self):
|
|
96
97
|
options = {
|
|
@@ -107,6 +108,7 @@ class OptionsDialog(QDialog):
|
|
|
107
108
|
'ik_task_set_path': self._ui.lineEditIKTaskSet.text(),
|
|
108
109
|
'approximate_anthropometrics': self._ui.checkBoxApproximateAnthropometrics.isChecked(),
|
|
109
110
|
'running_gait': self._ui.checkBoxRunningGait.isChecked(),
|
|
111
|
+
'output_grf': self._ui.checkBoxOutputGRFs.isChecked(),
|
|
110
112
|
}
|
|
111
113
|
|
|
112
114
|
return options
|
|
@@ -6,18 +6,18 @@ import pandas as pd
|
|
|
6
6
|
from collections import defaultdict
|
|
7
7
|
|
|
8
8
|
from PySide6.QtGui import QPen, QColor, QFontMetrics
|
|
9
|
-
from PySide6.QtCore import Qt, QSettings, QPoint, QThread, Signal, QObject,
|
|
9
|
+
from PySide6.QtCore import Qt, QSettings, QPoint, QThread, Signal, QObject, QAbstractTableModel, QRect
|
|
10
10
|
from PySide6.QtWidgets import (QApplication, QMainWindow, QMenu, QFileDialog, QListWidgetItem, QInputDialog,
|
|
11
11
|
QMessageBox, QTableView, QLabel, QAbstractButton, QHeaderView, QStyledItemDelegate,
|
|
12
|
-
QWidget, QVBoxLayout, QHBoxLayout, QTableWidgetItem)
|
|
12
|
+
QWidget, QVBoxLayout, QHBoxLayout, QTableWidgetItem, QComboBox, QSpinBox)
|
|
13
13
|
from matplotlib.backends.backend_qtagg import FigureCanvasQTAgg
|
|
14
14
|
from matplotlib.figure import Figure
|
|
15
15
|
from pyvistaqt import QtInteractor
|
|
16
16
|
from ll_visualiser.visualiser import visualise_model
|
|
17
17
|
|
|
18
18
|
from c3d_parser.core.c3d_parser import (parse_session, extract_static_data, extract_marker_names, is_dynamic,
|
|
19
|
-
CancelException, write_normalised_kinematics, write_normalised_kinetics,
|
|
20
|
-
approximate_anthropometrics)
|
|
19
|
+
CancelException, write_normalised_grfs, write_normalised_kinematics, write_normalised_kinetics,
|
|
20
|
+
write_spatiotemporal_data, approximate_anthropometrics)
|
|
21
21
|
from c3d_parser.settings.general import (APPLICATION_NAME, VERSION, DEFAULT_STYLE_SHEET, INVALID_STYLE_SHEET,
|
|
22
22
|
get_marker_maps_dir)
|
|
23
23
|
from c3d_parser.view.ui.ui_main_window import Ui_MainWindow
|
|
@@ -57,6 +57,7 @@ class ProgressTracker(QObject):
|
|
|
57
57
|
progress = Signal(str, str)
|
|
58
58
|
|
|
59
59
|
|
|
60
|
+
# TODO: CGM2 event detector on GitHub...?
|
|
60
61
|
class MainWindow(QMainWindow):
|
|
61
62
|
|
|
62
63
|
def __init__(self):
|
|
@@ -78,6 +79,7 @@ class MainWindow(QMainWindow):
|
|
|
78
79
|
self._ik_task_set_path = ''
|
|
79
80
|
self._approximate_anthropometrics = False
|
|
80
81
|
self._running_gait = False
|
|
82
|
+
self._output_grf = False
|
|
81
83
|
|
|
82
84
|
self._colour_left = '#A52A2A'
|
|
83
85
|
self._colour_right = '#0F52BA'
|
|
@@ -86,6 +88,7 @@ class MainWindow(QMainWindow):
|
|
|
86
88
|
self._static_trial = None
|
|
87
89
|
self._analog_data = None
|
|
88
90
|
self._subject_weight = None
|
|
91
|
+
self._grf_data = {}
|
|
89
92
|
self._kinematic_data = {}
|
|
90
93
|
self._kinetic_data = {}
|
|
91
94
|
self._s_t_data = {}
|
|
@@ -245,6 +248,12 @@ class MainWindow(QMainWindow):
|
|
|
245
248
|
table.deleteLater()
|
|
246
249
|
self._spatiotemporal_tables = []
|
|
247
250
|
|
|
251
|
+
# TODO: The legend is not correctly displayed if the ST tab is open while processing...
|
|
252
|
+
# Repositioning and updating the legend do nothing.
|
|
253
|
+
|
|
254
|
+
# TODO: The border is still having issues.
|
|
255
|
+
# Seems to be related to the area being scrollable.
|
|
256
|
+
# Only happens on some PCs/monitors...
|
|
248
257
|
def _visualise_spatiotemporal_data(self):
|
|
249
258
|
|
|
250
259
|
# Temporarily disable spatio-temporal visualisation for running gait.
|
|
@@ -478,28 +487,30 @@ class MainWindow(QMainWindow):
|
|
|
478
487
|
self._ui.doubleSpinBoxRightLegLength.setValue(0)
|
|
479
488
|
|
|
480
489
|
def _set_subject_info(self, static_trial):
|
|
490
|
+
field_map = {
|
|
491
|
+
'sex': self._ui.comboBoxSex,
|
|
492
|
+
'age': self._ui.spinBoxAge,
|
|
493
|
+
'height': self._ui.doubleSpinBoxHeight,
|
|
494
|
+
'mass': self._ui.doubleSpinBoxBodyMass,
|
|
495
|
+
'asis_width': self._ui.doubleSpinBoxASISWidth,
|
|
496
|
+
'left_knee_width': self._ui.doubleSpinBoxLeftKneeWidth,
|
|
497
|
+
'right_knee_width': self._ui.doubleSpinBoxRightKneeWidth,
|
|
498
|
+
'left_ankle_width': self._ui.doubleSpinBoxLeftAnkleWidth,
|
|
499
|
+
'right_ankle_width': self._ui.doubleSpinBoxRightAnkleWidth,
|
|
500
|
+
'left_leg_length': self._ui.doubleSpinBoxLeftLegLength,
|
|
501
|
+
'right_leg_length': self._ui.doubleSpinBoxRightLegLength,
|
|
502
|
+
}
|
|
503
|
+
|
|
481
504
|
input_directory = self._ui.lineEditInputDirectory.text()
|
|
482
505
|
c3d_file = os.path.join(input_directory, static_trial)
|
|
483
|
-
static_data =
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
if static_data[3] is not None:
|
|
492
|
-
self._ui.doubleSpinBoxLeftKneeWidth.setValue(static_data[3])
|
|
493
|
-
if static_data[4] is not None:
|
|
494
|
-
self._ui.doubleSpinBoxRightKneeWidth.setValue(static_data[4])
|
|
495
|
-
if static_data[5] is not None:
|
|
496
|
-
self._ui.doubleSpinBoxLeftAnkleWidth.setValue(static_data[5])
|
|
497
|
-
if static_data[6] is not None:
|
|
498
|
-
self._ui.doubleSpinBoxRightAnkleWidth.setValue(static_data[6])
|
|
499
|
-
if static_data[7] is not None:
|
|
500
|
-
self._ui.doubleSpinBoxLeftLegLength.setValue(static_data[7])
|
|
501
|
-
if static_data[8] is not None:
|
|
502
|
-
self._ui.doubleSpinBoxRightLegLength.setValue(static_data[8])
|
|
506
|
+
static_data = extract_static_data(c3d_file)
|
|
507
|
+
for key, widget in field_map.items():
|
|
508
|
+
value = static_data.get(key)
|
|
509
|
+
if value is not None:
|
|
510
|
+
if isinstance(widget, QComboBox):
|
|
511
|
+
widget.setCurrentText(value)
|
|
512
|
+
else:
|
|
513
|
+
widget.setValue(value)
|
|
503
514
|
|
|
504
515
|
@handle_runtime_error
|
|
505
516
|
def _parse_c3d_data(self):
|
|
@@ -611,9 +622,9 @@ class MainWindow(QMainWindow):
|
|
|
611
622
|
|
|
612
623
|
@handle_runtime_error
|
|
613
624
|
def _parse_finished(self, result):
|
|
614
|
-
|
|
625
|
+
self._grf_data, self._kinematic_data, self._kinetic_data, self._s_t_data, self._deidentified_file_names = result
|
|
615
626
|
|
|
616
|
-
self._visualise_grf_data(
|
|
627
|
+
self._visualise_grf_data(self._grf_data)
|
|
617
628
|
self._visualise_kinematic_data(self._kinematic_data)
|
|
618
629
|
self._visualise_kinetic_data(self._kinetic_data)
|
|
619
630
|
self._visualise_spatiotemporal_data()
|
|
@@ -674,6 +685,7 @@ class MainWindow(QMainWindow):
|
|
|
674
685
|
kinematic_exclusions = self._kinematic_curves.get_excluded_cycles()
|
|
675
686
|
kinetic_exclusions = self._kinetic_curves.get_excluded_cycles()
|
|
676
687
|
self._update_s_t_data_from_tables()
|
|
688
|
+
write_normalised_grfs(self._grf_data, selected_trials, self._output_directory)
|
|
677
689
|
write_normalised_kinematics(self._kinematic_data, selected_trials, kinematic_exclusions, self._output_directory)
|
|
678
690
|
write_normalised_kinetics(self._kinetic_data, selected_trials, kinetic_exclusions, self._output_directory)
|
|
679
691
|
write_spatiotemporal_data(self._s_t_data, selected_trials, self._output_directory)
|
|
@@ -875,6 +887,7 @@ class MainWindow(QMainWindow):
|
|
|
875
887
|
self._update_s_t_colours()
|
|
876
888
|
self._update_legend()
|
|
877
889
|
|
|
890
|
+
# TODO: For some reason the legend doesn't get created at all if we are visualising the ST tab while processing...
|
|
878
891
|
def _update_legend(self):
|
|
879
892
|
if self._boxes:
|
|
880
893
|
for side, boxes in self._boxes.items():
|
|
@@ -897,6 +910,7 @@ class MainWindow(QMainWindow):
|
|
|
897
910
|
'ik_task_set_path': self._ik_task_set_path,
|
|
898
911
|
'approximate_anthropometrics': self._approximate_anthropometrics,
|
|
899
912
|
'running_gait': self._running_gait,
|
|
913
|
+
'output_grf': self._output_grf,
|
|
900
914
|
}
|
|
901
915
|
|
|
902
916
|
return options
|
|
@@ -915,6 +929,7 @@ class MainWindow(QMainWindow):
|
|
|
915
929
|
self._ik_task_set_path = options['ik_task_set_path']
|
|
916
930
|
self._approximate_anthropometrics = options['approximate_anthropometrics']
|
|
917
931
|
self._running_gait = options['running_gait']
|
|
932
|
+
self._output_grf = options['output_grf']
|
|
918
933
|
|
|
919
934
|
def _show_custom_marker_set_dialog(self):
|
|
920
935
|
static_trials = []
|
|
@@ -964,6 +979,8 @@ class MainWindow(QMainWindow):
|
|
|
964
979
|
settings.beginGroup('MainWindow')
|
|
965
980
|
settings.setValue('size', self.size())
|
|
966
981
|
settings.setValue('pos', self.pos())
|
|
982
|
+
# TODO: The taskbar icon fails if the application opens in full-screen...
|
|
983
|
+
# DON'T open in full screen...?
|
|
967
984
|
settings.setValue('is_maximized', self.isMaximized())
|
|
968
985
|
settings.setValue('lab', self._ui.comboBoxLab.currentText())
|
|
969
986
|
settings.setValue('marker_diameter', self._ui.doubleSpinBoxMarkerDiameter.value())
|
|
@@ -985,6 +1002,7 @@ class MainWindow(QMainWindow):
|
|
|
985
1002
|
settings.setValue('ik_task_set_path', self._ik_task_set_path)
|
|
986
1003
|
settings.setValue('approximate_anthropometrics', self._approximate_anthropometrics)
|
|
987
1004
|
settings.setValue('running_gait', self._running_gait)
|
|
1005
|
+
settings.setValue('output_grf', self._output_grf)
|
|
988
1006
|
settings.endGroup()
|
|
989
1007
|
|
|
990
1008
|
def _load_settings(self):
|
|
@@ -1037,6 +1055,8 @@ class MainWindow(QMainWindow):
|
|
|
1037
1055
|
self._approximate_anthropometrics = settings.value('approximate_anthropometrics') == 'true'
|
|
1038
1056
|
if settings.contains('running_gait'):
|
|
1039
1057
|
self._running_gait = settings.value('running_gait') == 'true'
|
|
1058
|
+
if settings.contains('output_grf'):
|
|
1059
|
+
self._output_grf = settings.value('output_grf') == 'true'
|
|
1040
1060
|
settings.endGroup()
|
|
1041
1061
|
|
|
1042
1062
|
def _quit_application(self):
|
|
@@ -1153,6 +1173,9 @@ class GaitCurves(defaultdict):
|
|
|
1153
1173
|
line.set_color(colour)
|
|
1154
1174
|
line.set_zorder(z_order)
|
|
1155
1175
|
|
|
1176
|
+
# TODO: Replace all these with `draw_idle`?
|
|
1177
|
+
# Just the ones that handle many curves...?
|
|
1178
|
+
# Probably all...?
|
|
1156
1179
|
self._canvas.draw()
|
|
1157
1180
|
|
|
1158
1181
|
def show_curve_menu(self, event):
|
|
@@ -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.10.2
|
|
7
7
|
##
|
|
8
8
|
## WARNING! All changes made in this file will be lost when recompiling UI file!
|
|
9
9
|
################################################################################
|
|
@@ -268,6 +268,11 @@ class Ui_OptionsDialog(object):
|
|
|
268
268
|
|
|
269
269
|
self.verticalLayout_4.addWidget(self.checkBoxRunningGait)
|
|
270
270
|
|
|
271
|
+
self.checkBoxOutputGRFs = QCheckBox(self.groupBox_7)
|
|
272
|
+
self.checkBoxOutputGRFs.setObjectName(u"checkBoxOutputGRFs")
|
|
273
|
+
|
|
274
|
+
self.verticalLayout_4.addWidget(self.checkBoxOutputGRFs)
|
|
275
|
+
|
|
271
276
|
|
|
272
277
|
self.verticalLayout.addWidget(self.groupBox_7)
|
|
273
278
|
|
|
@@ -329,6 +334,7 @@ class Ui_OptionsDialog(object):
|
|
|
329
334
|
self.checkBoxApproximateAnthropometrics.setText(QCoreApplication.translate("OptionsDialog", u"Approximate missing anthropometrics", None))
|
|
330
335
|
self.groupBox_7.setTitle(QCoreApplication.translate("OptionsDialog", u"Experimental", None))
|
|
331
336
|
self.checkBoxRunningGait.setText(QCoreApplication.translate("OptionsDialog", u"Running gait", None))
|
|
337
|
+
self.checkBoxOutputGRFs.setText(QCoreApplication.translate("OptionsDialog", u"Output GRF data to CSV", None))
|
|
332
338
|
self.pushButtonOK.setText(QCoreApplication.translate("OptionsDialog", u"OK", None))
|
|
333
339
|
self.pushButtonCancel.setText(QCoreApplication.translate("OptionsDialog", u"Cancel", None))
|
|
334
340
|
# retranslateUi
|
|
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.4 → c3d_parser-1.3.0}/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.4 → c3d_parser-1.3.0}/src/c3d_parser/view/dialogs/delete_marker_set_dialog.py
RENAMED
|
File without changes
|
|
File without changes
|
{c3d_parser-1.2.4 → c3d_parser-1.3.0}/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
|
|
File without changes
|