c3d-parser 1.1.3__tar.gz → 1.2.2__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.1.3/src/c3d_parser.egg-info → c3d_parser-1.2.2}/PKG-INFO +3 -3
  2. {c3d_parser-1.1.3 → c3d_parser-1.2.2}/requirements.txt +2 -2
  3. c3d_parser-1.2.2/src/c3d_parser/__init__.py +2 -0
  4. {c3d_parser-1.1.3 → c3d_parser-1.2.2}/src/c3d_parser/core/c3d_parser.py +20 -66
  5. {c3d_parser-1.1.3 → c3d_parser-1.2.2}/src/c3d_parser/core/osim.py +60 -0
  6. {c3d_parser-1.1.3 → c3d_parser-1.2.2}/src/c3d_parser/view/main_window.py +0 -19
  7. {c3d_parser-1.1.3 → c3d_parser-1.2.2/src/c3d_parser.egg-info}/PKG-INFO +3 -3
  8. {c3d_parser-1.1.3 → c3d_parser-1.2.2}/src/c3d_parser.egg-info/requires.txt +2 -2
  9. c3d_parser-1.1.3/src/c3d_parser/__init__.py +0 -2
  10. {c3d_parser-1.1.3 → c3d_parser-1.2.2}/LICENSE +0 -0
  11. {c3d_parser-1.1.3 → c3d_parser-1.2.2}/README.md +0 -0
  12. {c3d_parser-1.1.3 → c3d_parser-1.2.2}/pyproject.toml +0 -0
  13. {c3d_parser-1.1.3 → c3d_parser-1.2.2}/setup.cfg +0 -0
  14. {c3d_parser-1.1.3 → c3d_parser-1.2.2}/src/c3d_parser/application.py +0 -0
  15. {c3d_parser-1.1.3 → c3d_parser-1.2.2}/src/c3d_parser/core/c3d_patch.py +0 -0
  16. {c3d_parser-1.1.3 → c3d_parser-1.2.2}/src/c3d_parser/core/osim_resources/external_loads_template.xml +0 -0
  17. {c3d_parser-1.1.3 → c3d_parser-1.2.2}/src/c3d_parser/core/osim_resources/ik_task_set.xml +0 -0
  18. {c3d_parser-1.1.3 → c3d_parser-1.2.2}/src/c3d_parser/core/utils.py +0 -0
  19. {c3d_parser-1.1.3 → c3d_parser-1.2.2}/src/c3d_parser/settings/general.py +0 -0
  20. {c3d_parser-1.1.3 → c3d_parser-1.2.2}/src/c3d_parser/settings/logging.py +0 -0
  21. {c3d_parser-1.1.3 → c3d_parser-1.2.2}/src/c3d_parser/settings/marker_maps/AUC.json +0 -0
  22. {c3d_parser-1.1.3 → c3d_parser-1.2.2}/src/c3d_parser/settings/marker_maps/FMC.json +0 -0
  23. {c3d_parser-1.1.3 → c3d_parser-1.2.2}/src/c3d_parser/settings/marker_maps/MH.json +0 -0
  24. {c3d_parser-1.1.3 → c3d_parser-1.2.2}/src/c3d_parser/settings/marker_maps/QCMAS.json +0 -0
  25. {c3d_parser-1.1.3 → c3d_parser-1.2.2}/src/c3d_parser/settings/marker_maps/RBWH.json +0 -0
  26. {c3d_parser-1.1.3 → c3d_parser-1.2.2}/src/c3d_parser/settings/marker_maps/RCH.json +0 -0
  27. {c3d_parser-1.1.3 → c3d_parser-1.2.2}/src/c3d_parser/settings/marker_maps/Sydney.json +0 -0
  28. {c3d_parser-1.1.3 → c3d_parser-1.2.2}/src/c3d_parser/splash_rc.py +0 -0
  29. {c3d_parser-1.1.3 → c3d_parser-1.2.2}/src/c3d_parser/splashscreen.py +0 -0
  30. {c3d_parser-1.1.3 → c3d_parser-1.2.2}/src/c3d_parser/view/dialogs/about_dialog.py +0 -0
  31. {c3d_parser-1.1.3 → c3d_parser-1.2.2}/src/c3d_parser/view/dialogs/delete_marker_set_dialog.py +0 -0
  32. {c3d_parser-1.1.3 → c3d_parser-1.2.2}/src/c3d_parser/view/dialogs/marker_set_dialog.py +0 -0
  33. {c3d_parser-1.1.3 → c3d_parser-1.2.2}/src/c3d_parser/view/dialogs/marker_set_import_dialog.py +0 -0
  34. {c3d_parser-1.1.3 → c3d_parser-1.2.2}/src/c3d_parser/view/dialogs/options_dialog.py +0 -0
  35. {c3d_parser-1.1.3 → c3d_parser-1.2.2}/src/c3d_parser/view/ui/resources_rc.py +0 -0
  36. {c3d_parser-1.1.3 → c3d_parser-1.2.2}/src/c3d_parser/view/ui/ui_delete_marker_set_dialog.py +0 -0
  37. {c3d_parser-1.1.3 → c3d_parser-1.2.2}/src/c3d_parser/view/ui/ui_main_window.py +0 -0
  38. {c3d_parser-1.1.3 → c3d_parser-1.2.2}/src/c3d_parser/view/ui/ui_marker_set_dialog.py +0 -0
  39. {c3d_parser-1.1.3 → c3d_parser-1.2.2}/src/c3d_parser/view/ui/ui_marker_set_import_dialog.py +0 -0
  40. {c3d_parser-1.1.3 → c3d_parser-1.2.2}/src/c3d_parser/view/ui/ui_options_dialog.py +0 -0
  41. {c3d_parser-1.1.3 → c3d_parser-1.2.2}/src/c3d_parser/view/utils.py +0 -0
  42. {c3d_parser-1.1.3 → c3d_parser-1.2.2}/src/c3d_parser/view/widgets.py +0 -0
  43. {c3d_parser-1.1.3 → c3d_parser-1.2.2}/src/c3d_parser.egg-info/SOURCES.txt +0 -0
  44. {c3d_parser-1.1.3 → c3d_parser-1.2.2}/src/c3d_parser.egg-info/dependency_links.txt +0 -0
  45. {c3d_parser-1.1.3 → c3d_parser-1.2.2}/src/c3d_parser.egg-info/entry_points.txt +0 -0
  46. {c3d_parser-1.1.3 → c3d_parser-1.2.2}/src/c3d_parser.egg-info/top_level.txt +0 -0
  47. {c3d_parser-1.1.3 → c3d_parser-1.2.2}/tests/test_parser.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: c3d-parser
