autogaita 0.3.0__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.3.0 → autogaita-0.4.0}/PKG-INFO +2 -2
- {autogaita-0.3.0 → autogaita-0.4.0}/README.md +1 -1
- {autogaita-0.3.0 → autogaita-0.4.0}/autogaita/autogaita_dlc.py +238 -80
- {autogaita-0.3.0 → autogaita-0.4.0}/autogaita/autogaita_dlc_gui.py +674 -330
- {autogaita-0.3.0 → autogaita-0.4.0}/autogaita/autogaita_group.py +323 -164
- {autogaita-0.3.0 → autogaita-0.4.0}/autogaita/autogaita_group_gui.py +221 -150
- {autogaita-0.3.0 → autogaita-0.4.0}/autogaita/autogaita_simi.py +313 -154
- {autogaita-0.3.0 → autogaita-0.4.0}/autogaita/autogaita_simi_gui.py +388 -230
- {autogaita-0.3.0 → autogaita-0.4.0}/autogaita/batchrun_scripts/autogaita_dlc_multirun.py +34 -7
- {autogaita-0.3.0 → autogaita-0.4.0}/autogaita/batchrun_scripts/autogaita_dlc_singlerun.py +6 -2
- autogaita-0.4.0/autogaita/batchrun_scripts/autogaita_group_dlcrun.py +88 -0
- {autogaita-0.3.0 → autogaita-0.4.0}/autogaita/batchrun_scripts/autogaita_group_simirun.py +17 -12
- {autogaita-0.3.0 → autogaita-0.4.0}/autogaita/batchrun_scripts/autogaita_simi_multirun.py +2 -2
- {autogaita-0.3.0 → 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.0 → autogaita-0.4.0}/autogaita/simi_gui_config.json +7 -7
- {autogaita-0.3.0 → autogaita-0.4.0}/autogaita.egg-info/PKG-INFO +2 -2
- {autogaita-0.3.0 → autogaita-0.4.0}/setup.py +2 -2
- {autogaita-0.3.0 → autogaita-0.4.0}/tests/test_dlc_approval.py +6 -2
- {autogaita-0.3.0 → autogaita-0.4.0}/tests/test_dlc_unit1_preparation.py +61 -10
- {autogaita-0.3.0 → autogaita-0.4.0}/tests/test_dlc_unit2_sc_extraction.py +31 -7
- {autogaita-0.3.0 → autogaita-0.4.0}/tests/test_dlc_unit3_main_analysis.py +37 -13
- autogaita-0.3.0/autogaita/batchrun_scripts/autogaita_group_dlcrun.py +0 -56
- autogaita-0.3.0/autogaita/group_gui_config.json +0 -40
- {autogaita-0.3.0 → autogaita-0.4.0}/LICENSE +0 -0
- {autogaita-0.3.0 → autogaita-0.4.0}/autogaita/__init__.py +0 -0
- {autogaita-0.3.0 → autogaita-0.4.0}/autogaita/__main__.py +0 -0
- {autogaita-0.3.0 → autogaita-0.4.0}/autogaita/autogaita.py +0 -0
- {autogaita-0.3.0 → autogaita-0.4.0}/autogaita/autogaita_icon.icns +0 -0
- {autogaita-0.3.0 → autogaita-0.4.0}/autogaita/autogaita_icon.ico +0 -0
- {autogaita-0.3.0 → autogaita-0.4.0}/autogaita/autogaita_logo.png +0 -0
- {autogaita-0.3.0 → autogaita-0.4.0}/autogaita/autogaita_utils.py +0 -0
- {autogaita-0.3.0 → autogaita-0.4.0}/autogaita/batchrun_scripts/__init__.py +0 -0
- {autogaita-0.3.0 → autogaita-0.4.0}/autogaita/batchrun_scripts/autogaita_simi_singlerun.py +0 -0
- {autogaita-0.3.0 → autogaita-0.4.0}/autogaita.egg-info/SOURCES.txt +0 -0
- {autogaita-0.3.0 → autogaita-0.4.0}/autogaita.egg-info/dependency_links.txt +0 -0
- {autogaita-0.3.0 → autogaita-0.4.0}/autogaita.egg-info/requires.txt +0 -0
- {autogaita-0.3.0 → autogaita-0.4.0}/autogaita.egg-info/top_level.txt +0 -0
- {autogaita-0.3.0 → autogaita-0.4.0}/setup.cfg +0 -0
- {autogaita-0.3.0 → autogaita-0.4.0}/tests/test_group_approval.py +0 -0
- {autogaita-0.3.0 → autogaita-0.4.0}/tests/test_simi_approval.py +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: autogaita
|
|
3
|
-
Version: 0.
|
|
4
|
-
Summary: Automatic Gait Analysis in Python
|
|
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
5
|
Home-page: https://github.com/mahan-hosseini/AutoGaitA/
|
|
6
6
|
Author: Mahan Hosseini
|
|
7
7
|
License: GPLv3
|
|
@@ -31,7 +31,7 @@ It is strongly recommended that a separate virtual environment for AutoGaitA is
|
|
|
31
31
|
|
|
32
32
|
- Access the main user interface via `python -m autogaita`.
|
|
33
33
|
|
|
34
|
-
- To update to the latest release (see the *Releases* panel on the right for the
|
|
34
|
+
- To update to the latest release (see the *Releases* panel on the right for the latest release) activate the virtual environment and: `pip install autogaita -U`.
|
|
35
35
|
|
|
36
36
|
## Tutorials and Examples
|
|
37
37
|
|
|
@@ -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,8 +83,10 @@ 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)
|
|
88
|
+
if cfg["dont_show_plots"] is False: # otherwise stuck at loading
|
|
89
|
+
plot_panel_instance.destroy_plot_panel()
|
|
87
90
|
return
|
|
88
91
|
|
|
89
92
|
# ......... main analysis: sc-lvl y-norm, features, df-creation & export ..........
|
|
@@ -103,6 +106,7 @@ def some_prep(info, folderinfo, cfg):
|
|
|
103
106
|
"""Preparation of the data & cfg file for later analyses"""
|
|
104
107
|
|
|
105
108
|
# ............................ unpack stuff ......................................
|
|
109
|
+
# => DON'T unpack (joint) cfg-keys that are tested later by check_and_expand_cfg
|
|
106
110
|
name = info["name"]
|
|
107
111
|
results_dir = info["results_dir"]
|
|
108
112
|
data_string = folderinfo["data_string"]
|
|
@@ -111,10 +115,12 @@ def some_prep(info, folderinfo, cfg):
|
|
|
111
115
|
subtract_beam = cfg["subtract_beam"]
|
|
112
116
|
convert_to_mm = cfg["convert_to_mm"]
|
|
113
117
|
pixel_to_mm_ratio = cfg["pixel_to_mm_ratio"]
|
|
114
|
-
|
|
118
|
+
standardise_y_at_SC_level = cfg["standardise_y_at_SC_level"]
|
|
115
119
|
invert_y_axis = cfg["invert_y_axis"]
|
|
116
120
|
flip_gait_direction = cfg["flip_gait_direction"]
|
|
117
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"]
|
|
118
124
|
|
|
119
125
|
# ............................. move data ........................................
|
|
120
126
|
# => see if we can delete a previous runs results folder if existant. if not, it's a
|
|
@@ -245,11 +251,10 @@ def some_prep(info, folderinfo, cfg):
|
|
|
245
251
|
data = datadf.copy(deep=True)
|
|
246
252
|
|
|
247
253
|
# ................ final data checks, conversions & additions ....................
|
|
248
|
-
# IMPORTANT
|
|
249
|
-
#
|
|
250
|
-
# 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!
|
|
251
256
|
cfg = check_and_expand_cfg(data, cfg, info)
|
|
252
|
-
if cfg is None: #
|
|
257
|
+
if cfg is None: # some critical error occured
|
|
253
258
|
return
|
|
254
259
|
hind_joints = cfg["hind_joints"]
|
|
255
260
|
fore_joints = cfg["fore_joints"]
|
|
@@ -257,6 +262,9 @@ def some_prep(info, folderinfo, cfg):
|
|
|
257
262
|
beam_hind_jointadd = cfg["beam_hind_jointadd"]
|
|
258
263
|
beam_fore_jointadd = cfg["beam_fore_jointadd"]
|
|
259
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]
|
|
260
268
|
# store config json file @ group path
|
|
261
269
|
# !!! NU - do this @ mouse path!
|
|
262
270
|
group_path = results_dir.split(name)[0]
|
|
@@ -264,8 +272,12 @@ def some_prep(info, folderinfo, cfg):
|
|
|
264
272
|
config_vars_to_json = {
|
|
265
273
|
"sampling_rate": sampling_rate,
|
|
266
274
|
"convert_to_mm": convert_to_mm,
|
|
267
|
-
"
|
|
275
|
+
"standardise_y_at_SC_level": standardise_y_at_SC_level,
|
|
268
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,
|
|
269
281
|
"hind_joints": hind_joints,
|
|
270
282
|
"fore_joints": fore_joints,
|
|
271
283
|
"angles": angles,
|
|
@@ -302,12 +314,15 @@ def some_prep(info, folderinfo, cfg):
|
|
|
302
314
|
for col in data.columns:
|
|
303
315
|
if col.endswith(" y"):
|
|
304
316
|
data[col] = data[col] * -1
|
|
305
|
-
# 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
|
|
306
318
|
if not subtract_beam:
|
|
307
|
-
|
|
319
|
+
y_min = float("inf")
|
|
308
320
|
y_cols = [col for col in data.columns if col.endswith("y")]
|
|
309
|
-
|
|
310
|
-
|
|
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
|
|
311
326
|
# convert pixels to millimeters
|
|
312
327
|
if convert_to_mm:
|
|
313
328
|
for column in data.columns:
|
|
@@ -317,7 +332,7 @@ def some_prep(info, folderinfo, cfg):
|
|
|
317
332
|
data = check_gait_direction(data, direction_joint, flip_gait_direction, info)
|
|
318
333
|
if data is None: # this means DLC file is broken
|
|
319
334
|
return
|
|
320
|
-
# subtract the beam from the joints to
|
|
335
|
+
# subtract the beam from the joints to standardise y
|
|
321
336
|
# => bc. we simulate that all mice run from left to right, we can write:
|
|
322
337
|
# (note that we also flip beam x columns, but never y-columns!)
|
|
323
338
|
# => & bc. we multiply y values by *-1 earlier, it's a neg_num - - neg_num
|
|
@@ -334,14 +349,8 @@ def some_prep(info, folderinfo, cfg):
|
|
|
334
349
|
for joint in list(set(fore_joints + beam_fore_jointadd)):
|
|
335
350
|
data[joint + "y"] = data[joint + "y"] - data[beam_col_right + "y"]
|
|
336
351
|
data.drop(columns=list(beamdf.columns), inplace=True) # beam not needed anymore
|
|
337
|
-
# add Time
|
|
352
|
+
# add Time
|
|
338
353
|
data[TIME_COL] = data.index * (1 / sampling_rate)
|
|
339
|
-
if sampling_rate <= 100:
|
|
340
|
-
data[TIME_COL] = round(data[TIME_COL], 2)
|
|
341
|
-
elif 100 < sampling_rate <= 1000:
|
|
342
|
-
data[TIME_COL] = round(data[TIME_COL], 3)
|
|
343
|
-
else:
|
|
344
|
-
data[TIME_COL] = round(data[TIME_COL], 4)
|
|
345
354
|
# reorder the columns we added
|
|
346
355
|
cols = [TIME_COL, "Flipped"]
|
|
347
356
|
data = data[cols + [c for c in data.columns if c not in cols]]
|
|
@@ -433,17 +442,15 @@ def check_and_expand_cfg(data, cfg, info):
|
|
|
433
442
|
Add plot_joints & direction_joint
|
|
434
443
|
Make sure to set dont_show_plots to True if Python is not in interactive mode
|
|
435
444
|
If users subtract a beam, set normalise @ sc level to False
|
|
445
|
+
String-checks for standardisation joints
|
|
436
446
|
"""
|
|
437
447
|
|
|
438
448
|
# run the tests first
|
|
449
|
+
# => note that beamcols & standardisation joints are tested separately further down.
|
|
439
450
|
for cfg_key in [
|
|
440
451
|
"angles",
|
|
441
452
|
"hind_joints",
|
|
442
453
|
"fore_joints",
|
|
443
|
-
"beam_col_left", # note beamcols are lists even though len=1 bc. of
|
|
444
|
-
"beam_col_right", # check function's procedure
|
|
445
|
-
"beam_hind_jointadd",
|
|
446
|
-
"beam_fore_jointadd",
|
|
447
454
|
]:
|
|
448
455
|
cfg[cfg_key] = check_and_fix_cfg_strings(data, cfg, cfg_key, info)
|
|
449
456
|
|
|
@@ -480,9 +487,18 @@ def check_and_expand_cfg(data, cfg, info):
|
|
|
480
487
|
|
|
481
488
|
# if subtracting beam, check identifier-strings & that beam colnames were valid.
|
|
482
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)
|
|
483
499
|
beam_col_error_message = (
|
|
484
500
|
"\n******************\n! CRITICAL ERROR !\n******************\n"
|
|
485
|
-
+ "It seems like you want to
|
|
501
|
+
+ "It seems like you want to standardise heights to a baseline (beam)."
|
|
486
502
|
+ "\nUnfortunately we were unable to find the y-columns you listed in "
|
|
487
503
|
+ "your beam's csv-file.\nPlease try again.\nInvalid beam side(s) was/were:"
|
|
488
504
|
)
|
|
@@ -501,9 +517,38 @@ def check_and_expand_cfg(data, cfg, info):
|
|
|
501
517
|
print(beam_col_error_message)
|
|
502
518
|
return
|
|
503
519
|
|
|
504
|
-
# never
|
|
520
|
+
# never standardise @ SC level if user subtracted a beam
|
|
505
521
|
if cfg["subtract_beam"]:
|
|
506
|
-
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
|
|
507
552
|
|
|
508
553
|
return cfg
|
|
509
554
|
|
|
@@ -511,8 +556,12 @@ def check_and_expand_cfg(data, cfg, info):
|
|
|
511
556
|
def check_and_fix_cfg_strings(data, cfg, cfg_key, info):
|
|
512
557
|
"""Check and fix strings in our joint & angle lists so that:
|
|
513
558
|
1) They don't include empty strings
|
|
514
|
-
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)
|
|
515
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"
|
|
516
565
|
"""
|
|
517
566
|
|
|
518
567
|
# work on this variable (we return to cfg[key] outside of here)
|
|
@@ -524,11 +573,17 @@ def check_and_fix_cfg_strings(data, cfg, cfg_key, info):
|
|
|
524
573
|
string_variable = [s if s.endswith(" ") else s + " " for s in string_variable]
|
|
525
574
|
clean_string_list = []
|
|
526
575
|
invalid_strings_message = ""
|
|
527
|
-
for
|
|
528
|
-
if
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
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
|
|
532
587
|
if invalid_strings_message:
|
|
533
588
|
# print and save warning
|
|
534
589
|
strings_warning = (
|
|
@@ -860,26 +915,43 @@ def extract_stepcycles(data, info, folderinfo, cfg):
|
|
|
860
915
|
# extract the SC times
|
|
861
916
|
start_in_s = float(SCdf.iloc[info_row, start_col].values[0])
|
|
862
917
|
end_in_s = float(SCdf.iloc[info_row, end_col].values[0])
|
|
863
|
-
if
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
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
|
|
867
945
|
else:
|
|
868
|
-
|
|
869
|
-
start_in_s = round(start_in_s, float_precision)
|
|
870
|
-
end_in_s = round(end_in_s, float_precision)
|
|
871
|
-
try:
|
|
872
|
-
all_cycles[s][0] = np.where(data[TIME_COL] == start_in_s)[0][0]
|
|
873
|
-
all_cycles[s][1] = np.where(data[TIME_COL] == end_in_s)[0][0]
|
|
874
|
-
except IndexError:
|
|
946
|
+
all_cycles[s] = [None, None] # so they can be cleaned later
|
|
875
947
|
this_message = (
|
|
876
948
|
"\n***********\n! WARNING !\n***********\n"
|
|
877
949
|
+ "SC latencies of: "
|
|
878
950
|
+ str(start_in_s)
|
|
879
951
|
+ "s to "
|
|
880
952
|
+ str(end_in_s)
|
|
881
|
-
+ "s not in data/video range
|
|
882
|
-
+ "
|
|
953
|
+
+ "s not in data/video range!"
|
|
954
|
+
+ "\nSkipping!"
|
|
883
955
|
)
|
|
884
956
|
print(this_message)
|
|
885
957
|
write_issues_to_textfile(this_message, info)
|
|
@@ -887,25 +959,28 @@ def extract_stepcycles(data, info, folderinfo, cfg):
|
|
|
887
959
|
# ............................ clean all_cycles ..................................
|
|
888
960
|
# check if we skipped latencies because they were out of data-bounds
|
|
889
961
|
all_cycles = check_cycle_out_of_bounds(all_cycles)
|
|
890
|
-
#
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
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)
|
|
896
969
|
return all_cycles
|
|
897
970
|
|
|
898
971
|
|
|
899
972
|
# .............................. helper functions ....................................
|
|
900
973
|
def check_cycle_out_of_bounds(all_cycles):
|
|
901
974
|
"""Check if user provided SC latencies that were not in video/data bounds"""
|
|
902
|
-
clean_cycles =
|
|
975
|
+
clean_cycles = None
|
|
903
976
|
for cycle in all_cycles:
|
|
904
977
|
# below checks if values are any type of int (just in case this should
|
|
905
978
|
# for some super random reason change...)
|
|
906
979
|
if isinstance(cycle[0], (int, np.integer)) & isinstance(
|
|
907
980
|
cycle[1], (int, np.integer)
|
|
908
981
|
):
|
|
982
|
+
if clean_cycles is None:
|
|
983
|
+
clean_cycles = []
|
|
909
984
|
clean_cycles.append(cycle)
|
|
910
985
|
return clean_cycles
|
|
911
986
|
|
|
@@ -916,7 +991,6 @@ def check_cycle_duplicates(all_cycles):
|
|
|
916
991
|
all indices of all_cycles have to be unique. If any duplicates found, add one
|
|
917
992
|
datapoint to the start latency.
|
|
918
993
|
"""
|
|
919
|
-
|
|
920
994
|
for c, cycle in enumerate(all_cycles):
|
|
921
995
|
if c > 0:
|
|
922
996
|
if cycle[0] == all_cycles[c - 1][1]:
|
|
@@ -931,7 +1005,6 @@ def check_cycle_order(all_cycles, info):
|
|
|
931
1005
|
1. Start latency earlier than end latency of previous SC
|
|
932
1006
|
2. End latency earlier then start latency of current SC
|
|
933
1007
|
"""
|
|
934
|
-
|
|
935
1008
|
clean_cycles = []
|
|
936
1009
|
current_max_time = 0
|
|
937
1010
|
for c, cycle in enumerate(all_cycles):
|
|
@@ -1059,7 +1132,7 @@ def handle_issues(condition, info):
|
|
|
1059
1132
|
# 2) for each step's data we normalise all y (height) values to the body's minimum
|
|
1060
1133
|
# if wanted
|
|
1061
1134
|
# 3) we compute and add features (angles, velocities, accelerations)
|
|
1062
|
-
# ==> see
|
|
1135
|
+
# ==> see standardise_x_y_and_add_features_to_one_step & helper functions a
|
|
1063
1136
|
# 4) immediately after adding features, we normalise a step to bin_num
|
|
1064
1137
|
# ==> see normalise_one_steps_data & helper functions b
|
|
1065
1138
|
# 5) we add original and normalised steps to all_steps_data and normalised_steps_data
|
|
@@ -1076,20 +1149,40 @@ def analyse_and_export_stepcycles(data, all_cycles, info, folderinfo, cfg):
|
|
|
1076
1149
|
save_to_xls = cfg["save_to_xls"]
|
|
1077
1150
|
analyse_average_x = cfg["analyse_average_x"]
|
|
1078
1151
|
bin_num = cfg["bin_num"]
|
|
1152
|
+
standardise_x_coordinates = cfg["standardise_x_coordinates"]
|
|
1079
1153
|
# do everything on a copy of the data df
|
|
1080
1154
|
data_copy = data.copy()
|
|
1081
1155
|
# exactly 1 step
|
|
1082
1156
|
if len(all_cycles) == 1:
|
|
1083
1157
|
this_step = data_copy.loc[all_cycles[0][0] : all_cycles[0][1]]
|
|
1084
|
-
|
|
1085
|
-
|
|
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)
|
|
1086
1170
|
# 2 or more steps - build dataframe
|
|
1087
1171
|
elif len(all_cycles) > 1:
|
|
1088
1172
|
# first- step is added manually
|
|
1089
1173
|
first_step = data_copy.loc[all_cycles[0][0] : all_cycles[0][1]]
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
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)
|
|
1093
1186
|
# some prep for addition of further steps
|
|
1094
1187
|
sc_num = len(all_cycles)
|
|
1095
1188
|
nanvector = data_copy.loc[[1]]
|
|
@@ -1105,13 +1198,29 @@ def analyse_and_export_stepcycles(data, all_cycles, info, folderinfo, cfg):
|
|
|
1105
1198
|
with warnings.catch_warnings():
|
|
1106
1199
|
warnings.simplefilter("ignore")
|
|
1107
1200
|
numvector[:] = s + 1
|
|
1108
|
-
all_steps_data = add_step_separators(all_steps_data, nanvector, numvector)
|
|
1109
1201
|
# this_step
|
|
1110
1202
|
this_step = data_copy.loc[all_cycles[s][0] : all_cycles[s][1]]
|
|
1111
|
-
|
|
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)
|
|
1112
1216
|
all_steps_data = pd.concat([all_steps_data, this_step], axis=0)
|
|
1113
|
-
|
|
1114
|
-
|
|
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
|
+
)
|
|
1115
1224
|
normalised_steps_data = add_step_separators(
|
|
1116
1225
|
normalised_steps_data, nanvector, numvector
|
|
1117
1226
|
)
|
|
@@ -1119,6 +1228,8 @@ def analyse_and_export_stepcycles(data, all_cycles, info, folderinfo, cfg):
|
|
|
1119
1228
|
[normalised_steps_data, this_normalised_step], axis=0
|
|
1120
1229
|
)
|
|
1121
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
|
|
1122
1233
|
average_data, std_data = compute_average_and_std_data(
|
|
1123
1234
|
name, normalised_steps_data, bin_num, analyse_average_x
|
|
1124
1235
|
)
|
|
@@ -1128,6 +1239,8 @@ def analyse_and_export_stepcycles(data, all_cycles, info, folderinfo, cfg):
|
|
|
1128
1239
|
results["average_data"] = average_data
|
|
1129
1240
|
results["std_data"] = std_data
|
|
1130
1241
|
results["all_cycles"] = all_cycles
|
|
1242
|
+
if standardise_x_coordinates:
|
|
1243
|
+
results["x_standardised_steps_data"] = x_standardised_steps_data
|
|
1131
1244
|
# save to files
|
|
1132
1245
|
save_results_sheet(
|
|
1133
1246
|
all_steps_data,
|
|
@@ -1147,27 +1260,47 @@ def analyse_and_export_stepcycles(data, all_cycles, info, folderinfo, cfg):
|
|
|
1147
1260
|
save_results_sheet(
|
|
1148
1261
|
std_data, save_to_xls, os.path.join(results_dir, name + STD_XLS_FILENAME)
|
|
1149
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
|
+
)
|
|
1150
1269
|
return results
|
|
1151
1270
|
|
|
1152
1271
|
|
|
1153
1272
|
# ......................................................................................
|
|
1154
|
-
#
|
|
1273
|
+
# ............ helper functions a - standardise x, y and add features ................
|
|
1155
1274
|
# ......................................................................................
|
|
1156
1275
|
|
|
1157
1276
|
|
|
1158
|
-
def
|
|
1159
|
-
"""For a single step cycle's data,
|
|
1160
|
-
|
|
1161
|
-
"""
|
|
1162
|
-
# 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
|
|
1163
1280
|
step_copy = step.copy()
|
|
1164
|
-
if cfg["
|
|
1281
|
+
if cfg["standardise_y_at_SC_level"] is True:
|
|
1165
1282
|
y_cols = [col for col in step_copy.columns if col.endswith("y")]
|
|
1166
|
-
|
|
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()
|
|
1167
1288
|
step_copy[y_cols] -= this_y_min
|
|
1168
1289
|
# add angles and velocities
|
|
1169
1290
|
step_copy = add_features(step_copy, cfg)
|
|
1170
|
-
|
|
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
|
|
1171
1304
|
|
|
1172
1305
|
|
|
1173
1306
|
def add_features(step, cfg):
|
|
@@ -1397,6 +1530,22 @@ def add_step_separators(dataframe, nanvector, numvector):
|
|
|
1397
1530
|
|
|
1398
1531
|
# .............................. master function .............................
|
|
1399
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
|
+
|
|
1400
1549
|
# A note on updated colour cyclers after pull request that was merged 20.06.2024
|
|
1401
1550
|
# => Using color palettes instead of colour maps as I had previously means that
|
|
1402
1551
|
# we cycle through neighbouring colours
|
|
@@ -1443,6 +1592,9 @@ def plot_results(info, results, folderinfo, cfg, plot_panel_instance):
|
|
|
1443
1592
|
# ...............................3 - angles by time.................................
|
|
1444
1593
|
if angles["name"]:
|
|
1445
1594
|
plot_angles_by_time(all_steps_data, sc_idxs, info, cfg, plot_panel_instance)
|
|
1595
|
+
# regularly closing figures to save memory
|
|
1596
|
+
# => no problem to do this since we pass figure-vars to save-functions and PlotPanel
|
|
1597
|
+
plt.close("all")
|
|
1446
1598
|
|
|
1447
1599
|
# ..........................4 - hindlimb stick diagram..............................
|
|
1448
1600
|
plot_hindlimb_stickdiagram(all_steps_data, sc_idxs, info, cfg, plot_panel_instance)
|
|
@@ -1461,6 +1613,7 @@ def plot_results(info, results, folderinfo, cfg, plot_panel_instance):
|
|
|
1461
1613
|
plot_joint_x_by_average_SC(
|
|
1462
1614
|
average_data, std_data, info, cfg, plot_panel_instance
|
|
1463
1615
|
)
|
|
1616
|
+
plt.close("all")
|
|
1464
1617
|
|
|
1465
1618
|
# ........................8 - average angles over SC percentage.....................
|
|
1466
1619
|
if angles["name"]:
|
|
@@ -1491,11 +1644,12 @@ def plot_results(info, results, folderinfo, cfg, plot_panel_instance):
|
|
|
1491
1644
|
plot_angular_acceleration_by_average_SC(
|
|
1492
1645
|
average_data, std_data, info, cfg, plot_panel_instance
|
|
1493
1646
|
)
|
|
1647
|
+
plt.close("all")
|
|
1494
1648
|
|
|
1495
1649
|
# ........................optional - 13 - build plot panel..........................
|
|
1496
|
-
if
|
|
1650
|
+
if dont_show_plots is True:
|
|
1497
1651
|
pass # going on without building the plot window
|
|
1498
|
-
elif
|
|
1652
|
+
elif dont_show_plots is False: # -> show plot panel
|
|
1499
1653
|
# Destroy loading screen and build plot panel with all figures
|
|
1500
1654
|
plot_panel_instance.destroy_plot_panel_loading_screen()
|
|
1501
1655
|
plot_panel_instance.build_plot_panel()
|
|
@@ -1763,7 +1917,7 @@ def plot_hindlimb_stickdiagram(all_steps_data, sc_idxs, info, cfg, plot_panel_in
|
|
|
1763
1917
|
ax.plot(this_xs, this_ys, color=this_color, label=this_label)
|
|
1764
1918
|
else:
|
|
1765
1919
|
ax.plot(this_xs, this_ys, color=this_color)
|
|
1766
|
-
ax.set_title(name + " -
|
|
1920
|
+
ax.set_title(name + " - Primary Stick Diagram")
|
|
1767
1921
|
# legend adjustments
|
|
1768
1922
|
if legend_outside is True:
|
|
1769
1923
|
ax.legend(
|
|
@@ -1779,7 +1933,7 @@ def plot_hindlimb_stickdiagram(all_steps_data, sc_idxs, info, cfg, plot_panel_in
|
|
|
1779
1933
|
else:
|
|
1780
1934
|
ax.set_xlabel("x (pixels)")
|
|
1781
1935
|
ax.set_ylabel("y (pixels)")
|
|
1782
|
-
figure_file_string = " -
|
|
1936
|
+
figure_file_string = " - Primary Stick Diagram"
|
|
1783
1937
|
save_figures(f, results_dir, name, figure_file_string)
|
|
1784
1938
|
if dont_show_plots:
|
|
1785
1939
|
plt.close(f)
|
|
@@ -1827,7 +1981,7 @@ def plot_forelimb_stickdiagram(all_steps_data, sc_idxs, info, cfg, plot_panel_in
|
|
|
1827
1981
|
ax.plot(this_xs, this_ys, color=this_color, label=this_label)
|
|
1828
1982
|
else:
|
|
1829
1983
|
ax.plot(this_xs, this_ys, color=this_color)
|
|
1830
|
-
ax.set_title(name + " -
|
|
1984
|
+
ax.set_title(name + " - Secondary Stick Diagram")
|
|
1831
1985
|
if convert_to_mm:
|
|
1832
1986
|
tickconvert_mm_to_cm(ax, "both")
|
|
1833
1987
|
# legend adjustments
|
|
@@ -1845,7 +1999,7 @@ def plot_forelimb_stickdiagram(all_steps_data, sc_idxs, info, cfg, plot_panel_in
|
|
|
1845
1999
|
else:
|
|
1846
2000
|
ax.set_xlabel("x (pixels)")
|
|
1847
2001
|
ax.set_ylabel("y (pixels)")
|
|
1848
|
-
figure_file_string = " -
|
|
2002
|
+
figure_file_string = " - Secondary Stick Diagram"
|
|
1849
2003
|
save_figures(f, results_dir, name, figure_file_string)
|
|
1850
2004
|
if dont_show_plots:
|
|
1851
2005
|
plt.close(f)
|
|
@@ -2427,6 +2581,10 @@ class PlotPanel:
|
|
|
2427
2581
|
f"AutoGaitA Plot Panel {self.current_fig_index+1}/{len(self.figures)}"
|
|
2428
2582
|
)
|
|
2429
2583
|
|
|
2584
|
+
def destroy_plot_panel(self):
|
|
2585
|
+
# Needed if no SCs after checks
|
|
2586
|
+
self.loading_screen.destroy()
|
|
2587
|
+
|
|
2430
2588
|
|
|
2431
2589
|
# %% local functions 5 - print finish
|
|
2432
2590
|
|