autogaita 1.2.0__tar.gz → 1.3.1__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 (92) hide show
  1. {autogaita-1.2.0 → autogaita-1.3.1}/PKG-INFO +1 -1
  2. {autogaita-1.2.0 → autogaita-1.3.1}/autogaita/common2D/common2D_1_preparation.py +17 -2
  3. {autogaita-1.2.0 → autogaita-1.3.1}/autogaita/common2D/common2D_2_sc_extraction.py +22 -10
  4. {autogaita-1.2.0 → autogaita-1.3.1}/autogaita/common2D/common2D_3_analysis.py +28 -29
  5. {autogaita-1.2.0 → autogaita-1.3.1}/autogaita/common2D/common2D_utils.py +95 -5
  6. {autogaita-1.2.0 → autogaita-1.3.1}/autogaita/group/group_1_preparation.py +144 -85
  7. {autogaita-1.2.0 → autogaita-1.3.1}/autogaita/group/group_main.py +3 -2
  8. {autogaita-1.2.0 → autogaita-1.3.1}/autogaita/gui/group_gui.py +1 -2
  9. {autogaita-1.2.0 → autogaita-1.3.1}/autogaita/resources/utils.py +68 -3
  10. {autogaita-1.2.0 → autogaita-1.3.1}/autogaita/universal3D/universal3D_1_preparation.py +4 -35
  11. {autogaita-1.2.0 → autogaita-1.3.1}/autogaita/universal3D/universal3D_2_sc_extraction.py +96 -0
  12. {autogaita-1.2.0 → autogaita-1.3.1}/autogaita/universal3D/universal3D_3_analysis.py +25 -31
  13. {autogaita-1.2.0 → autogaita-1.3.1}/autogaita.egg-info/PKG-INFO +1 -1
  14. {autogaita-1.2.0 → autogaita-1.3.1}/autogaita.egg-info/SOURCES.txt +2 -0
  15. {autogaita-1.2.0 → autogaita-1.3.1}/setup.py +1 -1
  16. autogaita-1.3.1/tests/test_common2D_unit_1_preparation.py +334 -0
  17. {autogaita-1.2.0 → autogaita-1.3.1}/tests/test_common2D_unit_2_sc_extraction.py +55 -3
  18. {autogaita-1.2.0 → autogaita-1.3.1}/tests/test_common2D_unit_3_analysis.py +2 -31
  19. autogaita-1.3.1/tests/test_common2D_unit_utils.py +134 -0
  20. {autogaita-1.2.0 → autogaita-1.3.1}/tests/test_dlc_unit_1_preparation.py +0 -14
  21. {autogaita-1.2.0 → autogaita-1.3.1}/tests/test_group_unit.py +67 -8
  22. autogaita-1.3.1/tests/test_universal3D_unit_2_sc_extraction.py +108 -0
  23. {autogaita-1.2.0 → autogaita-1.3.1}/tests/test_universal3D_unit_3_analysis.py +24 -11
  24. {autogaita-1.2.0 → autogaita-1.3.1}/tests/test_utils.py +97 -5
  25. autogaita-1.2.0/tests/test_common2D_unit_1_preparation.py +0 -235
  26. {autogaita-1.2.0 → autogaita-1.3.1}/LICENSE +0 -0
  27. {autogaita-1.2.0 → autogaita-1.3.1}/README.md +0 -0
  28. {autogaita-1.2.0 → autogaita-1.3.1}/autogaita/__init__.py +0 -0
  29. {autogaita-1.2.0 → autogaita-1.3.1}/autogaita/__main__.py +0 -0
  30. {autogaita-1.2.0 → autogaita-1.3.1}/autogaita/batchrun_scripts/__init__.py +0 -0
  31. {autogaita-1.2.0 → autogaita-1.3.1}/autogaita/batchrun_scripts/dlc_multirun.py +0 -0
  32. {autogaita-1.2.0 → autogaita-1.3.1}/autogaita/batchrun_scripts/dlc_singlerun.py +0 -0
  33. {autogaita-1.2.0 → autogaita-1.3.1}/autogaita/batchrun_scripts/group_dlcrun.py +0 -0
  34. {autogaita-1.2.0 → autogaita-1.3.1}/autogaita/batchrun_scripts/group_dlcrun_forpaper.py +0 -0
  35. {autogaita-1.2.0 → autogaita-1.3.1}/autogaita/batchrun_scripts/group_universal3Drun.py +0 -0
  36. {autogaita-1.2.0 → autogaita-1.3.1}/autogaita/batchrun_scripts/sleap_singlerun.py +0 -0
  37. {autogaita-1.2.0 → autogaita-1.3.1}/autogaita/batchrun_scripts/universal3D_multirun.py +0 -0
  38. {autogaita-1.2.0 → autogaita-1.3.1}/autogaita/batchrun_scripts/universal3D_singlerun.py +0 -0
  39. {autogaita-1.2.0 → autogaita-1.3.1}/autogaita/common2D/__init__.py +0 -0
  40. {autogaita-1.2.0 → autogaita-1.3.1}/autogaita/common2D/common2D_4_plots.py +0 -0
  41. {autogaita-1.2.0 → autogaita-1.3.1}/autogaita/common2D/common2D_constants.py +0 -0
  42. {autogaita-1.2.0 → autogaita-1.3.1}/autogaita/dlc/__init__.py +0 -0
  43. {autogaita-1.2.0 → autogaita-1.3.1}/autogaita/dlc/dlc_main.py +0 -0
  44. {autogaita-1.2.0 → autogaita-1.3.1}/autogaita/group/__init__.py +0 -0
  45. {autogaita-1.2.0 → autogaita-1.3.1}/autogaita/group/group_2_data_processing.py +0 -0
  46. {autogaita-1.2.0 → autogaita-1.3.1}/autogaita/group/group_3_PCA.py +0 -0
  47. {autogaita-1.2.0 → autogaita-1.3.1}/autogaita/group/group_4_stats.py +0 -0
  48. {autogaita-1.2.0 → autogaita-1.3.1}/autogaita/group/group_5_plots.py +0 -0
  49. {autogaita-1.2.0 → autogaita-1.3.1}/autogaita/group/group_constants.py +0 -0
  50. {autogaita-1.2.0 → autogaita-1.3.1}/autogaita/group/group_utils.py +0 -0
  51. {autogaita-1.2.0 → autogaita-1.3.1}/autogaita/gui/__init__.py +0 -0
  52. {autogaita-1.2.0 → autogaita-1.3.1}/autogaita/gui/common2D_advanced_config_gui.py +0 -0
  53. {autogaita-1.2.0 → autogaita-1.3.1}/autogaita/gui/common2D_columninfo_gui.py +0 -0
  54. {autogaita-1.2.0 → autogaita-1.3.1}/autogaita/gui/common2D_gui_constants.py +0 -0
  55. {autogaita-1.2.0 → autogaita-1.3.1}/autogaita/gui/common2D_gui_utils.py +0 -0
  56. {autogaita-1.2.0 → autogaita-1.3.1}/autogaita/gui/common2D_main_gui.py +0 -0
  57. {autogaita-1.2.0 → autogaita-1.3.1}/autogaita/gui/common2D_run_and_done_gui.py +0 -0
  58. {autogaita-1.2.0 → autogaita-1.3.1}/autogaita/gui/dlc_gui.py +0 -0
  59. {autogaita-1.2.0 → autogaita-1.3.1}/autogaita/gui/dlc_gui_config.json +0 -0
  60. {autogaita-1.2.0 → autogaita-1.3.1}/autogaita/gui/first_level_gui_utils.py +0 -0
  61. {autogaita-1.2.0 → autogaita-1.3.1}/autogaita/gui/gaita_widgets.py +0 -0
  62. {autogaita-1.2.0 → autogaita-1.3.1}/autogaita/gui/group_gui_config.json +0 -0
  63. {autogaita-1.2.0 → autogaita-1.3.1}/autogaita/gui/gui_constants.py +0 -0
  64. {autogaita-1.2.0 → autogaita-1.3.1}/autogaita/gui/gui_utils.py +0 -0
  65. {autogaita-1.2.0 → autogaita-1.3.1}/autogaita/gui/main_gui.py +0 -0
  66. {autogaita-1.2.0 → autogaita-1.3.1}/autogaita/gui/sleap_gui.py +0 -0
  67. {autogaita-1.2.0 → autogaita-1.3.1}/autogaita/gui/sleap_gui_config.json +0 -0
  68. {autogaita-1.2.0 → autogaita-1.3.1}/autogaita/gui/universal3D_gui.py +0 -0
  69. {autogaita-1.2.0 → autogaita-1.3.1}/autogaita/gui/universal3D_gui_config.json +0 -0
  70. {autogaita-1.2.0 → autogaita-1.3.1}/autogaita/resources/__init__.py +0 -0
  71. {autogaita-1.2.0 → autogaita-1.3.1}/autogaita/resources/constants.py +0 -0
  72. {autogaita-1.2.0 → autogaita-1.3.1}/autogaita/resources/icon.icns +0 -0
  73. {autogaita-1.2.0 → autogaita-1.3.1}/autogaita/resources/icon.ico +0 -0
  74. {autogaita-1.2.0 → autogaita-1.3.1}/autogaita/resources/logo.png +0 -0
  75. {autogaita-1.2.0 → autogaita-1.3.1}/autogaita/resources/pic_to_demo_for_repo.png +0 -0
  76. {autogaita-1.2.0 → autogaita-1.3.1}/autogaita/sleap/__init__.py +0 -0
  77. {autogaita-1.2.0 → autogaita-1.3.1}/autogaita/sleap/sleap_main.py +0 -0
  78. {autogaita-1.2.0 → autogaita-1.3.1}/autogaita/universal3D/__init__.py +0 -0
  79. {autogaita-1.2.0 → autogaita-1.3.1}/autogaita/universal3D/universal3D_4_plots.py +0 -0
  80. {autogaita-1.2.0 → autogaita-1.3.1}/autogaita/universal3D/universal3D_constants.py +0 -0
  81. {autogaita-1.2.0 → autogaita-1.3.1}/autogaita/universal3D/universal3D_datafile_preparation.py +0 -0
  82. {autogaita-1.2.0 → autogaita-1.3.1}/autogaita/universal3D/universal3D_main.py +0 -0
  83. {autogaita-1.2.0 → autogaita-1.3.1}/autogaita/universal3D/universal3D_utils.py +0 -0
  84. {autogaita-1.2.0 → autogaita-1.3.1}/autogaita.egg-info/dependency_links.txt +0 -0
  85. {autogaita-1.2.0 → autogaita-1.3.1}/autogaita.egg-info/requires.txt +0 -0
  86. {autogaita-1.2.0 → autogaita-1.3.1}/autogaita.egg-info/top_level.txt +0 -0
  87. {autogaita-1.2.0 → autogaita-1.3.1}/setup.cfg +0 -0
  88. {autogaita-1.2.0 → autogaita-1.3.1}/tests/__init__.py +0 -0
  89. {autogaita-1.2.0 → autogaita-1.3.1}/tests/test_dlc_approval.py +0 -0
  90. {autogaita-1.2.0 → autogaita-1.3.1}/tests/test_group_approval.py +0 -0
  91. {autogaita-1.2.0 → autogaita-1.3.1}/tests/test_universal3D_approval.py +0 -0
  92. {autogaita-1.2.0 → autogaita-1.3.1}/tests/test_universal3D_unit_1_preparation.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: autogaita
