autogaita 1.3.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.3.0 → autogaita-1.3.1}/PKG-INFO +1 -1
  2. {autogaita-1.3.0 → autogaita-1.3.1}/autogaita/common2D/common2D_1_preparation.py +17 -2
  3. {autogaita-1.3.0 → autogaita-1.3.1}/autogaita/common2D/common2D_utils.py +16 -3
  4. {autogaita-1.3.0 → autogaita-1.3.1}/autogaita/group/group_1_preparation.py +12 -5
  5. {autogaita-1.3.0 → autogaita-1.3.1}/autogaita/gui/group_gui.py +1 -2
  6. {autogaita-1.3.0 → autogaita-1.3.1}/autogaita/universal3D/universal3D_1_preparation.py +2 -0
  7. {autogaita-1.3.0 → autogaita-1.3.1}/autogaita.egg-info/PKG-INFO +1 -1
  8. {autogaita-1.3.0 → autogaita-1.3.1}/autogaita.egg-info/SOURCES.txt +1 -0
  9. {autogaita-1.3.0 → autogaita-1.3.1}/setup.py +1 -1
  10. autogaita-1.3.1/tests/test_common2D_unit_1_preparation.py +334 -0
  11. autogaita-1.3.1/tests/test_common2D_unit_utils.py +134 -0
  12. {autogaita-1.3.0 → autogaita-1.3.1}/tests/test_dlc_unit_1_preparation.py +0 -14
  13. {autogaita-1.3.0 → autogaita-1.3.1}/tests/test_group_unit.py +44 -3
  14. autogaita-1.3.0/tests/test_common2D_unit_1_preparation.py +0 -240
  15. {autogaita-1.3.0 → autogaita-1.3.1}/LICENSE +0 -0
  16. {autogaita-1.3.0 → autogaita-1.3.1}/README.md +0 -0
  17. {autogaita-1.3.0 → autogaita-1.3.1}/autogaita/__init__.py +0 -0
  18. {autogaita-1.3.0 → autogaita-1.3.1}/autogaita/__main__.py +0 -0
  19. {autogaita-1.3.0 → autogaita-1.3.1}/autogaita/batchrun_scripts/__init__.py +0 -0
  20. {autogaita-1.3.0 → autogaita-1.3.1}/autogaita/batchrun_scripts/dlc_multirun.py +0 -0
  21. {autogaita-1.3.0 → autogaita-1.3.1}/autogaita/batchrun_scripts/dlc_singlerun.py +0 -0
  22. {autogaita-1.3.0 → autogaita-1.3.1}/autogaita/batchrun_scripts/group_dlcrun.py +0 -0
  23. {autogaita-1.3.0 → autogaita-1.3.1}/autogaita/batchrun_scripts/group_dlcrun_forpaper.py +0 -0
  24. {autogaita-1.3.0 → autogaita-1.3.1}/autogaita/batchrun_scripts/group_universal3Drun.py +0 -0
  25. {autogaita-1.3.0 → autogaita-1.3.1}/autogaita/batchrun_scripts/sleap_singlerun.py +0 -0
  26. {autogaita-1.3.0 → autogaita-1.3.1}/autogaita/batchrun_scripts/universal3D_multirun.py +0 -0
  27. {autogaita-1.3.0 → autogaita-1.3.1}/autogaita/batchrun_scripts/universal3D_singlerun.py +0 -0
  28. {autogaita-1.3.0 → autogaita-1.3.1}/autogaita/common2D/__init__.py +0 -0
  29. {autogaita-1.3.0 → autogaita-1.3.1}/autogaita/common2D/common2D_2_sc_extraction.py +0 -0
  30. {autogaita-1.3.0 → autogaita-1.3.1}/autogaita/common2D/common2D_3_analysis.py +0 -0
  31. {autogaita-1.3.0 → autogaita-1.3.1}/autogaita/common2D/common2D_4_plots.py +0 -0
  32. {autogaita-1.3.0 → autogaita-1.3.1}/autogaita/common2D/common2D_constants.py +0 -0
  33. {autogaita-1.3.0 → autogaita-1.3.1}/autogaita/dlc/__init__.py +0 -0
  34. {autogaita-1.3.0 → autogaita-1.3.1}/autogaita/dlc/dlc_main.py +0 -0
  35. {autogaita-1.3.0 → autogaita-1.3.1}/autogaita/group/__init__.py +0 -0
  36. {autogaita-1.3.0 → autogaita-1.3.1}/autogaita/group/group_2_data_processing.py +0 -0
  37. {autogaita-1.3.0 → autogaita-1.3.1}/autogaita/group/group_3_PCA.py +0 -0
  38. {autogaita-1.3.0 → autogaita-1.3.1}/autogaita/group/group_4_stats.py +0 -0
  39. {autogaita-1.3.0 → autogaita-1.3.1}/autogaita/group/group_5_plots.py +0 -0
  40. {autogaita-1.3.0 → autogaita-1.3.1}/autogaita/group/group_constants.py +0 -0
  41. {autogaita-1.3.0 → autogaita-1.3.1}/autogaita/group/group_main.py +0 -0
  42. {autogaita-1.3.0 → autogaita-1.3.1}/autogaita/group/group_utils.py +0 -0
  43. {autogaita-1.3.0 → autogaita-1.3.1}/autogaita/gui/__init__.py +0 -0
  44. {autogaita-1.3.0 → autogaita-1.3.1}/autogaita/gui/common2D_advanced_config_gui.py +0 -0
  45. {autogaita-1.3.0 → autogaita-1.3.1}/autogaita/gui/common2D_columninfo_gui.py +0 -0
  46. {autogaita-1.3.0 → autogaita-1.3.1}/autogaita/gui/common2D_gui_constants.py +0 -0
  47. {autogaita-1.3.0 → autogaita-1.3.1}/autogaita/gui/common2D_gui_utils.py +0 -0
  48. {autogaita-1.3.0 → autogaita-1.3.1}/autogaita/gui/common2D_main_gui.py +0 -0
  49. {autogaita-1.3.0 → autogaita-1.3.1}/autogaita/gui/common2D_run_and_done_gui.py +0 -0
  50. {autogaita-1.3.0 → autogaita-1.3.1}/autogaita/gui/dlc_gui.py +0 -0
  51. {autogaita-1.3.0 → autogaita-1.3.1}/autogaita/gui/dlc_gui_config.json +0 -0
  52. {autogaita-1.3.0 → autogaita-1.3.1}/autogaita/gui/first_level_gui_utils.py +0 -0
  53. {autogaita-1.3.0 → autogaita-1.3.1}/autogaita/gui/gaita_widgets.py +0 -0
  54. {autogaita-1.3.0 → autogaita-1.3.1}/autogaita/gui/group_gui_config.json +0 -0
  55. {autogaita-1.3.0 → autogaita-1.3.1}/autogaita/gui/gui_constants.py +0 -0
  56. {autogaita-1.3.0 → autogaita-1.3.1}/autogaita/gui/gui_utils.py +0 -0
  57. {autogaita-1.3.0 → autogaita-1.3.1}/autogaita/gui/main_gui.py +0 -0
  58. {autogaita-1.3.0 → autogaita-1.3.1}/autogaita/gui/sleap_gui.py +0 -0
  59. {autogaita-1.3.0 → autogaita-1.3.1}/autogaita/gui/sleap_gui_config.json +0 -0
  60. {autogaita-1.3.0 → autogaita-1.3.1}/autogaita/gui/universal3D_gui.py +0 -0
  61. {autogaita-1.3.0 → autogaita-1.3.1}/autogaita/gui/universal3D_gui_config.json +0 -0
  62. {autogaita-1.3.0 → autogaita-1.3.1}/autogaita/resources/__init__.py +0 -0
  63. {autogaita-1.3.0 → autogaita-1.3.1}/autogaita/resources/constants.py +0 -0
  64. {autogaita-1.3.0 → autogaita-1.3.1}/autogaita/resources/icon.icns +0 -0
  65. {autogaita-1.3.0 → autogaita-1.3.1}/autogaita/resources/icon.ico +0 -0
  66. {autogaita-1.3.0 → autogaita-1.3.1}/autogaita/resources/logo.png +0 -0
  67. {autogaita-1.3.0 → autogaita-1.3.1}/autogaita/resources/pic_to_demo_for_repo.png +0 -0
  68. {autogaita-1.3.0 → autogaita-1.3.1}/autogaita/resources/utils.py +0 -0
  69. {autogaita-1.3.0 → autogaita-1.3.1}/autogaita/sleap/__init__.py +0 -0
  70. {autogaita-1.3.0 → autogaita-1.3.1}/autogaita/sleap/sleap_main.py +0 -0
  71. {autogaita-1.3.0 → autogaita-1.3.1}/autogaita/universal3D/__init__.py +0 -0
  72. {autogaita-1.3.0 → autogaita-1.3.1}/autogaita/universal3D/universal3D_2_sc_extraction.py +0 -0
  73. {autogaita-1.3.0 → autogaita-1.3.1}/autogaita/universal3D/universal3D_3_analysis.py +0 -0
  74. {autogaita-1.3.0 → autogaita-1.3.1}/autogaita/universal3D/universal3D_4_plots.py +0 -0
  75. {autogaita-1.3.0 → autogaita-1.3.1}/autogaita/universal3D/universal3D_constants.py +0 -0
  76. {autogaita-1.3.0 → autogaita-1.3.1}/autogaita/universal3D/universal3D_datafile_preparation.py +0 -0
  77. {autogaita-1.3.0 → autogaita-1.3.1}/autogaita/universal3D/universal3D_main.py +0 -0
  78. {autogaita-1.3.0 → autogaita-1.3.1}/autogaita/universal3D/universal3D_utils.py +0 -0
  79. {autogaita-1.3.0 → autogaita-1.3.1}/autogaita.egg-info/dependency_links.txt +0 -0
  80. {autogaita-1.3.0 → autogaita-1.3.1}/autogaita.egg-info/requires.txt +0 -0
  81. {autogaita-1.3.0 → autogaita-1.3.1}/autogaita.egg-info/top_level.txt +0 -0
  82. {autogaita-1.3.0 → autogaita-1.3.1}/setup.cfg +0 -0
  83. {autogaita-1.3.0 → autogaita-1.3.1}/tests/__init__.py +0 -0
  84. {autogaita-1.3.0 → autogaita-1.3.1}/tests/test_common2D_unit_2_sc_extraction.py +0 -0
  85. {autogaita-1.3.0 → autogaita-1.3.1}/tests/test_common2D_unit_3_analysis.py +0 -0
  86. {autogaita-1.3.0 → autogaita-1.3.1}/tests/test_dlc_approval.py +0 -0
  87. {autogaita-1.3.0 → autogaita-1.3.1}/tests/test_group_approval.py +0 -0
  88. {autogaita-1.3.0 → autogaita-1.3.1}/tests/test_universal3D_approval.py +0 -0
  89. {autogaita-1.3.0 → autogaita-1.3.1}/tests/test_universal3D_unit_1_preparation.py +0 -0
  90. {autogaita-1.3.0 → autogaita-1.3.1}/tests/test_universal3D_unit_2_sc_extraction.py +0 -0
  91. {autogaita-1.3.0 → autogaita-1.3.1}/tests/test_universal3D_unit_3_analysis.py +0 -0
  92. {autogaita-1.3.0 → autogaita-1.3.1}/tests/test_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: autogaita