3
- Version: 1.1.3
3
+ Version: 1.2.2
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
@@ -11,8 +11,8 @@ Requires-Dist: scipy
11
11
  Requires-Dist: numpy==1.26.4
12
12
  Requires-Dist: pandas
13
13
  Requires-Dist: c3d==0.5.2
14
- Requires-Dist: trc-data-reader
15
- Requires-Dist: opensim-model-creator==0.7.0
14
+ Requires-Dist: trc-data-reader>=0.4.1
15
+ Requires-Dist: opensim-model-creator==0.7.1
16
16
  Requires-Dist: PySide6
17
17
  Requires-Dist: matplotlib
18
18
  Requires-Dist: mplcursors
@@ -2,8 +2,8 @@ scipy
2
2
  numpy==1.26.4
3
3
  pandas
4
4
  c3d==0.5.2
5
- trc-data-reader
6
- opensim-model-creator==0.7.0
5
+ trc-data-reader>=0.4.1
6
+ opensim-model-creator==0.7.1
7
7
  PySide6
8
8
  matplotlib
9
9
  mplcursors
@@ -0,0 +1,2 @@
1
+
2
+ __version__ = "1.2.2"
@@ -18,7 +18,7 @@ from opensim_model_creator.Create_Model import create_model
18
18
 
19
19
  from c3d_parser.core.c3d_patch import c3d
20
20
  from c3d_parser.core.utils import clear_directory
