autogaita 1.5.2__py3-none-any.whl → 1.5.4__py3-none-any.whl

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.
@@ -0,0 +1,167 @@
1
+ # %% imports
2
+ from autogaita.resources.utils import write_issues_to_textfile
3
+ from autogaita.common2D.common2D_utils import (
4
+ check_cycle_out_of_bounds,
5
+ check_cycle_duplicates,
6
+ check_cycle_order,
7
+ check_tracking,
8
+ handle_issues,
9
+ )
10
+ import os
11
+ import pandas as pd
12
+ import numpy as np
13
+
14
+ # %% constants
15
+ from autogaita.common2D.common2D_constants import (
16
+ SCXLS_MOUSECOLS,
17
+ SCXLS_SCCOLS,
18
+ SWINGSTART_COL,
19
+ STANCEEND_COL,
20
+ )
21
+
22
+ # %% workflow step #2 - SC extraction (reading user-provided SC Table)
23
+
24
+
25
+ def extract_stepcycles(data, info, folderinfo, cfg):
26
+ """Read XLS file with SC annotations, find correct row & return all_cycles"""
27
+
28
+ # ............................... preparation ....................................
29
+ # unpack
30
+ name = info["name"]
31
+ root_dir = folderinfo["root_dir"]
32
+ sctable_filename = folderinfo["sctable_filename"]
33
+ sampling_rate = cfg["sampling_rate"]
34
+
35
+ # check if excel file is .xlsx or .xls, if none found try to fix it
36
+ if (".xls" in sctable_filename) | (".xlsx" in sctable_filename):
37
+ if os.path.exists(os.path.join(root_dir, sctable_filename)):
38
+ SCdf = pd.read_excel(os.path.join(root_dir, sctable_filename))
39
+ else:
40
+ raise FileNotFoundError(
41
+ "No Annotation Table found! sctable_filename has to be @ root_dir"
42
+ )
43
+ else:
44
+ # in cases below use string-concat (+) - otherwise xls added as path
45
+ if os.path.exists(os.path.join(root_dir, sctable_filename + ".xls")):
46
+ SCdf = pd.read_excel(os.path.join(root_dir, sctable_filename + ".xls"))
47
+ elif os.path.exists(os.path.join(root_dir, sctable_filename + ".xlsx")):
48
+ SCdf = pd.read_excel(os.path.join(root_dir, sctable_filename + ".xlsx"))
49
+ else:
50
+ raise FileNotFoundError(
51
+ "No Annotation Table found! sctable_filename has to be @ root_dir"
52
+ )
53
+ # see if table columns are labelled correctly (try a couple to allow user typos)
54
+ valid_col_flags = [False, False]
55
+ header_columns = ["", ""]
56
+ for h, header in enumerate([SCXLS_MOUSECOLS, SCXLS_SCCOLS]):
57
+ for header_col in header:
58
+ if header_col in SCdf.columns:
59
+ valid_col_flags[h] = True
60
+ header_columns[h] = header_col
61
+ break
62
+ if not all(valid_col_flags):
63
+ handle_issues("wrong_scxls_colnames", info)
64
+ return
65
+ # find our info columns & rows
66
+ mouse_col = SCdf.columns.get_loc(header_columns[0]) # INDEXING! (see list above)
67
+ sc_col = SCdf.columns.get_loc(header_columns[1])
68
+ SCdf[header_columns[0]] = SCdf[header_columns[0]].astype(str)
69
+ mouse_row = SCdf.index[SCdf[header_columns[0]] == name] # find this mouse
70
+ # this mouse was not included in sc xls
71
+ if len(mouse_row) == 0:
72
+ handle_issues("no_mouse", info)
73
+ return
74
+ # this mouse was included more than once
75
+ if len(mouse_row) > 1:
76
+ handle_issues("double_mouse", info)
77
+ return
78
+
79
+ # next_mouse_idx = mouse_row # search idx of first row of next mouse
80
+
81
+ # .............................. main xls read ...................................
82
+ sc_num = 0
83
+ for column in SCdf.columns:
84
+ if STANCEEND_COL in column:
85
+ if np.isnan(SCdf[column][mouse_row].values[0]) == False:
86
+ sc_num += 1
87
+ if sc_num == 0:
88
+ handle_issues("no_scs", info)
89
+ return
90
+ user_scnum = SCdf.iloc[mouse_row, sc_col].values[0] # sanity check input
91
+ if user_scnum != sc_num: # warn the user, take the values we found
92
+ this_message = (
93
+ "\n***********\n! WARNING !\n***********\n"
94
+ + "Mismatch between stepcycle number of SC Number column & "
95
+ + "entries in swing/stance latency columns!"
96
+ + "\nUsing all valid swing/stance entries."
97
+ )
98
+ print(this_message)
99
+ write_issues_to_textfile(this_message, info)
100
+
101
+ # ........................... idxs to all_cycles .................................
102
+ # use value we found, loop over all runs, throw all scs into all_cycles
103
+ all_cycles = [[None, None] for s in range(sc_num)] # fill :sc_num x 2 list
104
+ for s in range(sc_num):
105
+ if s == 0:
106
+ start_col = SCdf.columns.get_loc(SWINGSTART_COL)
107
+ end_col = SCdf.columns.get_loc(STANCEEND_COL)
108
+ else:
109
+ # str(s) because colnames match s for s>0!
110
+ start_col = SCdf.columns.get_loc(SWINGSTART_COL + "." + str(s))
111
+ end_col = SCdf.columns.get_loc(STANCEEND_COL + "." + str(s))
112
+ user_scnum += 1
113
+ # extract the SC times
114
+ start_in_s = float(SCdf.iloc[mouse_row, start_col].values[0])
115
+ end_in_s = float(SCdf.iloc[mouse_row, end_col].values[0])
116
+ # see if we are rounding to fix inaccurate user input
117
+ # => account for python's float precision leading to inaccuracies
118
+ # => two important steps here (sanity_check_vals only used for these checks)
119
+ # 1. round to 10th decimal to fix python making
120
+ # 3211.999999999999999995 out of 3212
121
+ sanity_check_start = round(start_in_s * sampling_rate, 10)
122
+ sanity_check_end = round(end_in_s * sampling_rate, 10)
123
+ # 2. comparing abs(sanity check vals) to 1e-7 just to be 1000% sure
124
+ if (abs(sanity_check_start % 1) > 1e-7) | (abs(sanity_check_end % 1) > 1e-7):
125
+ round_message = (
126
+ "\n***********\n! WARNING !\n***********\n"
127
+ + "SC latencies of "
128
+ + str(start_in_s)
129
+ + "s to "
130
+ + str(end_in_s)
131
+ + "s were not provided in units of the frame rate!"
132
+ + "\nWe thus use the previous possible frame(s)."
133
+ + "\nDouble check if this worked as expected or fix annotation table!"
134
+ )
135
+ print(round_message)
136
+ write_issues_to_textfile(round_message, info)
137
+ # assign to all_cycles (note int() rounds down!)
138
+ all_cycles[s][0] = int(start_in_s * sampling_rate)
139
+ all_cycles[s][1] = int(end_in_s * sampling_rate)
140
+ # check if we are in data-bounds
141
+ if (all_cycles[s][0] in data.index) & (all_cycles[s][1] in data.index):
142
+ pass
143
+ else:
144
+ all_cycles[s] = [None, None] # so they can be cleaned later
145
+ this_message = (
146
+ "\n***********\n! WARNING !\n***********\n"
147
+ + "SC latencies of: "
148
+ + str(start_in_s)
149
+ + "s to "
150
+ + str(end_in_s)
151
+ + "s not in data/video range!"
152
+ + "\nSkipping!"
153
+ )
154
+ print(this_message)
155
+ write_issues_to_textfile(this_message, info)
156
+
157
+ # ............................ clean all_cycles ..................................
158
+ # check if we skipped latencies because they were out of data-bounds
159
+ all_cycles = check_cycle_out_of_bounds(all_cycles)
160
+ if all_cycles: # can be None if all SCs were out of bounds
161
+ # check if there are any duplicates (e.g., SC2's start-lat == SC1's end-lat)
162
+ all_cycles = check_cycle_duplicates(all_cycles)
163
+ # check if user input progressively later latencies
164
+ all_cycles = check_cycle_order(all_cycles, info)
165
+ # check if tracking broke for any SCs - if so remove them
166
+ all_cycles = check_tracking(data, info, all_cycles, cfg)
167
+ return all_cycles
@@ -0,0 +1,359 @@
1
+ # %% imports
2
+ import autogaita.gui.gaita_widgets as gaita_widgets
3
+ import autogaita.gui.gui_utils as gui_utils
4
+ from autogaita.gui.common2D_columninfo_gui import build_column_info_window
5
+ from autogaita.gui.common2D_advanced_config_gui import build_cfg_window
6
+ from autogaita.gui.common2D_run_and_done_gui import build_run_and_done_windows
7
+ from autogaita.gui.first_level_gui_utils import (
8
+ update_config_file,
9
+ extract_cfg_from_json_file,
10
+ )
11
+
12
+ import tkinter as tk
13
+ import customtkinter as ctk
14
+ import os
15
+ import platform
16
+
17
+
18
+ # %% global constants
19
+ from autogaita.gui.gui_constants import (
20
+ SLEAP_FG_COLOR,
21
+ SLEAP_HOVER_COLOR,
22
+ TEXT_FONT_NAME,
23
+ TEXT_FONT_SIZE,
24
+ WINDOWS_TASKBAR_MAXHEIGHT,
25
+ AUTOGAITA_FOLDER_PATH,
26
+ get_widget_cfg_dict, # function!
27
+ )
28
+
29
+ # these colors are GUI-specific - add to common widget cfg
30
+ FG_COLOR = SLEAP_FG_COLOR
31
+ HOVER_COLOR = SLEAP_HOVER_COLOR
32
+ WIDGET_CFG = get_widget_cfg_dict()
33
+ WIDGET_CFG["FG_COLOR"] = FG_COLOR
34
+ WIDGET_CFG["HOVER_COLOR"] = HOVER_COLOR
35
+
36
+ CONFIG_FILE_NAME = "sleap_gui_config.json"
37
+ FLOAT_VARS = ["pixel_to_mm_ratio"]
38
+ INT_VARS = [
39
+ "sampling_rate",
40
+ "x_sc_broken_threshold",
41
+ "y_sc_broken_threshold",
42
+ "bin_num",
43
+ # "mouse_num",
44
+ # "run_num",
45
+ "plot_joint_number",
46
+ ]
47
+ LIST_VARS = [
48
+ "hind_joints",
49
+ "fore_joints",
50
+ "x_standardisation_joint",
51
+ "y_standardisation_joint",
52
+ "beam_hind_jointadd",
53
+ "beam_fore_jointadd",
54
+ "beam_col_left",
55
+ "beam_col_right",
56
+ ]
57
+ DICT_VARS = ["angles"]
58
+ # TK_BOOL/STR_VARS are only used for initialising widgets based on cfg file
59
+ # (note that numbers are initialised as strings)
60
+ TK_BOOL_VARS = [
61
+ "subtract_beam",
62
+ "dont_show_plots",
63
+ "convert_to_mm",
64
+ "x_acceleration",
65
+ "angular_acceleration",
66
+ "save_to_xls",
67
+ "plot_SE",
68
+ "standardise_y_at_SC_level",
69
+ "standardise_y_to_a_joint",
70
+ "standardise_x_coordinates",
71
+ # "invert_y_axis",
72
+ "flip_gait_direction",
73
+ "analyse_average_x",
74
+ "legend_outside",
75
+ ]
76
+ TK_STR_VARS = [
77
+ "name",
78
+ "root_dir",
79
+ "results_dir",
80
+ "sctable_filename",
81
+ "data_string",
82
+ "beam_string",
83
+ "sampling_rate",
84
+ "pixel_to_mm_ratio",
85
+ "x_sc_broken_threshold",
86
+ "y_sc_broken_threshold",
87
+ "bin_num",
88
+ "plot_joint_number",
89
+ "color_palette",
90
+ ]
91
+ GUI_SPECIFIC_VARS = {
92
+ "CONFIG_FILE_NAME": CONFIG_FILE_NAME,
93
+ "FLOAT_VARS": FLOAT_VARS,
94
+ "INT_VARS": INT_VARS,
95
+ "LIST_VARS": LIST_VARS,
96
+ "DICT_VARS": DICT_VARS,
97
+ "TK_BOOL_VARS": TK_BOOL_VARS,
98
+ "TK_STR_VARS": TK_STR_VARS,
99
+ }
100
+
101
+ # %% An important Note
102
+ # I am using a global variable called cfg because I need its info to be shared
103
+ # between root and columnconfiguration window. This is not the object-oriented way
104
+ # that one would do this typically. However, it works as expected since:
105
+ # 1) cfg's values are only ever modified @ initialisation & by widgets
106
+ # 2) cfg's values are shared for analysis of a single and multiple video(s)
107
+ # 3) cfg's values are passed to all functions that need them
108
+ # 4) and (IMPORTANTLY!) just before running either (i.e. single/multi) analysis, cfg's
109
+ # and result's values are unpacked and assigned to "this_" dicts that are passed
110
+ # to the runanalysis local function. Hence, from that point onwards, only
111
+ # "this_" dicts are used, never cfg or result dicts themselves.
112
+ # ==> see donewindow for point 4)
113
+
114
+ # %%............................ MAIN PROGRAM ................................
115
+
116
+
117
+ def run_sleap_gui():
118
+ # ..........................................................................
119
+ # ...................... root window initialisation .......................
120
+ # ..........................................................................
121
+ # Check for config file
122
+ config_file_path = os.path.join(AUTOGAITA_FOLDER_PATH, CONFIG_FILE_NAME)
123
+ if not os.path.isfile(config_file_path):
124
+ config_file_error_msg = (
125
+ "sleap_gui_config.json file not found in autogaita folder.\n"
126
+ "Confirm that the file exists and is named correctly.\n"
127
+ "If not, download it again from the GitHub repository."
128
+ )
129
+ tk.messagebox.showerror(
130
+ title="Config File Error", message=config_file_error_msg
131
+ )
132
+ exit()
133
+
134
+ # CustomTkinter vars
135
+ ctk.set_appearance_mode("dark") # Modes: system (default), light, dark
136
+ ctk.set_default_color_theme("green") # Themes: blue , dark-blue, green
137
+ # root
138
+ root = ctk.CTk()
139
+ # make window pretty
140
+ screen_width = root.winfo_screenwidth() # width of the screen
141
+ screen_height = root.winfo_screenheight() # height of the screen
142
+ if platform.system() == "Windows": # adjust for taskbar in windows only
143
+ screen_height -= WINDOWS_TASKBAR_MAXHEIGHT
144
+ # create root dimensions (based on fullscreen) to pass to other window-functions l8r
145
+ w, h, x, y = screen_width, screen_height, 0, 0
146
+ root_dimensions = (w, h, x, y)
147
+ # set the dimensions of the screen and where it is placed
148
+ # => have it half-wide starting at 1/4 of screen's width (dont change w & x!)
149
+ root.geometry(f"{int(screen_width / 2)}x{screen_height}+{int(screen_width / 4)}+0")
150
+ root.title("SLEAP GaitA")
151
+ gui_utils.fix_window_after_its_creation(root)
152
+ gui_utils.configure_the_icon(root)
153
+
154
+ # ..................... load cfg dict from config .....................
155
+ # use the values in the config json file for the results dictionary
156
+ global cfg
157
+ cfg = extract_cfg_from_json_file(
158
+ root,
159
+ AUTOGAITA_FOLDER_PATH,
160
+ CONFIG_FILE_NAME,
161
+ LIST_VARS,
162
+ DICT_VARS,
163
+ TK_STR_VARS,
164
+ TK_BOOL_VARS,
165
+ )
166
+
167
+ # ............................... header ..........................................
168
+ # main configuration header
169
+ main_cfg_header_label = gaita_widgets.header_label(
170
+ root,
171
+ "Main Configuration",
172
+ WIDGET_CFG,
173
+ )
174
+ main_cfg_header_label.grid(row=0, column=0, columnspan=3, sticky="nsew")
175
+
176
+ # ............................ main cfg section ..................................
177
+ # sampling rate
178
+ samprate_label, samprate_entry = gaita_widgets.label_and_entry_pair(
179
+ root,
180
+ "Sampling rate of videos in Hertz (frames/second):",
181
+ cfg["sampling_rate"],
182
+ WIDGET_CFG,
183
+ )
184
+ samprate_label.grid(row=1, column=0, columnspan=2, sticky="w")
185
+ samprate_entry.grid(row=1, column=2, sticky="w")
186
+
187
+ # convert pixel to mm - checkbox
188
+ convert_checkbox = gaita_widgets.checkbox(
189
+ root,
190
+ "Convert pixels to millimetres:",
191
+ cfg["convert_to_mm"],
192
+ WIDGET_CFG,
193
+ )
194
+ convert_checkbox.configure(
195
+ command=lambda: gui_utils.change_widget_state_based_on_checkbox(
196
+ cfg, "convert_to_mm", ratio_entry
197
+ ),
198
+ )
199
+ convert_checkbox.grid(row=2, column=0, columnspan=2, sticky="w")
200
+
201
+ # ratio label
202
+ ratio_entry = ctk.CTkEntry(
203
+ root,
204
+ textvariable=cfg["pixel_to_mm_ratio"],
205
+ font=(TEXT_FONT_NAME, TEXT_FONT_SIZE),
206
+ )
207
+ ratio_entry.grid(row=2, column=1, sticky="e")
208
+ ratio_right_string = "pixels = 1 mm"
209
+ ratio_right_label = ctk.CTkLabel(
210
+ root, text=ratio_right_string, font=(TEXT_FONT_NAME, TEXT_FONT_SIZE)
211
+ )
212
+ ratio_right_label.grid(row=2, column=2, sticky="w")
213
+ # to initialise the widget correctly, run this function once
214
+ gui_utils.change_widget_state_based_on_checkbox(cfg, "convert_to_mm", ratio_entry)
215
+
216
+ # NU - I'll implement beam subtraction and gait direction flipping after having
217
+ # data for testing
218
+
219
+ # subtract beam
220
+ subtract_beam_checkbox = gaita_widgets.checkbox(
221
+ root,
222
+ "(not supported yet) - Standardise y-coordinates to baseline height",
223
+ # "Standardise y-coordinates to baseline height (requires to be tracked),"
224
+ cfg["subtract_beam"],
225
+ WIDGET_CFG,
226
+ )
227
+ subtract_beam_checkbox.grid(row=3, column=0, columnspan=3, sticky="w")
228
+ subtract_beam_checkbox.configure(state="disabled")
229
+
230
+ # flip gait direction
231
+ flip_gait_direction_box = gaita_widgets.checkbox(
232
+ root,
233
+ "(not supported yet) - Adjust x-coordinates to follow direction of movement",
234
+ cfg["flip_gait_direction"],
235
+ WIDGET_CFG,
236
+ )
237
+ flip_gait_direction_box.grid(row=4, column=0, columnspan=3, sticky="w")
238
+ flip_gait_direction_box.configure(state="disabled")
239
+
240
+ # plot plots to python
241
+ showplots_checkbox = gaita_widgets.checkbox(
242
+ root,
243
+ "Don't show plots in Figure GUI (save only)",
244
+ cfg["dont_show_plots"],
245
+ WIDGET_CFG,
246
+ )
247
+ showplots_checkbox.grid(row=5, column=0, columnspan=2, sticky="w")
248
+
249
+ # bin number of SC normalisation
250
+ bin_num_label, bin_num_entry = gaita_widgets.label_and_entry_pair(
251
+ root,
252
+ "Number of bins used to normalise the step cycle:",
253
+ cfg["bin_num"],
254
+ WIDGET_CFG,
255
+ )
256
+ bin_num_label.grid(row=6, column=0, columnspan=2, sticky="w")
257
+ bin_num_entry.grid(row=6, column=2, sticky="w")
258
+
259
+ # empty label 1 (for spacing)
260
+ empty_label_one = ctk.CTkLabel(root, text="")
261
+ empty_label_one.grid(row=7, column=0)
262
+
263
+ # .......................... advanced cfg section ................................
264
+ # advanced header string
265
+ advanced_cfg_header_label = gaita_widgets.header_label(
266
+ root,
267
+ "Advanced Configuration",
268
+ WIDGET_CFG,
269
+ )
270
+ advanced_cfg_header_label.grid(row=8, column=0, columnspan=3, sticky="nsew")
271
+
272
+ # column name information window
273
+ column_info_button = gaita_widgets.header_button(
274
+ root, "Customise Joints & Angles", WIDGET_CFG
275
+ )
276
+ column_info_button.configure(
277
+ command=lambda: build_column_info_window(root, cfg, WIDGET_CFG, root_dimensions)
278
+ )
279
+ column_info_button.grid(row=9, column=0, columnspan=3)
280
+
281
+ # advanced cfg
282
+ cfg_window_button = gaita_widgets.header_button(
283
+ root, "Advanced Configuration", WIDGET_CFG
284
+ )
285
+ cfg_window_button.configure(
286
+ command=lambda: build_cfg_window(root, cfg, WIDGET_CFG, root_dimensions)
287
+ )
288
+ cfg_window_button.grid(row=10, column=0, columnspan=3)
289
+
290
+ # empty label 2 (for spacing)
291
+ empty_label_two = ctk.CTkLabel(root, text="")
292
+ empty_label_two.grid(row=11, column=0)
293
+
294
+ # run analysis label
295
+ runheader_label = gaita_widgets.header_label(root, "Run Analysis", WIDGET_CFG)
296
+ runheader_label.grid(row=12, column=0, columnspan=3, sticky="nsew")
297
+
298
+ # single gaita button
299
+ onevid_button = gaita_widgets.header_button(root, "One Video", WIDGET_CFG)
300
+ onevid_button.configure(
301
+ command=lambda: build_run_and_done_windows(
302
+ "SLEAP", "single", root, cfg, WIDGET_CFG, GUI_SPECIFIC_VARS, root_dimensions
303
+ )
304
+ )
305
+ onevid_button.grid(row=13, column=1, sticky="ew")
306
+
307
+ # multi gaita button
308
+ multivid_button = gaita_widgets.header_button(root, "Batch Analysis", WIDGET_CFG)
309
+ multivid_button.configure(
310
+ command=lambda: build_run_and_done_windows(
311
+ "SLEAP", "multi", root, cfg, WIDGET_CFG, GUI_SPECIFIC_VARS, root_dimensions
312
+ )
313
+ )
314
+ multivid_button.grid(row=14, column=1, sticky="ew")
315
+
316
+ # empty label 2 (for spacing)
317
+ empty_label_two = ctk.CTkLabel(root, text="")
318
+ empty_label_two.grid(row=15, column=0)
319
+
320
+ # close & exit button
321
+ exit_button = gaita_widgets.exit_button(root, WIDGET_CFG)
322
+ exit_button.configure(
323
+ command=lambda: (
324
+ # results variable is only defined later in populate_run_window()
325
+ # therefore only cfg settings will be updated
326
+ update_config_file(
327
+ "results dict not defined yet",
328
+ cfg,
329
+ AUTOGAITA_FOLDER_PATH,
330
+ CONFIG_FILE_NAME,
331
+ LIST_VARS,
332
+ DICT_VARS,
333
+ TK_STR_VARS,
334
+ TK_BOOL_VARS,
335
+ ),
336
+ root.withdraw(),
337
+ root.after(5000, root.destroy),
338
+ ),
339
+ )
340
+ exit_button.grid(row=16, column=0, columnspan=3)
341
+
342
+ # # ......................... widget configuration ...............................
343
+
344
+ # first maximise everything according to sticky
345
+ # => Silent_Creme is some undocumented option that makes stuff uniform
346
+ # see: https://stackoverflow.com/questions/45847313/tkinter-grid-rowconfigure-weight-doesnt-work
347
+ root.columnconfigure(list(range(3)), weight=1, uniform="Silent_Creme")
348
+ root.rowconfigure(list(range(17)), weight=1, uniform="Silent_Creme")
349
+
350
+ # then un-maximise main config rows to have them grouped together
351
+ root.rowconfigure(list(range(1, 7)), weight=0)
352
+
353
+ # main loop
354
+ root.mainloop()
355
+
356
+
357
+ # %% what happens if we hit run
358
+ if __name__ == "__main__":
359
+ run_sleap_gui()
@@ -1,40 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: autogaita
3
- Version: 1.5.2
4
- Summary: Automatic Gait Analysis in Python.
5
- Home-page: https://github.com/mahan-hosseini/AutoGaitA/
6
- Author: Mahan Hosseini
7
- License: GPLv3
8
- Requires-Python: >=3.10
9
- Description-Content-Type: text/plain
10
- License-File: LICENSE
11
- Requires-Dist: customtkinter>=5.2
12
- Requires-Dist: pandas<3.0,>=2.0
13
- Requires-Dist: numpy>=1.24
14
- Requires-Dist: seaborn>=0.13
15
- Requires-Dist: matplotlib>=3.7
16
- Requires-Dist: scikit-learn>=1.2
17
- Requires-Dist: pingouin>=0.5
18
- Requires-Dist: scipy>=1.11
19
- Requires-Dist: scikit-bio>=0.7.1
20
- Requires-Dist: statsmodels>=0.14
21
- Requires-Dist: ffmpeg-python>=0.2
22
- Requires-Dist: openpyxl>=3.1
23
- Requires-Dist: pillow>=10.3
24
- Requires-Dist: h5py>=3.11
25
- Requires-Dist: pyobjc; sys_platform == "darwin"
26
- Provides-Extra: dev
27
- Requires-Dist: pytest; extra == "dev"
28
- Requires-Dist: hypothesis; extra == "dev"
29
- Dynamic: author
30
- Dynamic: description
31
- Dynamic: description-content-type
32
- Dynamic: home-page
33
- Dynamic: license
34
- Dynamic: license-file
35
- Dynamic: provides-extra
36
- Dynamic: requires-dist
37
- Dynamic: requires-python
38
- Dynamic: summary
39
-
40
- 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.
@@ -1,2 +0,0 @@
1
- autogaita
2
- tests
tests/__init__.py DELETED
File without changes