c3d-parser 1.5.0__tar.gz → 1.5.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.5.0/src/c3d_parser.egg-info → c3d_parser-1.5.2}/PKG-INFO +2 -2
  2. {c3d_parser-1.5.0 → c3d_parser-1.5.2}/requirements.txt +1 -1
  3. c3d_parser-1.5.2/src/c3d_parser/__init__.py +2 -0
  4. {c3d_parser-1.5.0 → c3d_parser-1.5.2}/src/c3d_parser/core/c3d_parser.py +0 -14
  5. {c3d_parser-1.5.0 → c3d_parser-1.5.2}/src/c3d_parser/view/main_window.py +61 -16
  6. {c3d_parser-1.5.0 → c3d_parser-1.5.2}/src/c3d_parser/view/ui/ui_main_window.py +6 -0
  7. {c3d_parser-1.5.0 → c3d_parser-1.5.2/src/c3d_parser.egg-info}/PKG-INFO +2 -2
  8. {c3d_parser-1.5.0 → c3d_parser-1.5.2}/src/c3d_parser.egg-info/requires.txt +1 -1
  9. c3d_parser-1.5.0/src/c3d_parser/__init__.py +0 -2
  10. {c3d_parser-1.5.0 → c3d_parser-1.5.2}/LICENSE +0 -0
  11. {c3d_parser-1.5.0 → c3d_parser-1.5.2}/README.md +0 -0
  12. {c3d_parser-1.5.0 → c3d_parser-1.5.2}/pyproject.toml +0 -0
  13. {c3d_parser-1.5.0 → c3d_parser-1.5.2}/setup.cfg +0 -0
  14. {c3d_parser-1.5.0 → c3d_parser-1.5.2}/src/c3d_parser/application.py +0 -0
  15. {c3d_parser-1.5.0 → c3d_parser-1.5.2}/src/c3d_parser/core/c3d_patch.py +0 -0
  16. {c3d_parser-1.5.0 → c3d_parser-1.5.2}/src/c3d_parser/core/osim.py +0 -0
  17. {c3d_parser-1.5.0 → c3d_parser-1.5.2}/src/c3d_parser/core/osim_resources/external_loads_template.xml +0 -0
  18. {c3d_parser-1.5.0 → c3d_parser-1.5.2}/src/c3d_parser/core/osim_resources/ik_task_set.xml +0 -0
  19. {c3d_parser-1.5.0 → c3d_parser-1.5.2}/src/c3d_parser/core/utils.py +0 -0
  20. {c3d_parser-1.5.0 → c3d_parser-1.5.2}/src/c3d_parser/settings/general.py +0 -0
  21. {c3d_parser-1.5.0 → c3d_parser-1.5.2}/src/c3d_parser/settings/logging.py +0 -0
  22. {c3d_parser-1.5.0 → c3d_parser-1.5.2}/src/c3d_parser/settings/marker_maps/AUC.json +0 -0
  23. {c3d_parser-1.5.0 → c3d_parser-1.5.2}/src/c3d_parser/settings/marker_maps/FMC.json +0 -0
  24. {c3d_parser-1.5.0 → c3d_parser-1.5.2}/src/c3d_parser/settings/marker_maps/MH.json +0 -0
  25. {c3d_parser-1.5.0 → c3d_parser-1.5.2}/src/c3d_parser/settings/marker_maps/QCMAS.json +0 -0
  26. {c3d_parser-1.5.0 → c3d_parser-1.5.2}/src/c3d_parser/settings/marker_maps/RBWH.json +0 -0
  27. {c3d_parser-1.5.0 → c3d_parser-1.5.2}/src/c3d_parser/settings/marker_maps/RCH.json +0 -0
  28. {c3d_parser-1.5.0 → c3d_parser-1.5.2}/src/c3d_parser/settings/marker_maps/Sydney.json +0 -0
  29. {c3d_parser-1.5.0 → c3d_parser-1.5.2}/src/c3d_parser/splash_rc.py +0 -0
  30. {c3d_parser-1.5.0 → c3d_parser-1.5.2}/src/c3d_parser/splashscreen.py +0 -0
  31. {c3d_parser-1.5.0 → c3d_parser-1.5.2}/src/c3d_parser/view/dialogs/about_dialog.py +0 -0
  32. {c3d_parser-1.5.0 → c3d_parser-1.5.2}/src/c3d_parser/view/dialogs/delete_marker_set_dialog.py +0 -0
  33. {c3d_parser-1.5.0 → c3d_parser-1.5.2}/src/c3d_parser/view/dialogs/marker_set_dialog.py +0 -0
  34. {c3d_parser-1.5.0 → c3d_parser-1.5.2}/src/c3d_parser/view/dialogs/marker_set_import_dialog.py +0 -0
  35. {c3d_parser-1.5.0 → c3d_parser-1.5.2}/src/c3d_parser/view/dialogs/options_dialog.py +0 -0
  36. {c3d_parser-1.5.0 → c3d_parser-1.5.2}/src/c3d_parser/view/ui/resources_rc.py +0 -0
  37. {c3d_parser-1.5.0 → c3d_parser-1.5.2}/src/c3d_parser/view/ui/ui_delete_marker_set_dialog.py +0 -0
  38. {c3d_parser-1.5.0 → c3d_parser-1.5.2}/src/c3d_parser/view/ui/ui_marker_set_dialog.py +0 -0
  39. {c3d_parser-1.5.0 → c3d_parser-1.5.2}/src/c3d_parser/view/ui/ui_marker_set_import_dialog.py +0 -0
  40. {c3d_parser-1.5.0 → c3d_parser-1.5.2}/src/c3d_parser/view/ui/ui_options_dialog.py +0 -0
  41. {c3d_parser-1.5.0 → c3d_parser-1.5.2}/src/c3d_parser/view/utils.py +0 -0
  42. {c3d_parser-1.5.0 → c3d_parser-1.5.2}/src/c3d_parser/view/widgets.py +0 -0
  43. {c3d_parser-1.5.0 → c3d_parser-1.5.2}/src/c3d_parser.egg-info/SOURCES.txt +0 -0
  44. {c3d_parser-1.5.0 → c3d_parser-1.5.2}/src/c3d_parser.egg-info/dependency_links.txt +0 -0
  45. {c3d_parser-1.5.0 → c3d_parser-1.5.2}/src/c3d_parser.egg-info/entry_points.txt +0 -0
  46. {c3d_parser-1.5.0 → c3d_parser-1.5.2}/src/c3d_parser.egg-info/top_level.txt +0 -0
  47. {c3d_parser-1.5.0 → c3d_parser-1.5.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.5.0
3
+ Version: 1.5.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
@@ -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.8.0
15
+ Requires-Dist: opensim-model-creator==0.8.1
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.8.0
6
+ opensim-model-creator==0.8.1
7
7
  PySide6
8
8
  matplotlib
9
9
  mplcursors
@@ -0,0 +1,2 @@
1
+
2
+ __version__ = "1.5.2"
@@ -525,10 +525,8 @@ def trim_frames(frame_data):
525
525
  if not drop_frames.empty:
526
526
  frame_data.drop(drop_frames, inplace=True)
527
527
 
528
- # TODO: If we convert this to a dictionary we can keep the markers that are missing and check if they are required.
529
528
  remaining_frames = [frame for frame in incomplete_frames.keys() if trim_start <= frame <= trim_end]
530
529
  if remaining_frames:
531
- # TODO: We should raise a ParserError (skip this trial) if the missing markers are required...?
532
530
  logger.warn(f"Frames {remaining_frames} are incomplete.")
533
531
 
534
532
  return trim_start, trim_end
@@ -1092,8 +1090,6 @@ def scale_grf_data(analog_data):
1092
1090
  analog_data[columns] = analog_data[columns] / 1000
1093
1091
 
1094
1092
 
1095
- # TODO: This incorrectly classifies Adelaide's static trials as dynamic.
1096
- # Use `calculate_distance_covered` instead?
1097
1093
  def is_dynamic(file_path):
1098
1094
  with open(file_path, 'rb') as handle:
1099
1095
  try:
@@ -1269,11 +1265,6 @@ def normalise_kinetics(kinetic_data, events):
1269
1265
  normalised_data[foot][file_name] = {}
1270
1266
  data_segment = data.iloc[start:frame, 1:]
1271
1267
 
1272
- # TODO: If the model +tve and -tve are wrong, then we can't just flip these.
1273
- # Because the non-GRF parts of the moments would also be flipped.
1274
- # Just visually check the results against some clinical kinetics.
1275
- # ...
1276
- # Looks pretty good so far.
1277
1268
  # Perform sign transformations.
1278
1269
  data_segment[f"hip_flexion_{side}_moment"] = -data_segment[f"hip_flexion_{side}_moment"]
1279
1270
  data_segment[f"hip_adduction_{side}_moment"] = -data_segment[f"hip_adduction_{side}_moment"]
@@ -1366,9 +1357,6 @@ def write_normalised_data(data, column_names, selected_trials, excluded_cycles,
1366
1357
  file.write('\n')
1367
1358
 
1368
1359
 
1369
- # TODO: Rework this function to be less sequential.
1370
- # It should be able to calculate metrics for walking and running gait.
1371
- # Put this at the start of the sequential bit.
1372
1360
  def calculate_spatiotemporal_data(frame_data, events, static_data):
1373
1361
  stride_lengths = {"Left": {}, "Right": {}}
1374
1362
  step_lengths = {"Left": {}, "Right": {}}
@@ -1391,8 +1379,6 @@ def calculate_spatiotemporal_data(frame_data, events, static_data):
1391
1379
  for event_time, (event_type, event_plate) in stride_events.items():
1392
1380
  time_ordered_events[event_time][foot] = [event_type, stride_number]
1393
1381
 
1394
- # TODO: We should be able to use this to calculate ALL measurements non-sequentially.
1395
- # TODO: Double check that we don't have situations where the events are not ordered in increasing stride number.
1396
1382
  # Add terminal foot-strike to events for each cycle.
1397
1383
  full_cycle_events = copy.deepcopy(events)
1398
1384
  for foot, foot_events in full_cycle_events.items():
@@ -57,7 +57,6 @@ class ProgressTracker(QObject):
57
57
  progress = Signal(str, str)
58
58
 
59
59
 
60
- # TODO: CGM2 event detector on GitHub...?
61
60
  class MainWindow(QMainWindow):
62
61
 
63
62
  def __init__(self):
@@ -118,6 +117,10 @@ class MainWindow(QMainWindow):
118
117
  self._kinematic_curves.signals.cycles_included.connect(self._grf_curves.include_cycles)
119
118
  self._kinetic_curves.signals.cycles_excluded.connect(self._grf_curves.exclude_cycles)
120
119
  self._kinetic_curves.signals.cycles_included.connect(self._grf_curves.include_cycles)
120
+ self._kinematic_curves.signals.cycles_excluded.connect(self._update_cycle_count)
121
+ self._kinematic_curves.signals.cycles_included.connect(self._update_cycle_count)
122
+ self._kinetic_curves.signals.cycles_excluded.connect(self._update_cycle_count)
123
+ self._kinetic_curves.signals.cycles_included.connect(self._update_cycle_count)
121
124
 
122
125
  def _setup_progress_bar(self):
123
126
  self._progress_text = ""
@@ -255,12 +258,6 @@ class MainWindow(QMainWindow):
255
258
  table.deleteLater()
256
259
  self._spatiotemporal_tables = []
257
260
 
258
- # TODO: The legend is not correctly displayed if the ST tab is open while processing...
259
- # Repositioning and updating the legend do nothing.
260
-
261
- # TODO: The border is still having issues.
262
- # Seems to be related to the area being scrollable.
263
- # Only happens on some PCs/monitors...
264
261
  def _visualise_spatiotemporal_data(self):
265
262
 
266
263
  # Temporarily disable spatio-temporal visualisation for running gait.
@@ -351,6 +348,8 @@ class MainWindow(QMainWindow):
351
348
  self._ui.lineEditInputDirectory.textChanged.connect(self._validate_input_directory)
352
349
  self._ui.lineEditOutputDirectory.textChanged.connect(self._validate_directory)
353
350
  self._ui.listWidgetFiles.itemChanged.connect(self._update_plot_visibility)
351
+ self._ui.tabWidget.currentChanged.connect(self._update_cycle_count)
352
+ self._ui.listWidgetFiles.itemChanged.connect(self._update_cycle_count)
354
353
  self._ui.pushButtonInputDirectoryChooser.clicked.connect(self._open_input_directory_chooser)
355
354
  self._ui.pushButtonOutputDirectoryChooser.clicked.connect(self._open_output_directory_chooser)
356
355
  self._ui.pushButtonParseData.clicked.connect(self._parse_c3d_data)
@@ -380,6 +379,7 @@ class MainWindow(QMainWindow):
380
379
  if directory_valid:
381
380
  self._scan_directory()
382
381
 
382
+ self._clear_curves()
383
383
  self._reset_grf_plots()
384
384
  self._reset_kinematic_plots()
385
385
  self._reset_kinetic_plots()
@@ -398,6 +398,8 @@ class MainWindow(QMainWindow):
398
398
  self._ui.pushButtonParseData.setEnabled(input_directory_valid and output_directory_valid)
399
399
  self._ui.pushButtonFinalise.setEnabled(False)
400
400
 
401
+ self._ui.labelCount.setVisible(not (input_directory_valid and output_directory_valid))
402
+
401
403
  return input_directory_valid
402
404
 
403
405
  def _open_input_directory_chooser(self):
@@ -633,6 +635,7 @@ class MainWindow(QMainWindow):
633
635
  def _parse_finished(self, result):
634
636
  self._grf_data, self._kinematic_data, self._kinetic_data, self._s_t_data, self._deidentified_file_names = result
635
637
 
638
+ self._clear_curves()
636
639
  self._visualise_grf_data(self._grf_data)
637
640
  self._visualise_kinematic_data(self._kinematic_data)
638
641
  self._visualise_kinetic_data(self._kinetic_data)
@@ -640,9 +643,12 @@ class MainWindow(QMainWindow):
640
643
  self._visualise_model()
641
644
 
642
645
  self._show_selected_trials()
646
+ self._update_cycle_count()
643
647
  logger.info("Process completed successfully.")
644
648
  self._progress_tracker.progress.emit("Process completed successfully", "green")
645
649
 
650
+ self._ui.labelCount.setVisible(True)
651
+
646
652
  self._ui.pushButtonParseData.setEnabled(True)
647
653
  self._ui.pushButtonFinalise.setEnabled(True)
648
654
  self._ui.progressBar.setVisible(False)
@@ -703,6 +709,11 @@ class MainWindow(QMainWindow):
703
709
  logger.info(f"Final outputs written to {self._output_directory}.")
704
710
  self._progress_tracker.progress.emit(f"Final outputs written to {self._output_directory}", "green")
705
711
 
712
+ def _clear_curves(self):
713
+ self._grf_curves.clear()
714
+ self._kinematic_curves.clear()
715
+ self._kinetic_curves.clear()
716
+
706
717
  def _reset_grf_plots(self):
707
718
  self._plot_x.clear()
708
719
  self._plot_y.clear()
@@ -795,10 +806,8 @@ class MainWindow(QMainWindow):
795
806
  plot.set_ylim(-30, 70)
796
807
  elif i == 6:
797
808
  plot.set_ylim(-15, 75)
798
- elif i in [7, 8]:
809
+ elif i == 7:
799
810
  plot.set_ylim(-50, 30)
800
- elif i == 9:
801
- plot.set_ylim(-60, 30)
802
811
  else:
803
812
  plot.set_ylim(-30, 30)
804
813
  plot.axhline(y=0, color='gray', linewidth=1.0, zorder=1)
@@ -880,6 +889,30 @@ class MainWindow(QMainWindow):
880
889
  item = self._ui.listWidgetFiles.item(i)
881
890
  self._update_plot_visibility(item)
882
891
 
892
+ def _update_cycle_count(self):
893
+ def update_count(data, curves):
894
+ excluded_cycles = curves.get_excluded_cycles()
895
+ selected_trials = self._get_selected_trials()
896
+
897
+ counts = defaultdict(int)
898
+ for foot, files_dict in data.items():
899
+ for file_name, cycles in files_dict.items():
900
+ if file_name not in selected_trials:
901
+ continue
902
+ for cycle_number in cycles:
903
+ if (file_name, f"{foot}_{cycle_number}") not in excluded_cycles:
904
+ counts[foot] += 1
905
+
906
+ self._ui.labelCount.setText(f"Cycles: L {counts['Left']} / R {counts['Right']}")
907
+
908
+ tab = self._ui.tabWidget.currentWidget()
909
+ if tab == self._ui.tabKinematic:
910
+ update_count(self._kinematic_data, self._kinematic_curves)
911
+ elif tab == self._ui.tabKinetic:
912
+ update_count(self._kinetic_data, self._kinetic_curves)
913
+ else:
914
+ self._ui.labelCount.setText("")
915
+
883
916
  def _get_selected_trials(self):
884
917
  selected_trials = []
885
918
  for i in range(self._ui.listWidgetFiles.count()):
@@ -897,7 +930,6 @@ class MainWindow(QMainWindow):
897
930
  self._update_s_t_colours()
898
931
  self._update_legend()
899
932
 
900
- # TODO: For some reason the legend doesn't get created at all if we are visualising the ST tab while processing...
901
933
  def _update_legend(self):
902
934
  if self._boxes:
903
935
  for side, boxes in self._boxes.items():
@@ -989,8 +1021,6 @@ class MainWindow(QMainWindow):
989
1021
  settings.beginGroup('MainWindow')
990
1022
  settings.setValue('size', self.size())
991
1023
  settings.setValue('pos', self.pos())
992
- # TODO: The taskbar icon fails if the application opens in full-screen...
993
- # DON'T open in full screen...?
994
1024
  settings.setValue('is_maximized', self.isMaximized())
995
1025
  settings.setValue('lab', self._ui.comboBoxLab.currentText())
996
1026
  settings.setValue('marker_diameter', self._ui.doubleSpinBoxMarkerDiameter.value())
@@ -1195,9 +1225,6 @@ class GaitCurves(defaultdict):
1195
1225
  line.set_color(colour)
1196
1226
  line.set_zorder(z_order)
1197
1227
 
1198
- # TODO: Replace all these with `draw_idle`?
1199
- # Just the ones that handle many curves...?
1200
- # Probably all...?
1201
1228
  self._canvas.draw()
1202
1229
 
1203
1230
  def show_curve_menu(self, event):
@@ -1238,10 +1265,14 @@ class GaitCurves(defaultdict):
1238
1265
  self._canvas.draw()
1239
1266
 
1240
1267
  def include_all(self):
1268
+ identifiers = []
1241
1269
  for file_name, cycles in self.items():
1270
+ identifiers.extend((file_name, cycle) for cycle in cycles)
1242
1271
  self.include_trial(file_name)
1243
1272
  self._selected_curves = []
1244
1273
 
1274
+ self.signals.cycles_included.emit(identifiers)
1275
+
1245
1276
  def include_trial(self, file_name):
1246
1277
  for cycle in self[file_name].keys():
1247
1278
  colour = self._colour_left if "Left" in cycle else self._colour_right
@@ -1301,10 +1332,14 @@ class GaitCurves(defaultdict):
1301
1332
  self._canvas.draw()
1302
1333
 
1303
1334
  def exclude_all(self):
1335
+ identifiers = []
1304
1336
  for file_name, cycles in self.items():
1337
+ identifiers.extend((file_name, cycle) for cycle in cycles)
1305
1338
  self.exclude_trial(file_name)
1306
1339
  self._selected_curves = []
1307
1340
 
1341
+ self.signals.cycles_excluded.emit(identifiers)
1342
+
1308
1343
  def exclude_trial(self, file_name):
1309
1344
  for cycle in self[file_name].keys():
1310
1345
  colour = self._colour_left if "Left" in cycle else self._colour_right
@@ -1328,6 +1363,16 @@ class GaitCurves(defaultdict):
1328
1363
  def get_excluded_cycles(self):
1329
1364
  return self._excluded_cycles
1330
1365
 
1366
+ def clear(self):
1367
+ for cursor in self._cursors.values():
1368
+ cursor.remove()
1369
+ self._cursors = {}
1370
+
1371
+ self._selected_curves = []
1372
+ self._excluded_cycles = set()
1373
+
1374
+ super().clear()
1375
+
1331
1376
  def clear_selected(self):
1332
1377
  for (file_name, cycle) in self._selected_curves:
1333
1378
  colour = self._colour_left if "Left" in cycle else self._colour_right
@@ -637,6 +637,11 @@ class Ui_MainWindow(object):
637
637
  self.horizontalLayout_7.setSpacing(8)
638
638
  self.horizontalLayout_7.setObjectName(u"horizontalLayout_7")
639
639
  self.horizontalLayout_7.setContentsMargins(6, 1, 16, -1)
640
+ self.labelCount = QLabel(self.frameVisualisation)
641
+ self.labelCount.setObjectName(u"labelCount")
642
+
643
+ self.horizontalLayout_7.addWidget(self.labelCount)
644
+
640
645
  self.horizontalSpacer = QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
641
646
 
642
647
  self.horizontalLayout_7.addItem(self.horizontalSpacer)
@@ -766,6 +771,7 @@ class Ui_MainWindow(object):
766
771
  ___qtablewidgetitem3.setText(QCoreApplication.translate("MainWindow", u"Description", None));
767
772
  self.checkBoxSuppressWarnings.setText(QCoreApplication.translate("MainWindow", u"Suppress warnings", None))
768
773
  self.tabWidget.setTabText(self.tabWidget.indexOf(self.tabLog), QCoreApplication.translate("MainWindow", u"Log", None))
774
+ self.labelCount.setText("")
769
775
  self.labelProgress.setText("")
770
776
  self.menuFile.setTitle(QCoreApplication.translate("MainWindow", u"File", None))
771
777
  self.menuView.setTitle(QCoreApplication.translate("MainWindow", u"View", None))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: c3d-parser
3
- Version: 1.5.0
3
+ Version: 1.5.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
@@ -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.8.0
15
+ Requires-Dist: opensim-model-creator==0.8.1
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.8.0
6
+ opensim-model-creator==0.8.1
7
7
  PySide6
8
8
  matplotlib
9
9
  mplcursors
@@ -1,2 +0,0 @@
1
-
2
- __version__ = "1.5.0"
File without changes
File without changes
File without changes
File without changes