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.
Files changed (47) hide show
  1. {c3d_parser-1.2.4/src/c3d_parser.egg-info → c3d_parser-1.4.0}/PKG-INFO +1 -1
  2. c3d_parser-1.4.0/src/c3d_parser/__init__.py +2 -0
  3. {c3d_parser-1.2.4 → c3d_parser-1.4.0}/src/c3d_parser/core/c3d_parser.py +88 -21
  4. {c3d_parser-1.2.4 → c3d_parser-1.4.0}/src/c3d_parser/view/dialogs/options_dialog.py +2 -0
  5. {c3d_parser-1.2.4 → c3d_parser-1.4.0}/src/c3d_parser/view/main_window.py +68 -41
  6. {c3d_parser-1.2.4 → c3d_parser-1.4.0}/src/c3d_parser/view/ui/ui_options_dialog.py +7 -1
  7. {c3d_parser-1.2.4 → c3d_parser-1.4.0/src/c3d_parser.egg-info}/PKG-INFO +1 -1
  8. c3d_parser-1.2.4/src/c3d_parser/__init__.py +0 -2
  9. {c3d_parser-1.2.4 → c3d_parser-1.4.0}/LICENSE +0 -0
  10. {c3d_parser-1.2.4 → c3d_parser-1.4.0}/README.md +0 -0
  11. {c3d_parser-1.2.4 → c3d_parser-1.4.0}/pyproject.toml +0 -0
  12. {c3d_parser-1.2.4 → c3d_parser-1.4.0}/requirements.txt +0 -0
  13. {c3d_parser-1.2.4 → c3d_parser-1.4.0}/setup.cfg +0 -0
  14. {c3d_parser-1.2.4 → c3d_parser-1.4.0}/src/c3d_parser/application.py +0 -0
  15. {c3d_parser-1.2.4 → c3d_parser-1.4.0}/src/c3d_parser/core/c3d_patch.py +0 -0
  16. {c3d_parser-1.2.4 → c3d_parser-1.4.0}/src/c3d_parser/core/osim.py +0 -0
  17. {c3d_parser-1.2.4 → c3d_parser-1.4.0}/src/c3d_parser/core/osim_resources/external_loads_template.xml +0 -0
  18. {c3d_parser-1.2.4 → c3d_parser-1.4.0}/src/c3d_parser/core/osim_resources/ik_task_set.xml +0 -0
  19. {c3d_parser-1.2.4 → c3d_parser-1.4.0}/src/c3d_parser/core/utils.py +0 -0
  20. {c3d_parser-1.2.4 → c3d_parser-1.4.0}/src/c3d_parser/settings/general.py +0 -0
  21. {c3d_parser-1.2.4 → c3d_parser-1.4.0}/src/c3d_parser/settings/logging.py +0 -0
  22. {c3d_parser-1.2.4 → c3d_parser-1.4.0}/src/c3d_parser/settings/marker_maps/AUC.json +0 -0
  23. {c3d_parser-1.2.4 → c3d_parser-1.4.0}/src/c3d_parser/settings/marker_maps/FMC.json +0 -0
  24. {c3d_parser-1.2.4 → c3d_parser-1.4.0}/src/c3d_parser/settings/marker_maps/MH.json +0 -0
  25. {c3d_parser-1.2.4 → c3d_parser-1.4.0}/src/c3d_parser/settings/marker_maps/QCMAS.json +0 -0
  26. {c3d_parser-1.2.4 → c3d_parser-1.4.0}/src/c3d_parser/settings/marker_maps/RBWH.json +0 -0
  27. {c3d_parser-1.2.4 → c3d_parser-1.4.0}/src/c3d_parser/settings/marker_maps/RCH.json +0 -0
  28. {c3d_parser-1.2.4 → c3d_parser-1.4.0}/src/c3d_parser/settings/marker_maps/Sydney.json +0 -0
  29. {c3d_parser-1.2.4 → c3d_parser-1.4.0}/src/c3d_parser/splash_rc.py +0 -0
  30. {c3d_parser-1.2.4 → c3d_parser-1.4.0}/src/c3d_parser/splashscreen.py +0 -0
  31. {c3d_parser-1.2.4 → c3d_parser-1.4.0}/src/c3d_parser/view/dialogs/about_dialog.py +0 -0
  32. {c3d_parser-1.2.4 → c3d_parser-1.4.0}/src/c3d_parser/view/dialogs/delete_marker_set_dialog.py +0 -0
  33. {c3d_parser-1.2.4 → c3d_parser-1.4.0}/src/c3d_parser/view/dialogs/marker_set_dialog.py +0 -0
  34. {c3d_parser-1.2.4 → c3d_parser-1.4.0}/src/c3d_parser/view/dialogs/marker_set_import_dialog.py +0 -0
  35. {c3d_parser-1.2.4 → c3d_parser-1.4.0}/src/c3d_parser/view/ui/resources_rc.py +0 -0
  36. {c3d_parser-1.2.4 → c3d_parser-1.4.0}/src/c3d_parser/view/ui/ui_delete_marker_set_dialog.py +0 -0
  37. {c3d_parser-1.2.4 → c3d_parser-1.4.0}/src/c3d_parser/view/ui/ui_main_window.py +0 -0
  38. {c3d_parser-1.2.4 → c3d_parser-1.4.0}/src/c3d_parser/view/ui/ui_marker_set_dialog.py +0 -0
  39. {c3d_parser-1.2.4 → c3d_parser-1.4.0}/src/c3d_parser/view/ui/ui_marker_set_import_dialog.py +0 -0
  40. {c3d_parser-1.2.4 → c3d_parser-1.4.0}/src/c3d_parser/view/utils.py +0 -0
  41. {c3d_parser-1.2.4 → c3d_parser-1.4.0}/src/c3d_parser/view/widgets.py +0 -0
  42. {c3d_parser-1.2.4 → c3d_parser-1.4.0}/src/c3d_parser.egg-info/SOURCES.txt +0 -0
  43. {c3d_parser-1.2.4 → c3d_parser-1.4.0}/src/c3d_parser.egg-info/dependency_links.txt +0 -0
  44. {c3d_parser-1.2.4 → c3d_parser-1.4.0}/src/c3d_parser.egg-info/entry_points.txt +0 -0
  45. {c3d_parser-1.2.4 → c3d_parser-1.4.0}/src/c3d_parser.egg-info/requires.txt +0 -0
  46. {c3d_parser-1.2.4 → c3d_parser-1.4.0}/src/c3d_parser.egg-info/top_level.txt +0 -0
  47. {c3d_parser-1.2.4 → c3d_parser-1.4.0}/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.4
