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.
Files changed (47) hide show
  1. {c3d_parser-1.2.4/src/c3d_parser.egg-info → c3d_parser-1.3.0}/PKG-INFO +1 -1
  2. c3d_parser-1.3.0/src/c3d_parser/__init__.py +2 -0
  3. {c3d_parser-1.2.4 → c3d_parser-1.3.0}/src/c3d_parser/core/c3d_parser.py +56 -18
  4. {c3d_parser-1.2.4 → c3d_parser-1.3.0}/src/c3d_parser/view/dialogs/options_dialog.py +2 -0
  5. {c3d_parser-1.2.4 → c3d_parser-1.3.0}/src/c3d_parser/view/main_window.py +49 -26
  6. {c3d_parser-1.2.4 → c3d_parser-1.3.0}/src/c3d_parser/view/ui/ui_options_dialog.py +7 -1
  7. {c3d_parser-1.2.4 → c3d_parser-1.3.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.3.0}/LICENSE +0 -0
  10. {c3d_parser-1.2.4 → c3d_parser-1.3.0}/README.md +0 -0
  11. {c3d_parser-1.2.4 → c3d_parser-1.3.0}/pyproject.toml +0 -0
  12. {c3d_parser-1.2.4 → c3d_parser-1.3.0}/requirements.txt +0 -0
  13. {c3d_parser-1.2.4 → c3d_parser-1.3.0}/setup.cfg +0 -0
  14. {c3d_parser-1.2.4 → c3d_parser-1.3.0}/src/c3d_parser/application.py +0 -0
  15. {c3d_parser-1.2.4 → c3d_parser-1.3.0}/src/c3d_parser/core/c3d_patch.py +0 -0
  16. {c3d_parser-1.2.4 → c3d_parser-1.3.0}/src/c3d_parser/core/osim.py +0 -0
  17. {c3d_parser-1.2.4 → c3d_parser-1.3.0}/src/c3d_parser/core/osim_resources/external_loads_template.xml +0 -0
  18. {c3d_parser-1.2.4 → c3d_parser-1.3.0}/src/c3d_parser/core/osim_resources/ik_task_set.xml +0 -0
  19. {c3d_parser-1.2.4 → c3d_parser-1.3.0}/src/c3d_parser/core/utils.py +0 -0
  20. {c3d_parser-1.2.4 → c3d_parser-1.3.0}/src/c3d_parser/settings/general.py +0 -0
  21. {c3d_parser-1.2.4 → c3d_parser-1.3.0}/src/c3d_parser/settings/logging.py +0 -0
  22. {c3d_parser-1.2.4 → c3d_parser-1.3.0}/src/c3d_parser/settings/marker_maps/AUC.json +0 -0
  23. {c3d_parser-1.2.4 → c3d_parser-1.3.0}/src/c3d_parser/settings/marker_maps/FMC.json +0 -0
  24. {c3d_parser-1.2.4 → c3d_parser-1.3.0}/src/c3d_parser/settings/marker_maps/MH.json +0 -0
  25. {c3d_parser-1.2.4 → c3d_parser-1.3.0}/src/c3d_parser/settings/marker_maps/QCMAS.json +0 -0
  26. {c3d_parser-1.2.4 → c3d_parser-1.3.0}/src/c3d_parser/settings/marker_maps/RBWH.json +0 -0
  27. {c3d_parser-1.2.4 → c3d_parser-1.3.0}/src/c3d_parser/settings/marker_maps/RCH.json +0 -0
  28. {c3d_parser-1.2.4 → c3d_parser-1.3.0}/src/c3d_parser/settings/marker_maps/Sydney.json +0 -0
  29. {c3d_parser-1.2.4 → c3d_parser-1.3.0}/src/c3d_parser/splash_rc.py +0 -0
  30. {c3d_parser-1.2.4 → c3d_parser-1.3.0}/src/c3d_parser/splashscreen.py +0 -0
  31. {c3d_parser-1.2.4 → c3d_parser-1.3.0}/src/c3d_parser/view/dialogs/about_dialog.py +0 -0
  32. {c3d_parser-1.2.4 → c3d_parser-1.3.0}/src/c3d_parser/view/dialogs/delete_marker_set_dialog.py +0 -0
  33. {c3d_parser-1.2.4 → c3d_parser-1.3.0}/src/c3d_parser/view/dialogs/marker_set_dialog.py +0 -0
  34. {c3d_parser-1.2.4 → c3d_parser-1.3.0}/src/c3d_parser/view/dialogs/marker_set_import_dialog.py +0 -0
  35. {c3d_parser-1.2.4 → c3d_parser-1.3.0}/src/c3d_parser/view/ui/resources_rc.py +0 -0
  36. {c3d_parser-1.2.4 → c3d_parser-1.3.0}/src/c3d_parser/view/ui/ui_delete_marker_set_dialog.py +0 -0
  37. {c3d_parser-1.2.4 → c3d_parser-1.3.0}/src/c3d_parser/view/ui/ui_main_window.py +0 -0
  38. {c3d_parser-1.2.4 → c3d_parser-1.3.0}/src/c3d_parser/view/ui/ui_marker_set_dialog.py +0 -0
  39. {c3d_parser-1.2.4 → c3d_parser-1.3.0}/src/c3d_parser/view/ui/ui_marker_set_import_dialog.py +0 -0
  40. {c3d_parser-1.2.4 → c3d_parser-1.3.0}/src/c3d_parser/view/utils.py +0 -0
  41. {c3d_parser-1.2.4 → c3d_parser-1.3.0}/src/c3d_parser/view/widgets.py +0 -0
  42. {c3d_parser-1.2.4 → c3d_parser-1.3.0}/src/c3d_parser.egg-info/SOURCES.txt +0 -0
  43. {c3d_parser-1.2.4 → c3d_parser-1.3.0}/src/c3d_parser.egg-info/dependency_links.txt +0 -0
  44. {c3d_parser-1.2.4 → c3d_parser-1.3.0}/src/c3d_parser.egg-info/entry_points.txt +0 -0
  45. {c3d_parser-1.2.4 → c3d_parser-1.3.0}/src/c3d_parser.egg-info/requires.txt +0 -0
  46. {c3d_parser-1.2.4 → c3d_parser-1.3.0}/src/c3d_parser.egg-info/top_level.txt +0 -0
  47. {c3d_parser-1.2.4 → c3d_parser-1.3.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.3.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.3.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 None, None, None, None, None, None, None, None, None
745
-
749
+ return static_data
746
750
  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
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 (height, mass, asis_width, left_knee_width, right_knee_width,
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, 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
@@ -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 = 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()
@@ -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.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.3.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