c3d-parser 1.2.4__tar.gz → 1.4.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.4.0}/PKG-INFO +1 -1
- c3d_parser-1.4.0/src/c3d_parser/__init__.py +2 -0
- {c3d_parser-1.2.4 → c3d_parser-1.4.0}/src/c3d_parser/core/c3d_parser.py +88 -21
- {c3d_parser-1.2.4 → c3d_parser-1.4.0}/src/c3d_parser/view/dialogs/options_dialog.py +2 -0
- {c3d_parser-1.2.4 → c3d_parser-1.4.0}/src/c3d_parser/view/main_window.py +68 -41
- {c3d_parser-1.2.4 → c3d_parser-1.4.0}/src/c3d_parser/view/ui/ui_options_dialog.py +7 -1
- {c3d_parser-1.2.4 → c3d_parser-1.4.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.4.0}/LICENSE +0 -0
- {c3d_parser-1.2.4 → c3d_parser-1.4.0}/README.md +0 -0
- {c3d_parser-1.2.4 → c3d_parser-1.4.0}/pyproject.toml +0 -0
- {c3d_parser-1.2.4 → c3d_parser-1.4.0}/requirements.txt +0 -0
- {c3d_parser-1.2.4 → c3d_parser-1.4.0}/setup.cfg +0 -0
- {c3d_parser-1.2.4 → c3d_parser-1.4.0}/src/c3d_parser/application.py +0 -0
- {c3d_parser-1.2.4 → c3d_parser-1.4.0}/src/c3d_parser/core/c3d_patch.py +0 -0
- {c3d_parser-1.2.4 → c3d_parser-1.4.0}/src/c3d_parser/core/osim.py +0 -0
- {c3d_parser-1.2.4 → c3d_parser-1.4.0}/src/c3d_parser/core/osim_resources/external_loads_template.xml +0 -0
- {c3d_parser-1.2.4 → c3d_parser-1.4.0}/src/c3d_parser/core/osim_resources/ik_task_set.xml +0 -0
- {c3d_parser-1.2.4 → c3d_parser-1.4.0}/src/c3d_parser/core/utils.py +0 -0
- {c3d_parser-1.2.4 → c3d_parser-1.4.0}/src/c3d_parser/settings/general.py +0 -0
- {c3d_parser-1.2.4 → c3d_parser-1.4.0}/src/c3d_parser/settings/logging.py +0 -0
- {c3d_parser-1.2.4 → c3d_parser-1.4.0}/src/c3d_parser/settings/marker_maps/AUC.json +0 -0
- {c3d_parser-1.2.4 → c3d_parser-1.4.0}/src/c3d_parser/settings/marker_maps/FMC.json +0 -0
- {c3d_parser-1.2.4 → c3d_parser-1.4.0}/src/c3d_parser/settings/marker_maps/MH.json +0 -0
- {c3d_parser-1.2.4 → c3d_parser-1.4.0}/src/c3d_parser/settings/marker_maps/QCMAS.json +0 -0
- {c3d_parser-1.2.4 → c3d_parser-1.4.0}/src/c3d_parser/settings/marker_maps/RBWH.json +0 -0
- {c3d_parser-1.2.4 → c3d_parser-1.4.0}/src/c3d_parser/settings/marker_maps/RCH.json +0 -0
- {c3d_parser-1.2.4 → c3d_parser-1.4.0}/src/c3d_parser/settings/marker_maps/Sydney.json +0 -0
- {c3d_parser-1.2.4 → c3d_parser-1.4.0}/src/c3d_parser/splash_rc.py +0 -0
- {c3d_parser-1.2.4 → c3d_parser-1.4.0}/src/c3d_parser/splashscreen.py +0 -0
- {c3d_parser-1.2.4 → c3d_parser-1.4.0}/src/c3d_parser/view/dialogs/about_dialog.py +0 -0
- {c3d_parser-1.2.4 → c3d_parser-1.4.0}/src/c3d_parser/view/dialogs/delete_marker_set_dialog.py +0 -0
- {c3d_parser-1.2.4 → c3d_parser-1.4.0}/src/c3d_parser/view/dialogs/marker_set_dialog.py +0 -0
- {c3d_parser-1.2.4 → c3d_parser-1.4.0}/src/c3d_parser/view/dialogs/marker_set_import_dialog.py +0 -0
- {c3d_parser-1.2.4 → c3d_parser-1.4.0}/src/c3d_parser/view/ui/resources_rc.py +0 -0
- {c3d_parser-1.2.4 → c3d_parser-1.4.0}/src/c3d_parser/view/ui/ui_delete_marker_set_dialog.py +0 -0
- {c3d_parser-1.2.4 → c3d_parser-1.4.0}/src/c3d_parser/view/ui/ui_main_window.py +0 -0
- {c3d_parser-1.2.4 → c3d_parser-1.4.0}/src/c3d_parser/view/ui/ui_marker_set_dialog.py +0 -0
- {c3d_parser-1.2.4 → c3d_parser-1.4.0}/src/c3d_parser/view/ui/ui_marker_set_import_dialog.py +0 -0
- {c3d_parser-1.2.4 → c3d_parser-1.4.0}/src/c3d_parser/view/utils.py +0 -0
- {c3d_parser-1.2.4 → c3d_parser-1.4.0}/src/c3d_parser/view/widgets.py +0 -0
- {c3d_parser-1.2.4 → c3d_parser-1.4.0}/src/c3d_parser.egg-info/SOURCES.txt +0 -0
- {c3d_parser-1.2.4 → c3d_parser-1.4.0}/src/c3d_parser.egg-info/dependency_links.txt +0 -0
- {c3d_parser-1.2.4 → c3d_parser-1.4.0}/src/c3d_parser.egg-info/entry_points.txt +0 -0
- {c3d_parser-1.2.4 → c3d_parser-1.4.0}/src/c3d_parser.egg-info/requires.txt +0 -0
- {c3d_parser-1.2.4 → c3d_parser-1.4.0}/src/c3d_parser.egg-info/top_level.txt +0 -0
- {c3d_parser-1.2.4 → c3d_parser-1.4.0}/tests/test_parser.py +0 -0
|
@@ -16,6 +16,7 @@ from decimal import Decimal, ROUND_CEILING, ROUND_FLOOR
|
|
|
16
16
|
|
|
17
17
|
from trc import TRCData
|
|
18
18
|
from opensim_model_creator.Create_Model import create_model
|
|
19
|
+
from ll_visualiser.utils import get_fit_metrics, load_landmarks, define_measurements
|
|
19
20
|
|
|
20
21
|
from c3d_parser.core.c3d_patch import c3d
|
|
21
22
|
from c3d_parser.core.utils import clear_directory
|
|
@@ -82,13 +83,13 @@ def parse_session(static_trial, dynamic_trials, input_directory, output_director
|
|
|
82
83
|
grf_file_paths[trial] = grf_file_path
|
|
83
84
|
deidentified_file_names[trial] = os.path.basename(trc_file_path).rsplit(".", 1)[0]
|
|
84
85
|
|
|
85
|
-
write_c3d_parser_history(input_directory, static_trial, deidentified_file_names)
|
|
86
|
-
|
|
87
86
|
logger.info("Fitting shape model.")
|
|
88
87
|
dynamic_trc_path = list(trc_file_paths.values())[0] if trc_file_paths else ""
|
|
89
88
|
osim_model = create_osim_model(static_trc_path, dynamic_trc_path, frame, marker_diameter, static_data,
|
|
90
89
|
output_directory, left_foot_flat, right_foot_flat, optimise_knee_axis, progress_tracker)
|
|
91
90
|
|
|
91
|
+
write_c3d_parser_history(input_directory, output_directory, static_trial, deidentified_file_names, static_data)
|
|
92
|
+
|
|
92
93
|
if not trc_file_paths:
|
|
93
94
|
raise CancelException("No dynamic trials found.")
|
|
94
95
|
|
|
@@ -236,7 +237,7 @@ def parse_dynamic_trial(c3d_file, lab, output_directory, trial_index, marker_dat
|
|
|
236
237
|
return analog_data, events, s_t_data, trc_file_path, grf_file_path
|
|
237
238
|
|
|
238
239
|
|
|
239
|
-
def write_c3d_parser_history(input_directory, static_trial, deidentified_file_names):
|
|
240
|
+
def write_c3d_parser_history(input_directory, output_directory, static_trial, deidentified_file_names, static_data):
|
|
240
241
|
log_path = os.path.join(input_directory, "c3d_parser_history.log")
|
|
241
242
|
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M")
|
|
242
243
|
|
|
@@ -251,11 +252,56 @@ def write_c3d_parser_history(input_directory, static_trial, deidentified_file_na
|
|
|
251
252
|
f.write("Deidentified file names:\n")
|
|
252
253
|
for original_name, updated_name in deidentified_file_names.items():
|
|
253
254
|
f.write(f"{original_name}: {updated_name}\n")
|
|
255
|
+
f.write("\n")
|
|
256
|
+
|
|
257
|
+
if static_data:
|
|
258
|
+
units = {
|
|
259
|
+
"Height": "mm",
|
|
260
|
+
"Weight": "kg",
|
|
261
|
+
"ASIS Width": "mm",
|
|
262
|
+
"Left Knee Width": "mm",
|
|
263
|
+
"Right Knee Width": "mm",
|
|
264
|
+
"Left Ankle Width": "mm",
|
|
265
|
+
"Right Ankle Width": "mm",
|
|
266
|
+
"Left Leg Length": "mm",
|
|
267
|
+
"Right Leg Length": "mm",
|
|
268
|
+
}
|
|
269
|
+
f.write("Subject Information:\n")
|
|
270
|
+
for key, value in static_data.items():
|
|
271
|
+
label = "Body Mass" if key == "Weight" else key
|
|
272
|
+
unit = f" {units[key]}" if key in units else ""
|
|
273
|
+
f.write(f"{label}: {value}{unit}\n")
|
|
274
|
+
|
|
275
|
+
metrics, measurements = define_metrics_and_measurements(output_directory)
|
|
276
|
+
if metrics:
|
|
277
|
+
f.write("\n")
|
|
278
|
+
f.write("ASM Fit Metrics:\n")
|
|
279
|
+
for key, value in metrics.items():
|
|
280
|
+
f.write(f"{key}: {value} mm\n")
|
|
281
|
+
if measurements:
|
|
282
|
+
f.write("\n")
|
|
283
|
+
f.write("Bone Surface Measurements:\n")
|
|
284
|
+
for key, value in measurements.items():
|
|
285
|
+
f.write(f"{key}: {value} mm\n")
|
|
254
286
|
|
|
255
287
|
except (OSError, IOError):
|
|
256
288
|
raise ParserError("Could not write c3d_parser_history.log")
|
|
257
289
|
|
|
258
290
|
|
|
291
|
+
def define_metrics_and_measurements(output_directory):
|
|
292
|
+
model_directory = os.path.join(output_directory, "Models", "Meshes")
|
|
293
|
+
left_predicted_landmark_file = os.path.join(model_directory, 'predicted_lms_left.txt')
|
|
294
|
+
right_predicted_landmark_file = os.path.join(model_directory, 'predicted_lms_right.txt')
|
|
295
|
+
|
|
296
|
+
left_predicted_landmarks = load_landmarks(left_predicted_landmark_file)
|
|
297
|
+
right_predicted_landmarks = load_landmarks(right_predicted_landmark_file)
|
|
298
|
+
|
|
299
|
+
metrics = get_fit_metrics(model_directory)
|
|
300
|
+
measurements = define_measurements(left_predicted_landmarks, right_predicted_landmarks)
|
|
301
|
+
|
|
302
|
+
return metrics, measurements
|
|
303
|
+
|
|
304
|
+
|
|
259
305
|
def write_event_data(events, file_name, output_directory):
|
|
260
306
|
event_directory = os.path.join(output_directory, "events")
|
|
261
307
|
if not os.path.exists(event_directory):
|
|
@@ -739,38 +785,41 @@ def extract_static_data(file_path):
|
|
|
739
785
|
with open(file_path, 'rb') as handle:
|
|
740
786
|
reader = c3d.Reader(handle)
|
|
741
787
|
|
|
788
|
+
static_data = {}
|
|
742
789
|
if 'PROCESSING' not in reader:
|
|
743
790
|
logger.warn("No processing section found in static trial.")
|
|
744
|
-
return
|
|
745
|
-
|
|
791
|
+
return static_data
|
|
746
792
|
processing_group = reader.get('PROCESSING')
|
|
747
|
-
|
|
748
|
-
|
|
793
|
+
|
|
794
|
+
if 'SUBJECT_INFO' in reader:
|
|
795
|
+
subject_info = reader.get('SUBJECT_INFO')
|
|
796
|
+
if 'SEX' in subject_info:
|
|
797
|
+
static_data['sex'] = subject_info.get('SEX').string_value.title()
|
|
798
|
+
if 'AGE' in subject_info:
|
|
799
|
+
static_data['age'] = int(subject_info.get('AGE').float_value)
|
|
749
800
|
|
|
750
801
|
if 'HEIGHT' in processing_group:
|
|
751
|
-
height = reader.get('PROCESSING:Height').float_value
|
|
802
|
+
static_data['height'] = reader.get('PROCESSING:Height').float_value
|
|
752
803
|
if 'BODYMASS' in processing_group:
|
|
753
|
-
mass = reader.get('PROCESSING:Bodymass').float_value
|
|
804
|
+
static_data['mass'] = reader.get('PROCESSING:Bodymass').float_value
|
|
805
|
+
if static_data['mass'] > 200:
|
|
806
|
+
static_data['mass'] = static_data['mass'] / 9.81
|
|
754
807
|
if 'INTERASISDISTANCE' in processing_group:
|
|
755
|
-
asis_width = reader.get('PROCESSING:InterAsisDistance').float_value
|
|
808
|
+
static_data['asis_width'] = reader.get('PROCESSING:InterAsisDistance').float_value
|
|
756
809
|
if 'LKNEEWIDTH' in processing_group:
|
|
757
|
-
left_knee_width = reader.get('PROCESSING:LKneeWidth').float_value
|
|
810
|
+
static_data['left_knee_width'] = reader.get('PROCESSING:LKneeWidth').float_value
|
|
758
811
|
if 'RKNEEWIDTH' in processing_group:
|
|
759
|
-
right_knee_width = reader.get('PROCESSING:RKneeWidth').float_value
|
|
812
|
+
static_data['right_knee_width'] = reader.get('PROCESSING:RKneeWidth').float_value
|
|
760
813
|
if 'LANKLEWIDTH' in processing_group:
|
|
761
|
-
left_ankle_width = reader.get('PROCESSING:LAnkleWidth').float_value
|
|
814
|
+
static_data['left_ankle_width'] = reader.get('PROCESSING:LAnkleWidth').float_value
|
|
762
815
|
if 'RANKLEWIDTH' in processing_group:
|
|
763
|
-
right_ankle_width = reader.get('PROCESSING:RAnkleWidth').float_value
|
|
816
|
+
static_data['right_ankle_width'] = reader.get('PROCESSING:RAnkleWidth').float_value
|
|
764
817
|
if 'LLEGLENGTH' in processing_group:
|
|
765
|
-
left_leg_length = reader.get('PROCESSING:LLegLength').float_value
|
|
818
|
+
static_data['left_leg_length'] = reader.get('PROCESSING:LLegLength').float_value
|
|
766
819
|
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
|
|
820
|
+
static_data['right_leg_length'] = reader.get('PROCESSING:RLegLength').float_value
|
|
771
821
|
|
|
772
|
-
return
|
|
773
|
-
left_ankle_width, right_ankle_width, left_leg_length, right_leg_length)
|
|
822
|
+
return static_data
|
|
774
823
|
|
|
775
824
|
|
|
776
825
|
def read_grf(file_path):
|
|
@@ -1046,6 +1095,15 @@ def is_dynamic(file_path):
|
|
|
1046
1095
|
finally:
|
|
1047
1096
|
logging.disable(logging.NOTSET)
|
|
1048
1097
|
|
|
1098
|
+
if 'PROCESSING' in reader:
|
|
1099
|
+
processing_group = reader.get('PROCESSING')
|
|
1100
|
+
if 'MEASUREMENT TYPE' in processing_group:
|
|
1101
|
+
measurement_type = reader.get('PROCESSING:Measurement type').string_value.strip().capitalize()
|
|
1102
|
+
if measurement_type == 'Dynamic':
|
|
1103
|
+
return True
|
|
1104
|
+
elif measurement_type == 'Static':
|
|
1105
|
+
return False
|
|
1106
|
+
|
|
1049
1107
|
if reader.analog_used > 0 and reader.frame_count > 100:
|
|
1050
1108
|
return True
|
|
1051
1109
|
else:
|
|
@@ -1227,6 +1285,15 @@ def normalise_kinetics(kinetic_data, events):
|
|
|
1227
1285
|
return normalised_data
|
|
1228
1286
|
|
|
1229
1287
|
|
|
1288
|
+
def write_normalised_grfs(grf_data, selected_trials, excluded_cycles, output_directory):
|
|
1289
|
+
normalised_directory = os.path.join(output_directory, 'normalised')
|
|
1290
|
+
if not os.path.exists(normalised_directory):
|
|
1291
|
+
os.makedirs(normalised_directory)
|
|
1292
|
+
output_file = os.path.join(normalised_directory, f"combined_grf.csv")
|
|
1293
|
+
columns = ['anterior_posterior', 'medial_lateral', 'vertical']
|
|
1294
|
+
write_normalised_data(grf_data, columns, selected_trials, excluded_cycles, output_file)
|
|
1295
|
+
|
|
1296
|
+
|
|
1230
1297
|
def write_normalised_kinematics(kinematic_data, selected_trials, excluded_cycles, output_directory):
|
|
1231
1298
|
normalised_directory = os.path.join(output_directory, 'normalised')
|
|
1232
1299
|
if not os.path.exists(normalised_directory):
|
|
@@ -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
|
|
@@ -71,13 +71,14 @@ class MainWindow(QMainWindow):
|
|
|
71
71
|
self._line_width = 1.0
|
|
72
72
|
self._input_data_directory = ''
|
|
73
73
|
self._output_data_directory = ''
|
|
74
|
-
self._optimise_knee_axis =
|
|
74
|
+
self._optimise_knee_axis = False
|
|
75
75
|
self._filter_trc = True
|
|
76
76
|
self._filter_grf = True
|
|
77
77
|
self._use_custom_ik_task_set = False
|
|
78
78
|
self._ik_task_set_path = ''
|
|
79
79
|
self._approximate_anthropometrics = False
|
|
80
80
|
self._running_gait = False
|
|
81
|
+
self._output_grf = False
|
|
81
82
|
|
|
82
83
|
self._colour_left = '#A52A2A'
|
|
83
84
|
self._colour_right = '#0F52BA'
|
|
@@ -86,6 +87,7 @@ class MainWindow(QMainWindow):
|
|
|
86
87
|
self._static_trial = None
|
|
87
88
|
self._analog_data = None
|
|
88
89
|
self._subject_weight = None
|
|
90
|
+
self._grf_data = {}
|
|
89
91
|
self._kinematic_data = {}
|
|
90
92
|
self._kinetic_data = {}
|
|
91
93
|
self._s_t_data = {}
|
|
@@ -109,6 +111,13 @@ class MainWindow(QMainWindow):
|
|
|
109
111
|
self._kinetic_curves = GaitCurves(self._kinetic_canvas, colours)
|
|
110
112
|
self._boxes = None
|
|
111
113
|
|
|
114
|
+
self._kinematic_curves.signals.cycles_excluded.connect(self._kinetic_curves.exclude_cycles)
|
|
115
|
+
self._kinematic_curves.signals.cycles_excluded.connect(self._grf_curves.exclude_cycles)
|
|
116
|
+
self._kinematic_curves.signals.cycles_included.connect(self._kinetic_curves.include_cycles)
|
|
117
|
+
self._kinematic_curves.signals.cycles_included.connect(self._grf_curves.include_cycles)
|
|
118
|
+
self._kinetic_curves.signals.cycles_excluded.connect(self._grf_curves.exclude_cycles)
|
|
119
|
+
self._kinetic_curves.signals.cycles_included.connect(self._grf_curves.include_cycles)
|
|
120
|
+
|
|
112
121
|
def _setup_progress_bar(self):
|
|
113
122
|
self._progress_text = ""
|
|
114
123
|
self._progress_value = 0
|
|
@@ -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()
|
|
@@ -673,7 +684,9 @@ class MainWindow(QMainWindow):
|
|
|
673
684
|
selected_trials = {key: value for key, value in self._deidentified_file_names.items() if key in selected}
|
|
674
685
|
kinematic_exclusions = self._kinematic_curves.get_excluded_cycles()
|
|
675
686
|
kinetic_exclusions = self._kinetic_curves.get_excluded_cycles()
|
|
687
|
+
grf_exclusions = self._grf_curves.get_excluded_cycles()
|
|
676
688
|
self._update_s_t_data_from_tables()
|
|
689
|
+
write_normalised_grfs(self._grf_data, selected_trials, grf_exclusions, self._output_directory)
|
|
677
690
|
write_normalised_kinematics(self._kinematic_data, selected_trials, kinematic_exclusions, self._output_directory)
|
|
678
691
|
write_normalised_kinetics(self._kinetic_data, selected_trials, kinetic_exclusions, self._output_directory)
|
|
679
692
|
write_spatiotemporal_data(self._s_t_data, selected_trials, self._output_directory)
|
|
@@ -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 = []
|
|
@@ -985,6 +1000,7 @@ class MainWindow(QMainWindow):
|
|
|
985
1000
|
settings.setValue('ik_task_set_path', self._ik_task_set_path)
|
|
986
1001
|
settings.setValue('approximate_anthropometrics', self._approximate_anthropometrics)
|
|
987
1002
|
settings.setValue('running_gait', self._running_gait)
|
|
1003
|
+
settings.setValue('output_grf', self._output_grf)
|
|
988
1004
|
settings.endGroup()
|
|
989
1005
|
|
|
990
1006
|
def _load_settings(self):
|
|
@@ -1037,6 +1053,8 @@ class MainWindow(QMainWindow):
|
|
|
1037
1053
|
self._approximate_anthropometrics = settings.value('approximate_anthropometrics') == 'true'
|
|
1038
1054
|
if settings.contains('running_gait'):
|
|
1039
1055
|
self._running_gait = settings.value('running_gait') == 'true'
|
|
1056
|
+
if settings.contains('output_grf'):
|
|
1057
|
+
self._output_grf = settings.value('output_grf') == 'true'
|
|
1040
1058
|
settings.endGroup()
|
|
1041
1059
|
|
|
1042
1060
|
def _quit_application(self):
|
|
@@ -1078,6 +1096,11 @@ class MainWindow(QMainWindow):
|
|
|
1078
1096
|
curves.exclude_side(file_name, "Right")
|
|
1079
1097
|
|
|
1080
1098
|
|
|
1099
|
+
class CurveSignals(QObject):
|
|
1100
|
+
cycles_included = Signal(list)
|
|
1101
|
+
cycles_excluded = Signal(list)
|
|
1102
|
+
|
|
1103
|
+
|
|
1081
1104
|
class GaitCurves(defaultdict):
|
|
1082
1105
|
|
|
1083
1106
|
def __init__(self, canvas, colours):
|
|
@@ -1087,6 +1110,8 @@ class GaitCurves(defaultdict):
|
|
|
1087
1110
|
self._canvas.mpl_connect('pick_event', lambda event: self.toggle_cycle(event))
|
|
1088
1111
|
self._canvas.mpl_connect('button_press_event', lambda event: self.show_curve_menu(event))
|
|
1089
1112
|
|
|
1113
|
+
self.signals = CurveSignals()
|
|
1114
|
+
|
|
1090
1115
|
self._selected_curves = []
|
|
1091
1116
|
self._excluded_cycles = set()
|
|
1092
1117
|
|
|
@@ -1178,15 +1203,16 @@ class GaitCurves(defaultdict):
|
|
|
1178
1203
|
context_menu.exec_(self._canvas.mapToGlobal(point))
|
|
1179
1204
|
|
|
1180
1205
|
def include_curves(self):
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
colour = self._colour_left if "Left" in cycle else self._colour_right
|
|
1184
|
-
for line in self[file_name][cycle]:
|
|
1185
|
-
line.set_linestyle('solid')
|
|
1186
|
-
line.set_color(colour)
|
|
1187
|
-
line.set_zorder(2)
|
|
1206
|
+
identifiers = list(self._selected_curves)
|
|
1207
|
+
self.include_cycles(identifiers)
|
|
1188
1208
|
self._selected_curves = []
|
|
1189
1209
|
|
|
1210
|
+
self.signals.cycles_included.emit(identifiers)
|
|
1211
|
+
|
|
1212
|
+
def include_cycles(self, identifiers):
|
|
1213
|
+
for file_name, cycle in identifiers:
|
|
1214
|
+
colour = self._colour_left if "Left" in cycle else self._colour_right
|
|
1215
|
+
self._include_cycle(file_name, cycle, colour)
|
|
1190
1216
|
self._canvas.draw()
|
|
1191
1217
|
|
|
1192
1218
|
def include_all(self):
|
|
@@ -1238,15 +1264,16 @@ class GaitCurves(defaultdict):
|
|
|
1238
1264
|
self._canvas.draw_idle()
|
|
1239
1265
|
|
|
1240
1266
|
def exclude_curves(self):
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
colour = self._colour_left if "Left" in cycle else self._colour_right
|
|
1244
|
-
for line in self[file_name][cycle]:
|
|
1245
|
-
line.set_linestyle('dotted')
|
|
1246
|
-
line.set_color(colour)
|
|
1247
|
-
line.set_zorder(1)
|
|
1267
|
+
identifiers = list(self._selected_curves)
|
|
1268
|
+
self.exclude_cycles(identifiers)
|
|
1248
1269
|
self._selected_curves = []
|
|
1249
1270
|
|
|
1271
|
+
self.signals.cycles_excluded.emit(identifiers)
|
|
1272
|
+
|
|
1273
|
+
def exclude_cycles(self, identifiers):
|
|
1274
|
+
for file_name, cycle in identifiers:
|
|
1275
|
+
colour = self._colour_left if "Left" in cycle else self._colour_right
|
|
1276
|
+
self._exclude_cycle(file_name, cycle, colour)
|
|
1250
1277
|
self._canvas.draw()
|
|
1251
1278
|
|
|
1252
1279
|
def exclude_all(self):
|
|
@@ -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.4.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.4.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.4.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
|