c3d-parser 1.1.2__tar.gz → 1.2.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.1.2/src/c3d_parser.egg-info → c3d_parser-1.2.0}/PKG-INFO +3 -3
  2. {c3d_parser-1.1.2 → c3d_parser-1.2.0}/requirements.txt +2 -2
  3. c3d_parser-1.2.0/src/c3d_parser/__init__.py +2 -0
  4. {c3d_parser-1.1.2 → c3d_parser-1.2.0}/src/c3d_parser/core/c3d_parser.py +14 -60
  5. {c3d_parser-1.1.2 → c3d_parser-1.2.0}/src/c3d_parser/core/osim.py +60 -0
  6. {c3d_parser-1.1.2 → c3d_parser-1.2.0}/src/c3d_parser/view/main_window.py +0 -19
  7. {c3d_parser-1.1.2 → c3d_parser-1.2.0/src/c3d_parser.egg-info}/PKG-INFO +3 -3
  8. {c3d_parser-1.1.2 → c3d_parser-1.2.0}/src/c3d_parser.egg-info/requires.txt +2 -2
  9. c3d_parser-1.1.2/src/c3d_parser/__init__.py +0 -2
  10. {c3d_parser-1.1.2 → c3d_parser-1.2.0}/LICENSE +0 -0
  11. {c3d_parser-1.1.2 → c3d_parser-1.2.0}/README.md +0 -0
  12. {c3d_parser-1.1.2 → c3d_parser-1.2.0}/pyproject.toml +0 -0
  13. {c3d_parser-1.1.2 → c3d_parser-1.2.0}/setup.cfg +0 -0
  14. {c3d_parser-1.1.2 → c3d_parser-1.2.0}/src/c3d_parser/application.py +0 -0
  15. {c3d_parser-1.1.2 → c3d_parser-1.2.0}/src/c3d_parser/core/c3d_patch.py +0 -0
  16. {c3d_parser-1.1.2 → c3d_parser-1.2.0}/src/c3d_parser/core/osim_resources/external_loads_template.xml +0 -0
  17. {c3d_parser-1.1.2 → c3d_parser-1.2.0}/src/c3d_parser/core/osim_resources/ik_task_set.xml +0 -0
  18. {c3d_parser-1.1.2 → c3d_parser-1.2.0}/src/c3d_parser/core/utils.py +0 -0
  19. {c3d_parser-1.1.2 → c3d_parser-1.2.0}/src/c3d_parser/settings/general.py +0 -0
  20. {c3d_parser-1.1.2 → c3d_parser-1.2.0}/src/c3d_parser/settings/logging.py +0 -0
  21. {c3d_parser-1.1.2 → c3d_parser-1.2.0}/src/c3d_parser/settings/marker_maps/AUC.json +0 -0
  22. {c3d_parser-1.1.2 → c3d_parser-1.2.0}/src/c3d_parser/settings/marker_maps/FMC.json +0 -0
  23. {c3d_parser-1.1.2 → c3d_parser-1.2.0}/src/c3d_parser/settings/marker_maps/MH.json +0 -0
  24. {c3d_parser-1.1.2 → c3d_parser-1.2.0}/src/c3d_parser/settings/marker_maps/QCMAS.json +0 -0
  25. {c3d_parser-1.1.2 → c3d_parser-1.2.0}/src/c3d_parser/settings/marker_maps/RBWH.json +0 -0
  26. {c3d_parser-1.1.2 → c3d_parser-1.2.0}/src/c3d_parser/settings/marker_maps/RCH.json +0 -0
  27. {c3d_parser-1.1.2 → c3d_parser-1.2.0}/src/c3d_parser/settings/marker_maps/Sydney.json +0 -0
  28. {c3d_parser-1.1.2 → c3d_parser-1.2.0}/src/c3d_parser/splash_rc.py +0 -0
  29. {c3d_parser-1.1.2 → c3d_parser-1.2.0}/src/c3d_parser/splashscreen.py +0 -0
  30. {c3d_parser-1.1.2 → c3d_parser-1.2.0}/src/c3d_parser/view/dialogs/about_dialog.py +0 -0
  31. {c3d_parser-1.1.2 → c3d_parser-1.2.0}/src/c3d_parser/view/dialogs/delete_marker_set_dialog.py +0 -0
  32. {c3d_parser-1.1.2 → c3d_parser-1.2.0}/src/c3d_parser/view/dialogs/marker_set_dialog.py +0 -0
  33. {c3d_parser-1.1.2 → c3d_parser-1.2.0}/src/c3d_parser/view/dialogs/marker_set_import_dialog.py +0 -0
  34. {c3d_parser-1.1.2 → c3d_parser-1.2.0}/src/c3d_parser/view/dialogs/options_dialog.py +0 -0
  35. {c3d_parser-1.1.2 → c3d_parser-1.2.0}/src/c3d_parser/view/ui/resources_rc.py +0 -0
  36. {c3d_parser-1.1.2 → c3d_parser-1.2.0}/src/c3d_parser/view/ui/ui_delete_marker_set_dialog.py +0 -0
  37. {c3d_parser-1.1.2 → c3d_parser-1.2.0}/src/c3d_parser/view/ui/ui_main_window.py +0 -0
  38. {c3d_parser-1.1.2 → c3d_parser-1.2.0}/src/c3d_parser/view/ui/ui_marker_set_dialog.py +0 -0
  39. {c3d_parser-1.1.2 → c3d_parser-1.2.0}/src/c3d_parser/view/ui/ui_marker_set_import_dialog.py +0 -0
  40. {c3d_parser-1.1.2 → c3d_parser-1.2.0}/src/c3d_parser/view/ui/ui_options_dialog.py +0 -0
  41. {c3d_parser-1.1.2 → c3d_parser-1.2.0}/src/c3d_parser/view/utils.py +0 -0
  42. {c3d_parser-1.1.2 → c3d_parser-1.2.0}/src/c3d_parser/view/widgets.py +0 -0
  43. {c3d_parser-1.1.2 → c3d_parser-1.2.0}/src/c3d_parser.egg-info/SOURCES.txt +0 -0
  44. {c3d_parser-1.1.2 → c3d_parser-1.2.0}/src/c3d_parser.egg-info/dependency_links.txt +0 -0
  45. {c3d_parser-1.1.2 → c3d_parser-1.2.0}/src/c3d_parser.egg-info/entry_points.txt +0 -0
  46. {c3d_parser-1.1.2 → c3d_parser-1.2.0}/src/c3d_parser.egg-info/top_level.txt +0 -0
  47. {c3d_parser-1.1.2 → c3d_parser-1.2.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.1.2
3
+ Version: 1.2.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
@@ -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.6.0
14
+ Requires-Dist: trc-data-reader>=0.4.1
15
+ Requires-Dist: opensim-model-creator==0.7.0
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.6.0
5
+ trc-data-reader>=0.4.1
6
+ opensim-model-creator==0.7.0
7
7
  PySide6
8
8
  matplotlib
9
9
  mplcursors
@@ -0,0 +1,2 @@
1
+
2
+ __version__ = "1.2.0"
@@ -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,21 +381,18 @@ def convert_old_marker_map(map_file, marker_mapping):
384
381
  return marker_set
385
382
 
386
383
 
387
- # TODO: The marker set should not be so sensitive to case...!
388
384
  def harmonise_markers(frame_data, lab, required_markers):
389
385
  marker_set = get_marker_map(lab)
390
386
 
391
387
  # Harmonise marker labels.
392
- reversed_mapping = {value: key for key, value in marker_set.items() if value is not None}
393
- 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:]}
394
390
  frame_data.rename(columns=header_mapping, inplace=True)