3
+ Version: 1.4.0
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
@@ -0,0 +1,2 @@
1
+
2
+ __version__ = "1.4.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 None, None, None, None, None, None, None, None, None
745
-
791
+ return static_data
746
792
  processing_group = reader.get('PROCESSING')
747
- height = mass = asis_width = left_knee_width = right_knee_width = None
748
- left_ankle_width = right_ankle_width = left_leg_length = right_leg_length = None
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 (height, mass, asis_width, left_knee_width, right_knee_width,
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, QTimer, QAbstractTableModel, QRect
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, write_spatiotemporal_data,
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 = True
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 = list(extract_static_data(c3d_file))
484
-
485
- if static_data[0] is not None:
486
- self._ui.doubleSpinBoxHeight.setValue(static_data[0])
487
- if static_data[1] is not None:
488
- self._ui.doubleSpinBoxBodyMass.setValue(static_data[1])
489
- if static_data[2] is not None:
490
- self._ui.doubleSpinBoxASISWidth.setValue(static_data[2])
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
- grf_data, self._kinematic_data, self._kinetic_data, self._s_t_data, self._deidentified_file_names = result
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(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
- self._excluded_cycles.difference_update(self._selected_curves)
1182
- for file_name, cycle in self._selected_curves:
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
- self._excluded_cycles.update(self._selected_curves)
1242
- for file_name, cycle in self._selected_curves:
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.9.0
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: c3d-parser
3
- Version: 1.2.4
3
+ Version: 1.4.0
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
@@ -1,2 +0,0 @@
1
-
2
- __version__ = "1.2.4"
File without changes
File without changes
File without changes
File without changes
File without changes