From eff4142845e741c5918a7f10ad591bcb69259732 Mon Sep 17 00:00:00 2001 From: Poruri Sai Rahul Date: Sat, 16 Jul 2022 10:04:50 +0100 Subject: [PATCH 1/7] REF: Denest filterpredictions for loop there were two try/excepts, one within the other. this commit simplifies the code by pulling the inner try/except out. the code now tries to look for filtered data. if filtered data is found for the video, we continue to the next item in the loop. if not found, we catch and ignore the error. we then try to load the data and filter it modified: deeplabcut/post_processing/filtering.py --- deeplabcut/post_processing/filtering.py | 119 ++++++++++++------------ 1 file changed, 62 insertions(+), 57 deletions(-) diff --git a/deeplabcut/post_processing/filtering.py b/deeplabcut/post_processing/filtering.py index 32dd89c79e..b349d4bd20 100644 --- a/deeplabcut/post_processing/filtering.py +++ b/deeplabcut/post_processing/filtering.py @@ -216,65 +216,70 @@ def filterpredictions( destfolder, vname, DLCscorer, True, track_method ) print(f"Data from {vname} were already filtered. Skipping...") - except FileNotFoundError: # Data haven't been filtered yet - try: - df, filepath, _, _ = auxiliaryfunctions.load_analyzed_data( - destfolder, vname, DLCscorer, track_method=track_method - ) - nrows = df.shape[0] - if filtertype == "arima": - temp = df.values.reshape((nrows, -1, 3)) - placeholder = np.empty_like(temp) - for i in range(temp.shape[1]): - x, y, p = temp[:, i].T - meanx, _ = FitSARIMAXModel( - x, p, p_bound, alpha, ARdegree, MAdegree, False - ) - meany, _ = FitSARIMAXModel( - y, p, p_bound, alpha, ARdegree, MAdegree, False - ) - meanx[0] = x[0] - meany[0] = y[0] - placeholder[:, i] = np.c_[meanx, meany, p] - data = pd.DataFrame( - placeholder.reshape((nrows, -1)), - columns=df.columns, - index=df.index, + # Data has been filtered so continue to the next video + continue + except FileNotFoundError: + pass + + # Data haven't been filtered yet + try: + df, filepath, _, _ = auxiliaryfunctions.load_analyzed_data( + destfolder, vname, DLCscorer, track_method=track_method + ) + nrows = df.shape[0] + if filtertype == "arima": + temp = df.values.reshape((nrows, -1, 3)) + placeholder = np.empty_like(temp) + for i in range(temp.shape[1]): + x, y, p = temp[:, i].T + meanx, _ = FitSARIMAXModel( + x, p, p_bound, alpha, ARdegree, MAdegree, False ) - elif filtertype == "median": - data = df.copy() - mask = data.columns.get_level_values("coords") != "likelihood" - data.loc[:, mask] = df.loc[:, mask].apply( - signal.medfilt, args=(windowlength,), axis=0 + meany, _ = FitSARIMAXModel( + y, p, p_bound, alpha, ARdegree, MAdegree, False ) - elif filtertype == "spline": - data = df.copy() - mask_data = data.columns.get_level_values("coords").isin(("x", "y")) - xy = data.loc[:, mask_data].values - prob = data.loc[:, ~mask_data].values - missing = np.isnan(xy) - xy_filled = columnwise_spline_interp(xy, windowlength) - filled = ~np.isnan(xy_filled) - xy[filled] = xy_filled[filled] - inds = np.argwhere(missing & filled) - if inds.size: - # Retrieve original individual label indices - inds[:, 1] //= 2 - inds = np.unique(inds, axis=0) - prob[inds[:, 0], inds[:, 1]] = 0.01 - data.loc[:, ~mask_data] = prob - data.loc[:, mask_data] = xy - else: - raise ValueError(f"Unknown filter type {filtertype}") - - outdataname = filepath.replace(".h5", "_filtered.h5") - data.to_hdf(outdataname, "df_with_missing", format="table", mode="w") - if save_as_csv: - print("Saving filtered csv poses!") - data.to_csv(outdataname.split(".h5")[0] + ".csv") - except FileNotFoundError as e: - print(e) - continue + meanx[0] = x[0] + meany[0] = y[0] + placeholder[:, i] = np.c_[meanx, meany, p] + data = pd.DataFrame( + placeholder.reshape((nrows, -1)), + columns=df.columns, + index=df.index, + ) + elif filtertype == "median": + data = df.copy() + mask = data.columns.get_level_values("coords") != "likelihood" + data.loc[:, mask] = df.loc[:, mask].apply( + signal.medfilt, args=(windowlength,), axis=0 + ) + elif filtertype == "spline": + data = df.copy() + mask_data = data.columns.get_level_values("coords").isin(("x", "y")) + xy = data.loc[:, mask_data].values + prob = data.loc[:, ~mask_data].values + missing = np.isnan(xy) + xy_filled = columnwise_spline_interp(xy, windowlength) + filled = ~np.isnan(xy_filled) + xy[filled] = xy_filled[filled] + inds = np.argwhere(missing & filled) + if inds.size: + # Retrieve original individual label indices + inds[:, 1] //= 2 + inds = np.unique(inds, axis=0) + prob[inds[:, 0], inds[:, 1]] = 0.01 + data.loc[:, ~mask_data] = prob + data.loc[:, mask_data] = xy + else: + raise ValueError(f"Unknown filter type {filtertype}") + + outdataname = filepath.replace(".h5", "_filtered.h5") + data.to_hdf(outdataname, "df_with_missing", format="table", mode="w") + if save_as_csv: + print("Saving filtered csv poses!") + data.to_csv(outdataname.split(".h5")[0] + ".csv") + except FileNotFoundError as e: + print(e) + continue if __name__ == "__main__": From 59ac8edc66ec94f45103594ac3765c894c3aad52 Mon Sep 17 00:00:00 2001 From: Poruri Sai Rahul Date: Sat, 16 Jul 2022 10:08:16 +0100 Subject: [PATCH 2/7] REF: Reduce the code within the try block the second try block in filterpredictions looked for the analyzed file and performed operations to filter it - and if the file is not found, continue to the next item in the for loop. this commit reduces the try block to only load the analyzed file and immediately catch the error and continue if the file doesnt exist. the rest of the filtering on the analyzed data is performed outside the try/except block. modified: deeplabcut/post_processing/filtering.py --- deeplabcut/post_processing/filtering.py | 103 ++++++++++++------------ 1 file changed, 52 insertions(+), 51 deletions(-) diff --git a/deeplabcut/post_processing/filtering.py b/deeplabcut/post_processing/filtering.py index b349d4bd20..f1d57b413f 100644 --- a/deeplabcut/post_processing/filtering.py +++ b/deeplabcut/post_processing/filtering.py @@ -226,61 +226,62 @@ def filterpredictions( df, filepath, _, _ = auxiliaryfunctions.load_analyzed_data( destfolder, vname, DLCscorer, track_method=track_method ) - nrows = df.shape[0] - if filtertype == "arima": - temp = df.values.reshape((nrows, -1, 3)) - placeholder = np.empty_like(temp) - for i in range(temp.shape[1]): - x, y, p = temp[:, i].T - meanx, _ = FitSARIMAXModel( - x, p, p_bound, alpha, ARdegree, MAdegree, False - ) - meany, _ = FitSARIMAXModel( - y, p, p_bound, alpha, ARdegree, MAdegree, False - ) - meanx[0] = x[0] - meany[0] = y[0] - placeholder[:, i] = np.c_[meanx, meany, p] - data = pd.DataFrame( - placeholder.reshape((nrows, -1)), - columns=df.columns, - index=df.index, - ) - elif filtertype == "median": - data = df.copy() - mask = data.columns.get_level_values("coords") != "likelihood" - data.loc[:, mask] = df.loc[:, mask].apply( - signal.medfilt, args=(windowlength,), axis=0 - ) - elif filtertype == "spline": - data = df.copy() - mask_data = data.columns.get_level_values("coords").isin(("x", "y")) - xy = data.loc[:, mask_data].values - prob = data.loc[:, ~mask_data].values - missing = np.isnan(xy) - xy_filled = columnwise_spline_interp(xy, windowlength) - filled = ~np.isnan(xy_filled) - xy[filled] = xy_filled[filled] - inds = np.argwhere(missing & filled) - if inds.size: - # Retrieve original individual label indices - inds[:, 1] //= 2 - inds = np.unique(inds, axis=0) - prob[inds[:, 0], inds[:, 1]] = 0.01 - data.loc[:, ~mask_data] = prob - data.loc[:, mask_data] = xy - else: - raise ValueError(f"Unknown filter type {filtertype}") - - outdataname = filepath.replace(".h5", "_filtered.h5") - data.to_hdf(outdataname, "df_with_missing", format="table", mode="w") - if save_as_csv: - print("Saving filtered csv poses!") - data.to_csv(outdataname.split(".h5")[0] + ".csv") except FileNotFoundError as e: print(e) continue + nrows = df.shape[0] + if filtertype == "arima": + temp = df.values.reshape((nrows, -1, 3)) + placeholder = np.empty_like(temp) + for i in range(temp.shape[1]): + x, y, p = temp[:, i].T + meanx, _ = FitSARIMAXModel( + x, p, p_bound, alpha, ARdegree, MAdegree, False + ) + meany, _ = FitSARIMAXModel( + y, p, p_bound, alpha, ARdegree, MAdegree, False + ) + meanx[0] = x[0] + meany[0] = y[0] + placeholder[:, i] = np.c_[meanx, meany, p] + data = pd.DataFrame( + placeholder.reshape((nrows, -1)), + columns=df.columns, + index=df.index, + ) + elif filtertype == "median": + data = df.copy() + mask = data.columns.get_level_values("coords") != "likelihood" + data.loc[:, mask] = df.loc[:, mask].apply( + signal.medfilt, args=(windowlength,), axis=0 + ) + elif filtertype == "spline": + data = df.copy() + mask_data = data.columns.get_level_values("coords").isin(("x", "y")) + xy = data.loc[:, mask_data].values + prob = data.loc[:, ~mask_data].values + missing = np.isnan(xy) + xy_filled = columnwise_spline_interp(xy, windowlength) + filled = ~np.isnan(xy_filled) + xy[filled] = xy_filled[filled] + inds = np.argwhere(missing & filled) + if inds.size: + # Retrieve original individual label indices + inds[:, 1] //= 2 + inds = np.unique(inds, axis=0) + prob[inds[:, 0], inds[:, 1]] = 0.01 + data.loc[:, ~mask_data] = prob + data.loc[:, mask_data] = xy + else: + raise ValueError(f"Unknown filter type {filtertype}") + + outdataname = filepath.replace(".h5", "_filtered.h5") + data.to_hdf(outdataname, "df_with_missing", format="table", mode="w") + if save_as_csv: + print("Saving filtered csv poses!") + data.to_csv(outdataname.split(".h5")[0] + ".csv") + if __name__ == "__main__": parser = argparse.ArgumentParser() From 8a0efd9122e073d7ff9973a1e9af568a78d7a945 Mon Sep 17 00:00:00 2001 From: Poruri Sai Rahul Date: Sat, 16 Jul 2022 10:16:58 +0100 Subject: [PATCH 3/7] FEAT: Return video filename to filtered dataframe mapping the filterpredictions now returns a mapping instead of returning None. modified: deeplabcut/post_processing/filtering.py --- deeplabcut/post_processing/filtering.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/deeplabcut/post_processing/filtering.py b/deeplabcut/post_processing/filtering.py index f1d57b413f..da33ccf506 100644 --- a/deeplabcut/post_processing/filtering.py +++ b/deeplabcut/post_processing/filtering.py @@ -148,7 +148,12 @@ def filterpredictions( Returns ------- - None + video_to_filtered_df + Dictionary mapping video filepaths to filtered dataframes. + + * If no videos exist, the dictionary will be empty. + * If a video is not analyzed, the corresponding value in the dictionary will be + None. Examples -------- @@ -200,9 +205,11 @@ def filterpredictions( ) Videos = auxiliaryfunctions.get_list_of_videos(video, videotype) + video_to_filtered_df = {} + if not len(Videos): print("No video(s) were found. Please check your paths and/or 'videotype'.") - return + return video_to_filtered_df for video in Videos: if destfolder is None: @@ -212,10 +219,11 @@ def filterpredictions( vname = Path(video).stem try: - _ = auxiliaryfunctions.load_analyzed_data( + df, filepath, _, _ = auxiliaryfunctions.load_analyzed_data( destfolder, vname, DLCscorer, True, track_method ) print(f"Data from {vname} were already filtered. Skipping...") + video_to_filtered_df[video] = df # Data has been filtered so continue to the next video continue except FileNotFoundError: @@ -227,6 +235,7 @@ def filterpredictions( destfolder, vname, DLCscorer, track_method=track_method ) except FileNotFoundError as e: + video_to_filtered_df[video] = None print(e) continue @@ -276,6 +285,8 @@ def filterpredictions( else: raise ValueError(f"Unknown filter type {filtertype}") + video_to_filtered_df[video] = data + outdataname = filepath.replace(".h5", "_filtered.h5") data.to_hdf(outdataname, "df_with_missing", format="table", mode="w") if save_as_csv: From b598c3392c086974fb532535f2419ead8c06451d Mon Sep 17 00:00:00 2001 From: Poruri Sai Rahul Date: Sat, 16 Jul 2022 10:27:09 +0100 Subject: [PATCH 4/7] REF: Reduce scope of try/except instead of finding and analyzing the skeleton in the try block, this commit reduces the scope of the try block to only finding the file and moves the skeleton calculations to outside the try/except. modified: deeplabcut/post_processing/analyze_skeleton.py --- .../post_processing/analyze_skeleton.py | 46 +++++++++---------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/deeplabcut/post_processing/analyze_skeleton.py b/deeplabcut/post_processing/analyze_skeleton.py index f2073fc494..7c92f07d80 100644 --- a/deeplabcut/post_processing/analyze_skeleton.py +++ b/deeplabcut/post_processing/analyze_skeleton.py @@ -256,33 +256,33 @@ def analyzeskeleton( df, filepath, scorer, _ = auxiliaryfunctions.load_analyzed_data( destfolder, vname, DLCscorer, filtered, track_method ) - output_name = filepath.replace(".h5", f"_skeleton.h5") - if os.path.isfile(output_name): - print(f"Skeleton in video {vname} already processed. Skipping...") - continue - - bones = {} - if "individuals" in df.columns.names: - for animal_name, df_ in df.groupby(level="individuals", axis=1): - temp = df_.droplevel(["scorer", "individuals"], axis=1) - if animal_name != "single": - for bp1, bp2 in cfg["skeleton"]: - name = "{}_{}_{}".format(animal_name, bp1, bp2) - bones[name] = analyzebone(temp[bp1], temp[bp2]) - else: - for bp1, bp2 in cfg["skeleton"]: - name = "{}_{}".format(bp1, bp2) - bones[name] = analyzebone(df[scorer][bp1], df[scorer][bp2]) - - skeleton = pd.concat(bones, axis=1) - skeleton.to_hdf(output_name, "df_with_missing", format="table", mode="w") - if save_as_csv: - skeleton.to_csv(output_name.replace(".h5", ".csv")) - except FileNotFoundError as e: print(e) continue + output_name = filepath.replace(".h5", f"_skeleton.h5") + if os.path.isfile(output_name): + print(f"Skeleton in video {vname} already processed. Skipping...") + continue + + bones = {} + if "individuals" in df.columns.names: + for animal_name, df_ in df.groupby(level="individuals", axis=1): + temp = df_.droplevel(["scorer", "individuals"], axis=1) + if animal_name != "single": + for bp1, bp2 in cfg["skeleton"]: + name = "{}_{}_{}".format(animal_name, bp1, bp2) + bones[name] = analyzebone(temp[bp1], temp[bp2]) + else: + for bp1, bp2 in cfg["skeleton"]: + name = "{}_{}".format(bp1, bp2) + bones[name] = analyzebone(df[scorer][bp1], df[scorer][bp2]) + + skeleton = pd.concat(bones, axis=1) + skeleton.to_hdf(output_name, "df_with_missing", format="table", mode="w") + if save_as_csv: + skeleton.to_csv(output_name.replace(".h5", ".csv")) + if __name__ == "__main__": parser = argparse.ArgumentParser() From 61ac8f587e4743ced82d4ce0c2ad262bbd1241ee Mon Sep 17 00:00:00 2001 From: Poruri Sai Rahul Date: Sat, 16 Jul 2022 10:33:06 +0100 Subject: [PATCH 5/7] FIX: Actually return the mapping modified: deeplabcut/post_processing/filtering.py --- deeplabcut/post_processing/filtering.py | 1 + 1 file changed, 1 insertion(+) diff --git a/deeplabcut/post_processing/filtering.py b/deeplabcut/post_processing/filtering.py index da33ccf506..04eef995c9 100644 --- a/deeplabcut/post_processing/filtering.py +++ b/deeplabcut/post_processing/filtering.py @@ -292,6 +292,7 @@ def filterpredictions( if save_as_csv: print("Saving filtered csv poses!") data.to_csv(outdataname.split(".h5")[0] + ".csv") + return video_to_filtered_df if __name__ == "__main__": From b6b408d862be7920676851b644ae5762e13b026f Mon Sep 17 00:00:00 2001 From: Poruri Sai Rahul Date: Sat, 16 Jul 2022 10:34:08 +0100 Subject: [PATCH 6/7] FEAT: Return mapping from video filename to skeleton dataframe the analyze_skeleton function now returns a mapping instead of None modified: deeplabcut/post_processing/analyze_skeleton.py --- deeplabcut/post_processing/analyze_skeleton.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/deeplabcut/post_processing/analyze_skeleton.py b/deeplabcut/post_processing/analyze_skeleton.py index 7c92f07d80..ee36c97a27 100644 --- a/deeplabcut/post_processing/analyze_skeleton.py +++ b/deeplabcut/post_processing/analyze_skeleton.py @@ -230,13 +230,20 @@ def analyzeskeleton( Returns ------- - None + video_to_skeleton_df + Dictionary mapping video filepaths to skeleton dataframes. + + * If no videos exist, the dictionary will be empty. + * If a video is not analyzed, the corresponding value in the dictionary will be + None. """ # Load config file, scorer and videos cfg = auxiliaryfunctions.read_config(config) if not cfg["skeleton"]: raise ValueError("No skeleton defined in the config.yaml.") + video_to_skeleton_df = {} + track_method = auxfun_multianimal.get_track_method(cfg, track_method=track_method) DLCscorer, DLCscorerlegacy = auxiliaryfunctions.GetScorerName( cfg, @@ -258,11 +265,13 @@ def analyzeskeleton( ) except FileNotFoundError as e: print(e) + video_to_skeleton_df[video] = None continue output_name = filepath.replace(".h5", f"_skeleton.h5") if os.path.isfile(output_name): print(f"Skeleton in video {vname} already processed. Skipping...") + video_to_skeleton_df[video] = pd.read_hdf(output_name, "df_with_missing") continue bones = {} @@ -279,9 +288,11 @@ def analyzeskeleton( bones[name] = analyzebone(df[scorer][bp1], df[scorer][bp2]) skeleton = pd.concat(bones, axis=1) + video_to_skeleton_df[video] = skeleton skeleton.to_hdf(output_name, "df_with_missing", format="table", mode="w") if save_as_csv: skeleton.to_csv(output_name.replace(".h5", ".csv")) + return video_to_skeleton_df if __name__ == "__main__": From 6e7e47e0da99c19ef3c9e657ca50ec14598be4ef Mon Sep 17 00:00:00 2001 From: Jessy Lauer <30733203+jeylau@users.noreply.github.com> Date: Tue, 3 Oct 2023 14:27:38 +0200 Subject: [PATCH 7/7] Add return_data flag --- deeplabcut/post_processing/analyze_skeleton.py | 8 +++++++- deeplabcut/post_processing/filtering.py | 11 +++++++++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/deeplabcut/post_processing/analyze_skeleton.py b/deeplabcut/post_processing/analyze_skeleton.py index ee36c97a27..8040fb4921 100644 --- a/deeplabcut/post_processing/analyze_skeleton.py +++ b/deeplabcut/post_processing/analyze_skeleton.py @@ -177,6 +177,7 @@ def analyzeskeleton( destfolder=None, modelprefix="", track_method="", + return_data=False, ): """Extracts length and orientation of each "bone" of the skeleton. @@ -228,6 +229,9 @@ def analyzeskeleton( For multiple animals, must be either 'box', 'skeleton', or 'ellipse' and will be taken from the config.yaml file if none is given. + return_data: bool, optional, default=False + If True, returns a dictionary of the filtered data keyed by video names. + Returns ------- video_to_skeleton_df @@ -292,7 +296,9 @@ def analyzeskeleton( skeleton.to_hdf(output_name, "df_with_missing", format="table", mode="w") if save_as_csv: skeleton.to_csv(output_name.replace(".h5", ".csv")) - return video_to_skeleton_df + + if return_data: + return video_to_skeleton_df if __name__ == "__main__": diff --git a/deeplabcut/post_processing/filtering.py b/deeplabcut/post_processing/filtering.py index 04eef995c9..6cc3b4b8c8 100644 --- a/deeplabcut/post_processing/filtering.py +++ b/deeplabcut/post_processing/filtering.py @@ -80,6 +80,7 @@ def filterpredictions( destfolder=None, modelprefix="", track_method="", + return_data=False, ): """Fits frame-by-frame pose predictions. @@ -146,6 +147,9 @@ def filterpredictions( For multiple animals, must be either 'box', 'skeleton', or 'ellipse' and will be taken from the config.yaml file if none is given. + return_data: bool, optional, default=False + If True, returns a dictionary of the filtered data keyed by video names. + Returns ------- video_to_filtered_df @@ -209,7 +213,8 @@ def filterpredictions( if not len(Videos): print("No video(s) were found. Please check your paths and/or 'videotype'.") - return video_to_filtered_df + if return_data: + return video_to_filtered_df for video in Videos: if destfolder is None: @@ -292,7 +297,9 @@ def filterpredictions( if save_as_csv: print("Saving filtered csv poses!") data.to_csv(outdataname.split(".h5")[0] + ".csv") - return video_to_filtered_df + + if return_data: + return video_to_filtered_df if __name__ == "__main__":