21
- from c3d_parser.core.osim import perform_ik, perform_id
21
+ from c3d_parser.core.osim import perform_ik, perform_id, calculate_foot_progression_angles
22
22
  from c3d_parser.settings.general import get_marker_maps_dir
23
23
  from c3d_parser.settings.logging import logger
24
24
  from c3d_parser.settings.general import VERSION
@@ -53,7 +53,6 @@ def parse_session(static_trial, dynamic_trials, input_directory, output_director
53
53
  grf_data = {}
54
54
  event_data = {}
55
55
  spatiotemporal_data = {}
56
- foot_progression_data = {}
57
56
  kinematic_data = {}
58
57
  kinetic_data = {}
59
58
 
@@ -66,7 +65,7 @@ def parse_session(static_trial, dynamic_trials, input_directory, output_director
66
65
  for trial_index, trial in enumerate(dynamic_trials, start=1):
67
66
  file_path = os.path.normpath(os.path.join(input_directory, trial))
68
67
  try:
69
- analog_data, events, foot_progression, s_t_data, trc_file_path, grf_file_path = \
68
+ analog_data, events, s_t_data, trc_file_path, grf_file_path = \
70
69
  parse_dynamic_trial(file_path, lab, output_directory, trial_index, marker_data_rate, static_data,
71
70
  filter_trc, filter_grf)
72
71
  except ParserError as e:
@@ -76,7 +75,6 @@ def parse_session(static_trial, dynamic_trials, input_directory, output_director
76
75
  grf_data[trial] = analog_data
77
76
  event_data[trial] = events
78
77
  spatiotemporal_data[trial] = s_t_data
79
- foot_progression_data[trial] = foot_progression
80
78
 
81
79
  trc_file_paths[trial] = trc_file_path
82
80
  grf_file_paths[trial] = grf_file_path
@@ -96,9 +94,12 @@ def parse_session(static_trial, dynamic_trials, input_directory, output_director
96
94
 
97
95
  for trial in trc_file_paths.keys():
98
96
  logger.info(f"Running IK and ID for {trial}.")
99
- ik_data, ik_output = run_ik(osim_model, trc_file_paths[trial], output_directory, marker_data_rate, ik_task_set)
100
- ik_data = pd.concat([ik_data, foot_progression_data[trial]], axis=1)
101
- id_data = run_id(osim_model, ik_data, ik_output, grf_file_paths[trial], output_directory, marker_data_rate, event_data[trial], weight)
97
+ ik_data, ik_output = run_ik(osim_model, trc_file_paths[trial], output_directory, ik_task_set)
98
+ foot_progression = calculate_foot_progression_angles(osim_model, ik_output)
99
+ ik_data = pd.concat([ik_data, foot_progression], axis=1)
100
+ filter_data(ik_data, marker_data_rate)
101
+
102
+ id_data = run_id(osim_model, ik_data, ik_output, grf_file_paths[trial], output_directory, event_data[trial], weight)
102
103
 
103
104
  kinematic_data[trial] = ik_data
104
105
  kinetic_data[trial] = id_data
@@ -220,12 +221,9 @@ def parse_dynamic_trial(c3d_file, lab, output_directory, trial_index, marker_dat
220
221
  set_marker_data(trc_data, frame_data, rate=marker_data_rate)
221
222
  trc_file_path = write_trc_data(trc_data, output_file_name, output_directory)
222
223
 
223
- foot_progression = calculate_foot_progression_angles(frame_data)
224
- resample_data(foot_progression, trc_data['DataRate'], marker_data_rate)
225
-
226
224
  s_t_data = calculate_spatiotemporal_data(frame_data, events, static_data)
227
225
 
228
- return analog_data, events, foot_progression, s_t_data, trc_file_path, grf_file_path
226
+ return analog_data, events, s_t_data, trc_file_path, grf_file_path
229
227
 
230
228
 
231
229
  def write_c3d_parser_history(input_directory, static_trial, deidentified_file_names):
@@ -248,7 +246,7 @@ def write_c3d_parser_history(input_directory, static_trial, deidentified_file_na
248
246
  raise ParserError("Could not write c3d_parser_history.log")
249
247
 
250
248
 
251
- def run_ik(osim_model, trc_file_path, output_directory, marker_data_rate, ik_task_set):
249
+ def run_ik(osim_model, trc_file_path, output_directory, ik_task_set):
252
250
  # Perform inverse kinematics.
253
251
  file_name = os.path.splitext(os.path.basename(trc_file_path))[0]
254
252
  ik_directory = os.path.join(output_directory, 'ik')
@@ -257,12 +255,11 @@ def run_ik(osim_model, trc_file_path, output_directory, marker_data_rate, ik_tas
257
255
  ik_output = os.path.join(ik_directory, f"{file_name}_IK.mot")
258
256
  perform_ik(osim_model, trc_file_path, ik_output, ik_task_set)
259
257
  ik_data = read_data(ik_output)
260
- filter_data(ik_data, marker_data_rate)
261
258
 
262
259
  return ik_data, ik_output
263
260
 
264
261
 
265
- def run_id(osim_model, ik_data, ik_output, grf_file_path, output_directory, marker_data_rate, events, subject_mass):
262
+ def run_id(osim_model, ik_data, ik_output, grf_file_path, output_directory, events, subject_mass):
266
263
  # Perform inverse dynamics.
267
264
  file_name = os.path.basename(grf_file_path).replace("_grf.mot", "")
268
265
  id_directory = os.path.join(output_directory, 'id')
@@ -384,24 +381,18 @@ def convert_old_marker_map(map_file, marker_mapping):
384
381
  return marker_set
385
382
 
386
383
 
387
- # TODO: Is the feet_scaled.osim error maybe related to left over output files...?
388
- # Since it sounds like Keeran maybe got it to work...?
389
-
390
- # TODO: The marker set should not be so sensitive to case...!
391
384
  def harmonise_markers(frame_data, lab, required_markers):
392
385
  marker_set = get_marker_map(lab)
393
386
 
394
387
  # Harmonise marker labels.
395
- reversed_mapping = {value: key for key, value in marker_set.items() if value is not None}
396
- header_mapping = {header: reversed_mapping.get(header, None) for header in frame_data.columns[1:]}
388
+ reversed_mapping = {value.upper(): key for key, value in marker_set.items() if value is not None}
389
+ header_mapping = {header: reversed_mapping.get(header.upper(), None) for header in frame_data.columns[1:]}
397
390
  frame_data.rename(columns=header_mapping, inplace=True)
398
391
 
399
392
  # Filter out non-harmonised data points.
400
393
  if None in frame_data.columns:
401
394
  frame_data.drop(columns=frame_data.columns[frame_data.columns.isna()], inplace=True)
402
395
 
403
- # TODO: This error window does not indicate which trial has the issue.
404
- # Update this.
405
396
  # Ensure required markers are present.
406
397
  available = set(frame_data.columns)
407
398
  for item in required_markers:
@@ -457,10 +448,8 @@ def trim_frames(frame_data):
457
448
  if not drop_frames.empty:
458
449
  frame_data.drop(drop_frames, inplace=True)
459
450
 
460
- # TODO: If we convert this to a dictionary we can keep the markers that are missing and check if they are required.
461
451
  remaining_frames = [frame for frame in incomplete_frames.keys() if trim_start <= frame <= trim_end]
462
452
  if remaining_frames:
463
- # TODO: We should raise a ParserError (skip this trial) if the missing markers are required...?
464
453
  logger.warn(f"Frames {remaining_frames} are incomplete.")
465
454
 
466
455
  return trim_start, trim_end
@@ -636,11 +625,6 @@ def extract_data(file_path, start_frame, end_frame):
636
625
  analog_data[label].extend(analog[j - 1])
637
626
  analog_data = pd.DataFrame(analog_data)
638
627
 
639
- # TODO: ACTUALLY, this would also be a good place to just apply the channel order to the analog columns...
640
- # ???
641
- # We will need this if we want to support the files with the deleted plates...!
642
- # And their new force plate setup...!
643
-
644
628
  # Extract event information.
645
629
  event_group = get_metadata(reader, 'EVENT')
646
630
  event_count = get_metadata(event_group, 'USED').int8_value
@@ -667,6 +651,7 @@ def extract_data(file_path, start_frame, end_frame):
667
651
  stride_numbers = {"Left": 0, "Right": 0}
668
652
  for foot, event in events.items():
669
653
  for event_time, event_type in event.items():
654
+ event_type = event_type.title()
670
655
  if event_type == "Foot Strike":
671
656
  stride_numbers[foot] += 1
672
657
  stride_number = stride_numbers[foot]
@@ -723,13 +708,13 @@ def extract_static_data(file_path):
723
708
  return None, None, None, None, None, None, None, None, None
724
709
 
725
710
  processing_group = reader.get('PROCESSING')
726
- height = weight = asis_width = left_knee_width = right_knee_width = None
711
+ height = mass = asis_width = left_knee_width = right_knee_width = None
727
712
  left_ankle_width = right_ankle_width = left_leg_length = right_leg_length = None
728
713
 
729
714
  if 'HEIGHT' in processing_group:
730
715
  height = reader.get('PROCESSING:Height').float_value
731
716
  if 'BODYMASS' in processing_group:
732
- weight = reader.get('PROCESSING:Bodymass').float_value
717
+ mass = reader.get('PROCESSING:Bodymass').float_value
733
718
  if 'INTERASISDISTANCE' in processing_group:
734
719
  asis_width = reader.get('PROCESSING:InterAsisDistance').float_value
735
720
  if 'LKNEEWIDTH' in processing_group:
@@ -745,7 +730,10 @@ def extract_static_data(file_path):
745
730
  if 'RLEGLENGTH' in processing_group:
746
731
  right_leg_length = reader.get('PROCESSING:RLegLength').float_value
747
732
 
748
- return (height, weight, asis_width, left_knee_width, right_knee_width,
733
+ if mass > 200:
734
+ mass = mass / 9.81
735
+
736
+ return (height, mass, asis_width, left_knee_width, right_knee_width,
749
737
  left_ankle_width, right_ankle_width, left_leg_length, right_leg_length)
750
738
 
751
739
 
@@ -1022,8 +1010,6 @@ def scale_grf_data(analog_data):
1022
1010
  analog_data[columns] = analog_data[columns] / 1000
1023
1011
 
1024
1012
 
1025
- # TODO: This incorrectly classifies Adelaide's static trials as dynamic.
1026
- # Use `calculate_distance_covered`...?
1027
1013
  def is_dynamic(file_path):
1028
1014
  with open(file_path, 'rb') as handle:
1029
1015
  try:
@@ -1190,11 +1176,6 @@ def normalise_kinetics(kinetic_data, events):
1190
1176
  normalised_data[foot][file_name] = {}
1191
1177
  data_segment = data.iloc[start:frame, 1:]
1192
1178
 
1193
- # TODO: If the model +tve and -tve are wrong, then we can't just flip these.
1194
- # Because the non-GRF parts of the moments would also be flipped.
1195
- # Just visually check the results against some clinical kinetics.
1196
- # ...
1197
- # Looks pretty good so far.
1198
1179
  # Perform sign transformations.
1199
1180
  data_segment[f"hip_flexion_{side}_moment"] = -data_segment[f"hip_flexion_{side}_moment"]
1200
1181
  data_segment[f"hip_adduction_{side}_moment"] = -data_segment[f"hip_adduction_{side}_moment"]
@@ -1428,33 +1409,6 @@ def calculate_walking_direction(frame_data):
1428
1409
  return walking_direction
1429
1410
 
1430
1411
 
1431
- # TODO: First, we need to calculate the static flexion angle between the foot vector (heel to toe) and the floor.
1432
- # (before this function)
1433
- # Then, when we do this, we should adjust the heel position using a rotation matrix defined by that flexion angle.
1434
- # (before projecting on to the floor)
1435
- def calculate_foot_progression_angles(frame_data):
1436
- walking_direction = calculate_walking_direction(frame_data)
1437
- walking_angle = np.arctan2(walking_direction[1], walking_direction[0])
1438
-
1439
- foot_progression = pd.DataFrame()
1440
- for foot in ['Left', 'Right']:
1441
- side = foot[0]
1442
- heel_xz = np.stack(frame_data[f"{side}HEE"].values)[:, [0, 2]]
1443
- toe_xz = np.stack(frame_data[f"{side}TOE"].values)[:, [0, 2]]
1444
-
1445
- foot_vectors = toe_xz - heel_xz
1446
- foot_unit_vectors = foot_vectors / np.linalg.norm(foot_vectors, axis=1, keepdims=True)
1447
- foot_angles = np.arctan2(foot_unit_vectors[:, 1], foot_unit_vectors[:, 0])
1448
- angles = np.degrees(foot_angles - walking_angle)
1449
-
1450
- if foot == 'Right':
1451
- angles = -angles
1452
-
1453
- foot_progression[f'foot_progression_{side.lower()}'] = angles
1454
-
1455
- return foot_progression
1456
-
1457
-
1458
1412
  def normalise_angle(angle):
1459
1413
  if angle < -180:
1460
1414
  angle += 360
@@ -1,9 +1,13 @@
1
1
 
2
2
  import os
3
3
  import copy
4
+ import numpy as np
5
+ import pandas as pd
4
6
  import opensim as osim
5
7
  import xml.etree.ElementTree as ET
6
8
 
9
+ from scipy.spatial.transform import Rotation
10
+
7
11
  from c3d_parser.settings.logging import logger
8
12
 
9
13
 
@@ -95,3 +99,59 @@ def setup_external_loads(output_directory, grf_file):
95
99
  tree.write(external_loads_file)
96
100
 
97
101
  return external_loads_file
102
+
103
+
104
+ def calculate_foot_progression_angles(model_file, ik_file):
105
+
106
+ # Load model information.
107
+ model = osim.Model(model_file)
108
+ model.initSystem()
109
+ coordinate_set = model.getCoordinateSet()
110
+ n_coordinates = coordinate_set.getSize()
111
+
112
+ # Load IK solution.
113
+ storage = osim.Storage(ik_file)
114
+ labels = storage.getColumnLabels()
115
+ n_frames = storage.getSize()
116
+
117
+ foot_progression = {"l": [], "r": []}
118
+ for i in range(n_frames):
119
+
120
+ # Build a state vector from the IK solution at this frame.
121
+ time_stamp = storage.getStateVector(i).getTime()
122
+ state = model.initSystem()
123
+ state.setTime(time_stamp)
124
+ ik_row = storage.getStateVector(i).getData()
125
+
126
+ # Set body coordinates from the IK solution.
127
+ for j in range(n_coordinates):
128
+ coordinate = coordinate_set.get(j)
129
+ coordinate_name = coordinate.getName()
130
+ column_index = labels.findIndex(coordinate_name)
131
+ if column_index >= 0:
132
+ value = ik_row.get(column_index - 1)
133
+
134
+ if coordinate.getMotionType() == osim.Coordinate.Rotational:
135
+ value = np.deg2rad(value)
136
+ coordinate.setValue(state, value)
137
+ model.realizePosition(state)
138
+
139
+ # Extract foot segment rotation.
140
+ for side in foot_progression.keys():
141
+ foot_body = model.getBodySet().get(f"calcn_{side}")
142
+ transform = foot_body.getTransformInGround(state)
143
+
144
+ # Decompose rotation matrix into ZXY Euler angles.
145
+ rotation_matrix = np.array([[transform.R().get(row, col) for col in range(3)] for row in range(3)])
146
+ angles = Rotation.from_matrix(rotation_matrix).as_euler('ZXY')
147
+ foot_progression_angle = np.rad2deg(angles[2])
148
+
149
+ if side == "l":
150
+ foot_progression_angle = -foot_progression_angle
151
+ foot_progression[side].append(foot_progression_angle)
152
+
153
+ data_frame = pd.DataFrame({
154
+ "foot_progression_l": foot_progression["l"],
155
+ "foot_progression_r": foot_progression["r"],
156
+ })
157
+ return data_frame
@@ -57,13 +57,6 @@ class ProgressTracker(QObject):
57
57
  progress = Signal(str, str)
58
58
 
59
59
 
60
- # TODO: Add the deidentified name to the column in the file details.
61
- # Instead of just "Dynamic", "Dynamic_1" or whatever.
62
- # ...
63
- # Nah just create local mapping file directly in input directory.
64
-
65
-
66
- # TODO: CGM2 event detector on GitHub...?
67
60
  class MainWindow(QMainWindow):
68
61
 
69
62
  def __init__(self):
@@ -251,12 +244,6 @@ class MainWindow(QMainWindow):
251
244
  table.deleteLater()
252
245
  self._spatiotemporal_tables = []
253
246
 
254
- # TODO: The legend is not correctly displayed if the ST tab is open while processing...
255
- # Repositioning and updating the legend do nothing.
256
-
257
- # TODO: The border is still having issues.
258
- # Seems to be related to the area being scrollable.
259
- # Only happens on some PCs/monitors...
260
247
  def _visualise_spatiotemporal_data(self):
261
248
  layout = self._ui.scrollAreaSpatiotemporal.layout()
262
249
  self._clear_spatiotemporal_data()
@@ -882,7 +869,6 @@ class MainWindow(QMainWindow):
882
869
  self._update_s_t_colours()
883
870
  self._update_legend()
884
871
 
885
- # TODO: For some reason the legend doesn't get created at all if we are visualising the ST tab while processing...
886
872
  def _update_legend(self):
887
873
  if self._boxes:
888
874
  for side, boxes in self._boxes.items():
@@ -970,8 +956,6 @@ class MainWindow(QMainWindow):
970
956
  settings.beginGroup('MainWindow')
971
957
  settings.setValue('size', self.size())
972
958
  settings.setValue('pos', self.pos())
973
- # TODO: The taskbar icon fails if the application opens in full-screen...
974
- # DON'T open in full screen...?
975
959
  settings.setValue('is_maximized', self.isMaximized())
976
960
  settings.setValue('lab', self._ui.comboBoxLab.currentText())
977
961
  settings.setValue('marker_diameter', self._ui.doubleSpinBoxMarkerDiameter.value())
@@ -1158,9 +1142,6 @@ class GaitCurves(defaultdict):
1158
1142
  line.set_color(colour)
1159
1143
  line.set_zorder(z_order)
1160
1144
 
1161
- # TODO: Replace all these with `draw_idle`?
1162
- # Just the ones that handle many curves...?
1163
- # Probably all...?
1164
1145
  self._canvas.draw()
1165
1146
 
1166
1147
  def show_curve_menu(self, event):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: c3d-parser
3
- Version: 1.1.3
3
+ Version: 1.2.2
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
@@ -11,8 +11,8 @@ Requires-Dist: scipy
11
11
  Requires-Dist: numpy==1.26.4
12
12
  Requires-Dist: pandas
13
13
  Requires-Dist: c3d==0.5.2
14
- Requires-Dist: trc-data-reader
15
- Requires-Dist: opensim-model-creator==0.7.0
14
+ Requires-Dist: trc-data-reader>=0.4.1
15
+ Requires-Dist: opensim-model-creator==0.7.1
16
16
  Requires-Dist: PySide6
17
17
  Requires-Dist: matplotlib
18
18
  Requires-Dist: mplcursors
@@ -2,8 +2,8 @@ scipy
2
2
  numpy==1.26.4
3
3
  pandas
4
4
  c3d==0.5.2
5
- trc-data-reader
6
- opensim-model-creator==0.7.0
5
+ trc-data-reader>=0.4.1
6
+ opensim-model-creator==0.7.1
7
7
  PySide6
8
8
  matplotlib
9
9
  mplcursors
@@ -1,2 +0,0 @@
1
-
2
- __version__ = "1.1.3"
File without changes
File without changes
File without changes
File without changes