From fe8224b677b69028700f2ed4f06f6ec62c92d60e Mon Sep 17 00:00:00 2001 From: Thinh Nguyen Date: Wed, 7 Aug 2024 16:03:26 -0500 Subject: [PATCH 1/8] feat(pose_estimation): use `memoized_results` --- element_deeplabcut/model.py | 32 +++++++++++++++++------- element_deeplabcut/readers/dlc_reader.py | 1 + 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/element_deeplabcut/model.py b/element_deeplabcut/model.py index 52d54b2..08b1a9a 100644 --- a/element_deeplabcut/model.py +++ b/element_deeplabcut/model.py @@ -15,7 +15,7 @@ from pathlib import Path from typing import Optional from datetime import datetime -from element_interface.utils import find_full_path, find_root_directory +from element_interface.utils import find_full_path, find_root_directory, memoized_result from .readers import dlc_reader schema = dj.schema() @@ -728,22 +728,36 @@ def make(self, key): project_path = find_full_path( get_dlc_root_data_dir(), dlc_model["project_path"] ) + video_relpaths = list((VideoRecording.File & key).fetch("file_path")) video_filepaths = [ find_full_path(get_dlc_root_data_dir(), fp).as_posix() - for fp in (VideoRecording.File & key).fetch("file_path") + for fp in video_relpaths ] analyze_video_params = (PoseEstimationTask & key).fetch1( "pose_estimation_params" ) or {} - dlc_reader.do_pose_estimation( - key, - video_filepaths, - dlc_model, - project_path, - output_dir, - **analyze_video_params, + @memoized_result( + uniqueness_dict={ + **analyze_video_params, + "project_path": dlc_model["project_path"], + "shuffle": dlc_model["shuffle"], + "trainingsetindex": dlc_model["trainingsetindex"], + "video_filepaths": video_relpaths, + }, + output_directory=output_dir, ) + def do_pose_estimation(): + dlc_reader.do_pose_estimation( + key, + video_filepaths, + dlc_model, + project_path, + output_dir, + **analyze_video_params, + ) + + do_pose_estimation() dlc_result = dlc_reader.PoseEstimation(output_dir) creation_time = datetime.fromtimestamp(dlc_result.creation_time).strftime( diff --git a/element_deeplabcut/readers/dlc_reader.py b/element_deeplabcut/readers/dlc_reader.py index e29066f..5dcaaa0 100644 --- a/element_deeplabcut/readers/dlc_reader.py +++ b/element_deeplabcut/readers/dlc_reader.py @@ -332,6 +332,7 @@ def do_pose_estimation( dlc_config["project_path"] = dlc_project_path.as_posix() # ---- Add current video to config --- + # FIXME: I don't think the code block below is necessary for video_filepath in video_filepaths: if video_filepath not in dlc_config["video_sets"]: try: From bdf0ae250a3bf2f17a55dc3d2d364e94a92c9584 Mon Sep 17 00:00:00 2001 From: Thinh Nguyen Date: Wed, 7 Aug 2024 16:58:10 -0500 Subject: [PATCH 2/8] chore: rename `dlc_model` to avoid easy naming collision --- element_deeplabcut/model.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/element_deeplabcut/model.py b/element_deeplabcut/model.py index 08b1a9a..3e846bb 100644 --- a/element_deeplabcut/model.py +++ b/element_deeplabcut/model.py @@ -705,7 +705,7 @@ class BodyPartPosition(dj.Part): def make(self, key): """.populate() method will launch training for each PoseEstimationTask""" # ID model and directories - dlc_model = (Model & key).fetch1() + dlc_model_ = (Model & key).fetch1() task_mode, output_dir = (PoseEstimationTask & key).fetch1( "task_mode", "pose_estimation_output_dir" ) @@ -726,7 +726,7 @@ def make(self, key): # - video_filepaths: full paths to the video files for inference # - analyze_video_params: optional parameters to analyze video project_path = find_full_path( - get_dlc_root_data_dir(), dlc_model["project_path"] + get_dlc_root_data_dir(), dlc_model_["project_path"] ) video_relpaths = list((VideoRecording.File & key).fetch("file_path")) video_filepaths = [ @@ -740,9 +740,9 @@ def make(self, key): @memoized_result( uniqueness_dict={ **analyze_video_params, - "project_path": dlc_model["project_path"], - "shuffle": dlc_model["shuffle"], - "trainingsetindex": dlc_model["trainingsetindex"], + "project_path": dlc_model_["project_path"], + "shuffle": dlc_model_["shuffle"], + "trainingsetindex": dlc_model_["trainingsetindex"], "video_filepaths": video_relpaths, }, output_directory=output_dir, @@ -751,7 +751,7 @@ def do_pose_estimation(): dlc_reader.do_pose_estimation( key, video_filepaths, - dlc_model, + dlc_model_, project_path, output_dir, **analyze_video_params, From c7aba7d1b16fe0a906418b95cab373ffbafe5960 Mon Sep 17 00:00:00 2001 From: Thinh Nguyen Date: Thu, 8 Aug 2024 10:50:00 -0500 Subject: [PATCH 3/8] fix: directly implement logic of `do_pose_estimation` in the `make` --- element_deeplabcut/model.py | 47 ++++++++++++++++++++++++++++--------- 1 file changed, 36 insertions(+), 11 deletions(-) diff --git a/element_deeplabcut/model.py b/element_deeplabcut/model.py index 3e846bb..eabce07 100644 --- a/element_deeplabcut/model.py +++ b/element_deeplabcut/model.py @@ -327,7 +327,8 @@ class Model(dj.Manual): snapshotindex : int # which snapshot for prediction (if -1, latest) shuffle : int # Shuffle (1) or not (0) trainingsetindex : int # Index of training fraction list in config.yaml - unique index (task, date, iteration, shuffle, snapshotindex, trainingsetindex) + engine='tensorflow' : varchar(16) # DLC engine + unique index (task, date, iteration, shuffle, snapshotindex, trainingsetindex, engine) scorer : varchar(64) # Scorer/network name - DLC's GetScorerName() config_template : longblob # Dictionary of the config for analyze_videos() project_path : varchar(255) # DLC's project_path in config relative to root @@ -719,7 +720,7 @@ def make(self, key): ) output_dir = find_full_path(get_dlc_root_data_dir(), output_dir) - # Triger PoseEstimation + # Trigger PoseEstimation if task_mode == "trigger": # Triggering dlc for pose estimation required: # - project_path: full path to the directory containing the trained model @@ -747,17 +748,41 @@ def make(self, key): }, output_directory=output_dir, ) - def do_pose_estimation(): - dlc_reader.do_pose_estimation( - key, - video_filepaths, - dlc_model_, - project_path, - output_dir, - **analyze_video_params, + def do_analyze_videos(): + from deeplabcut.pose_estimation_tensorflow import analyze_videos + from .readers import save_yaml + + # ---- Build and save DLC configuration (yaml) file ---- + dlc_config = dlc_model_["config_template"] + dlc_project_path = Path(project_path) + dlc_config["project_path"] = dlc_project_path.as_posix() + + # ---- Write config files ---- + # To output dir: Important for loading/parsing output in datajoint + _ = save_yaml(output_dir, dlc_config) + # To project dir: Required by DLC to run the analyze_videos + if dlc_project_path != output_dir: + config_filepath = save_yaml(dlc_project_path, dlc_config) + + # ---- Take valid parameters for analyze_videos ---- + kwargs = { + k: v + for k, v in analyze_video_params.items() + if k in inspect.signature(analyze_videos).parameters + } + + # ---- Trigger DLC prediction job ---- + analyze_videos( + config=config_filepath, + videos=video_filepaths, + shuffle=dlc_model_["shuffle"], + trainingsetindex=dlc_model_["trainingsetindex"], + destfolder=output_dir, + modelprefix=dlc_model_["model_prefix"], + **kwargs, ) - do_pose_estimation() + do_analyze_videos() dlc_result = dlc_reader.PoseEstimation(output_dir) creation_time = datetime.fromtimestamp(dlc_result.creation_time).strftime( From c061e9af342c30894345eccc809c40ce51082147 Mon Sep 17 00:00:00 2001 From: Thinh Nguyen Date: Thu, 8 Aug 2024 10:51:10 -0500 Subject: [PATCH 4/8] fix: minor import bug --- element_deeplabcut/model.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/element_deeplabcut/model.py b/element_deeplabcut/model.py index eabce07..e365c3b 100644 --- a/element_deeplabcut/model.py +++ b/element_deeplabcut/model.py @@ -750,7 +750,6 @@ def make(self, key): ) def do_analyze_videos(): from deeplabcut.pose_estimation_tensorflow import analyze_videos - from .readers import save_yaml # ---- Build and save DLC configuration (yaml) file ---- dlc_config = dlc_model_["config_template"] @@ -759,10 +758,10 @@ def do_analyze_videos(): # ---- Write config files ---- # To output dir: Important for loading/parsing output in datajoint - _ = save_yaml(output_dir, dlc_config) + _ = dlc_reader.save_yaml(output_dir, dlc_config) # To project dir: Required by DLC to run the analyze_videos if dlc_project_path != output_dir: - config_filepath = save_yaml(dlc_project_path, dlc_config) + config_filepath = dlc_reader.save_yaml(dlc_project_path, dlc_config) # ---- Take valid parameters for analyze_videos ---- kwargs = { From 52175f4704bd0b93e541e9afd599fef1450bdff1 Mon Sep 17 00:00:00 2001 From: Thinh Nguyen Date: Thu, 8 Aug 2024 10:56:41 -0500 Subject: [PATCH 5/8] fix: remove prototyping code --- element_deeplabcut/model.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/element_deeplabcut/model.py b/element_deeplabcut/model.py index e365c3b..153fb7c 100644 --- a/element_deeplabcut/model.py +++ b/element_deeplabcut/model.py @@ -327,8 +327,7 @@ class Model(dj.Manual): snapshotindex : int # which snapshot for prediction (if -1, latest) shuffle : int # Shuffle (1) or not (0) trainingsetindex : int # Index of training fraction list in config.yaml - engine='tensorflow' : varchar(16) # DLC engine - unique index (task, date, iteration, shuffle, snapshotindex, trainingsetindex, engine) + unique index (task, date, iteration, shuffle, snapshotindex, trainingsetindex) scorer : varchar(64) # Scorer/network name - DLC's GetScorerName() config_template : longblob # Dictionary of the config for analyze_videos() project_path : varchar(255) # DLC's project_path in config relative to root From fd6ba277204fe2f3a3f9f5d092f38e319f2ee628 Mon Sep 17 00:00:00 2001 From: Thinh Nguyen Date: Thu, 8 Aug 2024 11:42:39 -0500 Subject: [PATCH 6/8] feat: better handling for "cropping" input --- element_deeplabcut/model.py | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/element_deeplabcut/model.py b/element_deeplabcut/model.py index 153fb7c..340f3c0 100644 --- a/element_deeplabcut/model.py +++ b/element_deeplabcut/model.py @@ -14,7 +14,7 @@ import pandas as pd from pathlib import Path from typing import Optional -from datetime import datetime +from datetime import datetime, timezone from element_interface.utils import find_full_path, find_root_directory, memoized_result from .readers import dlc_reader @@ -755,12 +755,34 @@ def do_analyze_videos(): dlc_project_path = Path(project_path) dlc_config["project_path"] = dlc_project_path.as_posix() + # ---- Special handling for "cropping" ---- + # `analyze_videos` behavior: + # i) if is None, use the "cropping" from the config file + # ii) if defined, use the specified "cropping" values but not updating the config file + # new behavior: if defined as "False", overwrite "cropping" to False in config file + cropping = analyze_video_params.get("cropping", None) + if cropping is not None: + if cropping: + dlc_config["cropping"] = True + ( + dlc_config["x1"], + dlc_config["x2"], + dlc_config["y1"], + dlc_config["y2"], + ) = cropping + else: # cropping is False + dlc_config["cropping"] = False + # ---- Write config files ---- # To output dir: Important for loading/parsing output in datajoint _ = dlc_reader.save_yaml(output_dir, dlc_config) # To project dir: Required by DLC to run the analyze_videos if dlc_project_path != output_dir: - config_filepath = dlc_reader.save_yaml(dlc_project_path, dlc_config) + config_filepath = dlc_reader.save_yaml( + dlc_project_path, + dlc_config, + filename=f"config_{datetime.now(tz=timezone.utc).strftime('%Y%m%d_%H%M%S')}.yaml", + ) # ---- Take valid parameters for analyze_videos ---- kwargs = { From 129f661b8d905320f06bfd5d46b09757c97cd387 Mon Sep 17 00:00:00 2001 From: Thinh Nguyen Date: Thu, 8 Aug 2024 14:59:36 -0500 Subject: [PATCH 7/8] chore: explicit & consistent `config_filename ` --- element_deeplabcut/model.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/element_deeplabcut/model.py b/element_deeplabcut/model.py index 340f3c0..2bb0bd6 100644 --- a/element_deeplabcut/model.py +++ b/element_deeplabcut/model.py @@ -774,14 +774,17 @@ def do_analyze_videos(): dlc_config["cropping"] = False # ---- Write config files ---- + config_filename = f"dj_dlc_config_{datetime.now(tz=timezone.utc).strftime('%Y%m%d_%H%M%S')}.yaml" # To output dir: Important for loading/parsing output in datajoint - _ = dlc_reader.save_yaml(output_dir, dlc_config) + _ = dlc_reader.save_yaml( + output_dir, dlc_config, filename=config_filename + ) # To project dir: Required by DLC to run the analyze_videos if dlc_project_path != output_dir: config_filepath = dlc_reader.save_yaml( dlc_project_path, dlc_config, - filename=f"config_{datetime.now(tz=timezone.utc).strftime('%Y%m%d_%H%M%S')}.yaml", + filename=config_filename, ) # ---- Take valid parameters for analyze_videos ---- From c2eb09029ca4cfd1f182f7472799170c09b72d2e Mon Sep 17 00:00:00 2001 From: Thinh Nguyen Date: Thu, 8 Aug 2024 15:34:42 -0500 Subject: [PATCH 8/8] update: bump version --- element_deeplabcut/readers/dlc_reader.py | 6 ++++++ element_deeplabcut/version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/element_deeplabcut/readers/dlc_reader.py b/element_deeplabcut/readers/dlc_reader.py index 5dcaaa0..f682b6a 100644 --- a/element_deeplabcut/readers/dlc_reader.py +++ b/element_deeplabcut/readers/dlc_reader.py @@ -324,6 +324,12 @@ def do_pose_estimation( resulting in constant memory footprint. """ + # this function should no longer be used, throw a deprecation warning + logger.warning( + "This function is deprecated and will be removed in a future release. " + + "Its usage is now incorporated into model.PoseEstimation's `make` function" + ) + from deeplabcut.pose_estimation_tensorflow import analyze_videos # ---- Build and save DLC configuration (yaml) file ---- diff --git a/element_deeplabcut/version.py b/element_deeplabcut/version.py index 48d8b3a..316ba32 100644 --- a/element_deeplabcut/version.py +++ b/element_deeplabcut/version.py @@ -2,4 +2,4 @@ Package metadata """ -__version__ = "0.2.14" +__version__ = "0.3.0"