3
- Version: 1.3.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
@@ -64,7 +64,7 @@ def extract_info(tracking_software, folderinfo, in_GUI=False):
64
64
  for string_addition in FILE_ID_STRING_ADDITIONS:
65
65
  try:
66
66
  candidate_postmouse_string = string_addition + postmouse_string
67
- this_mouse_num = find_number(
67
+ this_mouse_num, leading_mouse_num_zeros = find_number(
68
68
  filename,
69
69
  premouse_string,
70
70
  candidate_postmouse_string,
@@ -87,7 +87,7 @@ def extract_info(tracking_software, folderinfo, in_GUI=False):
87
87
  for string_addition in FILE_ID_STRING_ADDITIONS:
88
88
  try:
89
89
  candidate_postrun_string = string_addition + postrun_string
90
- this_run_num = find_number(
90
+ this_run_num, leading_run_num_zeros = find_number(
91
91
  filename, prerun_string, candidate_postrun_string
92
92
  )
93
93
  except:
@@ -109,6 +109,14 @@ def extract_info(tracking_software, folderinfo, in_GUI=False):
109
109
  info["name"].append(this_name)
110
110
  info["mouse_num"].append(this_mouse_num)
111
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]
112
120
  # this might happen if user entered wrong identifiers or folder
113
121
  if len(info["name"]) < 1:
114
122
  no_files_message = (
@@ -146,7 +154,12 @@ def find_number(fullstring, prestring, poststring):
146
154
  """Find (mouse/run) number based on user-defined strings in filenames"""
147
155
  start_idx = fullstring.find(prestring) + len(prestring)
148
156
  end_idx = fullstring.find(poststring)
149
- 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
150
163
 
151
164
 
152
165
  # ........................... SC extraction helpers ..................................
@@ -28,10 +28,11 @@ def some_prep(folderinfo, cfg):
28
28
  # Alright so the group pipeline's cfg (and thus, of course, config.json) is a bit
29
29
  # special because it includes:
30
30
  # 1) first-level config-keys, such as "joints" or "angles", that reflect what has
31
- # been analysed at the first level. These are also checked for equivalence
32
- # across groups when running this without load_dir (see the for g_idx loop
33
- # below) to ensure we are not comparing different sampling rates or so with a
34
- # group analysis
31
+ # been analysed at the first level. Some of these are used in group workflow
32
+ # (e.g. tracking_software when plotting or sampling_rate for PCA) and all of
33
+ # these are checked for equivalence across groups when running this without
34
+ # load_dir (see the for g_idx loop below) to ensure we are not comparing
35
+ # different sampling rates or so with a group analysis
35
36
  # 2) group-level config-keys, such as "do_permtest" or "PCA_variables" that define
36
37
  # how group analysis should be done
37
38
  # Now:
@@ -145,14 +146,20 @@ def some_prep(folderinfo, cfg):
145
146
 
146
147
 
147
148
  def load_previous_runs_first_level_cfg_vars(folderinfo, cfg):
148
- """There are only a few "first-level" cfg vars (like "joints") we require for group gaita's workflow - load them here"""
149
+ """There are a couple "first-level" cfg vars (like "joints") we require for group gaita's workflow - load them here"""
149
150
  with open(
150
151
  os.path.join(folderinfo["load_dir"], CONFIG_JSON_FILENAME), "r"
151
152
  ) as config_json_file:
152
153
  old_cfg = json.load(config_json_file)
154
+ cfg["sampling_rate"] = old_cfg["sampling_rate"]
153
155
  cfg["save_to_xls"] = old_cfg["save_to_xls"]
154
156
  cfg["joints"] = old_cfg["joints"]
155
157
  cfg["angles"] = old_cfg["angles"]
158
+ cfg["tracking_software"] = old_cfg["tracking_software"]
159
+ if "analyse_average_x" in old_cfg.keys():
160
+ cfg["analyse_average_x"] = old_cfg["analyse_average_x"]
161
+ if "analyse_average_y" in old_cfg.keys():
162
+ cfg["analyse_average_y"] = old_cfg["analyse_average_y"]
156
163
  return cfg
157
164
 
158
165
 
@@ -12,7 +12,6 @@ import platform
12
12
  import json
13
13
  import copy
14
14
 
15
-
16
15
  # %% global constants
17
16
  from autogaita.gui.gui_constants import (
18
17
  GROUP_FG_COLOR,
@@ -917,7 +916,7 @@ def check_folderinfo_and_cfg(folderinfo, cfg):
917
916
  for key in inner_dict.keys():
918
917
  # check string vars: group dirs & names and results dir
919
918
  if key in STRING_VARS:
920
- if key == "group_dirs" and len(folderinfo["load_dir"]) > 0:
919
+ if key == "group_dirs" and len(folderinfo["load_dir"]) == 0:
921
920
  for g_idx, group_dir in enumerate(inner_dict[key]):
922
921
  if not os.path.exists(group_dir):
923
922
  this_msg = (
@@ -30,6 +30,7 @@ def some_prep(info, folderinfo, cfg):
30
30
  results_dir = info["results_dir"]
31
31
  postname_string = folderinfo["postname_string"]
32
32
  sampling_rate = cfg["sampling_rate"]
33
+ flip_gait_direction = cfg["flip_gait_direction"]
33
34
  standardise_z_at_SC_level = cfg["standardise_z_at_SC_level"]
34
35
  analyse_average_y = cfg["analyse_average_y"]
35
36
  standardise_y_coordinates = cfg["standardise_y_coordinates"]
@@ -122,6 +123,7 @@ def some_prep(info, folderinfo, cfg):
122
123
  config_json_path = os.path.join(group_path, CONFIG_JSON_FILENAME)
123
124
  config_vars_to_json = {
124
125
  "sampling_rate": sampling_rate,
126
+ "flip_gait_direction": flip_gait_direction,
125
127
  "standardise_z_at_SC_level": standardise_z_at_SC_level,
126
128
  "analyse_average_y": analyse_average_y,
127
129
  "standardise_y_coordinates": standardise_y_coordinates,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: autogaita
3
- Version: 1.3.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
@@ -77,6 +77,7 @@ tests/__init__.py
77
77
  tests/test_common2D_unit_1_preparation.py
78
78
  tests/test_common2D_unit_2_sc_extraction.py
79
79
  tests/test_common2D_unit_3_analysis.py
80
+ tests/test_common2D_unit_utils.py
80
81
  tests/test_dlc_approval.py
81
82
  tests/test_dlc_unit_1_preparation.py
82
83
  tests/test_group_approval.py
@@ -26,7 +26,7 @@ if platform.system() == "Darwin":
26
26
  setup(
27
27
  name="autogaita",
28
28
  python_requires=">=3.10",
29
- version="1.3.0", # rc == release candidate (before release is finished)
29
+ version="1.3.1", # rc == release candidate (before release is finished)
30
30
  author="Mahan Hosseini",
31
31
  description="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.",
32
32
  packages=find_packages(),
@@ -0,0 +1,334 @@
1
+ from autogaita.common2D.common2D_1_preparation import (
2
+ move_data_to_folders,
3
+ check_and_expand_cfg,
4
+ check_and_fix_cfg_strings,
5
+ flip_mouse_body,
6
+ some_prep, # note that first input of some_prep is set to "DLC" when not mattering!
7
+ )
8
+ from autogaita.common2D.common2D_utils import extract_info
9
+ import os
10
+ import copy
11
+ import math
12
+ import numpy as np
13
+ import pandas.testing as pdt
14
+ from hypothesis import given, strategies as st, settings, HealthCheck
15
+ import pytest
16
+
17
+
18
+ # %%.............................. fixtures ..........................................
19
+ # NOTE
20
+ # ----
21
+ # Calling them FIXTURE_extract_... since we have a function called extract_info!
22
+
23
+
24
+ @pytest.fixture
25
+ def extract_data_using_some_prep(
26
+ fixture_extract_info, fixture_extract_folderinfo, fixture_extract_cfg
27
+ ):
28
+ data = some_prep(
29
+ "DLC", fixture_extract_info, fixture_extract_folderinfo, fixture_extract_cfg
30
+ )
31
+ return data
32
+
33
+
34
+ @pytest.fixture
35
+ def fixture_extract_info(tmp_path):
36
+ info = {}
37
+ info["mouse_num"] = 15
38
+ info["run_num"] = 3
39
+ info["name"] = "ID " + str(info["mouse_num"]) + " - Run " + str(info["run_num"])
40
+ info["results_dir"] = os.path.join(tmp_path, info["name"])
41
+ return info
42
+
43
+
44
+ @pytest.fixture
45
+ def fixture_extract_folderinfo():
46
+ folderinfo = {}
47
+ folderinfo["root_dir"] = "tests/test_data/dlc_data"
48
+ folderinfo["sctable_filename"] = (
49
+ "correct_annotation_table.xlsx" # has to be an excel file
50
+ )
51
+ folderinfo["data_string"] = "SIMINewOct"
52
+ folderinfo["beam_string"] = "BeamTraining"
53
+ folderinfo["premouse_string"] = "Mouse"
54
+ folderinfo["postmouse_string"] = "25mm"
55
+ folderinfo["prerun_string"] = "run"
56
+ folderinfo["postrun_string"] = "6DLC"
57
+ return folderinfo
58
+
59
+
60
+ @pytest.fixture
61
+ def fixture_extract_cfg():
62
+ cfg = {}
63
+ cfg["sampling_rate"] = 100
64
+ cfg["subtract_beam"] = True
65
+ cfg["dont_show_plots"] = True
66
+ cfg["convert_to_mm"] = True
67
+ cfg["pixel_to_mm_ratio"] = 3.76
68
+ cfg["x_sc_broken_threshold"] = 200
69
+ cfg["y_sc_broken_threshold"] = 50
70
+ cfg["x_acceleration"] = True
71
+ cfg["angular_acceleration"] = True
72
+ cfg["save_to_xls"] = True
73
+ cfg["bin_num"] = 25
74
+ cfg["plot_SE"] = True
75
+ cfg["standardise_y_at_SC_level"] = False
76
+ cfg["standardise_y_to_a_joint"] = True
77
+ cfg["y_standardisation_joint"] = ["Knee"]
78
+ cfg["plot_joint_number"] = 3
79
+ cfg["color_palette"] = "viridis"
80
+ cfg["legend_outside"] = True
81
+ cfg["invert_y_axis"] = True
82
+ cfg["flip_gait_direction"] = True
83
+ cfg["analyse_average_x"] = True
84
+ cfg["standardise_x_coordinates"] = True
85
+ cfg["x_standardisation_joint"] = ["Hind paw tao"]
86
+ cfg["coordinate_standardisation_xls"] = ""
87
+ cfg["hind_joints"] = ["Hind paw tao", "Ankle", "Knee", "Hip", "Iliac Crest"]
88
+ cfg["fore_joints"] = [
89
+ "Front paw tao ",
90
+ "Wrist ",
91
+ "Elbow ",
92
+ "Lower Shoulder ",
93
+ "Upper Shoulder ",
94
+ ]
95
+ cfg["beam_col_left"] = ["BeamLeft"] # BEAM_COL_LEFT & _RIGHT must be lists of len=1
96
+ cfg["beam_col_right"] = ["BeamRight"]
97
+ cfg["beam_hind_jointadd"] = ["Tail base ", "Tail center ", "Tail tip "]
98
+ cfg["beam_fore_jointadd"] = ["Nose ", "Ear base "]
99
+ cfg["angles"] = {
100
+ "name": ["Ankle ", "Knee ", "Hip "],
101
+ "lower_joint": ["Hind paw tao ", "Ankle ", "Knee "],
102
+ "upper_joint": ["Knee ", "Hip ", "Iliac Crest "],
103
+ }
104
+ return cfg
105
+
106
+
107
+ # %%.............................. preparation .......................................
108
+ # AN IMPORTANT NOTE ON THESE UNIT TESTS!
109
+ # Calling check_and_expand cfg outside of some_prep with the
110
+ # export_data_using_some_prep fixture leads to the cfg that is returned by
111
+ # check_and_expand to be None since the data var DOES NOT INCLUDE the beam!
112
+ # => We thus set cfg["subtract_beam"] to False prior to calling it (see e.g.the
113
+ # plot_joint test)
114
+
115
+
116
+ def test_move_data_to_folders_smoke_normal(
117
+ fixture_extract_info, fixture_extract_folderinfo
118
+ ):
119
+ move_data_to_folders(
120
+ "DLC", ".csv", fixture_extract_info, fixture_extract_folderinfo
121
+ )
122
+ assert len(os.listdir(fixture_extract_info["results_dir"])) == 3
123
+ for file in os.listdir(fixture_extract_info["results_dir"]):
124
+ if file.endswith(".csv"):
125
+ assert (
126
+ (fixture_extract_folderinfo["premouse_string"] in file)
127
+ & (fixture_extract_folderinfo["postmouse_string"] in file)
128
+ & (fixture_extract_folderinfo["prerun_string"] in file)
129
+ & (fixture_extract_folderinfo["postrun_string"] in file)
130
+ )
131
+
132
+
133
+ def test_move_data_to_folders_leading_zeros_handled(
134
+ tmp_path, fixture_extract_folderinfo
135
+ ):
136
+ # ensure files with leading zeros are moved too
137
+ # => this needs some preparation because I want to test extract_info, too, which is
138
+ # required to output lists in each of its keys because I use it in multiruns to
139
+ # iterate over each idxs which consistute separate runs
140
+ fixture_extract_folderinfo["root_dir"] = (
141
+ "tests/test_data/dlc_data/test_data/leading_zeros/"
142
+ )
143
+ # CARE! function: extract_info for info where we handle leading zeros
144
+ info = extract_info("DLC", fixture_extract_folderinfo)
145
+ info["results_dir"] = tmp_path
146
+ # move_data... creates it, make sure it's not there
147
+ if os.path.exists(info["results_dir"]):
148
+ for file in os.listdir(info["results_dir"]):
149
+ os.remove(os.path.join(info["results_dir"], file))
150
+ os.rmdir(info["results_dir"])
151
+ # now make sure info is not a list
152
+ for key in info:
153
+ if isinstance(info[key], list):
154
+ info[key] = info[key][0]
155
+ move_data_to_folders("DLC", ".csv", info, fixture_extract_folderinfo)
156
+ assert len(os.listdir(info["results_dir"])) == 2
157
+
158
+
159
+ # %%.......................... cfg & string stuff ....................................
160
+ def test_plot_joint_error(
161
+ extract_data_using_some_prep, fixture_extract_cfg, fixture_extract_info
162
+ ):
163
+ fixture_extract_cfg["plot_joint_number"] = 2000
164
+ fixture_extract_cfg["subtract_beam"] = False
165
+ check_and_expand_cfg(
166
+ extract_data_using_some_prep, fixture_extract_cfg, fixture_extract_info
167
+ )
168
+ with open(os.path.join(fixture_extract_info["results_dir"], "Issues.txt")) as f:
169
+ content = f.read()
170
+ assert "we can :)" in content
171
+ fixture_extract_cfg = check_and_expand_cfg(
172
+ extract_data_using_some_prep, fixture_extract_cfg, fixture_extract_info
173
+ )
174
+ assert fixture_extract_cfg["plot_joints"] == fixture_extract_cfg["hind_joints"]
175
+ fixture_extract_cfg["plot_joint_number"] = 2
176
+ fixture_extract_cfg = check_and_expand_cfg(
177
+ extract_data_using_some_prep, fixture_extract_cfg, fixture_extract_info
178
+ )
179
+ assert fixture_extract_cfg["plot_joints"] == fixture_extract_cfg["hind_joints"][:2]
180
+
181
+
182
+ def test_error_if_no_cfgkey_joints(
183
+ fixture_extract_info, fixture_extract_folderinfo, fixture_extract_cfg
184
+ ):
185
+ full_cfg = copy.deepcopy(
186
+ fixture_extract_cfg
187
+ ) # no referencing here - we need copies!
188
+ for cfg_key in [
189
+ "hind_joints",
190
+ "x_standardisation_joint",
191
+ "y_standardisation_joint",
192
+ ]:
193
+ fixture_extract_cfg = copy.deepcopy(full_cfg) # here too!
194
+ fixture_extract_cfg[cfg_key] = ["not_in_data"]
195
+ data = some_prep(
196
+ "DLC", fixture_extract_info, fixture_extract_folderinfo, fixture_extract_cfg
197
+ )
198
+ with open(os.path.join(fixture_extract_info["results_dir"], "Issues.txt")) as f:
199
+ content = f.read()
200
+ if cfg_key == "hind_joints":
201
+ assert "hind limb joint names" in content
202
+ elif cfg_key == "x_standardisation_joint":
203
+ assert "x-coordinate standardisation joint" in content
204
+ elif cfg_key == "y_standardisation_joint":
205
+ assert "y-coordinate standardisation joint" in content
206
+ assert data is None
207
+ # cannot loop case of x & y joints being broken
208
+ fixture_extract_cfg = copy.deepcopy(full_cfg)
209
+ fixture_extract_cfg["x_standardisation_joint"] = ["not_in_data"]
210
+ fixture_extract_cfg["y_standardisation_joint"] = ["not_in_data"]
211
+ data = some_prep(
212
+ "DLC", fixture_extract_info, fixture_extract_folderinfo, fixture_extract_cfg
213
+ )
214
+ with open(os.path.join(fixture_extract_info["results_dir"], "Issues.txt")) as f:
215
+ content = f.read()
216
+ assert "x & y-coordinate standardisation joint" in content
217
+ assert data is None
218
+
219
+
220
+ @given(test_list=st.lists(st.text(), min_size=1))
221
+ @settings(suppress_health_check=[HealthCheck.function_scoped_fixture])
222
+ def test_removal_of_wrong_strings_from_cfg_key(
223
+ test_list, extract_data_using_some_prep, fixture_extract_cfg, fixture_extract_info
224
+ ):
225
+ # the following loop is to account for hypothesis randomly generating strings that
226
+ # actually are data columns (happend for "Knee ")
227
+ for i in range(len(test_list)):
228
+ if test_list[i] + "x" in extract_data_using_some_prep.columns:
229
+ test_list.pop(i)
230
+ cfg_key = "hind_joints" # irrelevant since property testing
231
+ fixture_extract_cfg[cfg_key] = test_list
232
+ test_result = check_and_fix_cfg_strings(
233
+ extract_data_using_some_prep, fixture_extract_cfg, cfg_key, fixture_extract_info
234
+ )
235
+ assert not test_result # empty list is falsey
236
+
237
+
238
+ def test_wrong_data_and_beam_strings(
239
+ fixture_extract_info, fixture_extract_folderinfo, fixture_extract_cfg
240
+ ):
241
+ fixture_extract_folderinfo["beam_string"] = fixture_extract_folderinfo[
242
+ "data_string"
243
+ ]
244
+ some_prep(
245
+ "DLC", fixture_extract_info, fixture_extract_folderinfo, fixture_extract_cfg
246
+ )
247
+ with open(os.path.join(fixture_extract_info["results_dir"], "Issues.txt")) as f:
248
+ content = f.read()
249
+ assert "Your data & baseline (beam) identifiers ([G] in our" in content
250
+
251
+
252
+ def test_wrong_postmouse_string(
253
+ fixture_extract_info, fixture_extract_folderinfo, fixture_extract_cfg
254
+ ):
255
+ fixture_extract_folderinfo["postmouse_string"] = "this_is_a_test"
256
+ some_prep(
257
+ "DLC", fixture_extract_info, fixture_extract_folderinfo, fixture_extract_cfg
258
+ )
259
+ with open(os.path.join(fixture_extract_info["results_dir"], "Issues.txt")) as f:
260
+ content = f.read()
261
+ assert "Unable to identify ANY RELEVANT FILES for" in content
262
+
263
+
264
+ # %%........................... dataframe stuff ......................................
265
+ def test_cols_we_added_to_data(
266
+ fixture_extract_info, fixture_extract_folderinfo, fixture_extract_cfg
267
+ ):
268
+ data = some_prep(
269
+ "DLC", fixture_extract_info, fixture_extract_folderinfo, fixture_extract_cfg
270
+ )
271
+ assert (data.columns[0] == "Time") & (data.columns[1] == "Flipped")
272
+
273
+
274
+ def test_datas_indexing_and_time_column(
275
+ fixture_extract_info, fixture_extract_folderinfo, fixture_extract_cfg
276
+ ):
277
+ for fixture_extract_cfg["sampling_rate"] in [50, 500, 5000]:
278
+ data = some_prep(
279
+ "DLC", fixture_extract_info, fixture_extract_folderinfo, fixture_extract_cfg
280
+ )
281
+ # use isclose here because there are some floating point things going on (eg. 1.
282
+ # 66 and 1.660 for sampling rate of 500)
283
+ assert math.isclose(
284
+ data["Time"].max(),
285
+ (len(data) - 1) / (1 * fixture_extract_cfg["sampling_rate"]),
286
+ rel_tol=1e-9,
287
+ )
288
+
289
+
290
+ # %%........................... data manipulation ....................................
291
+ def test_global_min_standardisation(
292
+ fixture_extract_info, fixture_extract_folderinfo, fixture_extract_cfg
293
+ ):
294
+ fixture_extract_cfg["subtract_beam"] = False
295
+ fixture_extract_cfg["standardise_y_to_a_joint"] = False
296
+ data = some_prep(
297
+ "DLC", fixture_extract_info, fixture_extract_folderinfo, fixture_extract_cfg
298
+ )
299
+ y_cols = [c for c in data.columns if c.endswith(" y")]
300
+ assert data[y_cols].min().min() == 0
301
+ # approach here is find difference between global & standardisation joint minma and
302
+ # see if all y cols' difference is equal to that
303
+ # => this implies that joint-based y-standardisation worked
304
+ global_and_standardisation_joints_y_min_diff = data[
305
+ fixture_extract_cfg["y_standardisation_joint"][0] + " y"
306
+ ].min()
307
+ global_min_data = data.copy()
308
+ fixture_extract_cfg["standardise_y_to_a_joint"] = True
309
+ data = some_prep(
310
+ "DLC", fixture_extract_info, fixture_extract_folderinfo, fixture_extract_cfg
311
+ )
312
+ assert np.allclose( # use np.allclose here because we are comparing arrays
313
+ global_min_data[y_cols],
314
+ data[y_cols] + global_and_standardisation_joints_y_min_diff,
315
+ atol=1e-9,
316
+ )
317
+
318
+
319
+ def test_flip_mouse_body(
320
+ fixture_extract_info, fixture_extract_folderinfo, fixture_extract_cfg
321
+ ):
322
+ fixture_extract_cfg["flip_gait_direction"] = False
323
+ test_data = some_prep(
324
+ "DLC", fixture_extract_info, fixture_extract_folderinfo, fixture_extract_cfg
325
+ )
326
+ function_flipped_data = test_data.copy()
327
+ function_flipped_data = flip_mouse_body(test_data, fixture_extract_info)
328
+ x_cols = [col for col in function_flipped_data.columns if col.endswith(" x")]
329
+ global_x_max = max(test_data[x_cols].max())
330
+ for col in x_cols:
331
+ function_flipped_series = function_flipped_data[col]
332
+ function_flipped_series = function_flipped_series.astype(float)
333
+ manually_flipped_series = global_x_max - test_data[col]
334
+ pdt.assert_series_equal(function_flipped_series, manually_flipped_series)
@@ -0,0 +1,134 @@
1
+ import pytest
2
+ from autogaita.common2D.common2D_utils import extract_info, find_number
3
+ import os
4
+ import pytest
5
+
6
+
7
+ # %%.............................. fixtures ..........................................
8
+ # => CALL FIXTURES _fixture SINCE WE HAVE A FUNCTION CALLED extract_info!
9
+ @pytest.fixture
10
+ def fixture_extract_info(tmp_path):
11
+ info = {}
12
+ info["mouse_num"] = 15
13
+ info["run_num"] = 3
14
+ info["name"] = "ID " + str(info["mouse_num"]) + " - Run " + str(info["run_num"])
15
+ info["results_dir"] = os.path.join(tmp_path, info["name"])
16
+ return info
17
+
18
+
19
+ @pytest.fixture
20
+ def fixture_extract_folderinfo():
21
+ folderinfo = {}
22
+ folderinfo["root_dir"] = "tests/test_data/dlc_data"
23
+ folderinfo["sctable_filename"] = (
24
+ "correct_annotation_table.xlsx" # has to be an excel file
25
+ )
26
+ folderinfo["data_string"] = "SIMINewOct"
27
+ folderinfo["beam_string"] = "BeamTraining"
28
+ folderinfo["premouse_string"] = "Mouse"
29
+ folderinfo["postmouse_string"] = "_25mm"
30
+ folderinfo["prerun_string"] = "run"
31
+ folderinfo["postrun_string"] = "-6DLC"
32
+ return folderinfo
33
+
34
+
35
+ @pytest.fixture
36
+ def fixture_extract_cfg():
37
+ cfg = {}
38
+ cfg["sampling_rate"] = 100
39
+ cfg["subtract_beam"] = True
40
+ cfg["dont_show_plots"] = True
41
+ cfg["convert_to_mm"] = True
42
+ cfg["pixel_to_mm_ratio"] = 3.76
43
+ cfg["x_sc_broken_threshold"] = 200
44
+ cfg["y_sc_broken_threshold"] = 50
45
+ cfg["x_acceleration"] = True
46
+ cfg["angular_acceleration"] = True
47
+ cfg["save_to_xls"] = True
48
+ cfg["bin_num"] = 25
49
+ cfg["plot_SE"] = True
50
+ cfg["standardise_y_at_SC_level"] = False
51
+ cfg["standardise_y_to_a_joint"] = True
52
+ cfg["y_standardisation_joint"] = ["Knee"]
53
+ cfg["plot_joint_number"] = 3
54
+ cfg["color_palette"] = "viridis"
55
+ cfg["legend_outside"] = True
56
+ cfg["invert_y_axis"] = True
57
+ cfg["flip_gait_direction"] = True
58
+ cfg["analyse_average_x"] = True
59
+ cfg["standardise_x_coordinates"] = True
60
+ cfg["x_standardisation_joint"] = ["Hind paw tao"]
61
+ cfg["coordinate_standardisation_xls"] = ""
62
+ cfg["hind_joints"] = ["Hind paw tao", "Ankle", "Knee", "Hip", "Iliac Crest"]
63
+ cfg["fore_joints"] = [
64
+ "Front paw tao ",
65
+ "Wrist ",
66
+ "Elbow ",
67
+ "Lower Shoulder ",
68
+ "Upper Shoulder ",
69
+ ]
70
+ cfg["beam_col_left"] = ["BeamLeft"] # BEAM_COL_LEFT & _RIGHT must be lists of len=1
71
+ cfg["beam_col_right"] = ["BeamRight"]
72
+ cfg["beam_hind_jointadd"] = ["Tail base ", "Tail center ", "Tail tip "]
73
+ cfg["beam_fore_jointadd"] = ["Nose ", "Ear base "]
74
+ cfg["angles"] = {
75
+ "name": ["Ankle ", "Knee ", "Hip "],
76
+ "lower_joint": ["Hind paw tao ", "Ankle ", "Knee "],
77
+ "upper_joint": ["Knee ", "Hip ", "Iliac Crest "],
78
+ }
79
+ return cfg
80
+
81
+
82
+ # %%.............................. tests ..........................................
83
+ # 1) Test extract_info function
84
+ def test_extract_info_function(fixture_extract_folderinfo):
85
+ # => IT IS CORRECT THAT EXTRACT_INFO RETURNS lists of integers!
86
+ # a. smoke test standard case
87
+ info = extract_info("DLC", fixture_extract_folderinfo)
88
+ assert info["mouse_num"] == [15]
89
+ assert info["run_num"] == [3]
90
+ # b. test that underscores/hyphens are added
91
+ folderinfo = fixture_extract_folderinfo
92
+ folderinfo["postmouse_string"] = "25mm"
93
+ folderinfo["postrun_string"] = "6DLC"
94
+ info = extract_info("DLC", folderinfo)
95
+ assert info["mouse_num"] == [15]
96
+ assert info["run_num"] == [3]
97
+ # c. test that leading zeros are removed
98
+ folderinfo["root_dir"] = "tests/test_data/dlc_data/test_data/leading_zeros/"
99
+ info = extract_info("DLC", folderinfo)
100
+ assert info["mouse_num"] == [12]
101
+ assert info["run_num"] == [3]
102
+
103
+
104
+ # 2) Test find number function - also for leading zeros
105
+ def test_find_number_valid_case():
106
+ fullstring = "Mouse1_Run002"
107
+ prestring = "Mouse"
108
+ poststring = "_Run"
109
+ result = find_number(fullstring, prestring, poststring)
110
+ assert result == (1, "")
111
+
112
+
113
+ def test_find_number_prestring_not_found():
114
+ fullstring = "Mouse001_Run002"
115
+ prestring = "Cat"
116
+ poststring = "_Run"
117
+ with pytest.raises(ValueError):
118
+ find_number(fullstring, prestring, poststring)
119
+
120
+
121
+ def test_find_number_poststring_not_found():
122
+ fullstring = "Mouse001_Run002"
123
+ prestring = "Mouse"
124
+ poststring = "_Walk"
125
+ with pytest.raises(ValueError):
126
+ find_number(fullstring, prestring, poststring)
127
+
128
+
129
+ def test_find_number_leading_zeros():
130
+ fullstring = "Mouse001Run002"
131
+ prestring = "Mouse"
132
+ poststring = "Run"
133
+ result = find_number(fullstring, prestring, poststring)
134
+ assert result == (1, "00")