diff --git a/deeplabcut/api/create_project.py b/deeplabcut/api/create_project.py index d8d21b5ded..54e3938f60 100644 --- a/deeplabcut/api/create_project.py +++ b/deeplabcut/api/create_project.py @@ -17,7 +17,7 @@ from collections.abc import Sequence from deeplabcut.api._tf_routing import with_tensorflow_fallback -from deeplabcut.core.deprecation import renamed_parameter +from deeplabcut.core.deprecation import DeprecationRound, renamed_parameter from deeplabcut.core.engine import Engine @@ -26,7 +26,7 @@ tensorflow_module="deeplabcut.tensorflow_compat.create_project", tensorflow_name="_tf_create_pretrained_project", ) -@renamed_parameter(old="videotype", new="video_extensions", since="3.0.0") +@renamed_parameter(old="videotype", new="video_extensions", deprecation_round=DeprecationRound.PARAMETER_CONSISTENCY) def create_pretrained_project( project: str, experimenter: str, diff --git a/deeplabcut/api/modelzoo_inference.py b/deeplabcut/api/modelzoo_inference.py index e3f1803e75..b5e749cbe5 100644 --- a/deeplabcut/api/modelzoo_inference.py +++ b/deeplabcut/api/modelzoo_inference.py @@ -13,7 +13,7 @@ from pathlib import Path from deeplabcut.api._tf_routing import with_tensorflow_fallback -from deeplabcut.core.deprecation import renamed_parameter +from deeplabcut.core.deprecation import DeprecationRound, renamed_parameter @with_tensorflow_fallback( @@ -21,7 +21,7 @@ tensorflow_module="deeplabcut.tensorflow_compat.superanimal_inference", tensorflow_name="video_inference_superanimal_tf", ) -@renamed_parameter(old="videotype", new="video_extensions", since="3.0.0") +@renamed_parameter(old="videotype", new="video_extensions", deprecation_round=DeprecationRound.PARAMETER_CONSISTENCY) def video_inference_superanimal( videos: str | list, superanimal_name: str, diff --git a/deeplabcut/api/pose_estimation.py b/deeplabcut/api/pose_estimation.py index 4eef5c292c..e808f29d96 100644 --- a/deeplabcut/api/pose_estimation.py +++ b/deeplabcut/api/pose_estimation.py @@ -28,7 +28,7 @@ from deeplabcut.api._tf_routing import with_tensorflow_fallback -from deeplabcut.core.deprecation import deprecated, renamed_parameter +from deeplabcut.core.deprecation import DeprecationRound, deprecated, renamed_parameter @with_tensorflow_fallback( @@ -43,7 +43,7 @@ ], renamed_params={"keepdeconvweights": "load_head_weights"}, ) -@renamed_parameter(old="displayiters", new="display_iters", since="3.0.0") +@renamed_parameter(old="displayiters", new="display_iters", deprecation_round=DeprecationRound.PARAMETER_CONSISTENCY) def train_network( config: ProjectConfig | dict | Path | str, shuffle: int = 1, @@ -167,8 +167,10 @@ def return_train_network_path( @with_tensorflow_fallback(normalize_gputouse=True, dropped_params=["rescale"]) -@renamed_parameter(old="Shuffles", new="shuffles", since="3.0.0") -@renamed_parameter(old="comparisonbodyparts", new="comparison_bodyparts", since="3.0.0") +@renamed_parameter(old="Shuffles", new="shuffles", deprecation_round=DeprecationRound.PARAMETER_CONSISTENCY) +@renamed_parameter( + old="comparisonbodyparts", new="comparison_bodyparts", deprecation_round=DeprecationRound.PARAMETER_CONSISTENCY +) def evaluate_network( config: ProjectConfig | dict | Path | str, shuffles: Iterable[int] = (1,), @@ -269,8 +271,10 @@ def evaluate_network( @with_tensorflow_fallback -@renamed_parameter(old="comparisonbodyparts", new="comparison_bodyparts", since="3.0.0") -@renamed_parameter(old="Snapindex", new="snapshotindex", since="3.0.0") +@renamed_parameter( + old="comparisonbodyparts", new="comparison_bodyparts", deprecation_round=DeprecationRound.PARAMETER_CONSISTENCY +) +@renamed_parameter(old="Snapindex", new="snapshotindex", deprecation_round=DeprecationRound.PARAMETER_CONSISTENCY) def return_evaluate_network_data( config: ProjectConfig | dict | Path | str, shuffle: int = 0, @@ -295,8 +299,8 @@ def return_evaluate_network_data( "use_openvino", ], ) -@renamed_parameter(old="batchsize", new="batch_size", since="3.0.0") -@renamed_parameter(old="videotype", new="video_extensions", since="3.0.0") +@renamed_parameter(old="batchsize", new="batch_size", deprecation_round=DeprecationRound.PARAMETER_CONSISTENCY) +@renamed_parameter(old="videotype", new="video_extensions", deprecation_round=DeprecationRound.PARAMETER_CONSISTENCY) def analyze_videos( config: ProjectConfig | dict | Path | str, videos: str | list[str], @@ -448,8 +452,8 @@ def analyze_videos( @with_tensorflow_fallback(normalize_gputouse=True, dropped_params=["TFGPUinference"]) -@renamed_parameter(old="batchsize", new="batch_size", since="3.0.0") -@renamed_parameter(old="videotype", new="video_extensions", since="3.0.0") +@renamed_parameter(old="batchsize", new="batch_size", deprecation_round=DeprecationRound.PARAMETER_CONSISTENCY) +@renamed_parameter(old="videotype", new="video_extensions", deprecation_round=DeprecationRound.PARAMETER_CONSISTENCY) def create_tracking_dataset( config: ProjectConfig | dict | Path | str, videos: list[str] | list[Path], @@ -600,7 +604,7 @@ def analyze_images( ) -@deprecated(replacement="deeplabcut.analyze_images", since="3.1") +@deprecated(replacement="deeplabcut.analyze_images", deprecation_round=DeprecationRound.IMAGE_ANALYSIS_MIGRATION) @with_tensorflow_fallback(normalize_gputouse=True, dropped_params=["frametype"]) def analyze_time_lapse_frames( config: ProjectConfig | dict | Path | str, @@ -638,7 +642,7 @@ def analyze_time_lapse_frames( @with_tensorflow_fallback(normalize_gputouse=True) -@renamed_parameter(old="videotype", new="video_extensions", since="3.0.0") +@renamed_parameter(old="videotype", new="video_extensions", deprecation_round=DeprecationRound.PARAMETER_CONSISTENCY) def convert_detections2tracklets( config: ProjectConfig | dict | Path | str, videos: str | list[str], @@ -843,7 +847,9 @@ def visualize_paf( @with_tensorflow_fallback(normalize_gputouse=True, renamed_params={"Indices": "indices"}) -@renamed_parameter(old="comparisonbodyparts", new="comparison_bodyparts", since="3.0.0") +@renamed_parameter( + old="comparisonbodyparts", new="comparison_bodyparts", deprecation_round=DeprecationRound.PARAMETER_CONSISTENCY +) def extract_save_all_maps( config: ProjectConfig | dict | Path | str, shuffle: int = 1, diff --git a/deeplabcut/api/post_processing.py b/deeplabcut/api/post_processing.py index d856c840d5..936341dc9d 100644 --- a/deeplabcut/api/post_processing.py +++ b/deeplabcut/api/post_processing.py @@ -15,10 +15,10 @@ from collections.abc import Sequence from pathlib import Path -from deeplabcut.core.deprecation import renamed_parameter +from deeplabcut.core.deprecation import DeprecationRound, renamed_parameter -@renamed_parameter(old="videotype", new="video_extensions", since="3.0.0") +@renamed_parameter(old="videotype", new="video_extensions", deprecation_round=DeprecationRound.PARAMETER_CONSISTENCY) def analyzeskeleton( config: str | Path, videos: list[str | Path], @@ -100,7 +100,7 @@ def analyzeskeleton( ) -@renamed_parameter(old="videotype", new="video_extensions", since="3.0.0") +@renamed_parameter(old="videotype", new="video_extensions", deprecation_round=DeprecationRound.PARAMETER_CONSISTENCY) def filterpredictions( config: str | Path, video: str | Path, diff --git a/deeplabcut/api/refine_training.py b/deeplabcut/api/refine_training.py index 06ba74823a..038962c8a5 100644 --- a/deeplabcut/api/refine_training.py +++ b/deeplabcut/api/refine_training.py @@ -15,7 +15,7 @@ from collections.abc import Sequence from pathlib import Path -from deeplabcut.core.deprecation import renamed_parameter +from deeplabcut.core.deprecation import DeprecationRound, renamed_parameter def find_outliers_in_raw_data( @@ -66,7 +66,7 @@ def find_outliers_in_raw_data( ) -@renamed_parameter(old="videotype", new="video_extensions", since="3.0.0") +@renamed_parameter(old="videotype", new="video_extensions", deprecation_round=DeprecationRound.PARAMETER_CONSISTENCY) def extract_outlier_frames( config: str | Path, videos: list[str | Path], @@ -288,7 +288,7 @@ def merge_datasets(config: str | Path, forceiterate=None): return _merge_datasets(config=config, forceiterate=forceiterate) -@renamed_parameter(old="videotype", new="video_extensions", since="3.0.0") +@renamed_parameter(old="videotype", new="video_extensions", deprecation_round=DeprecationRound.PARAMETER_CONSISTENCY) def stitch_tracklets( config_path: str | Path, videos: list[str | Path], diff --git a/deeplabcut/core/config/utils.py b/deeplabcut/core/config/utils.py index cc0005bb76..3c850b0215 100644 --- a/deeplabcut/core/config/utils.py +++ b/deeplabcut/core/config/utils.py @@ -28,7 +28,7 @@ from pydantic import ValidationError from ruamel.yaml import YAML -from deeplabcut.core.deprecation import deprecated +from deeplabcut.core.deprecation import DeprecationRound, deprecated logger = logging.getLogger(__name__) @@ -238,7 +238,9 @@ def wrapper(*args, **kwargs): # ----------------------------------------------------------------------------- -@deprecated(replacement="deeplabcut.core.config.ProjectConfig", since="3.0.1") +@deprecated( + replacement="deeplabcut.core.config.ProjectConfig", deprecation_round=DeprecationRound.CONFIG_MODEL_MIGRATION +) def create_config_template(multianimal: bool = False) -> tuple: """ Creates a template for config.yaml file. This specific order is preserved while saving as yaml file. diff --git a/deeplabcut/core/deprecation.py b/deeplabcut/core/deprecation.py index ed0bd0c7d1..7e28c411c7 100644 --- a/deeplabcut/core/deprecation.py +++ b/deeplabcut/core/deprecation.py @@ -14,10 +14,12 @@ import inspect import warnings from collections.abc import Callable +from enum import Enum, unique from typing import Literal, ParamSpec, TypeVar from packaging.version import InvalidVersion, Version from pydantic import BaseModel, ConfigDict, field_validator, model_validator +from typing_extensions import Self P = ParamSpec("P") R = TypeVar("R") @@ -27,25 +29,22 @@ class DLCDeprecationWarning(DeprecationWarning): """Project-specific deprecation warning. Helps with filtering.""" -class DeprecationInfo(BaseModel): +class DeprecationRoundInfo(BaseModel): + """Description of a round of deprecations introduced together.""" + model_config = ConfigDict( frozen=True, arbitrary_types_allowed=True, ) - kind: Literal["callable", "parameter"] - target: str - replacement: str | None = None - - since: Version | None = None + since: Version removed_in: Version | None = None - - old_parameter: str | None = None - new_parameter: str | None = None + pull_request: int | None = None + summary: str | None = None @field_validator("since", "removed_in", mode="before") @classmethod - def _parse_version(cls, value): + def _coerce_version(cls, value): if value is None or isinstance(value, Version): return value try: @@ -54,11 +53,104 @@ def _parse_version(cls, value): raise ValueError(f"Invalid version: {value!r}") from e @model_validator(mode="after") - def _validate_version_order(self) -> DeprecationInfo: - if self.since and self.removed_in and self.removed_in <= self.since: + def _validate_version_order(self) -> Self: + if self.removed_in and self.removed_in <= self.since: raise ValueError(f"'removed_in' ({self.removed_in}) must be greater than 'since' ({self.since}).") return self + @property + def url(self) -> str | None: + if self.pull_request is None: + return None + return f"https://github.com/DeepLabCut/DeepLabCut/pull/{self.pull_request}" + + +@unique +class DeprecationRound(Enum): + """Collection of all deprecation rounds used in the deprecation markers - single source of truth.""" + + PARAMETER_CONSISTENCY = DeprecationRoundInfo( + since="3.0.0", + pull_request=3332, + summary=( + "Apply consistent naming in parameters (spacing, casing, etc). " + "e.g. renamed videotype -> video_extensions, batchsize -> batch_size, " + "Shuffles -> shuffles" + ), + ) + + VIDEO_PATH_MIGRATION = DeprecationRoundInfo( + since="3.0.0", + pull_request=3303, + summary="Migrate get_list_of_videos and friends to deeplabcut.collect_video_paths", + ) + + PRETRAINED_PROJECT_MIGRATION = DeprecationRoundInfo( + since="3.0.0", + pull_request=3382, + summary='create_pretrained_human_project -> create_pretrained_project(model="full_human")', + ) + + PATHLIB_MIGRATION = DeprecationRoundInfo( + since="3.0.1", + pull_request=3421, + summary="Migrate str path handling to pathlib.Path, including grab_files_in_folder", + ) + + CONFIG_MODEL_MIGRATION = DeprecationRoundInfo( + since="3.0.1", + pull_request=3198, + summary="Migrate raw config dicts and their helpers to validated config models", + ) + + IMAGE_ANALYSIS_MIGRATION = DeprecationRoundInfo( + since="3.0.2", + removed_in="3.1", + pull_request=3382, + summary="analyze_time_lapse_frames -> analyze_images (TF-only)", + ) + + +class DeprecationInfo(BaseModel): + """Description for a single deprecation marker.""" + + model_config = ConfigDict( + frozen=True, + arbitrary_types_allowed=True, + ) + + kind: Literal["callable", "parameter"] + target: str + replacement: str | None = None + deprecation_round: DeprecationRound | None = None + + old_parameter: str | None = None + new_parameter: str | None = None + + @model_validator(mode="after") + def _validate_kind_fields(self) -> Self: + if self.kind == "callable": + if self.old_parameter is not None or self.new_parameter is not None: + raise ValueError("Callable deprecations cannot specify parameter names.") + elif self.kind == "parameter": + if not self.old_parameter or not self.new_parameter: + raise ValueError("Parameter deprecations require both 'old_parameter' and 'new_parameter'.") + if self.replacement is not None: + raise ValueError("Parameter deprecations cannot specify 'replacement'.") + return self + + @property + def since(self) -> Version | None: + return self.deprecation_round.value.since if self.deprecation_round else None + + @property + def removed_in(self) -> Version | None: + return self.deprecation_round.value.removed_in if self.deprecation_round else None + + @property + def url(self) -> str | None: + return self.deprecation_round.value.url if self.deprecation_round else None + def format_message(self) -> str: if self.kind == "callable": parts = [f"{self.target} is deprecated"] @@ -68,6 +160,8 @@ def format_message(self) -> str: parts.append(f"Use {self.replacement} instead.") if self.removed_in: parts.append(f"It will be removed in {self.removed_in}.") + if self.url: + parts.append(f"See {self.url} for more details.") return " ".join(parts) if self.kind == "parameter": @@ -83,16 +177,14 @@ def format_message(self) -> str: def deprecated( *, replacement: str | None = None, - since: str | None = None, - removed_in: str | None = None, + deprecation_round: DeprecationRound | None = None, ) -> Callable[[Callable[P, R]], Callable[P, R]]: """Mark a function as deprecated. Args: replacement: Fully-qualified name of the replacement callable, e.g. ``"deeplabcut.utils.auxfun_videos.list_videos_in_folder"``. - since: Version in which the function was deprecated. - removed_in: Version in which the function will be removed. + deprecation_round: A ``DeprecationRound`` member (add a new one there if needed). """ def decorator(fn: Callable[P, R]) -> Callable[P, R]: @@ -100,8 +192,7 @@ def decorator(fn: Callable[P, R]) -> Callable[P, R]: kind="callable", target=fn.__qualname__, replacement=replacement, - since=since, - removed_in=removed_in, + deprecation_round=deprecation_round, ) message = info.format_message() @@ -121,14 +212,14 @@ def renamed_parameter( *, old: str, new: str, - since: str | None = None, + deprecation_round: DeprecationRound | None = None, ) -> Callable[[Callable[P, R]], Callable[P, R]]: """Support a renamed keyword argument while warning callers to update. Args: old: The old parameter name that callers may still pass. new: The current parameter name the function actually accepts. - since: Version when the rename happened. + deprecation_round: A ``DeprecationRound`` member (add a new one there if needed). Rules: - ``new`` must be the name used in the function signature and all @@ -137,8 +228,8 @@ def renamed_parameter( is later renamed to ``C``, replace the ``A→B`` decorator with ``A→C`` directly rather than stacking a second decorator. Example: - @renamed_parameter(old="A", new="C", since="12.4.0") - @renamed_parameter(old="B", new="C", since="13.0.0") + @renamed_parameter(old="A", new="C", deprecation_round=DeprecationRound.FIRST_RENAME) + @renamed_parameter(old="B", new="C", deprecation_round=DeprecationRound.SECOND_RENAME) def func(*, C: int): print(f"C={C}") - Multiple independent renames on the same function (e.g. @@ -182,7 +273,7 @@ def decorator(fn: Callable[P, R]) -> Callable[P, R]: info = DeprecationInfo( kind="parameter", target=fn.__qualname__, - since=since, + deprecation_round=deprecation_round, old_parameter=old, new_parameter=new, ) diff --git a/deeplabcut/create_project/modelzoo.py b/deeplabcut/create_project/modelzoo.py index ec6bb8634c..22d2d71410 100644 --- a/deeplabcut/create_project/modelzoo.py +++ b/deeplabcut/create_project/modelzoo.py @@ -22,7 +22,7 @@ import deeplabcut from deeplabcut.core.config import ProjectConfig -from deeplabcut.core.deprecation import deprecated, renamed_parameter +from deeplabcut.core.deprecation import DeprecationRound, deprecated, renamed_parameter from deeplabcut.core.engine import Engine from deeplabcut.generate_training_dataset.metadata import ( DataSplit, @@ -44,8 +44,11 @@ Modeloptions = MODELOPTIONS -@deprecated(replacement="deeplabcut.create_pretrained_project(..., model='full_human')", since="3.0.0") -@renamed_parameter(old="videotype", new="video_extensions", since="3.0.0") +@deprecated( + replacement="deeplabcut.create_pretrained_project(..., model='full_human')", + deprecation_round=DeprecationRound.PRETRAINED_PROJECT_MIGRATION, +) +@renamed_parameter(old="videotype", new="video_extensions", deprecation_round=DeprecationRound.PARAMETER_CONSISTENCY) def create_pretrained_human_project( project, experimenter, @@ -74,7 +77,7 @@ def create_pretrained_human_project( ) -@renamed_parameter(old="videotype", new="video_extensions", since="3.0.0") +@renamed_parameter(old="videotype", new="video_extensions", deprecation_round=DeprecationRound.PARAMETER_CONSISTENCY) def create_pretrained_project( project: str, experimenter: str, diff --git a/deeplabcut/create_project/new.py b/deeplabcut/create_project/new.py index 2f3400e427..8a144ba83f 100644 --- a/deeplabcut/create_project/new.py +++ b/deeplabcut/create_project/new.py @@ -18,12 +18,12 @@ from typing import Literal from deeplabcut import DEBUG -from deeplabcut.core.deprecation import renamed_parameter +from deeplabcut.core.deprecation import DeprecationRound, renamed_parameter from deeplabcut.core.engine import Engine from deeplabcut.utils.auxfun_videos import VideoReader, collect_video_paths -@renamed_parameter(old="videotype", new="video_extensions", since="3.0.0") +@renamed_parameter(old="videotype", new="video_extensions", deprecation_round=DeprecationRound.PARAMETER_CONSISTENCY) def create_new_project( project: str, experimenter: str, diff --git a/deeplabcut/modelzoo/video_inference.py b/deeplabcut/modelzoo/video_inference.py index dc2312d10c..c47b370c9a 100644 --- a/deeplabcut/modelzoo/video_inference.py +++ b/deeplabcut/modelzoo/video_inference.py @@ -18,7 +18,7 @@ import torch -from deeplabcut.core.deprecation import renamed_parameter +from deeplabcut.core.deprecation import DeprecationRound, renamed_parameter from deeplabcut.modelzoo.utils import get_super_animal_scorer from deeplabcut.pose_estimation_pytorch.config import PoseConfig from deeplabcut.pose_estimation_pytorch.modelzoo.train_from_coco import adaptation_train @@ -50,7 +50,7 @@ def get_checkpoint_epoch(checkpoint_path): return 0 -@renamed_parameter(old="videotype", new="video_extensions", since="3.0.0") +@renamed_parameter(old="videotype", new="video_extensions", deprecation_round=DeprecationRound.PARAMETER_CONSISTENCY) def video_inference_superanimal( videos: str | list, superanimal_name: str, diff --git a/deeplabcut/pose_estimation_pytorch/apis/tracking_dataset.py b/deeplabcut/pose_estimation_pytorch/apis/tracking_dataset.py index c608316e8a..1f413bfd59 100644 --- a/deeplabcut/pose_estimation_pytorch/apis/tracking_dataset.py +++ b/deeplabcut/pose_estimation_pytorch/apis/tracking_dataset.py @@ -22,7 +22,7 @@ import deeplabcut.pose_estimation_pytorch.runners as runners import deeplabcut.pose_estimation_pytorch.runners.shelving as shelving from deeplabcut.core.config import read_config_as_dict -from deeplabcut.core.deprecation import renamed_parameter +from deeplabcut.core.deprecation import DeprecationRound, renamed_parameter from deeplabcut.pose_estimation_pytorch.apis.videos import VideoIterator from deeplabcut.pose_estimation_pytorch.task import Task from deeplabcut.pose_tracking_pytorch import create_triplets_dataset @@ -128,7 +128,7 @@ def extract_features_for_video( shelf_writer.close() -@renamed_parameter(old="videotype", new="video_extensions", since="3.0.0") +@renamed_parameter(old="videotype", new="video_extensions", deprecation_round=DeprecationRound.PARAMETER_CONSISTENCY) def create_tracking_dataset( config: str | Path, videos: list[str] | list[Path], diff --git a/deeplabcut/pose_estimation_pytorch/apis/tracklets.py b/deeplabcut/pose_estimation_pytorch/apis/tracklets.py index 731b8b3d6f..217c3fcfe6 100644 --- a/deeplabcut/pose_estimation_pytorch/apis/tracklets.py +++ b/deeplabcut/pose_estimation_pytorch/apis/tracklets.py @@ -23,7 +23,7 @@ import deeplabcut.utils.auxfun_multianimal as auxfun_multianimal import deeplabcut.utils.auxiliaryfunctions as auxiliaryfunctions from deeplabcut.core import trackingutils -from deeplabcut.core.deprecation import renamed_parameter +from deeplabcut.core.deprecation import DeprecationRound, renamed_parameter from deeplabcut.core.engine import Engine from deeplabcut.core.inferenceutils import Assembly from deeplabcut.pose_estimation_pytorch.apis.utils import ( @@ -34,7 +34,7 @@ from deeplabcut.utils.auxfun_videos import collect_video_paths -@renamed_parameter(old="videotype", new="video_extensions", since="3.0.0") +@renamed_parameter(old="videotype", new="video_extensions", deprecation_round=DeprecationRound.PARAMETER_CONSISTENCY) def convert_detections2tracklets( config: str | Path, videos: str | list[str], diff --git a/deeplabcut/pose_estimation_pytorch/apis/utils.py b/deeplabcut/pose_estimation_pytorch/apis/utils.py index b82f15dec1..cbda436ff2 100644 --- a/deeplabcut/pose_estimation_pytorch/apis/utils.py +++ b/deeplabcut/pose_estimation_pytorch/apis/utils.py @@ -27,7 +27,7 @@ ) from deeplabcut.core.config import ProjectConfig -from deeplabcut.core.deprecation import deprecated +from deeplabcut.core.deprecation import DeprecationRound, deprecated from deeplabcut.core.engine import Engine from deeplabcut.pose_estimation_pytorch.config.ctd_conditions import ConditionsModelConfig from deeplabcut.pose_estimation_pytorch.config.pose import PoseConfig @@ -305,7 +305,7 @@ def get_scorer_name( return f"DLC_{name}_{task}{date}shuffle{shuffle}_{snapshot_uid}" -@deprecated(replacement="deeplabcut.collect_video_paths", since="3.0.0") +@deprecated(replacement="deeplabcut.collect_video_paths", deprecation_round=DeprecationRound.VIDEO_PATH_MIGRATION) def list_videos_in_folder( data_path: str | Path | list[str | Path], video_type: str | Sequence[str] | None = SUPPORTED_VIDEOS, diff --git a/deeplabcut/pose_estimation_pytorch/apis/videos.py b/deeplabcut/pose_estimation_pytorch/apis/videos.py index 019103dddc..e6fb4f0f93 100644 --- a/deeplabcut/pose_estimation_pytorch/apis/videos.py +++ b/deeplabcut/pose_estimation_pytorch/apis/videos.py @@ -27,7 +27,7 @@ import deeplabcut.pose_estimation_pytorch.apis.utils as utils import deeplabcut.pose_estimation_pytorch.runners.shelving as shelving from deeplabcut.core.config import ProjectConfig -from deeplabcut.core.deprecation import renamed_parameter +from deeplabcut.core.deprecation import DeprecationRound, renamed_parameter from deeplabcut.pose_estimation_pytorch.apis.ctd import get_conditions_provider_for_video from deeplabcut.pose_estimation_pytorch.apis.tracklets import ( convert_detections2tracklets, @@ -241,7 +241,7 @@ def video_inference( return predictions -@renamed_parameter(old="videotype", new="video_extensions", since="3.0.0") +@renamed_parameter(old="videotype", new="video_extensions", deprecation_round=DeprecationRound.PARAMETER_CONSISTENCY) def analyze_videos( config: str | Path, videos: str | Path | list[str | Path], diff --git a/deeplabcut/pose_estimation_pytorch/config/make_pose_config.py b/deeplabcut/pose_estimation_pytorch/config/make_pose_config.py index a75067e0e1..2c66fd17be 100644 --- a/deeplabcut/pose_estimation_pytorch/config/make_pose_config.py +++ b/deeplabcut/pose_estimation_pytorch/config/make_pose_config.py @@ -18,7 +18,7 @@ from typing import TYPE_CHECKING from deeplabcut.core.config import ProjectConfig, read_config_as_dict -from deeplabcut.core.deprecation import deprecated +from deeplabcut.core.deprecation import DeprecationRound, deprecated from deeplabcut.core.weight_init import WeightInitialization from deeplabcut.pose_estimation_pytorch.config.enums import DetectorType, NetType from deeplabcut.pose_estimation_pytorch.config.inference import InferenceConfig @@ -38,7 +38,10 @@ logger = logging.getLogger(__name__) -@deprecated(replacement="pose_estimation_pytorch.config.TestConfig.build", since="3.0.1") +@deprecated( + replacement="pose_estimation_pytorch.config.TestConfig.build", + deprecation_round=DeprecationRound.CONFIG_MODEL_MIGRATION, +) def make_pytorch_test_config( model_config: PoseConfig | dict | str | Path, test_config_path: str | Path, @@ -49,7 +52,10 @@ def make_pytorch_test_config( return TestConfig.build(model_config, test_config_path=test_config_path, save=save) -@deprecated(replacement="pose_estimation_pytorch.config.PoseConfig.build", since="3.0.1") +@deprecated( + replacement="pose_estimation_pytorch.config.PoseConfig.build", + deprecation_round=DeprecationRound.CONFIG_MODEL_MIGRATION, +) def make_pytorch_pose_config( project_config: ProjectConfig | dict | Path | str, pose_config_path: str | Path, @@ -74,7 +80,9 @@ def make_pytorch_pose_config( ) -@deprecated(replacement="pose_estimation_pytorch.config.PoseMetadata", since="3.0.1") +@deprecated( + replacement="pose_estimation_pytorch.config.PoseMetadata", deprecation_round=DeprecationRound.CONFIG_MODEL_MIGRATION +) def make_basic_project_config( dataset_path: Path | str, bodyparts: list[str], diff --git a/deeplabcut/pose_estimation_pytorch/config/utils.py b/deeplabcut/pose_estimation_pytorch/config/utils.py index 14e3101b57..920b44406d 100644 --- a/deeplabcut/pose_estimation_pytorch/config/utils.py +++ b/deeplabcut/pose_estimation_pytorch/config/utils.py @@ -16,7 +16,7 @@ from pathlib import Path from deeplabcut.core.config import ensure_plain_config, read_config_as_dict -from deeplabcut.core.deprecation import deprecated +from deeplabcut.core.deprecation import DeprecationRound, deprecated from deeplabcut.utils import auxiliaryfunctions @@ -118,7 +118,7 @@ def get_updated_value(variable: str) -> int | list[int]: return config -@deprecated(replacement=None, since="3.0.1") +@deprecated(replacement=None, deprecation_round=DeprecationRound.CONFIG_MODEL_MIGRATION) def update_config(config: dict, updates: dict, copy_original: bool = True) -> dict: """Deprecated helper for updating config dictionaries.""" from deeplabcut.pose_estimation_pytorch.config.make_pose_config import _update_config @@ -126,7 +126,7 @@ def update_config(config: dict, updates: dict, copy_original: bool = True) -> di return _update_config(config, updates, copy_original) -@deprecated(replacement=None, since="3.0.1") +@deprecated(replacement=None, deprecation_round=DeprecationRound.CONFIG_MODEL_MIGRATION) def update_config_by_dotpath(config: dict, updates: dict, copy_original: bool = True) -> dict: """Deprecated helper for updating config dictionaries using dot notation. ``DLCBaseConfig.set_nested`` (new in 3.0.1) can be used instead (not identical). diff --git a/deeplabcut/pose_estimation_pytorch/data/base.py b/deeplabcut/pose_estimation_pytorch/data/base.py index 39220ed2fe..2294b308af 100644 --- a/deeplabcut/pose_estimation_pytorch/data/base.py +++ b/deeplabcut/pose_estimation_pytorch/data/base.py @@ -16,7 +16,7 @@ import albumentations as A import numpy as np -from deeplabcut.core.deprecation import renamed_parameter +from deeplabcut.core.deprecation import DeprecationRound, renamed_parameter from deeplabcut.pose_estimation_pytorch.config import GenSamplingConfig, PoseConfig from deeplabcut.pose_estimation_pytorch.data.dataset import ( PoseDataset, @@ -47,7 +47,9 @@ class Loader(ABC): Returns a dictionary containing dataset parameters derived from the configuration. """ - @renamed_parameter(old="model_config_path", new="model_config", since="3.0.1") + @renamed_parameter( + old="model_config_path", new="model_config", deprecation_round=DeprecationRound.CONFIG_MODEL_MIGRATION + ) def __init__( self, project_root: str | Path, diff --git a/deeplabcut/pose_estimation_pytorch/data/cocoloader.py b/deeplabcut/pose_estimation_pytorch/data/cocoloader.py index 2f7ed00f36..f906f3616b 100644 --- a/deeplabcut/pose_estimation_pytorch/data/cocoloader.py +++ b/deeplabcut/pose_estimation_pytorch/data/cocoloader.py @@ -16,7 +16,7 @@ import numpy as np -from deeplabcut.core.deprecation import renamed_parameter +from deeplabcut.core.deprecation import DeprecationRound, renamed_parameter from deeplabcut.pose_estimation_pytorch.config import MethodType, PoseConfig from deeplabcut.pose_estimation_pytorch.data.base import Loader from deeplabcut.pose_estimation_pytorch.data.dataset import PoseDatasetParameters @@ -45,7 +45,9 @@ class COCOLoader(Loader): ) """ - @renamed_parameter(old="model_config_path", new="model_config", since="3.0.0") + @renamed_parameter( + old="model_config_path", new="model_config", deprecation_round=DeprecationRound.PARAMETER_CONSISTENCY + ) def __init__( self, project_root: str | Path, diff --git a/deeplabcut/pose_estimation_pytorch/modelzoo/config.py b/deeplabcut/pose_estimation_pytorch/modelzoo/config.py index 3ccbb12511..427a675fbd 100644 --- a/deeplabcut/pose_estimation_pytorch/modelzoo/config.py +++ b/deeplabcut/pose_estimation_pytorch/modelzoo/config.py @@ -27,7 +27,7 @@ get_yaml_dumper, read_config_as_dict, ) -from deeplabcut.core.deprecation import deprecated +from deeplabcut.core.deprecation import DeprecationRound, deprecated from deeplabcut.core.engine import Engine from deeplabcut.core.weight_init import WeightInitialization from deeplabcut.pose_estimation_pytorch.config.metadata import PoseMetadata @@ -140,7 +140,9 @@ def build_superanimal_finetune_config( return model_cfg -@deprecated(replacement="PoseConfig.build_for_superanimal_finetune", since="3.0.1") +@deprecated( + replacement="PoseConfig.build_for_superanimal_finetune", deprecation_round=DeprecationRound.CONFIG_MODEL_MIGRATION +) def make_super_animal_finetune_config( weight_init: WeightInitialization, project_config: dict, diff --git a/deeplabcut/pose_estimation_pytorch/modelzoo/utils.py b/deeplabcut/pose_estimation_pytorch/modelzoo/utils.py index 9911156105..33929f68c7 100644 --- a/deeplabcut/pose_estimation_pytorch/modelzoo/utils.py +++ b/deeplabcut/pose_estimation_pytorch/modelzoo/utils.py @@ -21,7 +21,7 @@ from dlclibrary import download_huggingface_model import deeplabcut.pose_estimation_pytorch.config.utils as config_utils -from deeplabcut.core.deprecation import deprecated +from deeplabcut.core.deprecation import DeprecationRound, deprecated from deeplabcut.utils import auxiliaryfunctions # COCO category ID for the "person" class. @@ -89,7 +89,9 @@ def get_super_animal_snapshot_path( return model_path -@deprecated(replacement="PoseConfig.build_for_superanimal_inference", since="3.0.1") +@deprecated( + replacement="PoseConfig.build_for_superanimal_inference", deprecation_round=DeprecationRound.CONFIG_MODEL_MIGRATION +) def load_super_animal_config( super_animal: str, model_name: str, diff --git a/deeplabcut/pose_estimation_tensorflow/modelzoo/api/spatiotemporal_adapt.py b/deeplabcut/pose_estimation_tensorflow/modelzoo/api/spatiotemporal_adapt.py index 6501363eff..974c8c0b08 100644 --- a/deeplabcut/pose_estimation_tensorflow/modelzoo/api/spatiotemporal_adapt.py +++ b/deeplabcut/pose_estimation_tensorflow/modelzoo/api/spatiotemporal_adapt.py @@ -11,7 +11,7 @@ from collections.abc import Sequence from pathlib import Path -from deeplabcut.core.deprecation import renamed_parameter +from deeplabcut.core.deprecation import DeprecationRound, renamed_parameter from deeplabcut.pose_estimation_tensorflow.modelzoo.api.superanimal_inference import ( video_inference, ) @@ -25,7 +25,9 @@ class SpatiotemporalAdaptation: - @renamed_parameter(old="videotype", new="video_extensions", since="3.0.0") + @renamed_parameter( + old="videotype", new="video_extensions", deprecation_round=DeprecationRound.PARAMETER_CONSISTENCY + ) def __init__( self, video_path, diff --git a/deeplabcut/pose_estimation_tensorflow/modelzoo/api/superanimal_inference.py b/deeplabcut/pose_estimation_tensorflow/modelzoo/api/superanimal_inference.py index d8157fe376..9234257725 100644 --- a/deeplabcut/pose_estimation_tensorflow/modelzoo/api/superanimal_inference.py +++ b/deeplabcut/pose_estimation_tensorflow/modelzoo/api/superanimal_inference.py @@ -20,7 +20,7 @@ from skimage.util import img_as_ubyte from tqdm import tqdm -from deeplabcut.core.deprecation import renamed_parameter +from deeplabcut.core.deprecation import DeprecationRound, renamed_parameter from deeplabcut.pose_estimation_tensorflow.config import load_config from deeplabcut.pose_estimation_tensorflow.core import predict as single_predict from deeplabcut.pose_estimation_tensorflow.core import predict_multianimal as predict @@ -241,7 +241,7 @@ def _video_inference( return PredicteData, nframes -@renamed_parameter(old="videotype", new="video_extensions", since="3.0.0") +@renamed_parameter(old="videotype", new="video_extensions", deprecation_round=DeprecationRound.PARAMETER_CONSISTENCY) def video_inference( videos, project_name, diff --git a/deeplabcut/pose_estimation_tensorflow/predict_videos.py b/deeplabcut/pose_estimation_tensorflow/predict_videos.py index ddb2845ece..2e4d147c30 100644 --- a/deeplabcut/pose_estimation_tensorflow/predict_videos.py +++ b/deeplabcut/pose_estimation_tensorflow/predict_videos.py @@ -32,7 +32,7 @@ from tqdm import tqdm from deeplabcut.core import inferenceutils, trackingutils -from deeplabcut.core.deprecation import renamed_parameter +from deeplabcut.core.deprecation import DeprecationRound, renamed_parameter from deeplabcut.pose_estimation_tensorflow.config import load_config from deeplabcut.pose_estimation_tensorflow.core import predict from deeplabcut.pose_estimation_tensorflow.core.openvino.session import ( @@ -48,7 +48,7 @@ #################################################### -@renamed_parameter(old="videotype", new="video_extensions", since="3.0.0") +@renamed_parameter(old="videotype", new="video_extensions", deprecation_round=DeprecationRound.PARAMETER_CONSISTENCY) def create_tracking_dataset( config, videos, @@ -253,7 +253,7 @@ def create_tracking_dataset( return DLCscorer -@renamed_parameter(old="videotype", new="video_extensions", since="3.0.0") +@renamed_parameter(old="videotype", new="video_extensions", deprecation_round=DeprecationRound.PARAMETER_CONSISTENCY) def analyze_videos( config, videos, @@ -1405,7 +1405,7 @@ def _convert_detections_to_tracklets( pickle.dump(tracklets, f, pickle.HIGHEST_PROTOCOL) -@renamed_parameter(old="videotype", new="video_extensions", since="3.0.0") +@renamed_parameter(old="videotype", new="video_extensions", deprecation_round=DeprecationRound.PARAMETER_CONSISTENCY) def convert_detections2tracklets( config, videos, diff --git a/deeplabcut/pose_tracking_pytorch/apis.py b/deeplabcut/pose_tracking_pytorch/apis.py index 44ff614411..181476b5fc 100644 --- a/deeplabcut/pose_tracking_pytorch/apis.py +++ b/deeplabcut/pose_tracking_pytorch/apis.py @@ -11,10 +11,10 @@ from collections.abc import Sequence -from deeplabcut.core.deprecation import renamed_parameter +from deeplabcut.core.deprecation import DeprecationRound, renamed_parameter -@renamed_parameter(old="videotype", new="video_extensions", since="3.0.0") +@renamed_parameter(old="videotype", new="video_extensions", deprecation_round=DeprecationRound.PARAMETER_CONSISTENCY) def transformer_reID( config: str, videos: list[str], diff --git a/deeplabcut/pose_tracking_pytorch/train_dlctransreid.py b/deeplabcut/pose_tracking_pytorch/train_dlctransreid.py index cbfd5d7061..a7d839b755 100644 --- a/deeplabcut/pose_tracking_pytorch/train_dlctransreid.py +++ b/deeplabcut/pose_tracking_pytorch/train_dlctransreid.py @@ -20,7 +20,7 @@ import numpy as np -from deeplabcut.core.deprecation import renamed_parameter +from deeplabcut.core.deprecation import DeprecationRound, renamed_parameter from deeplabcut.utils.auxfun_videos import collect_video_paths from .config import cfg @@ -66,7 +66,7 @@ def split_train_test(npy_list, train_frac): return train_list, test_list -@renamed_parameter(old="videotype", new="video_extensions", since="3.0.0") +@renamed_parameter(old="videotype", new="video_extensions", deprecation_round=DeprecationRound.PARAMETER_CONSISTENCY) def train_tracking_transformer( path_config_file, dlcscorer, diff --git a/deeplabcut/post_processing/analyze_skeleton.py b/deeplabcut/post_processing/analyze_skeleton.py index 6e552d40f4..782879a1c2 100644 --- a/deeplabcut/post_processing/analyze_skeleton.py +++ b/deeplabcut/post_processing/analyze_skeleton.py @@ -22,7 +22,7 @@ import pandas as pd from scipy.spatial import distance -from deeplabcut.core.deprecation import renamed_parameter +from deeplabcut.core.deprecation import DeprecationRound, renamed_parameter from deeplabcut.utils import auxfun_multianimal, auxiliaryfunctions from deeplabcut.utils.auxfun_videos import collect_video_paths @@ -173,7 +173,7 @@ def analyzebone(bp1, bp2): # MAIN FUNC -@renamed_parameter(old="videotype", new="video_extensions", since="3.0.0") +@renamed_parameter(old="videotype", new="video_extensions", deprecation_round=DeprecationRound.PARAMETER_CONSISTENCY) def analyzeskeleton( config: str | Path, videos: list[str | Path], diff --git a/deeplabcut/post_processing/filtering.py b/deeplabcut/post_processing/filtering.py index 2f013aa833..396b931023 100644 --- a/deeplabcut/post_processing/filtering.py +++ b/deeplabcut/post_processing/filtering.py @@ -18,7 +18,7 @@ from scipy import signal from scipy.interpolate import CubicSpline -from deeplabcut.core.deprecation import renamed_parameter +from deeplabcut.core.deprecation import DeprecationRound, renamed_parameter from deeplabcut.refine_training_dataset.outlier_frames import FitSARIMAXModel from deeplabcut.utils import auxfun_multianimal, auxiliaryfunctions from deeplabcut.utils.auxfun_videos import collect_video_paths @@ -61,7 +61,7 @@ def columnwise_spline_interp(data, max_gap=0): return temp -@renamed_parameter(old="videotype", new="video_extensions", since="3.0.0") +@renamed_parameter(old="videotype", new="video_extensions", deprecation_round=DeprecationRound.PARAMETER_CONSISTENCY) def filterpredictions( config: str | Path, video: str | Path, diff --git a/deeplabcut/refine_training_dataset/outlier_frames.py b/deeplabcut/refine_training_dataset/outlier_frames.py index 63efbb1c4b..c45cd651f1 100644 --- a/deeplabcut/refine_training_dataset/outlier_frames.py +++ b/deeplabcut/refine_training_dataset/outlier_frames.py @@ -23,7 +23,7 @@ from skimage.util import img_as_ubyte from deeplabcut.core import inferenceutils -from deeplabcut.core.deprecation import renamed_parameter +from deeplabcut.core.deprecation import DeprecationRound, renamed_parameter from deeplabcut.utils import ( auxfun_multianimal, auxiliaryfunctions, @@ -180,7 +180,7 @@ def _read_video_specific_cropping_margins(config: str | Path | dict, video_path: return x1, y1 -@renamed_parameter(old="videotype", new="video_extensions", since="3.0.0") +@renamed_parameter(old="videotype", new="video_extensions", deprecation_round=DeprecationRound.PARAMETER_CONSISTENCY) def extract_outlier_frames( config: str | Path, videos: list[str | Path], diff --git a/deeplabcut/refine_training_dataset/stitch.py b/deeplabcut/refine_training_dataset/stitch.py index a86c1d302b..b4cdff8fca 100644 --- a/deeplabcut/refine_training_dataset/stitch.py +++ b/deeplabcut/refine_training_dataset/stitch.py @@ -30,7 +30,7 @@ from tqdm import trange import deeplabcut -from deeplabcut.core.deprecation import renamed_parameter +from deeplabcut.core.deprecation import DeprecationRound, renamed_parameter from deeplabcut.core.trackingutils import ( TRACK_METHODS, calc_iou, @@ -958,7 +958,7 @@ def reconstruct_path(self, source): return path -@renamed_parameter(old="videotype", new="video_extensions", since="3.0.0") +@renamed_parameter(old="videotype", new="video_extensions", deprecation_round=DeprecationRound.PARAMETER_CONSISTENCY) def stitch_tracklets( config_path: str | Path, videos: list[str | Path], diff --git a/deeplabcut/tensorflow_compat/pose_estimation.py b/deeplabcut/tensorflow_compat/pose_estimation.py index a064532ecd..beb8df2ea9 100644 --- a/deeplabcut/tensorflow_compat/pose_estimation.py +++ b/deeplabcut/tensorflow_compat/pose_estimation.py @@ -19,7 +19,7 @@ import numpy as np import deeplabcut.core.visualization as visualization -from deeplabcut.core.deprecation import DLCDeprecationWarning, renamed_parameter +from deeplabcut.core.deprecation import DeprecationRound, DLCDeprecationWarning, renamed_parameter from deeplabcut.core.engine import Engine @@ -28,9 +28,9 @@ def get_available_aug_methods() -> tuple[str, ...]: return "imgaug", "default", "deterministic", "scalecrop", "tensorpack" -@renamed_parameter(old="maxiters", new="max_iters", since="3.0.0") -@renamed_parameter(old="saveiters", new="save_iters", since="3.0.0") -@renamed_parameter(old="displayiters", new="display_iters", since="3.0.0") +@renamed_parameter(old="maxiters", new="max_iters", deprecation_round=DeprecationRound.PARAMETER_CONSISTENCY) +@renamed_parameter(old="saveiters", new="save_iters", deprecation_round=DeprecationRound.PARAMETER_CONSISTENCY) +@renamed_parameter(old="displayiters", new="display_iters", deprecation_round=DeprecationRound.PARAMETER_CONSISTENCY) def train_network( config: str | Path, shuffle: int = 1, @@ -323,8 +323,10 @@ def return_train_network_path( ) -@renamed_parameter(old="comparisonbodyparts", new="comparison_bodyparts", since="3.0.0") -@renamed_parameter(old="Shuffles", new="shuffles", since="3.0.0") +@renamed_parameter( + old="comparisonbodyparts", new="comparison_bodyparts", deprecation_round=DeprecationRound.PARAMETER_CONSISTENCY +) +@renamed_parameter(old="Shuffles", new="shuffles", deprecation_round=DeprecationRound.PARAMETER_CONSISTENCY) def evaluate_network( config: str | Path, shuffles: Sequence[int] = (1,), @@ -472,8 +474,10 @@ def evaluate_network( ) -@renamed_parameter(old="comparisonbodyparts", new="comparison_bodyparts", since="3.0.0") -@renamed_parameter(old="Snapindex", new="snapshotindex", since="3.0.0") +@renamed_parameter( + old="comparisonbodyparts", new="comparison_bodyparts", deprecation_round=DeprecationRound.PARAMETER_CONSISTENCY +) +@renamed_parameter(old="Snapindex", new="snapshotindex", deprecation_round=DeprecationRound.PARAMETER_CONSISTENCY) def return_evaluate_network_data( config: str, shuffle: int = 0, @@ -559,8 +563,8 @@ def return_evaluate_network_data( ) -@renamed_parameter(old="batchsize", new="batch_size", since="3.0.0") -@renamed_parameter(old="videotype", new="video_extensions", since="3.0.0") +@renamed_parameter(old="batchsize", new="batch_size", deprecation_round=DeprecationRound.PARAMETER_CONSISTENCY) +@renamed_parameter(old="videotype", new="video_extensions", deprecation_round=DeprecationRound.PARAMETER_CONSISTENCY) def analyze_videos( config: str, videos: list[str], @@ -833,8 +837,8 @@ def analyze_videos( ) -@renamed_parameter(old="batchsize", new="batch_size", since="3.0.0") -@renamed_parameter(old="videotype", new="video_extensions", since="3.0.0") +@renamed_parameter(old="batchsize", new="batch_size", deprecation_round=DeprecationRound.PARAMETER_CONSISTENCY) +@renamed_parameter(old="videotype", new="video_extensions", deprecation_round=DeprecationRound.PARAMETER_CONSISTENCY) def create_tracking_dataset( config: str, videos: list[str], @@ -1187,7 +1191,7 @@ def analyze_time_lapse_frames( ) -@renamed_parameter(old="videotype", new="video_extensions", since="3.0.0") +@renamed_parameter(old="videotype", new="video_extensions", deprecation_round=DeprecationRound.PARAMETER_CONSISTENCY) def convert_detections2tracklets( config: str, videos: list[str], @@ -1457,7 +1461,9 @@ def visualize_paf( return visualization.visualize_paf(image, paf, step=step, colors=colors) -@renamed_parameter(old="comparisonbodyparts", new="comparison_bodyparts", since="3.0.0") +@renamed_parameter( + old="comparisonbodyparts", new="comparison_bodyparts", deprecation_round=DeprecationRound.PARAMETER_CONSISTENCY +) def extract_save_all_maps( config, shuffle: int = 1, diff --git a/deeplabcut/utils/auxiliaryfunctions.py b/deeplabcut/utils/auxiliaryfunctions.py index 12e6935def..e2f205f8bc 100644 --- a/deeplabcut/utils/auxiliaryfunctions.py +++ b/deeplabcut/utils/auxiliaryfunctions.py @@ -29,7 +29,7 @@ import pandas as pd from deeplabcut.core import config as core_config -from deeplabcut.core.deprecation import deprecated +from deeplabcut.core.deprecation import DeprecationRound, deprecated from deeplabcut.core.engine import Engine from deeplabcut.core.trackingutils import TRACK_METHODS from deeplabcut.utils import auxfun_multianimal @@ -151,7 +151,7 @@ def write_pickle(filename, data): pickle.dump(data, handle, protocol=pickle.HIGHEST_PROTOCOL) -@deprecated(replacement="deeplabcut.collect_video_paths", since="3.0.0") +@deprecated(replacement="deeplabcut.collect_video_paths", deprecation_round=DeprecationRound.VIDEO_PATH_MIGRATION) def get_list_of_videos( videos: list[str] | str, videotype: str | Sequence[str] | None = SUPPORTED_VIDEOS, @@ -203,7 +203,7 @@ def load_metadata(metadatafile: str | Path): return trainingdata_details, trainIndices, testIndices, testFraction_data -@deprecated(replacement="deeplabcut.collect_video_paths", since="3.0.1") +@deprecated(replacement="deeplabcut.collect_video_paths", deprecation_round=DeprecationRound.PATHLIB_MIGRATION) def grab_files_in_folder(folder, ext="", relative=True): """Return the paths of files with extension *ext* present in *folder*.""" for file in Path(folder).iterdir(): @@ -251,7 +251,7 @@ def filter_files_by_patterns( return matching_files -@deprecated(replacement="deeplabcut.collect_video_paths", since="3.0.0") +@deprecated(replacement="deeplabcut.collect_video_paths", deprecation_round=DeprecationRound.VIDEO_PATH_MIGRATION) def get_video_list(filename, videopath, videtype): """Get list of videos in a path (if filetype == all), otherwise just a specific file. diff --git a/deeplabcut/utils/make_labeled_video.py b/deeplabcut/utils/make_labeled_video.py index a50a483f31..d87c2c0c21 100644 --- a/deeplabcut/utils/make_labeled_video.py +++ b/deeplabcut/utils/make_labeled_video.py @@ -45,7 +45,7 @@ from skimage.util import img_as_ubyte from tqdm import trange -from deeplabcut.core.deprecation import renamed_parameter +from deeplabcut.core.deprecation import DeprecationRound, renamed_parameter from deeplabcut.core.engine import Engine from deeplabcut.pose_estimation_pytorch.config import PoseConfig from deeplabcut.utils import auxfun_multianimal, auxiliaryfunctions, visualization @@ -390,7 +390,7 @@ def CreateVideoSlow( plt.switch_backend(prev_backend) -@renamed_parameter(old="videotype", new="video_extensions", since="3.0.0") +@renamed_parameter(old="videotype", new="video_extensions", deprecation_round=DeprecationRound.PARAMETER_CONSISTENCY) def create_labeled_video( config: str | Path, videos: list[str | Path], @@ -1069,7 +1069,7 @@ def create_video_with_keypoints_only( plt.switch_backend(prev_backend) -@renamed_parameter(old="videotype", new="video_extensions", since="3.0.0") +@renamed_parameter(old="videotype", new="video_extensions", deprecation_round=DeprecationRound.PARAMETER_CONSISTENCY) def create_video_with_all_detections( config: str | Path, videos: list[str | Path], diff --git a/deeplabcut/utils/plotting.py b/deeplabcut/utils/plotting.py index 48ae8715c2..9caa7f3174 100644 --- a/deeplabcut/utils/plotting.py +++ b/deeplabcut/utils/plotting.py @@ -34,7 +34,7 @@ import pandas as pd from deeplabcut.core import crossvalutils -from deeplabcut.core.deprecation import renamed_parameter +from deeplabcut.core.deprecation import DeprecationRound, renamed_parameter from deeplabcut.utils import auxfun_multianimal, auxiliaryfunctions, visualization from deeplabcut.utils.auxfun_videos import collect_video_paths @@ -169,7 +169,7 @@ def PlottingResults( ################################################## -@renamed_parameter(old="videotype", new="video_extensions", since="3.0.0") +@renamed_parameter(old="videotype", new="video_extensions", deprecation_round=DeprecationRound.PARAMETER_CONSISTENCY) def plot_trajectories( config: str | Path, videos: list[str | Path], diff --git a/tests/utils/test_deprecation.py b/tests/utils/test_deprecation.py index 6b97b06190..e25c53378e 100644 --- a/tests/utils/test_deprecation.py +++ b/tests/utils/test_deprecation.py @@ -8,17 +8,28 @@ # # Licensed under GNU Lesser General Public License v3.0 # +import re import warnings +from pathlib import Path import pytest from packaging.version import Version +import deeplabcut from deeplabcut.core.deprecation import ( + DeprecationInfo, + DeprecationRound, + DeprecationRoundInfo, DLCDeprecationWarning, deprecated, renamed_parameter, ) +# Real rounds used by the tests below, so the suite exercises shipped data rather than +# fixtures. IMAGE_ANALYSIS_MIGRATION is the only round with a scheduled removal. +ROUND_WITH_REMOVAL = DeprecationRound.IMAGE_ANALYSIS_MIGRATION +ROUND_WITHOUT_REMOVAL = DeprecationRound.PARAMETER_CONSISTENCY + # --------------------------------------------------------------------------- # @deprecated # --------------------------------------------------------------------------- @@ -54,14 +65,27 @@ def old_fn(): def test_deprecated_warning_contains_since_and_removed_in(): - @deprecated(since="3.1", removed_in="4.0") + round_info = ROUND_WITH_REMOVAL.value + + @deprecated(deprecation_round=ROUND_WITH_REMOVAL) + def old_fn(): + pass + + with pytest.warns(DLCDeprecationWarning, match=str(round_info.since)) as record: + old_fn() + + assert str(round_info.removed_in) in str(record[0].message) + + +def test_deprecated_warning_contains_pull_request_url(): + @deprecated(deprecation_round=ROUND_WITH_REMOVAL) def old_fn(): pass - with pytest.warns(DLCDeprecationWarning, match="3.1") as record: + with pytest.warns(DLCDeprecationWarning, match=ROUND_WITH_REMOVAL.value.url) as record: old_fn() - assert "4.0" in str(record[0].message) + assert "for more details" in str(record[0].message) def test_deprecated_preserves_return_value_and_args(): @@ -85,7 +109,9 @@ def documented_fn(): def test_deprecated_attaches_metadata(): - @deprecated(replacement="new_fn", since="3.1", removed_in="4.0") + round_info = ROUND_WITH_REMOVAL.value + + @deprecated(replacement="new_fn", deprecation_round=ROUND_WITH_REMOVAL) def old_fn(): pass @@ -93,30 +119,29 @@ def old_fn(): assert info.kind == "callable" assert info.target.endswith("old_fn") assert info.replacement == "new_fn" - assert info.since == Version("3.1") - assert info.removed_in == Version("4.0") - - -def test_deprecated_invalid_since_raises(): - with pytest.raises(ValueError, match="Invalid version"): - - @deprecated(since="not-a-version") - def old_fn(): - pass + assert info.deprecation_round is ROUND_WITH_REMOVAL + assert info.since == round_info.since + assert info.removed_in == round_info.removed_in + assert info.url == round_info.url -def test_deprecated_invalid_removed_in_raises(): - with pytest.raises(ValueError, match="Invalid version"): +def test_deprecated_without_round_has_no_version_metadata(): + @deprecated(replacement="new_fn") + def old_fn(): + pass - @deprecated(removed_in="definitely-not-a-version") - def old_fn(): - pass + info = old_fn.__deprecated_info__ + assert info.deprecation_round is None + assert info.since is None + assert info.removed_in is None + assert info.url is None -def test_deprecated_removed_in_must_be_greater_than_since(): - with pytest.raises(ValueError, match="must be greater than"): +def test_deprecated_rejects_a_round_defined_outside_the_enum(): + """Rounds must be registered in ``DeprecationRound``, not built at the call site.""" + with pytest.raises(ValueError): - @deprecated(since="4.0", removed_in="4.0") + @deprecated(deprecation_round=DeprecationRoundInfo(since="9.9", summary="ad-hoc")) def old_fn(): pass @@ -160,7 +185,7 @@ def fn(shuffle=False): def test_renamed_parameter_warning_contains_names(): - @renamed_parameter(old="videotype", new="video_extensions", since="3.2") + @renamed_parameter(old="videotype", new="video_extensions", deprecation_round=ROUND_WITHOUT_REMOVAL) def fn(video_extensions=None): return video_extensions @@ -169,7 +194,7 @@ def fn(video_extensions=None): message = str(record[0].message) assert "video_extensions" in message - assert "3.2" in message + assert str(ROUND_WITHOUT_REMOVAL.value.since) in message def test_renamed_parameter_preserves_name(): @@ -190,7 +215,7 @@ def fn(video_extensions=None): def test_renamed_parameter_attaches_metadata(): - @renamed_parameter(old="videotype", new="video_extensions", since="3.2") + @renamed_parameter(old="videotype", new="video_extensions", deprecation_round=ROUND_WITHOUT_REMOVAL) def fn(video_extensions=None): return video_extensions @@ -202,15 +227,8 @@ def fn(video_extensions=None): assert info.target.endswith("fn") assert info.old_parameter == "videotype" assert info.new_parameter == "video_extensions" - assert info.since == Version("3.2") - - -def test_renamed_parameter_invalid_since_raises(): - with pytest.raises(ValueError, match="Invalid version"): - - @renamed_parameter(old="videotype", new="video_extensions", since="invalid-version") - def fn(video_extensions=None): - return video_extensions + assert info.deprecation_round is ROUND_WITHOUT_REMOVAL + assert info.since == ROUND_WITHOUT_REMOVAL.value.since def test_renamed_parameter_new_not_in_signature_raises(): @@ -277,8 +295,8 @@ def fn(shuffle=False): def test_multiple_subsequent_renames_allowed(): - @renamed_parameter(old="oldestname", new="newest", since="3.0.0") - @renamed_parameter(old="older_name", new="newest", since="4.0.0") + @renamed_parameter(old="oldestname", new="newest", deprecation_round=ROUND_WITHOUT_REMOVAL) + @renamed_parameter(old="older_name", new="newest", deprecation_round=DeprecationRound.CONFIG_MODEL_MIGRATION) def fn(*, newest): return newest @@ -289,3 +307,84 @@ def fn(*, newest): with pytest.warns(DLCDeprecationWarning): result = fn(older_name=2) assert result == 2 + + +# --------------------------------------------------------------------------- +# DeprecationInfo +# --------------------------------------------------------------------------- + + +def test_callable_info_rejects_parameter_names(): + with pytest.raises(ValueError, match="cannot specify parameter names"): + DeprecationInfo(kind="callable", target="func", old_parameter="old", new_parameter="new") + + +def test_parameter_info_requires_both_parameter_names(): + with pytest.raises(ValueError, match="require both"): + DeprecationInfo(kind="parameter", target="func", old_parameter="old") + + +def test_parameter_info_rejects_replacement(): + with pytest.raises(ValueError, match="cannot specify 'replacement'"): + DeprecationInfo( + kind="parameter", + target="func", + replacement="not valid", + old_parameter="old", + new_parameter="new", + ) + + +# --------------------------------------------------------------------------- +# DeprecationRound / DeprecationRoundInfo +# --------------------------------------------------------------------------- + + +# An enum accepts any value, so a member assigned a bare version string would only fail +# later, when a marker reaches through .value. +def test_every_round_holds_a_round_info(): + assert list(DeprecationRound) + + for member in DeprecationRound: + assert isinstance(member.value, DeprecationRoundInfo), member.name + assert isinstance(member.value.since, Version), member.name + + +def test_round_url_points_at_the_pull_request(): + assert DeprecationRoundInfo(since="3.1", pull_request=1234).url.endswith("/pull/1234") + assert DeprecationRoundInfo(since="3.1").url is None + + +def test_round_invalid_since_raises(): + with pytest.raises(ValueError, match="Invalid version"): + DeprecationRoundInfo(since="not-a-version") + + +def test_round_invalid_removed_in_raises(): + with pytest.raises(ValueError, match="Invalid version"): + DeprecationRoundInfo(since="3.1", removed_in="definitely-not-a-version") + + +def test_round_removed_in_must_be_greater_than_since(): + with pytest.raises(ValueError, match="must be greater than"): + DeprecationRoundInfo(since="4.0", removed_in="4.0") + + +INLINE_ROUND = re.compile(r"deprecation_round\s*=\s*DeprecationRoundInfo\(") +ROUND_FIELD_ACCESS = re.compile(r"DeprecationRound\.\w+\.value\.") + + +def test_markers_reference_rounds_without_reaching_into_them(): + """Markers must name a ``DeprecationRound`` member, not build or unpack a round.""" + package_root = Path(deeplabcut.__file__).parent + exempt = {package_root / "core" / "deprecation.py"} + + offenders = [ + f"{path.relative_to(package_root)}:{lineno}" + for path in package_root.rglob("*.py") + if path not in exempt + for lineno, line in enumerate(path.read_text(encoding="utf-8").splitlines(), start=1) + if INLINE_ROUND.search(line) or ROUND_FIELD_ACCESS.search(line) + ] + + assert not offenders, "Pass a DeprecationRound member instead: " + ", ".join(offenders)