395
391
 
396
392
  # Filter out non-harmonised data points.
397
393
  if None in frame_data.columns:
398
394
  frame_data.drop(columns=frame_data.columns[frame_data.columns.isna()], inplace=True)
399
395
 
400
- # TODO: This error window does not indicate which trial has the issue.
401
- # Update this.
402
396
  # Ensure required markers are present.
403
397
  available = set(frame_data.columns)
404
398
  for item in required_markers:
@@ -454,10 +448,8 @@ def trim_frames(frame_data):
454
448
  if not drop_frames.empty:
455
449
  frame_data.drop(drop_frames, inplace=True)
456
450
 
457
- # TODO: If we convert this to a dictionary we can keep the markers that are missing and check if they are required.
458
451
  remaining_frames = [frame for frame in incomplete_frames.keys() if trim_start <= frame <= trim_end]
459
452
  if remaining_frames:
460
- # TODO: We should raise a ParserError (skip this trial) if the missing markers are required...?
461
453
  logger.warn(f"Frames {remaining_frames} are incomplete.")
462
454
 
463
455
  return trim_start, trim_end
@@ -633,11 +625,6 @@ def extract_data(file_path, start_frame, end_frame):
633
625
  analog_data[label].extend(analog[j - 1])
634
626
  analog_data = pd.DataFrame(analog_data)