3
- Version: 1.2.0
3
+ Version: 1.3.1
4
4
  Summary: Automatic Gait Analysis in Python. A toolbox to streamline and standardise the analysis of kinematics across species after ML-based body posture tracking. Despite being optimised for gait analyses, AutoGaitA has the potential to be used for any kind of kinematic analysis.
5
5
  Home-page: https://github.com/mahan-hosseini/AutoGaitA/
6
6
  Author: Mahan Hosseini
@@ -45,6 +45,8 @@ def some_prep(tracking_software, info, folderinfo, cfg):
45
45
  subtract_beam = cfg["subtract_beam"]
46
46
  convert_to_mm = cfg["convert_to_mm"]
47
47
  pixel_to_mm_ratio = cfg["pixel_to_mm_ratio"]
48
+ x_sc_broken_threshold = cfg["x_sc_broken_threshold"]
49
+ y_sc_broken_threshold = cfg["y_sc_broken_threshold"]
48
50
  standardise_y_at_SC_level = cfg["standardise_y_at_SC_level"]
49
51
  invert_y_axis = cfg["invert_y_axis"]
50
52
  flip_gait_direction = cfg["flip_gait_direction"]
@@ -196,7 +198,11 @@ def some_prep(tracking_software, info, folderinfo, cfg):
196
198
  config_vars_to_json = {
197
199
  "sampling_rate": sampling_rate,
198
200
  "convert_to_mm": convert_to_mm,
201
+ "pixel_to_mm_ratio": pixel_to_mm_ratio,
202
+ "x_sc_broken_threshold": x_sc_broken_threshold,
203
+ "y_sc_broken_threshold": y_sc_broken_threshold,
199
204
  "standardise_y_at_SC_level": standardise_y_at_SC_level,
205
+ "flip_gait_direction": flip_gait_direction,
200
206
  "analyse_average_x": analyse_average_x,
201
207
  "standardise_x_coordinates": standardise_x_coordinates,
202
208
  "x_standardisation_joint": x_standardisation_joint,
@@ -402,11 +408,20 @@ def check_this_filename_configuration(
402
408
  prerun_string = folderinfo["prerun_string"]
403
409
  whichvideo = "" # initialise
404
410
  found_it = False
411
+ # handle leading zeros that we identified previously - if none convert nums to str
412
+ if "leading_mouse_num_zeros" in info.keys():
413
+ mouse_num = info["leading_mouse_num_zeros"] + str(mouse_num)
414
+ else:
415
+ mouse_num = str(mouse_num)
416
+ if "leading_run_num_zeros" in info.keys():
417
+ run_num = info["leading_run_num_zeros"] + str(run_num)
418
+ else:
419
+ run_num = str(run_num)
405
420
  for filename in os.listdir(root_dir):
406
421
  # the following condition is True for data & beam csv
407
422
  if (
408
- (premouse_string + str(mouse_num) + postmouse_string in filename)
409
- and (prerun_string + str(run_num) + postrun_string in filename)
423
+ (premouse_string + mouse_num + postmouse_string in filename)
424
+ and (prerun_string + run_num + postrun_string in filename)
410
425
  and (filename.endswith(file_type_string))
411
426
  ):
412
427
  found_it = True
@@ -4,6 +4,7 @@ from autogaita.common2D.common2D_utils import (
4
4
  check_cycle_out_of_bounds,
5
5
  check_cycle_duplicates,
6
6
  check_cycle_order,
7
+ check_differing_angle_joint_coords,
7
8
  check_tracking_xy_thresholds,
8
9
  check_tracking_SLEAP_nans,
9
10
  handle_issues,
@@ -208,14 +209,25 @@ def extract_stepcycles(tracking_software, data, info, folderinfo, cfg):
208
209
  # ............................ clean all_cycles ..................................
209
210
  # check if we skipped latencies because they were out of data-bounds
210
211
  all_cycles = check_cycle_out_of_bounds(all_cycles)
211
- if all_cycles: # can be None if all SCs were out of bounds
212
- # check if there are any duplicates (e.g., SC2's start-lat == SC1's end-lat)
213
- all_cycles = check_cycle_duplicates(all_cycles)
214
- # check if user input progressively later latencies
215
- all_cycles = check_cycle_order(all_cycles, info)
216
- # check if tracking broke for any SCs using user-provided x and y thresholds
217
- all_cycles = check_tracking_xy_thresholds(data, info, all_cycles, cfg)
218
- # for SLEAP - check if there were any NaNs in any joints/angle-joints in SCs
219
- if tracking_software == "SLEAP":
220
- all_cycles = check_tracking_SLEAP_nans(data, info, all_cycles, cfg)
212
+ if not all_cycles: # returns None if no clean cycles found
213
+ return None
214
+ # check if there are any duplicates (e.g., SC2's start-lat == SC1's end-lat)
215
+ all_cycles = check_cycle_duplicates(all_cycles) # doesnt return None!
216
+ # check if user input progressively later latencies
217
+ all_cycles = check_cycle_order(all_cycles, info)
218
+ if not all_cycles: # returns empty list if no clean cycles found
219
+ return None
220
+ # check that joints used in angle computations have different coords at all tps
221
+ all_cycles = check_differing_angle_joint_coords(all_cycles, data, info, cfg)
222
+ if not all_cycles:
223
+ return None
224
+ # check if tracking broke for any SCs using user-provided x and y thresholds
225
+ all_cycles = check_tracking_xy_thresholds(all_cycles, data, info, cfg)
226
+ if not all_cycles:
227
+ return None
228
+ # for SLEAP - check if there were any NaNs in any joints/angle-joints in SCs
229
+ if tracking_software == "SLEAP":
230
+ all_cycles = check_tracking_SLEAP_nans(all_cycles, data, info, cfg)
231
+ if not all_cycles:
232
+ return None
221
233
  return all_cycles
@@ -1,10 +1,13 @@
1
1
  # %% imports
2
- from autogaita.resources.utils import bin_num_to_percentages
2
+ from autogaita.resources.utils import (
3
+ bin_num_to_percentages,
4
+ compute_angle,
5
+ write_angle_warning,
6
+ )
3
7
  import os
4
8
  import warnings
5
9
  import pandas as pd
6
10
  import numpy as np
7
- import math
8
11
 
9
12
  # %% constants
10
13
  from autogaita.resources.constants import TIME_COL, SC_PERCENTAGE_COL
@@ -50,14 +53,14 @@ def analyse_and_export_stepcycles(data, all_cycles, info, cfg):
50
53
  this_step = data_copy.loc[all_cycles[0][0] : all_cycles[0][1]]
51
54
  if standardise_x_coordinates:
52
55
  all_steps_data, x_standardised_steps_data = (
53
- standardise_x_y_and_add_features_to_one_step(this_step, cfg)
56
+ standardise_x_y_and_add_features_to_one_step(this_step, info, cfg)
54
57
  )
55
58
  normalised_steps_data = normalise_one_steps_data(
56
59
  x_standardised_steps_data, bin_num
57
60
  )
58
61
  else:
59
62
  all_steps_data = standardise_x_y_and_add_features_to_one_step(
60
- this_step, cfg
63
+ this_step, info, cfg
61
64
  )
62
65
  normalised_steps_data = normalise_one_steps_data(all_steps_data, bin_num)
63
66
  # 2 or more steps - build dataframe
@@ -69,14 +72,14 @@ def analyse_and_export_stepcycles(data, all_cycles, info, cfg):
69
72
  first_step = data_copy.loc[all_cycles[0][0] : all_cycles[0][1]]
70
73
  if standardise_x_coordinates:
71
74
  all_steps_data, x_standardised_steps_data = (
72
- standardise_x_y_and_add_features_to_one_step(first_step, cfg)
75
+ standardise_x_y_and_add_features_to_one_step(first_step, info, cfg)
73
76
  )
74
77
  normalised_steps_data = normalise_one_steps_data(
75
78
  x_standardised_steps_data, bin_num
76
79
  )
77
80
  else:
78
81
  all_steps_data = standardise_x_y_and_add_features_to_one_step(
79
- first_step, cfg
82
+ first_step, info, cfg
80
83
  )
81
84
  normalised_steps_data = normalise_one_steps_data(all_steps_data, bin_num)
82
85
  # some prep for addition of further steps
@@ -98,13 +101,15 @@ def analyse_and_export_stepcycles(data, all_cycles, info, cfg):
98
101
  this_step = data_copy.loc[all_cycles[s][0] : all_cycles[s][1]]
99
102
  if standardise_x_coordinates:
100
103
  this_step, this_x_standardised_step = (
101
- standardise_x_y_and_add_features_to_one_step(this_step, cfg)
104
+ standardise_x_y_and_add_features_to_one_step(this_step, info, cfg)
102
105
  )
103
106
  this_normalised_step = normalise_one_steps_data(
104
107
  this_x_standardised_step, bin_num
105
108
  )
106
109
  else:
107
- this_step = standardise_x_y_and_add_features_to_one_step(this_step, cfg)
110
+ this_step = standardise_x_y_and_add_features_to_one_step(
111
+ this_step, info, cfg
112
+ )
108
113
  this_normalised_step = normalise_one_steps_data(this_step, bin_num)
109
114
  # step separators & step-to-rest-concatenation
110
115
  # => note that normalised_step is already based on x-stand if required
@@ -170,7 +175,7 @@ def analyse_and_export_stepcycles(data, all_cycles, info, cfg):
170
175
  # ......................................................................................
171
176
 
172
177
 
173
- def standardise_x_y_and_add_features_to_one_step(step, cfg):
178
+ def standardise_x_y_and_add_features_to_one_step(step, info, cfg):
174
179
  """For a single step cycle's data, standardise x & y if wanted and add features"""
175
180
  # if user wanted this, standardise y (height) at step-cycle level
176
181
  step_copy = step.copy()
@@ -184,11 +189,11 @@ def standardise_x_y_and_add_features_to_one_step(step, cfg):
184
189
  step_copy[y_cols] -= this_y_min
185
190
  # if no x-standardisation, just add features & return non-(x-)normalised step
186
191
  if cfg["standardise_x_coordinates"] is False:
187
- non_stand_step = add_features(step_copy, cfg)
192
+ non_stand_step = add_features(step_copy, info, cfg)
188
193
  return non_stand_step
189
194
  # else standardise x (horizontal dimension) at step-cycle level too
190
195
  else:
191
- non_stand_step = add_features(step_copy, cfg)
196
+ non_stand_step = add_features(step_copy, info, cfg)
192
197
  x_stand_step = step_copy.copy()
193
198
  x_cols = [col for col in x_stand_step.columns if col.endswith("x")]
194
199
  # note the [0] here is important because it's still a list of len=1!!
@@ -196,11 +201,11 @@ def standardise_x_y_and_add_features_to_one_step(step, cfg):
196
201
  cfg["x_standardisation_joint"][0] + "x"
197
202
  ].min()
198
203
  x_stand_step[x_cols] -= min_x_standardisation_joint
199
- x_stand_step = add_features(x_stand_step, cfg)
204
+ x_stand_step = add_features(x_stand_step, info, cfg)
200
205
  return non_stand_step, x_stand_step
201
206
 
202
207
 
203
- def add_features(step, cfg):
208
+ def add_features(step, info, cfg):
204
209
  """Add Features, i.e. Angles & Velocities"""
205
210
  # unpack
206
211
  hind_joints = cfg["hind_joints"]
@@ -208,12 +213,12 @@ def add_features(step, cfg):
208
213
  if hind_joints:
209
214
  step = add_x_velocities(step, cfg)
210
215
  if angles["name"]: # if there is at least 1 string in the list
211
- step = add_angles(step, cfg)
216
+ step = add_angles(step, info, cfg)
212
217
  step = add_angular_velocities(step, cfg)
213
218
  return step
214
219
 
215
220
 
216
- def add_angles(step, cfg):
221
+ def add_angles(step, info, cfg):
217
222
  """Feature #1: Joint Angles"""
218
223
  # unpack
219
224
  angles = cfg["angles"]
@@ -234,26 +239,20 @@ def add_angles(step, cfg):
234
239
  joint3[:, 1] = step[upper_joint + "y"]
235
240
  # initialise the angle vector and assign looping over timepoints
236
241
  this_angle = np.zeros(len(joint_angle))
242
+ broken_angle_idxs = [] # initialise broken idxs-list for each angle anew
237
243
  for t in range(len(joint_angle)):
238
- this_angle[t] = compute_angle(joint_angle[t, :], joint2[t, :], joint3[t, :])
244
+ this_angle[t], broken = compute_angle(
245
+ joint_angle[t, :], joint2[t, :], joint3[t, :]
246
+ )
247
+ if broken:
248
+ broken_angle_idxs.append(t)
249
+ if broken_angle_idxs:
250
+ write_angle_warning(step, a, angles, broken_angle_idxs, info)
239
251
  this_colname = angle + "Angle"
240
252
  step[this_colname] = this_angle
241
253
  return step
242
254
 
243
255
 
244
- def compute_angle(joint_angle, joint2, joint3):
245
- """Compute a given angle at a joint & a given timepoint"""
246
- # Get vectors between the joints
247
- v1 = (joint_angle[0] - joint2[0], joint_angle[1] - joint2[1])
248
- v2 = (joint_angle[0] - joint3[0], joint_angle[1] - joint3[1])
249
- # dot product, magnitude of vectors, angle in radians & convert 2 degrees
250
- dot_product = v1[0] * v2[0] + v1[1] * v2[1]
251
- mag_v1 = math.sqrt(v1[0] ** 2 + v1[1] ** 2)
252
- mag_v2 = math.sqrt(v2[0] ** 2 + v2[1] ** 2)
253
- angle = math.acos(dot_product / (mag_v1 * mag_v2))
254
- return math.degrees(angle)
255
-
256
-
257
256
  def add_x_velocities(step, cfg):
258
257
  """Feature #2: Joint x Velocities & Accelerations"""
259
258
  # unpack
@@ -6,6 +6,9 @@ import copy
6
6
  import numpy as np
7
7
  import tkinter as tk
8
8
 
9
+ # %% constants
10
+ from autogaita.resources.constants import TIME_COL
11
+
9
12
 
10
13
  def run_singlerun_in_multirun(tracking_software, idx, info, folderinfo, cfg):
11
14
  """When performing a multirun, either via Batch Analysis in GUI or batchrun scripts, run the analysis for a given dataset"""
@@ -61,7 +64,7 @@ def extract_info(tracking_software, folderinfo, in_GUI=False):
61
64
  for string_addition in FILE_ID_STRING_ADDITIONS:
62
65
  try:
63
66
  candidate_postmouse_string = string_addition + postmouse_string
64
- this_mouse_num = find_number(
67
+ this_mouse_num, leading_mouse_num_zeros = find_number(
65
68
  filename,
66
69
  premouse_string,
67
70
  candidate_postmouse_string,
@@ -84,7 +87,7 @@ def extract_info(tracking_software, folderinfo, in_GUI=False):
84
87
  for string_addition in FILE_ID_STRING_ADDITIONS:
85
88
  try:
86
89
  candidate_postrun_string = string_addition + postrun_string
87
- this_run_num = find_number(
90
+ this_run_num, leading_run_num_zeros = find_number(
88
91
  filename, prerun_string, candidate_postrun_string
89
92
  )
90
93
  except:
@@ -106,6 +109,14 @@ def extract_info(tracking_software, folderinfo, in_GUI=False):
106
109
  info["name"].append(this_name)
107
110
  info["mouse_num"].append(this_mouse_num)
108
111
  info["run_num"].append(this_run_num)
112
+ # if we had to fix leading zeros, make sure to save them so we can use them in
113
+ # move_data_to_folders function later
114
+ # => make sure it's lists of strings because this is needed by the
115
+ # run_singlerun_in_multiruns function later
116
+ if leading_mouse_num_zeros:
117
+ info["leading_mouse_num_zeros"] = [leading_mouse_num_zeros]
118
+ if leading_run_num_zeros:
119
+ info["leading_run_num_zeros"] = [leading_run_num_zeros]
109
120
  # this might happen if user entered wrong identifiers or folder
110
121
  if len(info["name"]) < 1:
111
122
  no_files_message = (
@@ -143,7 +154,12 @@ def find_number(fullstring, prestring, poststring):
143
154
  """Find (mouse/run) number based on user-defined strings in filenames"""
144
155
  start_idx = fullstring.find(prestring) + len(prestring)
145
156
  end_idx = fullstring.find(poststring)
146
- return int(fullstring[start_idx:end_idx])
157
+ # handle leading zeros
158
+ leading_zeros = ""
159
+ while fullstring[start_idx] == "0" and start_idx < end_idx - 1:
160
+ start_idx += 1
161
+ leading_zeros += "0"
162
+ return int(fullstring[start_idx:end_idx]), leading_zeros
147
163
 
148
164
 
149
165
  # ........................... SC extraction helpers ..................................
@@ -210,7 +226,81 @@ def check_cycle_order(all_cycles, info):
210
226
  return clean_cycles
211
227
 
212
228
 
213
- def check_tracking_xy_thresholds(data, info, all_cycles, cfg):
229
+ def check_differing_angle_joint_coords(all_cycles, data, info, cfg):
230
+ """Check if none of the joints used for angle computations later have equal values (since this would lead to math.domain errors due to floating point precision)"""
231
+
232
+ # Note
233
+ # ----
234
+ # In theory, I could fix this programatically in the add_angle function, but I feel
235
+ # like joint-coords should not often be exactly equal like this in a meaningful way
236
+ # We can still change it in the future.
237
+
238
+ # unpack
239
+ angles = cfg["angles"]
240
+
241
+ clean_cycles = None
242
+ for c, cycle in enumerate(all_cycles): # for each SC
243
+ cycle = check_a_single_cycle_for_joint_coords(cycle, angles, data, c, info)
244
+ if cycle: # if cycle was not valid (equal-joint-coords) this returns None
245
+ if clean_cycles == None:
246
+ clean_cycles = [cycle] # also makes a 2xscs list of lists
247
+ else:
248
+ clean_cycles.append(cycle)
249
+ return clean_cycles
250
+
251
+
252
+ def check_a_single_cycle_for_joint_coords(cycle, angles, data, c, info):
253
+ for a in range(len(angles["name"])): # for each angle configuration
254
+ # prepare a dict that has only the data of this angle config's joints
255
+ this_angle_data = {"name": [], "lower_joint": [], "upper_joint": []}
256
+ for key in this_angle_data.keys():
257
+ this_joint = angles[key][a]
258
+ this_angle_data[key] = np.array(
259
+ [data[this_joint + "x"], data[this_joint + "y"]]
260
+ )
261
+ # now check if any of the joints have the same coord at any idx
262
+ for idx in range(cycle[0], cycle[1]):
263
+ if (
264
+ np.array_equal(
265
+ this_angle_data["name"][:, idx],
266
+ this_angle_data["lower_joint"][:, idx],
267
+ )
268
+ or np.array_equal(
269
+ this_angle_data["name"][:, idx],
270
+ this_angle_data["upper_joint"][:, idx],
271
+ )
272
+ or np.array_equal(
273
+ this_angle_data["lower_joint"][:, idx],
274
+ this_angle_data["upper_joint"][:, idx],
275
+ )
276
+ ):
277
+ this_message = (
278
+ "\n***********\n! WARNING !\n***********\n"
279
+ + f"SC #{c + 1} has equal joint coordinates at "
280
+ + f"{round(data[TIME_COL][idx],4)}s:"
281
+ + "\n\nAngle - [x y]:\n"
282
+ + angles["name"][a]
283
+ + " - "
284
+ + str(this_angle_data["name"][:, idx])
285
+ + "\nLower joint: "
286
+ + angles["lower_joint"][a]
287
+ + " - "
288
+ + str(this_angle_data["lower_joint"][:, idx])
289
+ + "\nUpper joint: "
290
+ + angles["upper_joint"][a]
291
+ + " - "
292
+ + str(this_angle_data["upper_joint"][:, idx])
293
+ + "\nRemoving the SC from "
294
+ + f"{round(data[TIME_COL][cycle[0]], 4)}-"
295
+ + f"{round(data[TIME_COL][cycle[1]], 4)}s"
296
+ )
297
+ print(this_message)
298
+ write_issues_to_textfile(this_message, info)
299
+ return None # removes this SC
300
+ return cycle # if we never returned None, this SC is valid
301
+
302
+
303
+ def check_tracking_xy_thresholds(all_cycles, data, info, cfg):
214
304
  """Check if any x/y column of any joint has broken datapoints"""
215
305
  # unpack
216
306
  convert_to_mm = cfg["convert_to_mm"]
@@ -255,7 +345,7 @@ def check_tracking_xy_thresholds(data, info, all_cycles, cfg):
255
345
  return clean_cycles
256
346
 
257
347
 
258
- def check_tracking_SLEAP_nans(data, info, all_cycles, cfg):
348
+ def check_tracking_SLEAP_nans(all_cycles, data, info, cfg):
259
349
  """In SLEAP if tracking fails it generates NaNs - make sure we don't have those in any SC in any joint or angle-joint"""
260
350
  # unpack
261
351
  hind_joints = cfg["hind_joints"]