diff --git a/deeplabcut/compat.py b/deeplabcut/compat.py index ab0920a11..1b44968ce 100644 --- a/deeplabcut/compat.py +++ b/deeplabcut/compat.py @@ -21,6 +21,7 @@ import deeplabcut.core.visualization as visualization from deeplabcut.core.engine import Engine from deeplabcut.generate_training_dataset.metadata import get_shuffle_engine +from deeplabcut.utils.deprecation import renamed_parameter DEFAULT_ENGINE = Engine.PYTORCH @@ -59,14 +60,17 @@ def get_available_aug_methods(engine: Engine) -> tuple[str, ...]: raise RuntimeError(f"Unknown augmentation for engine: {engine}") +@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") def train_network( config: str | Path, shuffle: int = 1, trainingsetindex: int = 0, max_snapshots_to_keep: int | None = None, - displayiters: int | None = None, - saveiters: int | None = None, - maxiters: int | None = None, + display_iters: int | None = None, + save_iters: int | None = None, + max_iters: int | None = None, epochs: int | None = None, save_epochs: int | None = None, allow_growth: bool = True, @@ -108,13 +112,13 @@ def train_network( are kept. See: https://github.com/DeepLabCut/DeepLabCut/issues/8#issuecomment-387404835 - displayiters: optional, default=None + display_iters: optional, default=None This variable is actually set in ``pose_config.yaml``. However, you can overwrite it with this hack. Don't use this regularly, just if you are too lazy to dig out the ``pose_config.yaml`` file for the corresponding project. If ``None``, the value from there is used, otherwise it is overwritten! - saveiters: optional, default=None + save_iters: optional, default=None Only for the TensorFlow engine (for the PyTorch engine see the ``torch_kwargs``: you can use ``save_epochs``). This variable is actually set in ``pose_config.yaml``. However, you can @@ -122,7 +126,7 @@ def train_network( to dig out the ``pose_config.yaml`` file for the corresponding project. If ``None``, the value from there is used, otherwise it is overwritten! - maxiters: optional, default=None + max_iters: optional, default=None Only for the TensorFlow engine (for the PyTorch engine see the ``torch_kwargs``: you can use ``epochs``). This variable is actually set in ``pose_config.yaml``. However, you can @@ -131,7 +135,7 @@ def train_network( If ``None``, the value from there is used, otherwise it is overwritten! epochs: optional, default=None - Only for the PyTorch engine (equivalent to the `maxiters` parameter for the + Only for the PyTorch engine (equivalent to the `max_iters` parameter for the TensorFlow engine). The maximum number of epochs to train the model for. If None, the value will be read from the `pytorch_config.yaml` file. An epoch is a single pass through the training dataset, which means your model has seen each @@ -140,7 +144,7 @@ def train_network( 16 with batch size 4, etc.). save_epochs: optional, default=None - Only for the PyTorch engine (equivalent to the `saveiters` parameter for the + Only for the PyTorch engine (equivalent to the `save_iters` parameter for the TensorFlow engine). The number of epochs between each snapshot save. If None, the value will be read from the `pytorch_config.yaml` file. @@ -249,7 +253,7 @@ def train_network( batch_size=8, epochs=100, save_epochs=10, - displayiters=50, + display_iters=50, ) """ if engine is None: @@ -270,9 +274,9 @@ def train_network( shuffle=shuffle, trainingsetindex=trainingsetindex, max_snapshots_to_keep=max_snapshots_to_keep, - displayiters=displayiters, - saveiters=saveiters, - maxiters=maxiters, + displayiters=display_iters, + saveiters=save_iters, + maxiters=max_iters, allow_growth=allow_growth, gputouse=gputouse, autotune=autotune, @@ -299,7 +303,7 @@ def train_network( detector_batch_size=detector_batch_size, detector_epochs=detector_epochs, detector_save_epochs=detector_save_epochs, - display_iters=displayiters, + display_iters=display_iters, max_snapshots_to_keep=max_snapshots_to_keep, pose_threshold=pose_threshold, pytorch_cfg_updates=pytorch_cfg_updates, @@ -373,13 +377,15 @@ def return_train_network_path( raise NotImplementedError(f"This function is not implemented for {engine}") +@renamed_parameter(old="comparisonbodyparts", new="comparison_bodyparts", since="3.0.0") +@renamed_parameter(old="Shuffles", new="shuffles", since="3.0.0") def evaluate_network( config: str | Path, - Shuffles: Iterable[int] = (1,), + shuffles: Iterable[int] = (1,), trainingsetindex: int | str = 0, plotting: bool | str = False, show_errors: bool = True, - comparisonbodyparts: str | list[str] = "all", + comparison_bodyparts: str | list[str] = "all", gputouse: str | None = None, rescale: bool = False, modelprefix: str = "", @@ -401,7 +407,7 @@ def evaluate_network( config : string Full path of the config.yaml file. - Shuffles: list, optional, default=[1] + shuffles: list, optional, default=[1] List of integers specifying the shuffle indices of the training dataset. trainingsetindex: int or str, optional, default=0 @@ -419,7 +425,7 @@ def evaluate_network( show_errors: bool, optional, default=True Display train and test errors. - comparisonbodyparts: str or list, optional, default="all" + comparison_bodyparts: str or list, optional, default="all" The average error will be computed for those body parts only. The provided list has to be a subset of the defined body parts. @@ -481,14 +487,14 @@ def evaluate_network( If you do not want to plot and evaluate with shuffle set to 1. >>> deeplabcut.evaluate_network( - '/analysis/project/reaching-task/config.yaml', Shuffles=[1], + '/analysis/project/reaching-task/config.yaml', shuffles=[1], ) If you want to plot and evaluate with shuffle set to 0 and 1. >>> deeplabcut.evaluate_network( '/analysis/project/reaching-task/config.yaml', - Shuffles=[0, 1], + shuffles=[0, 1], plotting=True, ) @@ -496,7 +502,7 @@ def evaluate_network( >>> deeplabcut.evaluate_network( '/analysis/project/reaching-task/config.yaml', - Shuffles=[1], + shuffles=[1], plotting="individual", ) @@ -506,7 +512,7 @@ def evaluate_network( >>> deeplabcut.evaluate_network( >>> "/analysis/project/reaching-task/config.yaml", - >>> Shuffles=[0, 1], + >>> shuffles=[0, 1], >>> pcutoff={"left_ear": 0.8, "right_ear": 0.8}, >>> ) @@ -515,7 +521,7 @@ def evaluate_network( if engine is None: cfg = _load_config(config) engines = set() - for shuffle in Shuffles: + for shuffle in shuffles: engines.add( get_shuffle_engine( cfg, @@ -525,7 +531,7 @@ def evaluate_network( ) ) if len(engines) == 0: - raise ValueError(f"You must pass at least one shuffle to evaluate (had {list(Shuffles)})") + raise ValueError(f"You must pass at least one shuffle to evaluate (had {list(shuffles)})") elif len(engines) > 1: raise ValueError(f"All shuffles must have the same engine (found {list(engines)})") engine = engines.pop() @@ -535,11 +541,11 @@ def evaluate_network( return evaluate_network( str(config), - Shuffles=Shuffles, + Shuffles=shuffles, trainingsetindex=trainingsetindex, plotting=plotting, show_errors=show_errors, - comparisonbodyparts=comparisonbodyparts, + comparisonbodyparts=comparison_bodyparts, gputouse=gputouse, rescale=rescale, modelprefix=modelprefix, @@ -552,11 +558,11 @@ def evaluate_network( _update_device(gputouse, torch_kwargs) return evaluate_network( config, - shuffles=Shuffles, + shuffles=shuffles, trainingsetindex=trainingsetindex, plotting=plotting, show_errors=show_errors, - comparison_bodyparts=comparisonbodyparts, + comparison_bodyparts=comparison_bodyparts, snapshots_to_evaluate=snapshots_to_evaluate, per_keypoint_evaluation=per_keypoint_evaluation, modelprefix=modelprefix, @@ -567,12 +573,14 @@ def evaluate_network( raise NotImplementedError(f"This function is not implemented for {engine}") +@renamed_parameter(old="comparisonbodyparts", new="comparison_bodyparts", since="3.0.0") +@renamed_parameter(old="Snapindex", new="snapshotindex", since="3.0.0") def return_evaluate_network_data( config: str, shuffle: int = 0, trainingsetindex: int = 0, - comparisonbodyparts: str | list[str] = "all", - Snapindex: str | int | None = None, + comparison_bodyparts: str | list[str] = "all", + snapshotindex: str | int | None = None, rescale: bool = False, fulldata: bool = False, show_errors: bool = True, @@ -583,7 +591,7 @@ def return_evaluate_network_data( """Returns the results for (previously evaluated) network. deeplabcut.evaluate_network(..) Returns list of (per model): [trainingsiterations,tr ainfraction,shuffle,trainerror,testerror,pcutoff,trainerrorpcutoff,testerrorpcutoff, - Snapshots[snapindex],scale,net_type] + Snapshots[snapshotindex],scale,net_type] This function is only implemented for tensorflow models/shuffles, and will throw an error if called with a PyTorch shuffle. @@ -592,7 +600,7 @@ def return_evaluate_network_data( Returns list of: (DataMachine, Data, data, trainIndices, testIndices, trainFraction, DLCscorer, - comparisonbodyparts, cfg, Snapshots[snapindex] + comparison_bodyparts, cfg, Snapshots[snapshotindex] ) ---------- config : string @@ -607,7 +615,7 @@ def return_evaluate_network_data( By default the first (note that TrainingFraction is a list in config.yaml). This variable can also be set to "all". - comparisonbodyparts: list of bodyparts, Default is "all". + comparison_bodyparts: list of bodyparts, Default is "all". The average error will be computed for those body parts only (Has to be a subset of the body parts). @@ -649,8 +657,8 @@ def return_evaluate_network_data( config, shuffle=shuffle, trainingsetindex=trainingsetindex, - comparisonbodyparts=comparisonbodyparts, - Snapindex=Snapindex, + comparisonbodyparts=comparison_bodyparts, + Snapindex=snapshotindex, rescale=rescale, fulldata=fulldata, show_errors=show_errors, @@ -661,17 +669,19 @@ def return_evaluate_network_data( raise NotImplementedError(f"This function is not implemented for {engine}") +@renamed_parameter(old="batchsize", new="batch_size", since="3.0.0") +@renamed_parameter(old="videotype", new="extensions", since="3.0.0") def analyze_videos( config: str, videos: list[str], - videotype: str | Sequence[str] | None = None, + extensions: str | Sequence[str] | None = None, shuffle: int = 1, trainingsetindex: int = 0, gputouse: str | None = None, save_as_csv: bool = False, in_random_order: bool = True, destfolder: str | None = None, - batchsize: int = None, + batch_size: int = None, cropping: list[int] | None = None, TFGPUinference: bool = True, dynamic: tuple[bool, float, int] = (False, 0.5, 10), @@ -710,7 +720,7 @@ def analyze_videos( A list of strings containing the full paths to videos for analysis or a path to the directory, where all the videos with same extension are stored. - videotype : str | Sequence[str] | None, optional, default=None + extensions : str | Sequence[str] | None, optional, default=None Controls how ``videos`` are filtered, based on file extension. File paths and directory contents are treated differently: - ``None`` (default): file paths are accepted as-is; directories are @@ -746,7 +756,7 @@ def analyze_videos( the video is used. Note that for subsequent analysis this folder also needs to be passed. - batchsize: int or None, optional, default=None + batch_size: int or None, optional, default=None Currently not supported by the PyTorch engine. Change batch size for inference; if given overwrites value in ``pose_cfg.yaml``. @@ -869,7 +879,7 @@ def analyze_videos( >>> deeplabcut.analyze_videos( '/analysis/project/reaching-task/config.yaml', ['/analysis/project/videos'], - videotype='.avi', + extensions='.avi', ) Analyze multiple videos @@ -923,14 +933,14 @@ def analyze_videos( return analyze_videos( config, videos, - videotype=videotype, + extensions=extensions, shuffle=shuffle, trainingsetindex=trainingsetindex, gputouse=gputouse, save_as_csv=save_as_csv, in_random_order=in_random_order, destfolder=destfolder, - batchsize=batchsize, + batchsize=batch_size, cropping=cropping, TFGPUinference=TFGPUinference, dynamic=dynamic, @@ -950,20 +960,20 @@ def analyze_videos( _update_device(gputouse, torch_kwargs) - if batchsize is not None: + if batch_size is not None: if "batch_size" in torch_kwargs: print( - f"You called analyze_videos with parameters ``batchsize={batchsize}" + f"You called analyze_videos with parameters ``batch_size={batch_size}" f"`` and batch_size={torch_kwargs['batch_size']}. Only one is " f"needed/used. Using batch size {torch_kwargs['batch_size']}" ) else: - torch_kwargs["batch_size"] = batchsize + torch_kwargs["batch_size"] = batch_size return analyze_videos( config, videos=videos, - videotype=videotype, + extensions=extensions, shuffle=shuffle, trainingsetindex=trainingsetindex, save_as_csv=save_as_csv, @@ -986,16 +996,18 @@ def analyze_videos( raise NotImplementedError(f"This function is not implemented for {engine}") +@renamed_parameter(old="batchsize", new="batch_size", since="3.0.0") +@renamed_parameter(old="videotype", new="extensions", since="3.0.0") def create_tracking_dataset( config: str, videos: list[str], track_method: str, - videotype: str | Sequence[str] | None = None, + extensions: str | Sequence[str] | None = None, shuffle: int = 1, trainingsetindex: int = 0, gputouse: int | None = None, destfolder: str | None = None, - batchsize: int | None = None, + batch_size: int | None = None, cropping: list[int] | None = None, TFGPUinference: bool = True, modelprefix: str = "", @@ -1019,7 +1031,7 @@ def create_tracking_dataset( Specifies the tracker used to generate the pose estimation data. Must be either 'box', 'skeleton', or 'ellipse'. - videotype : str | Sequence[str] | None, optional, default=None + extensions : str | Sequence[str] | None, optional, default=None Controls how ``videos`` are filtered, based on file extension. File paths and directory contents are treated differently: - ``None`` (default): file paths are accepted as-is; directories are @@ -1091,12 +1103,12 @@ def create_tracking_dataset( config, videos, track_method, - videotype=videotype, + extensions=extensions, shuffle=shuffle, trainingsetindex=trainingsetindex, gputouse=gputouse, destfolder=destfolder, - batchsize=batchsize, + batchsize=batch_size, cropping=cropping, TFGPUinference=TFGPUinference, modelprefix=modelprefix, @@ -1110,11 +1122,11 @@ def create_tracking_dataset( config, videos, track_method, - videotype=videotype, + extensions=extensions, shuffle=shuffle, trainingsetindex=trainingsetindex, destfolder=destfolder, - batch_size=batchsize, + batch_size=batch_size, cropping=cropping, modelprefix=modelprefix, robust_nframes=robust_nframes, @@ -1413,10 +1425,11 @@ def analyze_time_lapse_frames( raise NotImplementedError(f"This function is not implemented for {engine}") +@renamed_parameter(old="videotype", new="extensions", since="3.0.0") def convert_detections2tracklets( config: str, videos: list[str], - videotype: str | Sequence[str] | None = None, + extensions: str | Sequence[str] | None = None, shuffle: int = 1, trainingsetindex: int = 0, overwrite: bool = False, @@ -1443,7 +1456,7 @@ def convert_detections2tracklets( A list of strings containing the full paths to videos for analysis or a path to the directory, where all the videos with same extension are stored. - videotype : str | Sequence[str] | None, optional, default=None + extensions : str | Sequence[str] | None, optional, default=None Controls how ``videos`` are filtered, based on file extension. File paths and directory contents are treated differently: - ``None`` (default): file paths are accepted as-is; directories are @@ -1509,7 +1522,7 @@ def convert_detections2tracklets( >>> deeplabcut.convert_detections2tracklets( >>> "/analysis/project/reaching-task/config.yaml", >>> ["/analysis/project/video1.mp4"], - >>> videotype='.mp4', + >>> extensions='.mp4', >>> ) If you want to convert detections to tracklets based on box_tracker: @@ -1517,7 +1530,7 @@ def convert_detections2tracklets( >>> deeplabcut.convert_detections2tracklets( >>> "/analysis/project/reaching-task/config.yaml", >>> ["/analysis/project/video1.mp4"], - >>> videotype=".mp4", + >>> extensions=".mp4", >>> track_method="box", >>> ) @@ -1537,7 +1550,7 @@ def convert_detections2tracklets( return convert_detections2tracklets( config, videos, - videotype=videotype, + extensions=extensions, shuffle=shuffle, trainingsetindex=trainingsetindex, overwrite=overwrite, @@ -1563,7 +1576,7 @@ def convert_detections2tracklets( return convert_detections2tracklets( config, videos, - videotype=videotype, + extensions=extensions, shuffle=shuffle, trainingsetindex=trainingsetindex, overwrite=overwrite, @@ -1730,11 +1743,12 @@ def visualize_paf( return visualization.visualize_paf(image, paf, step=step, colors=colors) +@renamed_parameter(old="comparisonbodyparts", new="comparison_bodyparts", since="3.0.0") def extract_save_all_maps( config, shuffle: int = 1, trainingsetindex: int = 0, - comparisonbodyparts: str | list[str] = "all", + comparison_bodyparts: str | list[str] = "all", extract_paf: bool = True, all_paf_in_one: bool = True, gputouse: int | None = None, @@ -1763,7 +1777,7 @@ def extract_save_all_maps( By default the first (note that TrainingFraction is a list in config.yaml). This variable can also be set to "all". - comparisonbodyparts: list of bodyparts, Default is "all". + comparison_bodyparts: list of bodyparts, Default is "all". The average error will be computed for those body parts only (Has to be a subset of the body parts). extract_paf : bool @@ -1824,7 +1838,7 @@ def extract_save_all_maps( config, shuffle=shuffle, trainingsetindex=trainingsetindex, - comparisonbodyparts=comparisonbodyparts, + comparisonbodyparts=comparison_bodyparts, extract_paf=extract_paf, all_paf_in_one=all_paf_in_one, gputouse=gputouse, @@ -1840,7 +1854,7 @@ def extract_save_all_maps( config, shuffle=shuffle, trainingsetindex=trainingsetindex, - comparison_bodyparts=comparisonbodyparts, + comparison_bodyparts=comparison_bodyparts, extract_paf=extract_paf, all_paf_in_one=all_paf_in_one, device=_gpu_to_use_to_device(gputouse, device), diff --git a/deeplabcut/pose_estimation_pytorch/apis/tracking_dataset.py b/deeplabcut/pose_estimation_pytorch/apis/tracking_dataset.py index b031e8618..4995e8a50 100644 --- a/deeplabcut/pose_estimation_pytorch/apis/tracking_dataset.py +++ b/deeplabcut/pose_estimation_pytorch/apis/tracking_dataset.py @@ -26,6 +26,7 @@ from deeplabcut.pose_estimation_pytorch.task import Task from deeplabcut.pose_tracking_pytorch import create_triplets_dataset from deeplabcut.utils.auxfun_videos import collect_video_paths +from deeplabcut.utils.deprecation import renamed_parameter def build_feature_extraction_runner( @@ -125,11 +126,12 @@ def extract_features_for_video( shelf_writer.close() +@renamed_parameter(old="videotype", new="extensions", since="3.0.0") def create_tracking_dataset( config: str, videos: list[str] | list[Path], track_method: str, - videotype: str | Sequence[str] | None = None, + extensions: str | Sequence[str] | None = None, shuffle: int = 1, trainingsetindex: int = 0, destfolder: str | None = None, @@ -149,7 +151,7 @@ def create_tracking_dataset( the videos with same extension are stored. track_method: Specifies the tracker used to generate the pose estimation data. Must be either 'box', 'skeleton', or 'ellipse'. - videotype: Controls how ``videos`` are filtered, based on file extension. + extensions: Controls how ``videos`` are filtered, based on file extension. File paths and directory contents are treated differently: - ``None`` (default): file paths are accepted as-is; directories are scanned for files with a recognized video extension. @@ -245,7 +247,7 @@ def create_tracking_dataset( modelprefix=modelprefix, ) - videos = collect_video_paths(videos, extensions=videotype) + videos = collect_video_paths(videos, extensions=extensions) for video_path in videos: print(f"Loading {video_path}") video = VideoIterator(video_path, cropping=cropping) diff --git a/deeplabcut/pose_estimation_pytorch/apis/tracklets.py b/deeplabcut/pose_estimation_pytorch/apis/tracklets.py index 121a11e1e..5c69432da 100644 --- a/deeplabcut/pose_estimation_pytorch/apis/tracklets.py +++ b/deeplabcut/pose_estimation_pytorch/apis/tracklets.py @@ -31,12 +31,14 @@ ) from deeplabcut.pose_estimation_pytorch.data.dlcloader import DLCLoader from deeplabcut.utils.auxfun_videos import collect_video_paths +from deeplabcut.utils.deprecation import renamed_parameter +@renamed_parameter(old="videotype", new="extensions", since="3.0.0") def convert_detections2tracklets( config: str, videos: str | list[str], - videotype: str | Sequence[str] | None = None, + extensions: str | Sequence[str] | None = None, shuffle: int = 1, trainingsetindex: int = 0, overwrite: bool = False, @@ -126,7 +128,7 @@ def convert_detections2tracklets( ) paths_input = videos - videos = collect_video_paths(videos, extensions=videotype) + videos = collect_video_paths(videos, extensions=extensions) if len(videos) == 0: print(f"No videos were found in {paths_input}") return diff --git a/deeplabcut/pose_estimation_pytorch/apis/utils.py b/deeplabcut/pose_estimation_pytorch/apis/utils.py index 879e449e3..07c96f14e 100644 --- a/deeplabcut/pose_estimation_pytorch/apis/utils.py +++ b/deeplabcut/pose_estimation_pytorch/apis/utils.py @@ -66,7 +66,7 @@ from deeplabcut.pose_estimation_pytorch.utils import resolve_device from deeplabcut.utils import auxiliaryfunctions from deeplabcut.utils.auxfun_videos import SUPPORTED_VIDEOS, collect_video_paths -from deeplabcut.utils.deprecation import deprecated +from deeplabcut.utils.deprecation import deprecated, renamed_parameter def parse_snapshot_index_for_analysis( @@ -295,14 +295,15 @@ def get_scorer_name( @deprecated(replacement="deeplabcut.collect_video_paths", since="3.0.0") +@renamed_parameter(old="video_type", new="extensions", since="3.0.0") def list_videos_in_folder( data_path: str | Path | list[str | Path], - video_type: str | Sequence[str] | None = SUPPORTED_VIDEOS, + extensions: str | Sequence[str] | None = SUPPORTED_VIDEOS, shuffle: bool = False, ) -> list[Path]: return collect_video_paths( data_path=data_path, - extensions=video_type, + extensions=extensions, shuffle=shuffle, ) diff --git a/deeplabcut/pose_estimation_pytorch/apis/videos.py b/deeplabcut/pose_estimation_pytorch/apis/videos.py index c41f7c775..4dffd1dc1 100644 --- a/deeplabcut/pose_estimation_pytorch/apis/videos.py +++ b/deeplabcut/pose_estimation_pytorch/apis/videos.py @@ -46,6 +46,7 @@ from deeplabcut.refine_training_dataset.stitch import stitch_tracklets from deeplabcut.utils import VideoReader, auxiliaryfunctions from deeplabcut.utils.auxfun_videos import collect_video_paths +from deeplabcut.utils.deprecation import renamed_parameter class VideoIterator(VideoReader): @@ -241,10 +242,11 @@ def video_inference( return predictions +@renamed_parameter(old="videotype", new="extensions", since="3.0.0") def analyze_videos( config: str, videos: str | list[str], - videotype: str | Sequence[str] | None = None, + extensions: str | Sequence[str] | None = None, shuffle: int = 1, trainingsetindex: int = 0, save_as_csv: bool = False, @@ -284,7 +286,7 @@ def analyze_videos( videos: a str (or list of strings) containing the full paths to videos for analysis or a path to the directory, where all the videos with same extension are stored. - videotype: Controls how ``videos`` are filtered, based on file extension. + extensions: Controls how ``videos`` are filtered, based on file extension. File paths and directory contents are treated differently: - ``None`` (default): file paths are accepted as-is; directories are scanned for files with a recognized video extension. @@ -546,7 +548,7 @@ def analyze_videos( print(f"Using scorer: {dlc_scorer}") # Reading video and init variables - videos = collect_video_paths(videos, extensions=videotype, shuffle=in_random_order) + videos = collect_video_paths(videos, extensions=extensions, shuffle=in_random_order) h5_files_created = False # Track if any .h5 files were created for video in videos: @@ -676,7 +678,7 @@ def analyze_videos( convert_detections2tracklets( config=config, videos=str(video), - videotype=videotype, + extensions=extensions, shuffle=shuffle, trainingsetindex=trainingsetindex, overwrite=False, @@ -688,7 +690,7 @@ def analyze_videos( stitch_tracklets( config, [str(video)], - videotype, + extensions, shuffle, trainingsetindex, n_tracks=n_tracks, diff --git a/deeplabcut/pose_estimation_tensorflow/modelzoo/api/superanimal_inference.py b/deeplabcut/pose_estimation_tensorflow/modelzoo/api/superanimal_inference.py index 9d0386490..f57897c3e 100644 --- a/deeplabcut/pose_estimation_tensorflow/modelzoo/api/superanimal_inference.py +++ b/deeplabcut/pose_estimation_tensorflow/modelzoo/api/superanimal_inference.py @@ -28,6 +28,7 @@ from deeplabcut.pose_estimation_tensorflow.core import predict_multianimal as predict from deeplabcut.utils import auxiliaryfunctions from deeplabcut.utils.auxfun_videos import VideoWriter, collect_video_paths +from deeplabcut.utils.deprecation import renamed_parameter warnings.simplefilter("ignore", category=RuntimeWarning) @@ -243,12 +244,13 @@ def _video_inference( return PredicteData, nframes +@renamed_parameter(old="videotype", new="extensions", since="3.0.0") def video_inference( videos, project_name, model_name, scale_list=None, - videotype: str | Sequence[str] | None = None, + extensions: str | Sequence[str] | None = None, destfolder=None, batchsize=1, robust_nframes=False, @@ -307,7 +309,7 @@ def video_inference( sess, inputs, outputs = single_predict.setup_pose_prediction(test_cfg, allow_growth=allow_growth) DLCscorer = "DLC_" + Path(test_cfg["init_weights"]).stem - videos = collect_video_paths(videos, extensions=videotype) + videos = collect_video_paths(videos, extensions=extensions) datafiles = [] for video in videos: diff --git a/deeplabcut/pose_estimation_tensorflow/predict_videos.py b/deeplabcut/pose_estimation_tensorflow/predict_videos.py index d40fa68d1..d63d6025d 100644 --- a/deeplabcut/pose_estimation_tensorflow/predict_videos.py +++ b/deeplabcut/pose_estimation_tensorflow/predict_videos.py @@ -42,17 +42,19 @@ from deeplabcut.refine_training_dataset.stitch import stitch_tracklets from deeplabcut.utils import auxfun_models, auxfun_multianimal, auxiliaryfunctions from deeplabcut.utils.auxfun_videos import collect_video_paths +from deeplabcut.utils.deprecation import renamed_parameter #################################################### # Loading data, and defining model folder #################################################### +@renamed_parameter(old="videotype", new="extensions", since="3.0.0") def create_tracking_dataset( config, videos, track_method, - videotype: str | Sequence[str] | None = None, + extensions: str | Sequence[str] | None = None, shuffle=1, trainingsetindex=0, gputouse=None, @@ -200,7 +202,7 @@ def create_tracking_dataset( ################################################## # Looping over videos ################################################## - Videos = collect_video_paths(videos, extensions=videotype) + Videos = collect_video_paths(videos, extensions=extensions) if len(Videos) > 0: if "multi-animal" in dlc_cfg["dataset_type"]: for video in Videos: @@ -249,14 +251,15 @@ def create_tracking_dataset( ) return DLCscorer # note: this is either DLCscorer or DLCscorerlegacy depending on what was used! else: - print("No video(s) were found. Please check your paths and/or 'videotype'.") + print("No video(s) were found. Please check your paths and/or extensions filter.") return DLCscorer +@renamed_parameter(old="videotype", new="extensions", since="3.0.0") def analyze_videos( config, videos, - videotype: str | Sequence[str] | None = None, + extensions: str | Sequence[str] | None = None, shuffle=1, trainingsetindex=0, gputouse=None, @@ -300,7 +303,7 @@ def analyze_videos( A list of strings containing the full paths to videos for analysis or a path to the directory, where all the videos with same extension are stored. - videotype : str | Sequence[str] | None, optional, default=None + extensions : str | Sequence[str] | None, optional, default=None Controls how ``videos`` are filtered, based on file extension. File paths and directory contents are treated differently: - ``None`` (default): file paths are accepted as-is; directories are @@ -445,7 +448,7 @@ def analyze_videos( >>> deeplabcut.analyze_videos( '/analysis/project/reaching-task/config.yaml', ['/analysis/project/videos'], - videotype='.avi', + extensions='.avi', ) Analyze multiple videos @@ -600,7 +603,7 @@ def analyze_videos( ################################################## # Looping over videos ################################################## - Videos = collect_video_paths(videos, extensions=videotype, shuffle=in_random_order) + Videos = collect_video_paths(videos, extensions=extensions, shuffle=in_random_order) if len(Videos) > 0: if "multi-animal" in dlc_cfg["dataset_type"]: from deeplabcut.pose_estimation_tensorflow.predict_multianimal import ( @@ -625,7 +628,7 @@ def analyze_videos( convert_detections2tracklets( config, [video], - videotype, + extensions, shuffle, trainingsetindex, destfolder=destfolder, @@ -636,7 +639,7 @@ def analyze_videos( stitch_tracklets( config, [video], - videotype, + extensions, shuffle, trainingsetindex, destfolder=destfolder, @@ -686,7 +689,7 @@ def analyze_videos( ) return DLCscorer # note: this is either DLCscorer or DLCscorerlegacy depending on what was used! else: - print("No video(s) were found. Please check your paths and/or 'videotype'.") + print("No video(s) were found. Please check your paths and/or extensions filter.") return DLCscorer @@ -1481,10 +1484,11 @@ def _convert_detections_to_tracklets( pickle.dump(tracklets, f, pickle.HIGHEST_PROTOCOL) +@renamed_parameter(old="videotype", new="extensions", since="3.0.0") def convert_detections2tracklets( config, videos, - videotype: str | Sequence[str] | None = None, + extensions: str | Sequence[str] | None = None, shuffle=1, trainingsetindex=0, overwrite=False, @@ -1510,7 +1514,7 @@ def convert_detections2tracklets( A list of strings containing the full paths to videos for analysis or a path to the directory, where all the videos with same extension are stored. - videotype : str | Sequence[str] | None, optional, default=None + extensions : str | Sequence[str] | None, optional, default=None Controls how ``videos`` are filtered, based on file extension. File paths and directory contents are treated differently: - ``None`` (default): file paths are accepted as-is; directories are @@ -1570,14 +1574,14 @@ def convert_detections2tracklets( >>> deeplabcut.convert_detections2tracklets( '/analysis/project/reaching-task/config.yaml', ['/analysis/project/video1.mp4'], - videotype='.mp4' + extensions='.mp4' ) If you want to convert detections to tracklets based on box_tracker: >>> deeplabcut.convert_detections2tracklets( '/analysis/project/reaching-task/config.yaml', ['/analysis/project/video1.mp4'], - videotype='.mp4', + extensions='.mp4', track_method='box' ) @@ -1661,7 +1665,7 @@ def convert_detections2tracklets( ################################################## # Looping over videos ################################################## - Videos = collect_video_paths(videos, extensions=videotype) + Videos = collect_video_paths(videos, extensions=extensions) if len(Videos) > 0: for video in Videos: print("Processing... ", video) diff --git a/deeplabcut/pose_tracking_pytorch/apis.py b/deeplabcut/pose_tracking_pytorch/apis.py index 166e0509c..9bff6c0d1 100644 --- a/deeplabcut/pose_tracking_pytorch/apis.py +++ b/deeplabcut/pose_tracking_pytorch/apis.py @@ -11,11 +11,14 @@ from collections.abc import Sequence +from deeplabcut.utils.deprecation import renamed_parameter + +@renamed_parameter(old="videotype", new="extensions", since="3.0.0") def transformer_reID( config: str, videos: list[str], - videotype: str | Sequence[str] | None = None, + extensions: str | Sequence[str] | None = None, shuffle: int = 1, trainingsetindex: int = 0, track_method: str = "ellipse", @@ -46,7 +49,7 @@ def transformer_reID( A list of strings containing the full paths to videos for analysis or a path to the directory, where all the videos with same extension are stored. - videotype : str | Sequence[str] | None, optional, default=None + extensions : str | Sequence[str] | None, optional, default=None Controls how ``videos`` are filtered, based on file extension. File paths and directory contents are treated differently: - ``None`` (default): file paths are accepted as-is; directories are @@ -112,7 +115,7 @@ def transformer_reID( config, videos, track_method, - videotype=videotype, + extensions=extensions, shuffle=shuffle, trainingsetindex=trainingsetindex, modelprefix=modelprefix, @@ -135,7 +138,7 @@ def transformer_reID( config, DLCscorer, videos, - videotype=videotype, + extensions=extensions, train_frac=train_frac, modelprefix=modelprefix, train_epochs=train_epochs, @@ -151,7 +154,7 @@ def transformer_reID( deeplabcut.stitch_tracklets( config, videos, - videotype=videotype, + extensions=extensions, shuffle=shuffle, trainingsetindex=trainingsetindex, track_method=track_method, diff --git a/deeplabcut/pose_tracking_pytorch/train_dlctransreid.py b/deeplabcut/pose_tracking_pytorch/train_dlctransreid.py index b1145dc93..12f284135 100644 --- a/deeplabcut/pose_tracking_pytorch/train_dlctransreid.py +++ b/deeplabcut/pose_tracking_pytorch/train_dlctransreid.py @@ -23,6 +23,7 @@ import numpy as np from deeplabcut.utils.auxfun_videos import collect_video_paths +from deeplabcut.utils.deprecation import renamed_parameter from .config import cfg from .datasets import make_dlc_dataloader @@ -67,11 +68,12 @@ def split_train_test(npy_list, train_frac): return train_list, test_list +@renamed_parameter(old="videotype", new="extensions", since="3.0.0") def train_tracking_transformer( path_config_file, dlcscorer, videos, - videotype: str | Sequence[str] | None = None, + extensions: str | Sequence[str] | None = None, train_frac=0.8, modelprefix="", train_epochs=100, @@ -80,7 +82,7 @@ def train_tracking_transformer( destfolder=None, ): npy_list = [] - videos = collect_video_paths(videos, extensions=videotype) + videos = collect_video_paths(videos, extensions=extensions) for video in videos: videofolder = str(Path(video).parents[0]) if destfolder is None: diff --git a/deeplabcut/post_processing/analyze_skeleton.py b/deeplabcut/post_processing/analyze_skeleton.py index 14d5ab50d..5389919fd 100644 --- a/deeplabcut/post_processing/analyze_skeleton.py +++ b/deeplabcut/post_processing/analyze_skeleton.py @@ -25,6 +25,7 @@ from deeplabcut.utils import auxfun_multianimal, auxiliaryfunctions from deeplabcut.utils.auxfun_videos import collect_video_paths +from deeplabcut.utils.deprecation import renamed_parameter # utility functions @@ -166,10 +167,11 @@ def analyzebone(bp1, bp2): # MAIN FUNC +@renamed_parameter(old="videotype", new="extensions", since="3.0.0") def analyzeskeleton( config, videos, - videotype: str | Sequence[str] | None = None, + extensions: str | Sequence[str] | None = None, shuffle=1, trainingsetindex=0, filtered=False, @@ -193,7 +195,7 @@ def analyzeskeleton( The full paths to videos for analysis or a path to the directory, where all the videos with same extension are stored. - videotype : str | Sequence[str] | None, optional, default=None + extensions : str | Sequence[str] | None, optional, default=None Controls how ``videos`` are filtered, based on file extension. File paths and directory contents are treated differently: - ``None`` (default): file paths are accepted as-is; directories are @@ -266,7 +268,7 @@ def analyzeskeleton( **kwargs, ) - Videos = collect_video_paths(videos, extensions=videotype) + Videos = collect_video_paths(videos, extensions=extensions) for video in Videos: print(f"Processing {video}") if destfolder is None: diff --git a/deeplabcut/post_processing/filtering.py b/deeplabcut/post_processing/filtering.py index f0ed95595..463803f4f 100644 --- a/deeplabcut/post_processing/filtering.py +++ b/deeplabcut/post_processing/filtering.py @@ -21,6 +21,7 @@ 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 +from deeplabcut.utils.deprecation import renamed_parameter def columnwise_spline_interp(data, max_gap=0): @@ -64,10 +65,11 @@ def columnwise_spline_interp(data, max_gap=0): return temp +@renamed_parameter(old="videotype", new="extensions", since="3.0.0") def filterpredictions( config, video, - videotype: str | Sequence[str] | None = None, + extensions: str | Sequence[str] | None = None, shuffle=1, trainingsetindex=0, filtertype="median", @@ -97,7 +99,7 @@ def filterpredictions( Full path of the video to extract the frame from. Make sure that this video is already analyzed. - videotype : str | Sequence[str] | None, optional, default=None + extensions : str | Sequence[str] | None, optional, default=None Controls how ``videos`` are filtered, based on file extension. File paths and directory contents are treated differently: - ``None`` (default): file paths are accepted as-is; directories are @@ -223,12 +225,12 @@ def filterpredictions( modelprefix=modelprefix, **kwargs, ) - Videos = collect_video_paths(video, extensions=videotype) + Videos = collect_video_paths(video, extensions=extensions) video_to_filtered_df = {} if not len(Videos): - print("No video(s) were found. Please check your paths and/or 'videotype'.") + print("No video(s) were found. Please check your paths and/or extensions filter.") if return_data: return video_to_filtered_df diff --git a/deeplabcut/refine_training_dataset/outlier_frames.py b/deeplabcut/refine_training_dataset/outlier_frames.py index fbaa7d663..f2c6a57f7 100644 --- a/deeplabcut/refine_training_dataset/outlier_frames.py +++ b/deeplabcut/refine_training_dataset/outlier_frames.py @@ -32,6 +32,7 @@ visualization, ) from deeplabcut.utils.auxfun_videos import VideoWriter, collect_video_paths +from deeplabcut.utils.deprecation import renamed_parameter def find_outliers_in_raw_data( @@ -197,10 +198,11 @@ def _read_video_specific_cropping_margins(config: str | Path | dict, video_path: return x1, y1 +@renamed_parameter(old="videotype", new="extensions", since="3.0.0") def extract_outlier_frames( config, videos, - videotype: str | Sequence[str] | None = None, + extensions: str | Sequence[str] | None = None, shuffle=1, trainingsetindex=0, outlieralgorithm="jump", @@ -240,7 +242,7 @@ def extract_outlier_frames( The full paths to videos for analysis or a path to the directory, where all the videos with same extension are stored. - videotype : str | Sequence[str] | None, optional, default=None + extensions : str | Sequence[str] | None, optional, default=None Controls how ``videos`` are filtered, based on file extension. File paths and directory contents are treated differently: - ``None`` (default): file paths are accepted as-is; directories are @@ -406,7 +408,7 @@ def extract_outlier_frames( **kwargs, ) - Videos = collect_video_paths(videos, extensions=videotype) + Videos = collect_video_paths(videos, extensions=extensions) if len(Videos) == 0: print("No suitable videos found in", videos) diff --git a/deeplabcut/refine_training_dataset/stitch.py b/deeplabcut/refine_training_dataset/stitch.py index 049c72d2b..188b60eb7 100644 --- a/deeplabcut/refine_training_dataset/stitch.py +++ b/deeplabcut/refine_training_dataset/stitch.py @@ -37,6 +37,7 @@ ) from deeplabcut.utils import auxfun_multianimal, auxiliaryfunctions from deeplabcut.utils.auxfun_videos import VideoWriter, collect_video_paths +from deeplabcut.utils.deprecation import renamed_parameter class Tracklet: @@ -958,10 +959,11 @@ def reconstruct_path(self, source): return path +@renamed_parameter(old="videotype", new="extensions", since="3.0.0") def stitch_tracklets( config_path, videos, - videotype: str | Sequence[str] | None = None, + extensions: str | Sequence[str] | None = None, shuffle=1, trainingsetindex=0, n_tracks=None, @@ -991,7 +993,7 @@ def stitch_tracklets( A list of strings containing the full paths to videos for analysis or a path to the directory, where all the videos with same extension are stored. - videotype : str | Sequence[str] | None, optional, default=None + extensions : str | Sequence[str] | None, optional, default=None Controls how ``videos`` are filtered, based on file extension. File paths and directory contents are treated differently: - ``None`` (default): file paths are accepted as-is; directories are @@ -1081,7 +1083,7 @@ def stitch_tracklets( ------- A TrackletStitcher object """ - vids = collect_video_paths(videos, extensions=videotype) + vids = collect_video_paths(videos, extensions=extensions) if not vids: print("No video(s) found. Please check your path!") return diff --git a/deeplabcut/utils/auxiliaryfunctions.py b/deeplabcut/utils/auxiliaryfunctions.py index 73b0c97cd..abf27ae09 100644 --- a/deeplabcut/utils/auxiliaryfunctions.py +++ b/deeplabcut/utils/auxiliaryfunctions.py @@ -35,7 +35,7 @@ from deeplabcut.core.trackingutils import TRACK_METHODS from deeplabcut.utils import auxfun_multianimal from deeplabcut.utils.auxfun_videos import SUPPORTED_VIDEOS, collect_video_paths -from deeplabcut.utils.deprecation import deprecated +from deeplabcut.utils.deprecation import deprecated, renamed_parameter def create_config_template(multianimal=False): @@ -391,14 +391,15 @@ def write_pickle(filename, data): @deprecated(replacement="deeplabcut.collect_video_paths", since="3.0.0") +@renamed_parameter(old="videotype", new="extensions", since="3.0.0") def get_list_of_videos( videos: list[str] | str, - videotype: str | Sequence[str] | None = SUPPORTED_VIDEOS, + extensions: str | Sequence[str] | None = SUPPORTED_VIDEOS, in_random_order: bool = True, ) -> list[str]: video_paths = collect_video_paths( data_path=videos, - extensions=videotype, + extensions=extensions, shuffle=in_random_order, ) return [str(path) for path in video_paths] diff --git a/deeplabcut/utils/deprecation.py b/deeplabcut/utils/deprecation.py index 8551929c5..5610e3ef1 100644 --- a/deeplabcut/utils/deprecation.py +++ b/deeplabcut/utils/deprecation.py @@ -11,6 +11,7 @@ from __future__ import annotations import functools +import inspect import warnings from collections.abc import Callable from typing import Literal, ParamSpec, TypeVar @@ -128,9 +129,43 @@ def renamed_parameter( 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. + + Rules: + - ``new`` must be the name used in the function signature and all + internal call-sites. ``old`` must **not** appear in the signature. + - Do **not** chain renames. If ``A`` was renamed to ``B`` and ``B`` + is later renamed to ``C``, replace the ``A→B`` decorator with + ``A→C`` directly rather than stacking a second decorator. + - Multiple independent renames on the same function (e.g. + ``batchsize→batch_size`` *and* ``videotype→extensions``) are fine + as long as they do not form a chain. + - This decorator only intercepts **keyword** arguments. Positional + arguments are passed through unchanged; renaming a parameter that + callers commonly pass positionally will not be caught. """ def decorator(fn: Callable[P, R]) -> Callable[P, R]: + # Guard: 'new' must actually exist in the function's signature. + sig = inspect.signature(fn) + if new not in sig.parameters: + raise ValueError( + f"@renamed_parameter: '{new}' is not a parameter of " + f"{fn.__qualname__}. " + f"Available parameters: {list(sig.parameters)}" + ) + + # Guard: disallow chaining renames (A→B stacked on top of B→C). + existing = getattr(fn, "__deprecated_params__", ()) + for prev in existing: + if prev.old_parameter == new: + raise ValueError( + f"@renamed_parameter: chaining renames is not allowed. " + f"'{old}' → '{new}' would chain with the existing " + f"'{prev.old_parameter}' → '{prev.new_parameter}' rename " + f"on {fn.__qualname__}. " + f"Use '{old}' → '{prev.new_parameter}' directly instead." + ) + info = DeprecationInfo( kind="parameter", target=fn.__qualname__, @@ -149,7 +184,6 @@ def wrapper(*args: P.args, **kwargs: P.kwargs) -> R: kwargs[new] = kwargs.pop(old) return fn(*args, **kwargs) - existing = getattr(fn, "__deprecated_params__", ()) wrapper.__deprecated_params__ = (*existing, info) return wrapper diff --git a/deeplabcut/utils/make_labeled_video.py b/deeplabcut/utils/make_labeled_video.py index c83778ba0..d8936ceb4 100644 --- a/deeplabcut/utils/make_labeled_video.py +++ b/deeplabcut/utils/make_labeled_video.py @@ -49,6 +49,7 @@ from deeplabcut.core.engine import Engine from deeplabcut.utils import auxfun_multianimal, auxiliaryfunctions, visualization from deeplabcut.utils.auxfun_videos import VideoWriter, collect_video_paths +from deeplabcut.utils.deprecation import renamed_parameter from deeplabcut.utils.video_processor import ( VideoProcessorCV as vp, ) # used to CreateVideo @@ -390,10 +391,11 @@ def CreateVideoSlow( plt.switch_backend(prev_backend) +@renamed_parameter(old="videotype", new="extensions", since="3.0.0") def create_labeled_video( config: str, videos: list[str], - videotype: str | Sequence[str] | None = None, + extensions: str | Sequence[str] | None = None, shuffle: int = 1, trainingsetindex: int = 0, filtered: bool = False, @@ -441,7 +443,7 @@ def create_labeled_video( A list of strings containing the full paths to videos for analysis or a path to the directory, where all the videos with same extension are stored. - videotype : str | Sequence[str] | None, optional, default=None + extensions : str | Sequence[str] | None, optional, default=None Controls how ``videos`` are filtered, based on file extension. File paths and directory contents are treated differently: - ``None`` (default): file paths are accepted as-is; directories are @@ -633,7 +635,7 @@ def create_labeled_video( >>> deeplabcut.create_labeled_video( '/analysis/project/reaching-task/config.yaml', ['/analysis/project/videos/'], - videotype='mp4', + extensions='mp4', ) """ if skeleton is None: @@ -736,7 +738,7 @@ def create_labeled_video( skeleton_color = None start_path = os.getcwd() - Videos = collect_video_paths(videos, extensions=videotype) + Videos = collect_video_paths(videos, extensions=extensions) if not Videos: return [] @@ -1147,10 +1149,11 @@ def create_video_with_keypoints_only( plt.switch_backend(prev_backend) +@renamed_parameter(old="videotype", new="extensions", since="3.0.0") def create_video_with_all_detections( config, videos, - videotype: str | Sequence[str] | None = None, + extensions: str | Sequence[str] | None = None, shuffle=1, trainingsetindex=0, displayedbodyparts="all", @@ -1172,7 +1175,7 @@ def create_video_with_all_detections( A list of strings containing the full paths to videos for analysis or a path to the directory, where all the videos with same extension are stored. - videotype : str | Sequence[str] | None, optional, default=None + extensions : str | Sequence[str] | None, optional, default=None Controls how ``videos`` are filtered, based on file extension. File paths and directory contents are treated differently: - ``None`` (default): file paths are accepted as-is; directories are @@ -1232,7 +1235,7 @@ def create_video_with_all_detections( **kwargs, ) - videos = collect_video_paths(videos, extensions=videotype) + videos = collect_video_paths(videos, extensions=extensions) if not videos: return diff --git a/deeplabcut/utils/plotting.py b/deeplabcut/utils/plotting.py index 51a8b575e..4ab6bcf25 100644 --- a/deeplabcut/utils/plotting.py +++ b/deeplabcut/utils/plotting.py @@ -38,6 +38,7 @@ from deeplabcut.core import crossvalutils from deeplabcut.utils import auxfun_multianimal, auxiliaryfunctions, visualization from deeplabcut.utils.auxfun_videos import collect_video_paths +from deeplabcut.utils.deprecation import renamed_parameter def Histogram(vector, color, bins, ax=None, linewidth=1.0): @@ -169,10 +170,11 @@ def PlottingResults( ################################################## +@renamed_parameter(old="videotype", new="extensions", since="3.0.0") def plot_trajectories( config, videos, - videotype: str | Sequence[str] | None = None, + extensions: str | Sequence[str] | None = None, shuffle=1, trainingsetindex=0, filtered=False, @@ -199,7 +201,7 @@ def plot_trajectories( Full paths to videos for analysis or a path to the directory, where all the videos with same extension are stored. - videotype : str | Sequence[str] | None, optional, default=None + extensions : str | Sequence[str] | None, optional, default=None Controls how ``videos`` are filtered, based on file extension. File paths and directory contents are treated differently: - ``None`` (default): file paths are accepted as-is; directories are @@ -293,9 +295,9 @@ def plot_trajectories( ) # automatically loads corresponding model (even training iteration based on snapshot index) bodyparts = auxiliaryfunctions.intersection_of_body_parts_and_ones_given_by_user(cfg, displayedbodyparts) individuals = auxfun_multianimal.IntersectionofIndividualsandOnesGivenbyUser(cfg, displayedindividuals) - Videos = collect_video_paths(videos, extensions=videotype) + Videos = collect_video_paths(videos, extensions=extensions) if not len(Videos): - print("No videos found. Make sure you passed a list of videos and that *videotype* is right.") + print("No videos found. Make sure you passed a list of videos and that extensions filter is right.") return failures, multianimal_errors = [], [] diff --git a/tests/utils/test_deprecation.py b/tests/utils/test_deprecation.py index 5938619f7..1c189e32a 100644 --- a/tests/utils/test_deprecation.py +++ b/tests/utils/test_deprecation.py @@ -211,3 +211,57 @@ def test_renamed_parameter_invalid_since_raises(): @renamed_parameter(old="videotype", new="extensions", since="invalid-version") def fn(extensions=None): return extensions + + +def test_renamed_parameter_new_not_in_signature_raises(): + with pytest.raises(ValueError, match="not a parameter"): + + @renamed_parameter(old="foo", new="nonexistent") + def fn(bar=None): + return bar + + +def test_new_not_in_signature_raises(): + """Applying a rename whose 'new' is not in the signature raises an error.""" + with pytest.raises(ValueError, match="not a parameter"): + + @renamed_parameter(old="videotype", new="video_type") + def fn(extensions=None): + return extensions + + +def test_renamed_parameter_chaining_raises(): + """Chaining renames A→B→C raises an error.""" + with pytest.raises(ValueError, match="chaining renames is not allowed"): + + @renamed_parameter(old="A", new="B") # outer: A→B, but B is already deprecated to C + @renamed_parameter(old="B", new="C") # inner: B→C + def fn(B=None, C=None): + return C or B + + +def test_renamed_parameter_multiple_independent_renames(): + @renamed_parameter(old="batchsize", new="batch_size") + @renamed_parameter(old="videotype", new="extensions") + def fn(extensions=None, batch_size=None): + return extensions, batch_size + + with pytest.warns(DLCDeprecationWarning): + result = fn(videotype="mp4") + assert result == ("mp4", None) + + with pytest.warns(DLCDeprecationWarning): + result = fn(batchsize=4) + assert result == (None, 4) + + +def test_renamed_parameter_positional_arg_unaffected(): + @renamed_parameter(old="in_random_order", new="shuffle") + def fn(shuffle=False): + return shuffle + + with warnings.catch_warnings(): + warnings.simplefilter("error", DLCDeprecationWarning) + result = fn(True) + + assert result is True