635
627
 
636
- # TODO: ACTUALLY, this would also be a good place to just apply the channel order to the analog columns...
637
- # ???
638
- # We will need this if we want to support the files with the deleted plates...!
639
- # And their new force plate setup...!
640
-
641
628
  # Extract event information.
642
629
  event_group = get_metadata(reader, 'EVENT')
643
630
  event_count = get_metadata(event_group, 'USED').int8_value
@@ -664,6 +651,7 @@ def extract_data(file_path, start_frame, end_frame):
664
651
  stride_numbers = {"Left": 0, "Right": 0}
665
652
  for foot, event in events.items():
666
653
  for event_time, event_type in event.items():
654
+ event_type = event_type.title()
667
655
  if event_type == "Foot Strike":
668
656
  stride_numbers[foot] += 1
669
657
  stride_number = stride_numbers[foot]
@@ -1019,8 +1007,6 @@ def scale_grf_data(analog_data):
1019
1007
  analog_data[columns] = analog_data[columns] / 1000
1020
1008
 
1021
1009
 
1022
- # TODO: This incorrectly classifies Adelaide's static trials as dynamic.
1023
- # Use `calculate_distance_covered`...?
1024
1010
  def is_dynamic(file_path):
1025
1011
  with open(file_path, 'rb') as handle:
1026
1012
  try:
@@ -1187,11 +1173,6 @@ def normalise_kinetics(kinetic_data, events):
1187
1173
  normalised_data[foot][file_name] = {}
1188
1174
  data_segment = data.iloc[start:frame, 1:]
1189
1175
 
1190
- # TODO: If the model +tve and -tve are wrong, then we can't just flip these.
1191
- # Because the non-GRF parts of the moments would also be flipped.
1192
- # Just visually check the results against some clinical kinetics.
1193
- # ...
1194
- # Looks pretty good so far.
1195
1176
  # Perform sign transformations.
1196
1177
  data_segment[f"hip_flexion_{side}_moment"] = -data_segment[f"hip_flexion_{side}_moment"]
1197
1178
  data_segment[f"hip_adduction_{side}_moment"] = -data_segment[f"hip_adduction_{side}_moment"]
@@ -1425,33 +1406,6 @@ def calculate_walking_direction(frame_data):
1425
1406
  return walking_direction
1426
1407
 
1427
1408
 
1428
- # TODO: First, we need to calculate the static flexion angle between the foot vector (heel to toe) and the floor.
1429
- # (before this function)
1430
- # Then, when we do this, we should adjust the heel position using a rotation matrix defined by that flexion angle.
1431
- # (before projecting on to the floor)
1432
- def calculate_foot_progression_angles(frame_data):
1433
- walking_direction = calculate_walking_direction(frame_data)
1434
- walking_angle = np.arctan2(walking_direction[1], walking_direction[0])
1435
-
1436
- foot_progression = pd.DataFrame()
1437
- for foot in ['Left', 'Right']:
1438
- side = foot[0]
1439
- heel_xz = np.stack(frame_data[f"{side}HEE"].values)[:, [0, 2]]
1440
- toe_xz = np.stack(frame_data[f"{side}TOE"].values)[:, [0, 2]]
1441
-
1442
- foot_vectors = toe_xz - heel_xz
1443
- foot_unit_vectors = foot_vectors / np.linalg.norm(foot_vectors, axis=1, keepdims=True)
1444
- foot_angles = np.arctan2(foot_unit_vectors[:, 1], foot_unit_vectors[:, 0])
1445
- angles = np.degrees(foot_angles - walking_angle)
1446
-
1447
- if foot == 'Right':
1448
- angles = -angles
1449
-
1450
- foot_progression[f'foot_progression_{side.lower()}'] = angles
1451
-
1452
- return foot_progression
1453
-
1454
-
1455
1409
  def normalise_angle(angle):
1456
1410
  if angle < -180:
1457
1411
  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.2
3
+ Version: 1.2.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
@@ -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.6.0
14
+ Requires-Dist: trc-data-reader>=0.4.1
15
+ Requires-Dist: opensim-model-creator==0.7.0
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.6.0
5
+ trc-data-reader>=0.4.1
6
+ opensim-model-creator==0.7.0
7
7
  PySide6
8
8
  matplotlib
9
9
  mplcursors
@@ -1,2 +0,0 @@
1
-
2
- __version__ = "1.1.2"
File without changes
File without changes
File without changes
File without changes