autogaita 0.3.1__tar.gz → 0.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- autogaita-0.4.0/PKG-INFO +24 -0
- {autogaita-0.3.1 → autogaita-0.4.0}/autogaita/autogaita_dlc.py +225 -78
- {autogaita-0.3.1 → autogaita-0.4.0}/autogaita/autogaita_dlc_gui.py +674 -330
- {autogaita-0.3.1 → autogaita-0.4.0}/autogaita/autogaita_group.py +313 -153
- {autogaita-0.3.1 → autogaita-0.4.0}/autogaita/autogaita_group_gui.py +221 -150
- {autogaita-0.3.1 → autogaita-0.4.0}/autogaita/autogaita_simi.py +300 -158
- {autogaita-0.3.1 → autogaita-0.4.0}/autogaita/autogaita_simi_gui.py +388 -230
- {autogaita-0.3.1 → autogaita-0.4.0}/autogaita/batchrun_scripts/autogaita_dlc_multirun.py +5 -1
- {autogaita-0.3.1 → autogaita-0.4.0}/autogaita/batchrun_scripts/autogaita_dlc_singlerun.py +5 -1
- autogaita-0.4.0/autogaita/batchrun_scripts/autogaita_group_dlcrun.py +88 -0
- {autogaita-0.3.1 → autogaita-0.4.0}/autogaita/batchrun_scripts/autogaita_group_simirun.py +17 -12
- {autogaita-0.3.1 → autogaita-0.4.0}/autogaita/batchrun_scripts/autogaita_simi_multirun.py +2 -2
- {autogaita-0.3.1 → autogaita-0.4.0}/autogaita/dlc_gui_config.json +24 -16
- autogaita-0.4.0/autogaita/group_gui_config.json +37 -0
- {autogaita-0.3.1 → autogaita-0.4.0}/autogaita/simi_gui_config.json +7 -7
- autogaita-0.4.0/autogaita.egg-info/PKG-INFO +24 -0
- {autogaita-0.3.1 → autogaita-0.4.0}/setup.py +2 -2
- {autogaita-0.3.1 → autogaita-0.4.0}/tests/test_dlc_approval.py +6 -2
- {autogaita-0.3.1 → autogaita-0.4.0}/tests/test_dlc_unit1_preparation.py +61 -10
- {autogaita-0.3.1 → autogaita-0.4.0}/tests/test_dlc_unit2_sc_extraction.py +31 -7
- {autogaita-0.3.1 → autogaita-0.4.0}/tests/test_dlc_unit3_main_analysis.py +37 -13
- autogaita-0.3.1/PKG-INFO +0 -10
- autogaita-0.3.1/autogaita/batchrun_scripts/autogaita_group_dlcrun.py +0 -56
- autogaita-0.3.1/autogaita/group_gui_config.json +0 -40
- autogaita-0.3.1/autogaita.egg-info/PKG-INFO +0 -10
- {autogaita-0.3.1 → autogaita-0.4.0}/LICENSE +0 -0
- {autogaita-0.3.1 → autogaita-0.4.0}/README.md +0 -0
- {autogaita-0.3.1 → autogaita-0.4.0}/autogaita/__init__.py +0 -0
- {autogaita-0.3.1 → autogaita-0.4.0}/autogaita/__main__.py +0 -0
- {autogaita-0.3.1 → autogaita-0.4.0}/autogaita/autogaita.py +0 -0
- {autogaita-0.3.1 → autogaita-0.4.0}/autogaita/autogaita_icon.icns +0 -0
- {autogaita-0.3.1 → autogaita-0.4.0}/autogaita/autogaita_icon.ico +0 -0
- {autogaita-0.3.1 → autogaita-0.4.0}/autogaita/autogaita_logo.png +0 -0
- {autogaita-0.3.1 → autogaita-0.4.0}/autogaita/autogaita_utils.py +0 -0
- {autogaita-0.3.1 → autogaita-0.4.0}/autogaita/batchrun_scripts/__init__.py +0 -0
- {autogaita-0.3.1 → autogaita-0.4.0}/autogaita/batchrun_scripts/autogaita_simi_singlerun.py +0 -0
- {autogaita-0.3.1 → autogaita-0.4.0}/autogaita.egg-info/SOURCES.txt +0 -0
- {autogaita-0.3.1 → autogaita-0.4.0}/autogaita.egg-info/dependency_links.txt +0 -0
- {autogaita-0.3.1 → autogaita-0.4.0}/autogaita.egg-info/requires.txt +0 -0
- {autogaita-0.3.1 → autogaita-0.4.0}/autogaita.egg-info/top_level.txt +0 -0
- {autogaita-0.3.1 → autogaita-0.4.0}/setup.cfg +0 -0
- {autogaita-0.3.1 → autogaita-0.4.0}/tests/test_group_approval.py +0 -0
- {autogaita-0.3.1 → autogaita-0.4.0}/tests/test_simi_approval.py +0 -0
autogaita-0.4.0/PKG-INFO
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: autogaita
|
|
3
|
+
Version: 0.4.0
|
|
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
|
+
Home-page: https://github.com/mahan-hosseini/AutoGaitA/
|
|
6
|
+
Author: Mahan Hosseini
|
|
7
|
+
License: GPLv3
|
|
8
|
+
Requires-Python: >=3.10
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Requires-Dist: customtkinter>=5.2
|
|
11
|
+
Requires-Dist: pandas>=2.0
|
|
12
|
+
Requires-Dist: numpy>=1.24
|
|
13
|
+
Requires-Dist: seaborn>=0.13
|
|
14
|
+
Requires-Dist: matplotlib>=3.7
|
|
15
|
+
Requires-Dist: scikit-learn>=1.2
|
|
16
|
+
Requires-Dist: pingouin>=0.5
|
|
17
|
+
Requires-Dist: scipy>=1.11
|
|
18
|
+
Requires-Dist: ffmpeg-python>=0.2
|
|
19
|
+
Requires-Dist: openpyxl>=3.1
|
|
20
|
+
Requires-Dist: pillow>=10.3
|
|
21
|
+
Requires-Dist: pyobjc
|
|
22
|
+
Provides-Extra: dev
|
|
23
|
+
Requires-Dist: pytest; extra == "dev"
|
|
24
|
+
Requires-Dist: hypothesis; extra == "dev"
|
|
@@ -44,6 +44,7 @@ ORIGINAL_XLS_FILENAME = " - Original Stepcycles" # filenames of sheet exports
|
|
|
44
44
|
NORMALISED_XLS_FILENAME = " - Normalised Stepcycles"
|
|
45
45
|
AVERAGE_XLS_FILENAME = " - Average Stepcycle"
|
|
46
46
|
STD_XLS_FILENAME = " - Standard Devs. Stepcycle"
|
|
47
|
+
X_STANDARDISED_XLS_FILENAME = " - X-Standardised Stepcycles"
|
|
47
48
|
SC_LAT_LEGEND_FONTSIZE = 8
|
|
48
49
|
# PLOT GUI COLORS
|
|
49
50
|
FG_COLOR = "#789b73" # grey green
|
|
@@ -60,7 +61,7 @@ def dlc(info, folderinfo, cfg):
|
|
|
60
61
|
---------
|
|
61
62
|
1) import & preparation
|
|
62
63
|
2) step cycle extraction
|
|
63
|
-
3) y-
|
|
64
|
+
3) x/y-standardisation & feature computation for individual step cycles
|
|
64
65
|
4) step cycle normalisation, dataframe creation & XLS-exportation
|
|
65
66
|
5) plots
|
|
66
67
|
"""
|
|
@@ -82,7 +83,7 @@ def dlc(info, folderinfo, cfg):
|
|
|
82
83
|
|
|
83
84
|
# ......................... step-cycle extraction ................................
|
|
84
85
|
all_cycles = extract_stepcycles(data, info, folderinfo, cfg)
|
|
85
|
-
if
|
|
86
|
+
if all_cycles is None:
|
|
86
87
|
handle_issues("scs_invalid", info)
|
|
87
88
|
if cfg["dont_show_plots"] is False: # otherwise stuck at loading
|
|
88
89
|
plot_panel_instance.destroy_plot_panel()
|
|
@@ -105,6 +106,7 @@ def some_prep(info, folderinfo, cfg):
|
|
|
105
106
|
"""Preparation of the data & cfg file for later analyses"""
|
|
106
107
|
|
|
107
108
|
# ............................ unpack stuff ......................................
|
|
109
|
+
# => DON'T unpack (joint) cfg-keys that are tested later by check_and_expand_cfg
|
|
108
110
|
name = info["name"]
|
|
109
111
|
results_dir = info["results_dir"]
|
|
110
112
|
data_string = folderinfo["data_string"]
|
|
@@ -113,10 +115,12 @@ def some_prep(info, folderinfo, cfg):
|
|
|
113
115
|
subtract_beam = cfg["subtract_beam"]
|
|
114
116
|
convert_to_mm = cfg["convert_to_mm"]
|
|
115
117
|
pixel_to_mm_ratio = cfg["pixel_to_mm_ratio"]
|
|
116
|
-
|
|
118
|
+
standardise_y_at_SC_level = cfg["standardise_y_at_SC_level"]
|
|
117
119
|
invert_y_axis = cfg["invert_y_axis"]
|
|
118
120
|
flip_gait_direction = cfg["flip_gait_direction"]
|
|
119
121
|
analyse_average_x = cfg["analyse_average_x"]
|
|
122
|
+
standardise_x_coordinates = cfg["standardise_x_coordinates"]
|
|
123
|
+
standardise_y_to_a_joint = cfg["standardise_y_to_a_joint"]
|
|
120
124
|
|
|
121
125
|
# ............................. move data ........................................
|
|
122
126
|
# => see if we can delete a previous runs results folder if existant. if not, it's a
|
|
@@ -247,11 +251,10 @@ def some_prep(info, folderinfo, cfg):
|
|
|
247
251
|
data = datadf.copy(deep=True)
|
|
248
252
|
|
|
249
253
|
# ................ final data checks, conversions & additions ....................
|
|
250
|
-
# IMPORTANT
|
|
251
|
-
#
|
|
252
|
-
# MAIN TESTS OF USER-INPUT VALIDITY OCCUR HERE!
|
|
254
|
+
# IMPORTANT - MAIN TESTS OF USER-INPUT VALIDITY OCCUR HERE!
|
|
255
|
+
# => UNPACK VARS FROM CFG THAT ARE TESTED BY check_and_expand HERE, NOT EARLIER!
|
|
253
256
|
cfg = check_and_expand_cfg(data, cfg, info)
|
|
254
|
-
if cfg is None: #
|
|
257
|
+
if cfg is None: # some critical error occured
|
|
255
258
|
return
|
|
256
259
|
hind_joints = cfg["hind_joints"]
|
|
257
260
|
fore_joints = cfg["fore_joints"]
|
|
@@ -259,6 +262,9 @@ def some_prep(info, folderinfo, cfg):
|
|
|
259
262
|
beam_hind_jointadd = cfg["beam_hind_jointadd"]
|
|
260
263
|
beam_fore_jointadd = cfg["beam_fore_jointadd"]
|
|
261
264
|
direction_joint = cfg["direction_joint"]
|
|
265
|
+
# important to unpack to vars hand not to cfg since cfg is overwritten in multiruns!
|
|
266
|
+
x_standardisation_joint = cfg["x_standardisation_joint"][0]
|
|
267
|
+
y_standardisation_joint = cfg["y_standardisation_joint"][0]
|
|
262
268
|
# store config json file @ group path
|
|
263
269
|
# !!! NU - do this @ mouse path!
|
|
264
270
|
group_path = results_dir.split(name)[0]
|
|
@@ -266,8 +272,12 @@ def some_prep(info, folderinfo, cfg):
|
|
|
266
272
|
config_vars_to_json = {
|
|
267
273
|
"sampling_rate": sampling_rate,
|
|
268
274
|
"convert_to_mm": convert_to_mm,
|
|
269
|
-
"
|
|
275
|
+
"standardise_y_at_SC_level": standardise_y_at_SC_level,
|
|
270
276
|
"analyse_average_x": analyse_average_x,
|
|
277
|
+
"standardise_x_coordinates": standardise_x_coordinates,
|
|
278
|
+
"x_standardisation_joint": x_standardisation_joint,
|
|
279
|
+
"standardise_y_to_a_joint": standardise_y_to_a_joint,
|
|
280
|
+
"y_standardisation_joint": y_standardisation_joint,
|
|
271
281
|
"hind_joints": hind_joints,
|
|
272
282
|
"fore_joints": fore_joints,
|
|
273
283
|
"angles": angles,
|
|
@@ -304,12 +314,15 @@ def some_prep(info, folderinfo, cfg):
|
|
|
304
314
|
for col in data.columns:
|
|
305
315
|
if col.endswith(" y"):
|
|
306
316
|
data[col] = data[col] * -1
|
|
307
|
-
# if we don't have a beam to subtract,
|
|
317
|
+
# if we don't have a beam to subtract, standardise y to a joint's or global ymin = 0
|
|
308
318
|
if not subtract_beam:
|
|
309
|
-
|
|
319
|
+
y_min = float("inf")
|
|
310
320
|
y_cols = [col for col in data.columns if col.endswith("y")]
|
|
311
|
-
|
|
312
|
-
|
|
321
|
+
if standardise_y_to_a_joint:
|
|
322
|
+
y_min = data[y_standardisation_joint + "y"].min()
|
|
323
|
+
else:
|
|
324
|
+
y_min = data[y_cols].min().min()
|
|
325
|
+
data[y_cols] -= y_min
|
|
313
326
|
# convert pixels to millimeters
|
|
314
327
|
if convert_to_mm:
|
|
315
328
|
for column in data.columns:
|
|
@@ -319,7 +332,7 @@ def some_prep(info, folderinfo, cfg):
|
|
|
319
332
|
data = check_gait_direction(data, direction_joint, flip_gait_direction, info)
|
|
320
333
|
if data is None: # this means DLC file is broken
|
|
321
334
|
return
|
|
322
|
-
# subtract the beam from the joints to
|
|
335
|
+
# subtract the beam from the joints to standardise y
|
|
323
336
|
# => bc. we simulate that all mice run from left to right, we can write:
|
|
324
337
|
# (note that we also flip beam x columns, but never y-columns!)
|
|
325
338
|
# => & bc. we multiply y values by *-1 earlier, it's a neg_num - - neg_num
|
|
@@ -336,14 +349,8 @@ def some_prep(info, folderinfo, cfg):
|
|
|
336
349
|
for joint in list(set(fore_joints + beam_fore_jointadd)):
|
|
337
350
|
data[joint + "y"] = data[joint + "y"] - data[beam_col_right + "y"]
|
|
338
351
|
data.drop(columns=list(beamdf.columns), inplace=True) # beam not needed anymore
|
|
339
|
-
# add Time
|
|
352
|
+
# add Time
|
|
340
353
|
data[TIME_COL] = data.index * (1 / sampling_rate)
|
|
341
|
-
if sampling_rate <= 100:
|
|
342
|
-
data[TIME_COL] = round(data[TIME_COL], 2)
|
|
343
|
-
elif 100 < sampling_rate <= 1000:
|
|
344
|
-
data[TIME_COL] = round(data[TIME_COL], 3)
|
|
345
|
-
else:
|
|
346
|
-
data[TIME_COL] = round(data[TIME_COL], 4)
|
|
347
354
|
# reorder the columns we added
|
|
348
355
|
cols = [TIME_COL, "Flipped"]
|
|
349
356
|
data = data[cols + [c for c in data.columns if c not in cols]]
|
|
@@ -435,17 +442,15 @@ def check_and_expand_cfg(data, cfg, info):
|
|
|
435
442
|
Add plot_joints & direction_joint
|
|
436
443
|
Make sure to set dont_show_plots to True if Python is not in interactive mode
|
|
437
444
|
If users subtract a beam, set normalise @ sc level to False
|
|
445
|
+
String-checks for standardisation joints
|
|
438
446
|
"""
|
|
439
447
|
|
|
440
448
|
# run the tests first
|
|
449
|
+
# => note that beamcols & standardisation joints are tested separately further down.
|
|
441
450
|
for cfg_key in [
|
|
442
451
|
"angles",
|
|
443
452
|
"hind_joints",
|
|
444
453
|
"fore_joints",
|
|
445
|
-
"beam_col_left", # note beamcols are lists even though len=1 bc. of
|
|
446
|
-
"beam_col_right", # check function's procedure
|
|
447
|
-
"beam_hind_jointadd",
|
|
448
|
-
"beam_fore_jointadd",
|
|
449
454
|
]:
|
|
450
455
|
cfg[cfg_key] = check_and_fix_cfg_strings(data, cfg, cfg_key, info)
|
|
451
456
|
|
|
@@ -482,9 +487,18 @@ def check_and_expand_cfg(data, cfg, info):
|
|
|
482
487
|
|
|
483
488
|
# if subtracting beam, check identifier-strings & that beam colnames were valid.
|
|
484
489
|
if cfg["subtract_beam"]:
|
|
490
|
+
# first, let's check the strings
|
|
491
|
+
# => note beamcols are lists of len=1 bc. of check function
|
|
492
|
+
for cfg_key in [
|
|
493
|
+
"beam_col_left",
|
|
494
|
+
"beam_col_right",
|
|
495
|
+
"beam_hind_jointadd",
|
|
496
|
+
"beam_fore_jointadd",
|
|
497
|
+
]:
|
|
498
|
+
cfg[cfg_key] = check_and_fix_cfg_strings(data, cfg, cfg_key, info)
|
|
485
499
|
beam_col_error_message = (
|
|
486
500
|
"\n******************\n! CRITICAL ERROR !\n******************\n"
|
|
487
|
-
+ "It seems like you want to
|
|
501
|
+
+ "It seems like you want to standardise heights to a baseline (beam)."
|
|
488
502
|
+ "\nUnfortunately we were unable to find the y-columns you listed in "
|
|
489
503
|
+ "your beam's csv-file.\nPlease try again.\nInvalid beam side(s) was/were:"
|
|
490
504
|
)
|
|
@@ -503,9 +517,38 @@ def check_and_expand_cfg(data, cfg, info):
|
|
|
503
517
|
print(beam_col_error_message)
|
|
504
518
|
return
|
|
505
519
|
|
|
506
|
-
# never
|
|
520
|
+
# never standardise @ SC level if user subtracted a beam
|
|
507
521
|
if cfg["subtract_beam"]:
|
|
508
|
-
cfg["
|
|
522
|
+
cfg["standardise_y_at_SC_level"] = False
|
|
523
|
+
|
|
524
|
+
# test x/y standardisation joints if needed
|
|
525
|
+
broken_standardisation_joint = ""
|
|
526
|
+
if cfg["standardise_x_coordinates"]:
|
|
527
|
+
cfg["x_standardisation_joint"] = check_and_fix_cfg_strings(
|
|
528
|
+
data, cfg, "x_standardisation_joint", info
|
|
529
|
+
)
|
|
530
|
+
if not cfg["x_standardisation_joint"]:
|
|
531
|
+
broken_standardisation_joint += "x"
|
|
532
|
+
if cfg["standardise_y_to_a_joint"]:
|
|
533
|
+
cfg["y_standardisation_joint"] = check_and_fix_cfg_strings(
|
|
534
|
+
data, cfg, "y_standardisation_joint", info
|
|
535
|
+
)
|
|
536
|
+
if not cfg["y_standardisation_joint"]:
|
|
537
|
+
if broken_standardisation_joint:
|
|
538
|
+
broken_standardisation_joint += " & y"
|
|
539
|
+
else:
|
|
540
|
+
broken_standardisation_joint += "y"
|
|
541
|
+
if broken_standardisation_joint:
|
|
542
|
+
no_standardisation_joint_message = (
|
|
543
|
+
"\n******************\n! CRITICAL ERROR !\n******************\n"
|
|
544
|
+
+ "After testing your standardisation joints we found an issue with "
|
|
545
|
+
+ broken_standardisation_joint
|
|
546
|
+
+ "-coordinate standardisation joint."
|
|
547
|
+
+ "\n Cancelling AutoGaitA - please try again!"
|
|
548
|
+
)
|
|
549
|
+
write_issues_to_textfile(no_standardisation_joint_message, info)
|
|
550
|
+
print(no_standardisation_joint_message)
|
|
551
|
+
return
|
|
509
552
|
|
|
510
553
|
return cfg
|
|
511
554
|
|
|
@@ -513,8 +556,12 @@ def check_and_expand_cfg(data, cfg, info):
|
|
|
513
556
|
def check_and_fix_cfg_strings(data, cfg, cfg_key, info):
|
|
514
557
|
"""Check and fix strings in our joint & angle lists so that:
|
|
515
558
|
1) They don't include empty strings
|
|
516
|
-
2) All strings end with the space character
|
|
559
|
+
2) All strings end with the space character
|
|
560
|
+
=> Important note: strings should never have the coordinate in them (since we do
|
|
561
|
+
string + "y" for example throughout this code)
|
|
517
562
|
3) All strings are valid columns of the DLC dataset
|
|
563
|
+
=> Note that x_standardisation_joint is tested against ending with "x" - rest
|
|
564
|
+
against "y"
|
|
518
565
|
"""
|
|
519
566
|
|
|
520
567
|
# work on this variable (we return to cfg[key] outside of here)
|
|
@@ -526,11 +573,17 @@ def check_and_fix_cfg_strings(data, cfg, cfg_key, info):
|
|
|
526
573
|
string_variable = [s if s.endswith(" ") else s + " " for s in string_variable]
|
|
527
574
|
clean_string_list = []
|
|
528
575
|
invalid_strings_message = ""
|
|
529
|
-
for
|
|
530
|
-
if
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
576
|
+
for string in string_variable:
|
|
577
|
+
if cfg_key == "x_standardisation_joint":
|
|
578
|
+
if string + "x" in data.columns:
|
|
579
|
+
clean_string_list.append(string)
|
|
580
|
+
else:
|
|
581
|
+
invalid_strings_message += "\n" + string
|
|
582
|
+
else: # for y_standardisation_joint (& all other cases)!!
|
|
583
|
+
if string + "y" in data.columns:
|
|
584
|
+
clean_string_list.append(string)
|
|
585
|
+
else:
|
|
586
|
+
invalid_strings_message += "\n" + string
|
|
534
587
|
if invalid_strings_message:
|
|
535
588
|
# print and save warning
|
|
536
589
|
strings_warning = (
|
|
@@ -862,26 +915,43 @@ def extract_stepcycles(data, info, folderinfo, cfg):
|
|
|
862
915
|
# extract the SC times
|
|
863
916
|
start_in_s = float(SCdf.iloc[info_row, start_col].values[0])
|
|
864
917
|
end_in_s = float(SCdf.iloc[info_row, end_col].values[0])
|
|
865
|
-
if
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
918
|
+
# see if we are rounding to fix inaccurate user input
|
|
919
|
+
# => account for python's float precision leading to inaccuracies
|
|
920
|
+
# => two important steps here (sanity_check_vals only used for these checks)
|
|
921
|
+
# 1. round to 10th decimal to fix python making
|
|
922
|
+
# 3211.999999999999999995 out of 3212
|
|
923
|
+
sanity_check_start = round(start_in_s * sampling_rate, 10)
|
|
924
|
+
sanity_check_end = round(end_in_s * sampling_rate, 10)
|
|
925
|
+
# 2. comparing abs(sanity check vals) to 1e-7 just to be 1000% sure
|
|
926
|
+
if (abs(sanity_check_start % 1) > 1e-7) | (abs(sanity_check_end % 1) > 1e-7):
|
|
927
|
+
round_message = (
|
|
928
|
+
"\n***********\n! WARNING !\n***********\n"
|
|
929
|
+
+ "SC latencies of "
|
|
930
|
+
+ str(start_in_s)
|
|
931
|
+
+ "s to "
|
|
932
|
+
+ str(end_in_s)
|
|
933
|
+
+ "s were not provided in units of the frame rate!"
|
|
934
|
+
+ "\nWe thus use the previous possible frame(s)."
|
|
935
|
+
+ "\nDouble check if this worked as expected or fix annotation table!"
|
|
936
|
+
)
|
|
937
|
+
print(round_message)
|
|
938
|
+
write_issues_to_textfile(round_message, info)
|
|
939
|
+
# assign to all_cycles (note int() rounds down!)
|
|
940
|
+
all_cycles[s][0] = int(start_in_s * sampling_rate)
|
|
941
|
+
all_cycles[s][1] = int(end_in_s * sampling_rate)
|
|
942
|
+
# check if we are in data-bounds
|
|
943
|
+
if (all_cycles[s][0] in data.index) & (all_cycles[s][1] in data.index):
|
|
944
|
+
pass
|
|
869
945
|
else:
|
|
870
|
-
|
|
871
|
-
start_in_s = round(start_in_s, float_precision)
|
|
872
|
-
end_in_s = round(end_in_s, float_precision)
|
|
873
|
-
try:
|
|
874
|
-
all_cycles[s][0] = np.where(data[TIME_COL] == start_in_s)[0][0]
|
|
875
|
-
all_cycles[s][1] = np.where(data[TIME_COL] == end_in_s)[0][0]
|
|
876
|
-
except IndexError:
|
|
946
|
+
all_cycles[s] = [None, None] # so they can be cleaned later
|
|
877
947
|
this_message = (
|
|
878
948
|
"\n***********\n! WARNING !\n***********\n"
|
|
879
949
|
+ "SC latencies of: "
|
|
880
950
|
+ str(start_in_s)
|
|
881
951
|
+ "s to "
|
|
882
952
|
+ str(end_in_s)
|
|
883
|
-
+ "s not in data/video range
|
|
884
|
-
+ "
|
|
953
|
+
+ "s not in data/video range!"
|
|
954
|
+
+ "\nSkipping!"
|
|
885
955
|
)
|
|
886
956
|
print(this_message)
|
|
887
957
|
write_issues_to_textfile(this_message, info)
|
|
@@ -889,25 +959,28 @@ def extract_stepcycles(data, info, folderinfo, cfg):
|
|
|
889
959
|
# ............................ clean all_cycles ..................................
|
|
890
960
|
# check if we skipped latencies because they were out of data-bounds
|
|
891
961
|
all_cycles = check_cycle_out_of_bounds(all_cycles)
|
|
892
|
-
#
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
962
|
+
if all_cycles: # can be None if all SCs were out of bounds
|
|
963
|
+
# check if there are any duplicates (e.g., SC2's start-lat == SC1's end-lat)
|
|
964
|
+
all_cycles = check_cycle_duplicates(all_cycles)
|
|
965
|
+
# check if user input progressively later latencies
|
|
966
|
+
all_cycles = check_cycle_order(all_cycles, info)
|
|
967
|
+
# check if DLC tracking broke for any SCs - if so remove them
|
|
968
|
+
all_cycles = check_DLC_tracking(data, info, all_cycles, cfg)
|
|
898
969
|
return all_cycles
|
|
899
970
|
|
|
900
971
|
|
|
901
972
|
# .............................. helper functions ....................................
|
|
902
973
|
def check_cycle_out_of_bounds(all_cycles):
|
|
903
974
|
"""Check if user provided SC latencies that were not in video/data bounds"""
|
|
904
|
-
clean_cycles =
|
|
975
|
+
clean_cycles = None
|
|
905
976
|
for cycle in all_cycles:
|
|
906
977
|
# below checks if values are any type of int (just in case this should
|
|
907
978
|
# for some super random reason change...)
|
|
908
979
|
if isinstance(cycle[0], (int, np.integer)) & isinstance(
|
|
909
980
|
cycle[1], (int, np.integer)
|
|
910
981
|
):
|
|
982
|
+
if clean_cycles is None:
|
|
983
|
+
clean_cycles = []
|
|
911
984
|
clean_cycles.append(cycle)
|
|
912
985
|
return clean_cycles
|
|
913
986
|
|
|
@@ -918,7 +991,6 @@ def check_cycle_duplicates(all_cycles):
|
|
|
918
991
|
all indices of all_cycles have to be unique. If any duplicates found, add one
|
|
919
992
|
datapoint to the start latency.
|
|
920
993
|
"""
|
|
921
|
-
|
|
922
994
|
for c, cycle in enumerate(all_cycles):
|
|
923
995
|
if c > 0:
|
|
924
996
|
if cycle[0] == all_cycles[c - 1][1]:
|
|
@@ -933,7 +1005,6 @@ def check_cycle_order(all_cycles, info):
|
|
|
933
1005
|
1. Start latency earlier than end latency of previous SC
|
|
934
1006
|
2. End latency earlier then start latency of current SC
|
|
935
1007
|
"""
|
|
936
|
-
|
|
937
1008
|
clean_cycles = []
|
|
938
1009
|
current_max_time = 0
|
|
939
1010
|
for c, cycle in enumerate(all_cycles):
|
|
@@ -1061,7 +1132,7 @@ def handle_issues(condition, info):
|
|
|
1061
1132
|
# 2) for each step's data we normalise all y (height) values to the body's minimum
|
|
1062
1133
|
# if wanted
|
|
1063
1134
|
# 3) we compute and add features (angles, velocities, accelerations)
|
|
1064
|
-
# ==> see
|
|
1135
|
+
# ==> see standardise_x_y_and_add_features_to_one_step & helper functions a
|
|
1065
1136
|
# 4) immediately after adding features, we normalise a step to bin_num
|
|
1066
1137
|
# ==> see normalise_one_steps_data & helper functions b
|
|
1067
1138
|
# 5) we add original and normalised steps to all_steps_data and normalised_steps_data
|
|
@@ -1078,20 +1149,40 @@ def analyse_and_export_stepcycles(data, all_cycles, info, folderinfo, cfg):
|
|
|
1078
1149
|
save_to_xls = cfg["save_to_xls"]
|
|
1079
1150
|
analyse_average_x = cfg["analyse_average_x"]
|
|
1080
1151
|
bin_num = cfg["bin_num"]
|
|
1152
|
+
standardise_x_coordinates = cfg["standardise_x_coordinates"]
|
|
1081
1153
|
# do everything on a copy of the data df
|
|
1082
1154
|
data_copy = data.copy()
|
|
1083
1155
|
# exactly 1 step
|
|
1084
1156
|
if len(all_cycles) == 1:
|
|
1085
1157
|
this_step = data_copy.loc[all_cycles[0][0] : all_cycles[0][1]]
|
|
1086
|
-
|
|
1087
|
-
|
|
1158
|
+
if standardise_x_coordinates:
|
|
1159
|
+
all_steps_data, x_standardised_steps_data = (
|
|
1160
|
+
standardise_x_y_and_add_features_to_one_step(this_step, cfg)
|
|
1161
|
+
)
|
|
1162
|
+
normalised_steps_data = normalise_one_steps_data(
|
|
1163
|
+
x_standardised_steps_data, bin_num
|
|
1164
|
+
)
|
|
1165
|
+
else:
|
|
1166
|
+
all_steps_data = standardise_x_y_and_add_features_to_one_step(
|
|
1167
|
+
this_step, cfg
|
|
1168
|
+
)
|
|
1169
|
+
normalised_steps_data = normalise_one_steps_data(all_steps_data, bin_num)
|
|
1088
1170
|
# 2 or more steps - build dataframe
|
|
1089
1171
|
elif len(all_cycles) > 1:
|
|
1090
1172
|
# first- step is added manually
|
|
1091
1173
|
first_step = data_copy.loc[all_cycles[0][0] : all_cycles[0][1]]
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1174
|
+
if standardise_x_coordinates:
|
|
1175
|
+
all_steps_data, x_standardised_steps_data = (
|
|
1176
|
+
standardise_x_y_and_add_features_to_one_step(first_step, cfg)
|
|
1177
|
+
)
|
|
1178
|
+
normalised_steps_data = normalise_one_steps_data(
|
|
1179
|
+
x_standardised_steps_data, bin_num
|
|
1180
|
+
)
|
|
1181
|
+
else:
|
|
1182
|
+
all_steps_data = standardise_x_y_and_add_features_to_one_step(
|
|
1183
|
+
first_step, cfg
|
|
1184
|
+
)
|
|
1185
|
+
normalised_steps_data = normalise_one_steps_data(all_steps_data, bin_num)
|
|
1095
1186
|
# some prep for addition of further steps
|
|
1096
1187
|
sc_num = len(all_cycles)
|
|
1097
1188
|
nanvector = data_copy.loc[[1]]
|
|
@@ -1107,13 +1198,29 @@ def analyse_and_export_stepcycles(data, all_cycles, info, folderinfo, cfg):
|
|
|
1107
1198
|
with warnings.catch_warnings():
|
|
1108
1199
|
warnings.simplefilter("ignore")
|
|
1109
1200
|
numvector[:] = s + 1
|
|
1110
|
-
all_steps_data = add_step_separators(all_steps_data, nanvector, numvector)
|
|
1111
1201
|
# this_step
|
|
1112
1202
|
this_step = data_copy.loc[all_cycles[s][0] : all_cycles[s][1]]
|
|
1113
|
-
|
|
1203
|
+
if standardise_x_coordinates:
|
|
1204
|
+
this_step, this_x_standardised_step = (
|
|
1205
|
+
standardise_x_y_and_add_features_to_one_step(this_step, cfg)
|
|
1206
|
+
)
|
|
1207
|
+
this_normalised_step = normalise_one_steps_data(
|
|
1208
|
+
this_x_standardised_step, bin_num
|
|
1209
|
+
)
|
|
1210
|
+
else:
|
|
1211
|
+
this_step = standardise_x_y_and_add_features_to_one_step(this_step, cfg)
|
|
1212
|
+
this_normalised_step = normalise_one_steps_data(this_step, bin_num)
|
|
1213
|
+
# step separators & step-to-rest-concatenation
|
|
1214
|
+
# => note that normalised_step is already based on x-stand if required
|
|
1215
|
+
all_steps_data = add_step_separators(all_steps_data, nanvector, numvector)
|
|
1114
1216
|
all_steps_data = pd.concat([all_steps_data, this_step], axis=0)
|
|
1115
|
-
|
|
1116
|
-
|
|
1217
|
+
if standardise_x_coordinates:
|
|
1218
|
+
x_standardised_steps_data = add_step_separators(
|
|
1219
|
+
x_standardised_steps_data, nanvector, numvector
|
|
1220
|
+
)
|
|
1221
|
+
x_standardised_steps_data = pd.concat(
|
|
1222
|
+
[x_standardised_steps_data, this_x_standardised_step], axis=0
|
|
1223
|
+
)
|
|
1117
1224
|
normalised_steps_data = add_step_separators(
|
|
1118
1225
|
normalised_steps_data, nanvector, numvector
|
|
1119
1226
|
)
|
|
@@ -1121,6 +1228,8 @@ def analyse_and_export_stepcycles(data, all_cycles, info, folderinfo, cfg):
|
|
|
1121
1228
|
[normalised_steps_data, this_normalised_step], axis=0
|
|
1122
1229
|
)
|
|
1123
1230
|
# compute average & std data
|
|
1231
|
+
# => note that normalised_steps_data is automatically based on x-standardisation
|
|
1232
|
+
# which translates to average_data & std_data
|
|
1124
1233
|
average_data, std_data = compute_average_and_std_data(
|
|
1125
1234
|
name, normalised_steps_data, bin_num, analyse_average_x
|
|
1126
1235
|
)
|
|
@@ -1130,6 +1239,8 @@ def analyse_and_export_stepcycles(data, all_cycles, info, folderinfo, cfg):
|
|
|
1130
1239
|
results["average_data"] = average_data
|
|
1131
1240
|
results["std_data"] = std_data
|
|
1132
1241
|
results["all_cycles"] = all_cycles
|
|
1242
|
+
if standardise_x_coordinates:
|
|
1243
|
+
results["x_standardised_steps_data"] = x_standardised_steps_data
|
|
1133
1244
|
# save to files
|
|
1134
1245
|
save_results_sheet(
|
|
1135
1246
|
all_steps_data,
|
|
@@ -1149,27 +1260,47 @@ def analyse_and_export_stepcycles(data, all_cycles, info, folderinfo, cfg):
|
|
|
1149
1260
|
save_results_sheet(
|
|
1150
1261
|
std_data, save_to_xls, os.path.join(results_dir, name + STD_XLS_FILENAME)
|
|
1151
1262
|
)
|
|
1263
|
+
if standardise_x_coordinates:
|
|
1264
|
+
save_results_sheet(
|
|
1265
|
+
x_standardised_steps_data,
|
|
1266
|
+
save_to_xls,
|
|
1267
|
+
os.path.join(results_dir, name + X_STANDARDISED_XLS_FILENAME),
|
|
1268
|
+
)
|
|
1152
1269
|
return results
|
|
1153
1270
|
|
|
1154
1271
|
|
|
1155
1272
|
# ......................................................................................
|
|
1156
|
-
#
|
|
1273
|
+
# ............ helper functions a - standardise x, y and add features ................
|
|
1157
1274
|
# ......................................................................................
|
|
1158
1275
|
|
|
1159
1276
|
|
|
1160
|
-
def
|
|
1161
|
-
"""For a single step cycle's data,
|
|
1162
|
-
|
|
1163
|
-
"""
|
|
1164
|
-
# if user wanted this, normalise z (height) at step-cycle level
|
|
1277
|
+
def standardise_x_y_and_add_features_to_one_step(step, cfg):
|
|
1278
|
+
"""For a single step cycle's data, standardise x & y if wanted and add features"""
|
|
1279
|
+
# if user wanted this, standardise y (height) at step-cycle level
|
|
1165
1280
|
step_copy = step.copy()
|
|
1166
|
-
if cfg["
|
|
1281
|
+
if cfg["standardise_y_at_SC_level"] is True:
|
|
1167
1282
|
y_cols = [col for col in step_copy.columns if col.endswith("y")]
|
|
1168
|
-
|
|
1283
|
+
if cfg["standardise_y_to_a_joint"] is True:
|
|
1284
|
+
# note the [0] here is important because it's still a list of len=1!!
|
|
1285
|
+
this_y_min = step_copy[cfg["y_standardisation_joint"][0] + "y"].min()
|
|
1286
|
+
else:
|
|
1287
|
+
this_y_min = step_copy[y_cols].min().min()
|
|
1169
1288
|
step_copy[y_cols] -= this_y_min
|
|
1170
1289
|
# add angles and velocities
|
|
1171
1290
|
step_copy = add_features(step_copy, cfg)
|
|
1172
|
-
|
|
1291
|
+
# standardise x (horizontal dimension) at step-cycle level too
|
|
1292
|
+
if cfg["standardise_x_coordinates"] is True:
|
|
1293
|
+
x_norm_step = step_copy.copy()
|
|
1294
|
+
x_cols = [col for col in x_norm_step.columns if col.endswith("x")]
|
|
1295
|
+
# note the [0] here is important because it's still a list of len=1!!
|
|
1296
|
+
min_x_standardisation_joint = x_norm_step[
|
|
1297
|
+
cfg["x_standardisation_joint"][0] + "x"
|
|
1298
|
+
].min()
|
|
1299
|
+
x_norm_step[x_cols] -= min_x_standardisation_joint
|
|
1300
|
+
x_norm_step = add_features(x_norm_step, cfg)
|
|
1301
|
+
return step_copy, x_norm_step
|
|
1302
|
+
else:
|
|
1303
|
+
return step_copy
|
|
1173
1304
|
|
|
1174
1305
|
|
|
1175
1306
|
def add_features(step, cfg):
|
|
@@ -1399,6 +1530,22 @@ def add_step_separators(dataframe, nanvector, numvector):
|
|
|
1399
1530
|
|
|
1400
1531
|
# .............................. master function .............................
|
|
1401
1532
|
|
|
1533
|
+
# A note on x-standardisation (14.08.2024)
|
|
1534
|
+
# => If x-standardisation was performed, original as well as x-standardised dfs are
|
|
1535
|
+
# generated and exported to xls
|
|
1536
|
+
# => Our plotting functions only used all_steps_data, average_data & std_data
|
|
1537
|
+
# => Conveniently, if x-standardisation is performed, all_steps_data DOES NOT include
|
|
1538
|
+
# x-standardisation and is thus still the correct choice for the step-cycle level
|
|
1539
|
+
# plots (#1-#5)
|
|
1540
|
+
# => On the other hand, average_data & std_data (plots #6-12) DO and SHOULD include
|
|
1541
|
+
# x-standardisation
|
|
1542
|
+
# => Note that there is a slight discrepancy because angle-plots are based on
|
|
1543
|
+
# x-standardised values for the step-cycle level plots (#3) but not for the average
|
|
1544
|
+
# plots (#8)
|
|
1545
|
+
# -- This is not an issue since x-standardisation does not affect angles, even though
|
|
1546
|
+
# the values of x change because they change by a constant for all joints (there
|
|
1547
|
+
# is a unit test for this)
|
|
1548
|
+
|
|
1402
1549
|
# A note on updated colour cyclers after pull request that was merged 20.06.2024
|
|
1403
1550
|
# => Using color palettes instead of colour maps as I had previously means that
|
|
1404
1551
|
# we cycle through neighbouring colours
|
|
@@ -1770,7 +1917,7 @@ def plot_hindlimb_stickdiagram(all_steps_data, sc_idxs, info, cfg, plot_panel_in
|
|
|
1770
1917
|
ax.plot(this_xs, this_ys, color=this_color, label=this_label)
|
|
1771
1918
|
else:
|
|
1772
1919
|
ax.plot(this_xs, this_ys, color=this_color)
|
|
1773
|
-
ax.set_title(name + " -
|
|
1920
|
+
ax.set_title(name + " - Primary Stick Diagram")
|
|
1774
1921
|
# legend adjustments
|
|
1775
1922
|
if legend_outside is True:
|
|
1776
1923
|
ax.legend(
|
|
@@ -1786,7 +1933,7 @@ def plot_hindlimb_stickdiagram(all_steps_data, sc_idxs, info, cfg, plot_panel_in
|
|
|
1786
1933
|
else:
|
|
1787
1934
|
ax.set_xlabel("x (pixels)")
|
|
1788
1935
|
ax.set_ylabel("y (pixels)")
|
|
1789
|
-
figure_file_string = " -
|
|
1936
|
+
figure_file_string = " - Primary Stick Diagram"
|
|
1790
1937
|
save_figures(f, results_dir, name, figure_file_string)
|
|
1791
1938
|
if dont_show_plots:
|
|
1792
1939
|
plt.close(f)
|
|
@@ -1834,7 +1981,7 @@ def plot_forelimb_stickdiagram(all_steps_data, sc_idxs, info, cfg, plot_panel_in
|
|
|
1834
1981
|
ax.plot(this_xs, this_ys, color=this_color, label=this_label)
|
|
1835
1982
|
else:
|
|
1836
1983
|
ax.plot(this_xs, this_ys, color=this_color)
|
|
1837
|
-
ax.set_title(name + " -
|
|
1984
|
+
ax.set_title(name + " - Secondary Stick Diagram")
|
|
1838
1985
|
if convert_to_mm:
|
|
1839
1986
|
tickconvert_mm_to_cm(ax, "both")
|
|
1840
1987
|
# legend adjustments
|
|
@@ -1852,7 +1999,7 @@ def plot_forelimb_stickdiagram(all_steps_data, sc_idxs, info, cfg, plot_panel_in
|
|
|
1852
1999
|
else:
|
|
1853
2000
|
ax.set_xlabel("x (pixels)")
|
|
1854
2001
|
ax.set_ylabel("y (pixels)")
|
|
1855
|
-
figure_file_string = " -
|
|
2002
|
+
figure_file_string = " - Secondary Stick Diagram"
|
|
1856
2003
|
save_figures(f, results_dir, name, figure_file_string)
|
|
1857
2004
|
if dont_show_plots:
|
|
1858
2005
|
plt.close(f)
|