c3d-parser 1.2.2__tar.gz → 1.2.4__tar.gz

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