From 2d41bee490431a75a5d8c5b5815e6cd21c7a3750 Mon Sep 17 00:00:00 2001 From: Jaap de Ruyter van Steveninck <32810691+deruyter92@users.noreply.github.com> Date: Mon, 4 May 2026 22:27:00 +0200 Subject: [PATCH 01/24] Update `list_video_files_in_folder` only filter explicitly provided files if filter `video_type` is set. - Accept files without extension - Default folder searching is kept as is (using valid video extensions) --- .../pose_estimation_pytorch/apis/utils.py | 29 +++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/deeplabcut/pose_estimation_pytorch/apis/utils.py b/deeplabcut/pose_estimation_pytorch/apis/utils.py index 8eb47886a..3ea40ea6d 100644 --- a/deeplabcut/pose_estimation_pytorch/apis/utils.py +++ b/deeplabcut/pose_estimation_pytorch/apis/utils.py @@ -302,8 +302,10 @@ def list_videos_in_folder( Args: data_path: Path or list of paths to folders containing videos, or individual video files. Can be a mix of directories and files. - video_type: The type of video to filter for (e.g., "mp4", ".mp4"). If None, - all supported video types are included. + video_type: The type of video to filter for (e.g., "mp4", ".mp4"). + - If set: filter both directory contents and supplied files. + - If ``None``, explicitly-supplied files are used as-is. (Directory + contents are filtered using ``SUPPORTED_VIDEOS`` by default). shuffle: Whether to shuffle the order of videos. If False, videos are returned in sorted order for deterministic behavior. @@ -316,20 +318,29 @@ def list_videos_in_folder( if isinstance(data_path, (str, Path)): data_path = [data_path] - if not video_type: - video_suffixes = {f".{ext.lower()}" for ext in auxfun_videos.SUPPORTED_VIDEOS} + if video_type: + explicit_suffixes: set[str] | None = {f".{video_type.lstrip('.').lower()}"} else: - video_suffixes = {f".{video_type.lstrip('.').lower()}"} + explicit_suffixes = None + implicit_suffixes = {f".{ext.lower()}" for ext in auxfun_videos.SUPPORTED_VIDEOS} - videos = [] + videos: list[Path] = [] for path in map(Path, data_path): if not path.exists(): raise FileNotFoundError(f"Could not find: {path}. Check access rights.") if path.is_dir(): - videos.extend(f for f in path.iterdir() if f.is_file() and f.suffix.lower() in video_suffixes) - elif path.is_file() and path.suffix.lower() in video_suffixes: - videos.append(path) + # Discriminate videos from other files; skip prior DLC outputs. + allowed = explicit_suffixes if explicit_suffixes else implicit_suffixes + videos.extend( + f + for f in path.iterdir() + if f.is_file() and f.suffix.lower() in allowed and "_labeled." not in f.name and "_full." not in f.name + ) + elif path.is_file(): + # Accept all caller-supplied files; only filter if video_type set. + if explicit_suffixes is None or path.suffix.lower() in explicit_suffixes: + videos.append(path) # Resolve video paths and remove duplicates unique_videos = list(dict.fromkeys(v.resolve() for v in videos)) From 96e31be134729d5f38a2ac04e3964e0ceeb9b15b Mon Sep 17 00:00:00 2001 From: Jaap de Ruyter van Steveninck <32810691+deruyter92@users.noreply.github.com> Date: Mon, 4 May 2026 22:27:22 +0200 Subject: [PATCH 02/24] add test for list_videos_in_folder --- .../apis/test_list_videos_in_folder.py | 162 ++++++++++++++++++ 1 file changed, 162 insertions(+) create mode 100644 tests/pose_estimation_pytorch/apis/test_list_videos_in_folder.py diff --git a/tests/pose_estimation_pytorch/apis/test_list_videos_in_folder.py b/tests/pose_estimation_pytorch/apis/test_list_videos_in_folder.py new file mode 100644 index 000000000..c72e06b31 --- /dev/null +++ b/tests/pose_estimation_pytorch/apis/test_list_videos_in_folder.py @@ -0,0 +1,162 @@ +# +# DeepLabCut Toolbox (deeplabcut.org) +# © A. & M.W. Mathis Labs +# https://github.com/DeepLabCut/DeepLabCut +# +# Please see AUTHORS for contributors. +# https://github.com/DeepLabCut/DeepLabCut/blob/main/AUTHORS +# +# Licensed under GNU Lesser General Public License v3.0 +# +"""Tests for ``list_videos_in_folder``. + +These tests pin down the rule: + +* When ``video_type`` is not set, directory enumeration filters by + ``SUPPORTED_VIDEOS`` but explicitly-supplied files are trusted (returned + as-is, even if they have no suffix). +* When ``video_type`` is set, it is honoured everywhere — both for files + pulled from directories and for files supplied by the caller. +""" + +from __future__ import annotations + +from pathlib import Path + +import pytest + +from deeplabcut.pose_estimation_pytorch.apis.utils import list_videos_in_folder +from deeplabcut.utils.auxfun_videos import SUPPORTED_VIDEOS + + +def _touch(path: Path) -> Path: + path.parent.mkdir(parents=True, exist_ok=True) + path.write_bytes(b"") + return path + + +def test_keeps_suffixless_files_when_explicitly_listed(tmp_path): + """Regression test: a caller-supplied file without an extension (e.g. + a content-addressed cache entry) must not be silently dropped.""" + suffixed = _touch(tmp_path / "video.mp4") + hashed = _touch(tmp_path / "abcd1234") + + result = list_videos_in_folder([suffixed, hashed], video_type=None) + + assert {p.name for p in result} == {"video.mp4", "abcd1234"} + + +def test_accepts_path_objects_and_strings(tmp_path): + suffixed = _touch(tmp_path / "video.mp4") + hashed = _touch(tmp_path / "abcd1234") + + result = list_videos_in_folder([str(suffixed), hashed], video_type=None) + + assert {p.name for p in result} == {"video.mp4", "abcd1234"} + + +def test_accepts_single_path_argument(tmp_path): + """A single path (not wrapped in a list) is also valid input.""" + hashed = _touch(tmp_path / "abcd1234") + + result = list_videos_in_folder(hashed, video_type=None) + + assert [p.name for p in result] == ["abcd1234"] + + +def test_explicit_video_type_filters_listed_files(tmp_path): + """When ``video_type`` is set, it filters explicitly-supplied files too.""" + mp4 = _touch(tmp_path / "video.mp4") + avi = _touch(tmp_path / "video.avi") + + result = list_videos_in_folder([mp4, avi], video_type="mp4") + + assert [p.name for p in result] == ["video.mp4"] + + +def test_explicit_video_type_accepts_leading_dot(tmp_path): + mp4 = _touch(tmp_path / "video.mp4") + avi = _touch(tmp_path / "video.avi") + + result = list_videos_in_folder([mp4, avi], video_type=".mp4") + + assert [p.name for p in result] == ["video.mp4"] + + +def test_directory_enumeration_filters_by_supported_videos(tmp_path): + """Directory scans must continue to discriminate videos from non-videos.""" + mp4 = _touch(tmp_path / "video.mp4") + _touch(tmp_path / "notes.txt") + _touch(tmp_path / "results.h5") + _touch(tmp_path / "abcd1234") # suffix-less file in a directory: not a video + + result = list_videos_in_folder(tmp_path, video_type=None) + + assert [p.name for p in result] == [mp4.name] + + +def test_directory_enumeration_skips_dlc_artifacts(tmp_path): + """``*_labeled.*`` and ``*_full.*`` are DLC outputs, not inputs.""" + mp4 = _touch(tmp_path / "video.mp4") + _touch(tmp_path / "video_labeled.mp4") + _touch(tmp_path / "video_full.mp4") + + result = list_videos_in_folder(tmp_path, video_type=None) + + assert [p.name for p in result] == [mp4.name] + + +def test_mixed_files_and_directories(tmp_path): + """The function handles a mix of explicit files and directories.""" + folder = tmp_path / "folder" + in_folder = _touch(folder / "from_dir.mp4") + _touch(folder / "ignored.txt") + + explicit_mp4 = _touch(tmp_path / "explicit.mp4") + explicit_hashed = _touch(tmp_path / "abcd1234") + + result = list_videos_in_folder( + [folder, explicit_mp4, explicit_hashed], + video_type=None, + ) + + assert {p.name for p in result} == { + in_folder.name, + explicit_mp4.name, + explicit_hashed.name, + } + + +def test_duplicates_are_removed(tmp_path): + mp4 = _touch(tmp_path / "video.mp4") + + result = list_videos_in_folder([mp4, mp4, str(mp4)], video_type=None) + + assert len(result) == 1 + assert result[0].name == "video.mp4" + + +def test_missing_path_raises(tmp_path): + with pytest.raises(FileNotFoundError): + list_videos_in_folder([tmp_path / "does_not_exist.mp4"], video_type=None) + + +@pytest.mark.parametrize("ext", SUPPORTED_VIDEOS) +def test_each_supported_extension_picked_up_in_directory(tmp_path, ext): + expected = _touch(tmp_path / f"clip.{ext}") + + result = list_videos_in_folder(tmp_path, video_type=None) + + assert [p.name for p in result] == [expected.name] + + +def test_sorted_by_default_when_not_shuffled(tmp_path): + a = _touch(tmp_path / "a.mp4") + b = _touch(tmp_path / "b.mp4") + c = _touch(tmp_path / "c.mp4") + + # The resolution order in the function is dict-insertion-stable; given a + # sorted input list we expect a sorted output list. + result = list_videos_in_folder([c, a, b], video_type=None, shuffle=False) + + assert [p.name for p in result] == ["c.mp4", "a.mp4", "b.mp4"] From 3c63e905d933854bb4f86fcd0785e4389aec0aed Mon Sep 17 00:00:00 2001 From: Jaap de Ruyter van Steveninck <32810691+deruyter92@users.noreply.github.com> Date: Thu, 7 May 2026 17:54:59 +0200 Subject: [PATCH 03/24] Add deprecation utils: decorator for deprecated functions --- deeplabcut/utils/deprecation.py | 78 ++++++++++++++++++ tests/utils/test_deprecation.py | 137 ++++++++++++++++++++++++++++++++ 2 files changed, 215 insertions(+) create mode 100644 deeplabcut/utils/deprecation.py create mode 100644 tests/utils/test_deprecation.py diff --git a/deeplabcut/utils/deprecation.py b/deeplabcut/utils/deprecation.py new file mode 100644 index 000000000..c3d98f222 --- /dev/null +++ b/deeplabcut/utils/deprecation.py @@ -0,0 +1,78 @@ +# +# DeepLabCut Toolbox (deeplabcut.org) +# © A. & M.W. Mathis Labs +# https://github.com/DeepLabCut/DeepLabCut +# +# Please see AUTHORS for contributors. +# https://github.com/DeepLabCut/DeepLabCut/blob/master/AUTHORS +# +# Licensed under GNU Lesser General Public License v3.0 +# +from __future__ import annotations + +import functools +import warnings +from collections.abc import Callable + + +def deprecated( + replacement: str | None = None, + since: str | None = None, + removed_in: str | None = None, +) -> Callable: + """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. + """ + + def decorator(fn: Callable) -> Callable: + parts = [f"{fn.__qualname__} is deprecated"] + if since: + parts[0] += f" since {since}" + if replacement: + parts.append(f"Use {replacement} instead.") + if removed_in: + parts.append(f"It will be removed in {removed_in}.") + message = " ".join(parts) + + @functools.wraps(fn) + def wrapper(*args, **kwargs): + warnings.warn(message, DeprecationWarning, stacklevel=2) + return fn(*args, **kwargs) + + wrapper.__doc__ = f"Deprecated. {message}\n\n" + (fn.__doc__ or "") + return wrapper + + return decorator + + +def renamed_parameter(old: str, new: str, since: str | None = None) -> Callable: + """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. + """ + + def decorator(fn: Callable) -> Callable: + msg = ( + f"Parameter '{old}' of {fn.__qualname__} is deprecated" + + (f" since {since}" if since else "") + + f"; use '{new}' instead." + ) + + @functools.wraps(fn) + def wrapper(*args, **kwargs): + if old in kwargs: + warnings.warn(msg, DeprecationWarning, stacklevel=2) + kwargs[new] = kwargs.pop(old) + return fn(*args, **kwargs) + + return wrapper + + return decorator diff --git a/tests/utils/test_deprecation.py b/tests/utils/test_deprecation.py new file mode 100644 index 000000000..7fbc65064 --- /dev/null +++ b/tests/utils/test_deprecation.py @@ -0,0 +1,137 @@ +# +# DeepLabCut Toolbox (deeplabcut.org) +# © A. & M.W. Mathis Labs +# https://github.com/DeepLabCut/DeepLabCut +# +# Please see AUTHORS for contributors. +# https://github.com/DeepLabCut/DeepLabCut/blob/main/AUTHORS +# +# Licensed under GNU Lesser General Public License v3.0 +# +import warnings + +import pytest + +from deeplabcut.utils.deprecation import deprecated, renamed_parameter + +# --------------------------------------------------------------------------- +# @deprecated +# --------------------------------------------------------------------------- + + +def test_deprecated_emits_deprecation_warning(): + @deprecated() + def old_fn(): + return 42 + + with pytest.warns(DeprecationWarning): + result = old_fn() + + assert result == 42 + + +def test_deprecated_warning_contains_function_name(): + @deprecated() + def my_old_function(): + pass + + with pytest.warns(DeprecationWarning, match="my_old_function"): + my_old_function() + + +def test_deprecated_warning_contains_replacement(): + @deprecated(replacement="new_module.new_fn") + def old_fn(): + pass + + with pytest.warns(DeprecationWarning, match="new_module.new_fn"): + old_fn() + + +def test_deprecated_warning_contains_since_and_removed_in(): + @deprecated(since="3.1", removed_in="4.0") + def old_fn(): + pass + + with pytest.warns(DeprecationWarning, match="3.1") as record: + old_fn() + + assert "4.0" in str(record[0].message) + + +def test_deprecated_preserves_return_value_and_args(): + @deprecated() + def add(a, b): + return a + b + + with pytest.warns(DeprecationWarning): + assert add(2, 3) == 5 + + +def test_deprecated_preserves_name_and_docstring(): + @deprecated(replacement="new_fn") + def documented_fn(): + """Original docstring.""" + + assert documented_fn.__name__ == "documented_fn" + assert "Original docstring." in documented_fn.__doc__ + assert "Deprecated." in documented_fn.__doc__ + + +# --------------------------------------------------------------------------- +# @renamed_parameter +# --------------------------------------------------------------------------- + + +def test_renamed_parameter_old_name_emits_warning(): + @renamed_parameter(old="in_random_order", new="shuffle") + def fn(shuffle=False): + return shuffle + + with pytest.warns(DeprecationWarning): + fn(in_random_order=True) + + +def test_renamed_parameter_old_name_is_forwarded(): + @renamed_parameter(old="in_random_order", new="shuffle") + def fn(shuffle=False): + return shuffle + + with pytest.warns(DeprecationWarning): + result = fn(in_random_order=True) + + assert result is True + + +def test_renamed_parameter_new_name_no_warning(): + @renamed_parameter(old="in_random_order", new="shuffle") + def fn(shuffle=False): + return shuffle + + # No warning should be emitted when using the current name. + with warnings.catch_warnings(): + warnings.simplefilter("error", DeprecationWarning) + result = fn(shuffle=True) + + assert result is True + + +def test_renamed_parameter_warning_contains_names(): + @renamed_parameter(old="videotype", new="extensions", since="3.2") + def fn(extensions=None): + return extensions + + with pytest.warns(DeprecationWarning, match="videotype") as record: + fn(videotype="mp4") + + message = str(record[0].message) + assert "extensions" in message + assert "3.2" in message + + +def test_renamed_parameter_preserves_name(): + @renamed_parameter(old="foo", new="bar") + def my_fn(bar=None): + """Docstring.""" + + assert my_fn.__name__ == "my_fn" From b92103ef66dab2e3607620db2da72df8b3b94f7a Mon Sep 17 00:00:00 2001 From: Jaap de Ruyter van Steveninck <32810691+deruyter92@users.noreply.github.com> Date: Thu, 7 May 2026 18:11:13 +0200 Subject: [PATCH 04/24] move `list_videos_in_folder` to `collect_video_paths` in auxfun_videos.py --- .../apis/tracking_dataset.py | 3 +- .../pose_estimation_pytorch/apis/tracklets.py | 4 +- .../pose_estimation_pytorch/apis/utils.py | 60 +++---------------- .../pose_estimation_pytorch/apis/videos.py | 3 +- deeplabcut/utils/auxfun_videos.py | 57 ++++++++++++++++++ .../test_collect_video_paths.py} | 29 +++++---- 6 files changed, 86 insertions(+), 70 deletions(-) rename tests/{pose_estimation_pytorch/apis/test_list_videos_in_folder.py => utils/test_collect_video_paths.py} (81%) diff --git a/deeplabcut/pose_estimation_pytorch/apis/tracking_dataset.py b/deeplabcut/pose_estimation_pytorch/apis/tracking_dataset.py index 3455105f2..97a1b1702 100644 --- a/deeplabcut/pose_estimation_pytorch/apis/tracking_dataset.py +++ b/deeplabcut/pose_estimation_pytorch/apis/tracking_dataset.py @@ -24,6 +24,7 @@ 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 +from deeplabcut.utils.auxfun_videos import collect_video_paths def build_feature_extraction_runner( @@ -240,7 +241,7 @@ def create_tracking_dataset( modelprefix=modelprefix, ) - videos = utils.list_videos_in_folder(videos, videotype) + videos = collect_video_paths(videos, videotype) 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 d3fea4a22..553a96aad 100644 --- a/deeplabcut/pose_estimation_pytorch/apis/tracklets.py +++ b/deeplabcut/pose_estimation_pytorch/apis/tracklets.py @@ -26,10 +26,10 @@ from deeplabcut.core.inferenceutils import Assembly from deeplabcut.pose_estimation_pytorch.apis.utils import ( get_scorer_name, - list_videos_in_folder, parse_snapshot_index_for_analysis, ) from deeplabcut.pose_estimation_pytorch.data.dlcloader import DLCLoader +from deeplabcut.utils.auxfun_videos import collect_video_paths def convert_detections2tracklets( @@ -124,7 +124,7 @@ def convert_detections2tracklets( modelprefix=modelprefix, ) - videos = list_videos_in_folder(videos, videotype) + videos = collect_video_paths(videos, videotype) if len(videos) == 0: print(f"No videos were found in {videos}") return diff --git a/deeplabcut/pose_estimation_pytorch/apis/utils.py b/deeplabcut/pose_estimation_pytorch/apis/utils.py index 3ea40ea6d..99cae69f1 100644 --- a/deeplabcut/pose_estimation_pytorch/apis/utils.py +++ b/deeplabcut/pose_estimation_pytorch/apis/utils.py @@ -11,7 +11,6 @@ from __future__ import annotations import logging -import random from collections.abc import Callable from pathlib import Path @@ -65,7 +64,9 @@ ) from deeplabcut.pose_estimation_pytorch.task import Task from deeplabcut.pose_estimation_pytorch.utils import resolve_device -from deeplabcut.utils import auxfun_videos, auxiliaryfunctions +from deeplabcut.utils import auxiliaryfunctions +from deeplabcut.utils.auxfun_videos import collect_video_paths +from deeplabcut.utils.deprecation import deprecated def parse_snapshot_index_for_analysis( @@ -293,60 +294,17 @@ def get_scorer_name( return f"DLC_{name}_{task}{date}shuffle{shuffle}_{snapshot_uid}" +@deprecated("deeplabcut.utils.auxfun_videos.collect_video_paths", since="3.0.0") def list_videos_in_folder( data_path: str | Path | list[str | Path], video_type: str | None = None, shuffle: bool = False, ) -> list[Path]: - """ - Args: - data_path: Path or list of paths to folders containing videos, or individual - video files. Can be a mix of directories and files. - video_type: The type of video to filter for (e.g., "mp4", ".mp4"). - - If set: filter both directory contents and supplied files. - - If ``None``, explicitly-supplied files are used as-is. (Directory - contents are filtered using ``SUPPORTED_VIDEOS`` by default). - shuffle: Whether to shuffle the order of videos. If False, videos are returned - in sorted order for deterministic behavior. - - Returns: - The paths of videos to analyze. Duplicate paths are removed. - - Raises: - FileNotFoundError: If any path in data_path does not exist. - """ - if isinstance(data_path, (str, Path)): - data_path = [data_path] - - if video_type: - explicit_suffixes: set[str] | None = {f".{video_type.lstrip('.').lower()}"} - else: - explicit_suffixes = None - implicit_suffixes = {f".{ext.lower()}" for ext in auxfun_videos.SUPPORTED_VIDEOS} - - videos: list[Path] = [] - for path in map(Path, data_path): - if not path.exists(): - raise FileNotFoundError(f"Could not find: {path}. Check access rights.") - - if path.is_dir(): - # Discriminate videos from other files; skip prior DLC outputs. - allowed = explicit_suffixes if explicit_suffixes else implicit_suffixes - videos.extend( - f - for f in path.iterdir() - if f.is_file() and f.suffix.lower() in allowed and "_labeled." not in f.name and "_full." not in f.name - ) - elif path.is_file(): - # Accept all caller-supplied files; only filter if video_type set. - if explicit_suffixes is None or path.suffix.lower() in explicit_suffixes: - videos.append(path) - - # Resolve video paths and remove duplicates - unique_videos = list(dict.fromkeys(v.resolve() for v in videos)) - if shuffle: - random.shuffle(unique_videos) - return unique_videos + return collect_video_paths( + data_path=data_path, + extensions=video_type, + shuffle=shuffle, + ) def ensure_multianimal_df_format(df_predictions: pd.DataFrame) -> pd.DataFrame: diff --git a/deeplabcut/pose_estimation_pytorch/apis/videos.py b/deeplabcut/pose_estimation_pytorch/apis/videos.py index 4341fdb2b..7f56b5876 100644 --- a/deeplabcut/pose_estimation_pytorch/apis/videos.py +++ b/deeplabcut/pose_estimation_pytorch/apis/videos.py @@ -44,6 +44,7 @@ from deeplabcut.pose_estimation_pytorch.task import Task from deeplabcut.refine_training_dataset.stitch import stitch_tracklets from deeplabcut.utils import VideoReader, auxiliaryfunctions +from deeplabcut.utils.auxfun_videos import collect_video_paths class VideoIterator(VideoReader): @@ -540,7 +541,7 @@ def analyze_videos( print(f"Using scorer: {dlc_scorer}") # Reading video and init variables - videos = utils.list_videos_in_folder(videos, videotype, shuffle=in_random_order) + videos = collect_video_paths(videos, videotype, shuffle=in_random_order) h5_files_created = False # Track if any .h5 files were created for video in videos: diff --git a/deeplabcut/utils/auxfun_videos.py b/deeplabcut/utils/auxfun_videos.py index 3b873762a..75fa59cc0 100644 --- a/deeplabcut/utils/auxfun_videos.py +++ b/deeplabcut/utils/auxfun_videos.py @@ -21,8 +21,10 @@ import datetime import os +import random import subprocess import warnings +from pathlib import Path import cv2 import numpy as np @@ -643,3 +645,58 @@ def display_help(*args): plt.close(fig) return bbox + + +def collect_video_paths( + data_path: str | Path | list[str | Path], + extensions: str | None = None, + shuffle: bool = False, +) -> list[Path]: + """ + Args: + data_path: Path or list of paths to folders containing videos, or individual + video files. Can be a mix of directories and files. + extensions: The types of videos to filter for (e.g., "mp4", ".mp4", ".avi", ".AVI", etc.). + - If set: filter all videos with the given extensions. Both for directory contents and supplied files. + - If ``None``, provided files are not filtered, but directory contents are filtered by ``SUPPORTED_VIDEOS``. + shuffle: Whether to shuffle the order of videos. If False, videos are returned + in sorted order for deterministic behavior. + + Returns: + The paths of videos to analyze. Duplicate paths are removed. + + Raises: + FileNotFoundError: If any path in data_path does not exist. + """ + if isinstance(data_path, (str, Path)): + data_path = [data_path] + + if extensions: + explicit_suffixes: set[str] | None = {f".{extensions.lstrip('.').lower()}"} + else: + explicit_suffixes = None + implicit_suffixes = {f".{ext.lower()}" for ext in SUPPORTED_VIDEOS} + + videos: list[Path] = [] + for path in map(Path, data_path): + if not path.exists(): + raise FileNotFoundError(f"Could not find: {path}. Check access rights.") + + if path.is_dir(): + # Discriminate videos from other files; skip prior DLC outputs. + allowed = explicit_suffixes if explicit_suffixes else implicit_suffixes + videos.extend( + f + for f in path.iterdir() + if f.is_file() and f.suffix.lower() in allowed and "_labeled." not in f.name and "_full." not in f.name + ) + elif path.is_file(): + # Accept all caller-supplied files; only filter if extensions set. + if explicit_suffixes is None or path.suffix.lower() in explicit_suffixes: + videos.append(path) + + # Resolve video paths and remove duplicates + unique_videos = list(dict.fromkeys(v.resolve() for v in videos)) + if shuffle: + random.shuffle(unique_videos) + return unique_videos diff --git a/tests/pose_estimation_pytorch/apis/test_list_videos_in_folder.py b/tests/utils/test_collect_video_paths.py similarity index 81% rename from tests/pose_estimation_pytorch/apis/test_list_videos_in_folder.py rename to tests/utils/test_collect_video_paths.py index c72e06b31..39055c402 100644 --- a/tests/pose_estimation_pytorch/apis/test_list_videos_in_folder.py +++ b/tests/utils/test_collect_video_paths.py @@ -8,7 +8,7 @@ # # Licensed under GNU Lesser General Public License v3.0 # -"""Tests for ``list_videos_in_folder``. +"""Tests for ``collect_video_paths``. These tests pin down the rule: @@ -25,8 +25,7 @@ import pytest -from deeplabcut.pose_estimation_pytorch.apis.utils import list_videos_in_folder -from deeplabcut.utils.auxfun_videos import SUPPORTED_VIDEOS +from deeplabcut.utils.auxfun_videos import SUPPORTED_VIDEOS, collect_video_paths def _touch(path: Path) -> Path: @@ -41,7 +40,7 @@ def test_keeps_suffixless_files_when_explicitly_listed(tmp_path): suffixed = _touch(tmp_path / "video.mp4") hashed = _touch(tmp_path / "abcd1234") - result = list_videos_in_folder([suffixed, hashed], video_type=None) + result = collect_video_paths([suffixed, hashed], video_type=None) assert {p.name for p in result} == {"video.mp4", "abcd1234"} @@ -50,7 +49,7 @@ def test_accepts_path_objects_and_strings(tmp_path): suffixed = _touch(tmp_path / "video.mp4") hashed = _touch(tmp_path / "abcd1234") - result = list_videos_in_folder([str(suffixed), hashed], video_type=None) + result = collect_video_paths([str(suffixed), hashed], video_type=None) assert {p.name for p in result} == {"video.mp4", "abcd1234"} @@ -59,7 +58,7 @@ def test_accepts_single_path_argument(tmp_path): """A single path (not wrapped in a list) is also valid input.""" hashed = _touch(tmp_path / "abcd1234") - result = list_videos_in_folder(hashed, video_type=None) + result = collect_video_paths(hashed, video_type=None) assert [p.name for p in result] == ["abcd1234"] @@ -69,7 +68,7 @@ def test_explicit_video_type_filters_listed_files(tmp_path): mp4 = _touch(tmp_path / "video.mp4") avi = _touch(tmp_path / "video.avi") - result = list_videos_in_folder([mp4, avi], video_type="mp4") + result = collect_video_paths([mp4, avi], video_type="mp4") assert [p.name for p in result] == ["video.mp4"] @@ -78,7 +77,7 @@ def test_explicit_video_type_accepts_leading_dot(tmp_path): mp4 = _touch(tmp_path / "video.mp4") avi = _touch(tmp_path / "video.avi") - result = list_videos_in_folder([mp4, avi], video_type=".mp4") + result = collect_video_paths([mp4, avi], video_type=".mp4") assert [p.name for p in result] == ["video.mp4"] @@ -90,7 +89,7 @@ def test_directory_enumeration_filters_by_supported_videos(tmp_path): _touch(tmp_path / "results.h5") _touch(tmp_path / "abcd1234") # suffix-less file in a directory: not a video - result = list_videos_in_folder(tmp_path, video_type=None) + result = collect_video_paths(tmp_path, video_type=None) assert [p.name for p in result] == [mp4.name] @@ -101,7 +100,7 @@ def test_directory_enumeration_skips_dlc_artifacts(tmp_path): _touch(tmp_path / "video_labeled.mp4") _touch(tmp_path / "video_full.mp4") - result = list_videos_in_folder(tmp_path, video_type=None) + result = collect_video_paths(tmp_path, video_type=None) assert [p.name for p in result] == [mp4.name] @@ -115,7 +114,7 @@ def test_mixed_files_and_directories(tmp_path): explicit_mp4 = _touch(tmp_path / "explicit.mp4") explicit_hashed = _touch(tmp_path / "abcd1234") - result = list_videos_in_folder( + result = collect_video_paths( [folder, explicit_mp4, explicit_hashed], video_type=None, ) @@ -130,7 +129,7 @@ def test_mixed_files_and_directories(tmp_path): def test_duplicates_are_removed(tmp_path): mp4 = _touch(tmp_path / "video.mp4") - result = list_videos_in_folder([mp4, mp4, str(mp4)], video_type=None) + result = collect_video_paths([mp4, mp4, str(mp4)], video_type=None) assert len(result) == 1 assert result[0].name == "video.mp4" @@ -138,14 +137,14 @@ def test_duplicates_are_removed(tmp_path): def test_missing_path_raises(tmp_path): with pytest.raises(FileNotFoundError): - list_videos_in_folder([tmp_path / "does_not_exist.mp4"], video_type=None) + collect_video_paths([tmp_path / "does_not_exist.mp4"], video_type=None) @pytest.mark.parametrize("ext", SUPPORTED_VIDEOS) def test_each_supported_extension_picked_up_in_directory(tmp_path, ext): expected = _touch(tmp_path / f"clip.{ext}") - result = list_videos_in_folder(tmp_path, video_type=None) + result = collect_video_paths(tmp_path, video_type=None) assert [p.name for p in result] == [expected.name] @@ -157,6 +156,6 @@ def test_sorted_by_default_when_not_shuffled(tmp_path): # The resolution order in the function is dict-insertion-stable; given a # sorted input list we expect a sorted output list. - result = list_videos_in_folder([c, a, b], video_type=None, shuffle=False) + result = collect_video_paths([c, a, b], video_type=None, shuffle=False) assert [p.name for p in result] == ["c.mp4", "a.mp4", "b.mp4"] From ee4d9b159a427963d680b242583a71d052f03fa5 Mon Sep 17 00:00:00 2001 From: Jaap de Ruyter van Steveninck <32810691+deruyter92@users.noreply.github.com> Date: Fri, 8 May 2026 14:08:20 +0200 Subject: [PATCH 05/24] export `deeplabcut.collect_video_paths(..)` from auxfun_videos.py --- deeplabcut/__init__.py | 1 + deeplabcut/pose_estimation_pytorch/apis/utils.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/deeplabcut/__init__.py b/deeplabcut/__init__.py index 9bec9ff67..df55aec39 100644 --- a/deeplabcut/__init__.py +++ b/deeplabcut/__init__.py @@ -102,6 +102,7 @@ DownSampleVideo, ShortenVideo, check_video_integrity, + collect_video_paths, ) # ----------------------------------------------------------------------------- diff --git a/deeplabcut/pose_estimation_pytorch/apis/utils.py b/deeplabcut/pose_estimation_pytorch/apis/utils.py index 99cae69f1..82d2d974d 100644 --- a/deeplabcut/pose_estimation_pytorch/apis/utils.py +++ b/deeplabcut/pose_estimation_pytorch/apis/utils.py @@ -294,7 +294,7 @@ def get_scorer_name( return f"DLC_{name}_{task}{date}shuffle{shuffle}_{snapshot_uid}" -@deprecated("deeplabcut.utils.auxfun_videos.collect_video_paths", since="3.0.0") +@deprecated(replacement="deeplabcut.collect_video_paths", since="3.0.0") def list_videos_in_folder( data_path: str | Path | list[str | Path], video_type: str | None = None, From 2014a5929b477e84cedcf8a0d025171bfb0516d5 Mon Sep 17 00:00:00 2001 From: Jaap de Ruyter van Steveninck <32810691+deruyter92@users.noreply.github.com> Date: Fri, 8 May 2026 14:55:19 +0200 Subject: [PATCH 06/24] update `collect_video_paths`: add exclude patterns --- deeplabcut/utils/auxfun_videos.py | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/deeplabcut/utils/auxfun_videos.py b/deeplabcut/utils/auxfun_videos.py index 75fa59cc0..13bcac2e1 100644 --- a/deeplabcut/utils/auxfun_videos.py +++ b/deeplabcut/utils/auxfun_videos.py @@ -651,16 +651,25 @@ def collect_video_paths( data_path: str | Path | list[str | Path], extensions: str | None = None, shuffle: bool = False, + exclude_patterns: list[str] | None = None, ) -> list[Path]: """ + Collects video paths from a given set of data paths: directories, files or mix of both. + Optionally filters paths by extension and excludes patterns. Files and directories are treated differently: + - Files are not filtered by extension by default. Set ``extensions`` if needed, to filter also supplied files. + - Directory contents are filtered by ``SUPPORTED_VIDEOS`` by default. Specify custom ``extensions`` if needed. + - exclude patterns are ALWAYS applied for directory contents and supplied files. Set to `[]` to exclude no patterns. + Args: data_path: Path or list of paths to folders containing videos, or individual video files. Can be a mix of directories and files. - extensions: The types of videos to filter for (e.g., "mp4", ".mp4", ".avi", ".AVI", etc.). + extensions: The types of videos to filter for (e.g., ".mp4", ".avi", etc.). - If set: filter all videos with the given extensions. Both for directory contents and supplied files. - If ``None``, provided files are not filtered, but directory contents are filtered by ``SUPPORTED_VIDEOS``. shuffle: Whether to shuffle the order of videos. If False, videos are returned in sorted order for deterministic behavior. + exclude_patterns: Patterns to exclude from the collection. Defaults to ["*_labeled.*", "*_full.*"]. + Set to [] to exclude no patterns. Returns: The paths of videos to analyze. Duplicate paths are removed. @@ -671,6 +680,9 @@ def collect_video_paths( if isinstance(data_path, (str, Path)): data_path = [data_path] + if exclude_patterns is None: + exclude_patterns = ["*_labeled.*", "*_full.*"] + if extensions: explicit_suffixes: set[str] | None = {f".{extensions.lstrip('.').lower()}"} else: @@ -683,17 +695,20 @@ def collect_video_paths( raise FileNotFoundError(f"Could not find: {path}. Check access rights.") if path.is_dir(): - # Discriminate videos from other files; skip prior DLC outputs. + # Discriminate videos from other files; skip excluded patterns (e.g. prior DLC outputs). allowed = explicit_suffixes if explicit_suffixes else implicit_suffixes videos.extend( f for f in path.iterdir() - if f.is_file() and f.suffix.lower() in allowed and "_labeled." not in f.name and "_full." not in f.name + if f.is_file() + and f.suffix.lower() in allowed + and not any(f.match(pattern) for pattern in exclude_patterns) ) elif path.is_file(): - # Accept all caller-supplied files; only filter if extensions set. + # Accept all caller-supplied files; ONLY filter extensions if set. ALWAYS filter exclude patterns. if explicit_suffixes is None or path.suffix.lower() in explicit_suffixes: - videos.append(path) + if not any(path.match(pattern) for pattern in exclude_patterns): + videos.append(path) # Resolve video paths and remove duplicates unique_videos = list(dict.fromkeys(v.resolve() for v in videos)) From f6f1ee55fba924a367fae643d8c42c7ce4c345ca Mon Sep 17 00:00:00 2001 From: Jaap de Ruyter van Steveninck <32810691+deruyter92@users.noreply.github.com> Date: Fri, 8 May 2026 15:01:35 +0200 Subject: [PATCH 07/24] deprecate auxfun `get_list_of_videos` in favor of `collect_video_paths` --- .../modelzoo/api/superanimal_inference.py | 4 +- .../predict_videos.py | 7 +- .../train_dlctransreid.py | 4 +- .../post_processing/analyze_skeleton.py | 3 +- deeplabcut/post_processing/filtering.py | 3 +- .../refine_training_dataset/outlier_frames.py | 4 +- deeplabcut/refine_training_dataset/stitch.py | 4 +- deeplabcut/utils/auxiliaryfunctions.py | 65 +++---------------- deeplabcut/utils/make_labeled_video.py | 6 +- deeplabcut/utils/plotting.py | 3 +- 10 files changed, 30 insertions(+), 73 deletions(-) diff --git a/deeplabcut/pose_estimation_tensorflow/modelzoo/api/superanimal_inference.py b/deeplabcut/pose_estimation_tensorflow/modelzoo/api/superanimal_inference.py index 0b9255dcb..705653807 100644 --- a/deeplabcut/pose_estimation_tensorflow/modelzoo/api/superanimal_inference.py +++ b/deeplabcut/pose_estimation_tensorflow/modelzoo/api/superanimal_inference.py @@ -26,7 +26,7 @@ from deeplabcut.pose_estimation_tensorflow.core import predict as single_predict from deeplabcut.pose_estimation_tensorflow.core import predict_multianimal as predict from deeplabcut.utils import auxiliaryfunctions -from deeplabcut.utils.auxfun_videos import VideoWriter +from deeplabcut.utils.auxfun_videos import VideoWriter, collect_video_paths warnings.simplefilter("ignore", category=RuntimeWarning) @@ -306,7 +306,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 = auxiliaryfunctions.get_list_of_videos(videos, videotype) + videos = collect_video_paths(videos, videotype) datafiles = [] for video in videos: diff --git a/deeplabcut/pose_estimation_tensorflow/predict_videos.py b/deeplabcut/pose_estimation_tensorflow/predict_videos.py index 3b9979b25..39607aa8a 100644 --- a/deeplabcut/pose_estimation_tensorflow/predict_videos.py +++ b/deeplabcut/pose_estimation_tensorflow/predict_videos.py @@ -40,6 +40,7 @@ ) 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 #################################################### # Loading data, and defining model folder @@ -198,7 +199,7 @@ def create_tracking_dataset( ################################################## # Looping over videos ################################################## - Videos = auxiliaryfunctions.get_list_of_videos(videos, videotype) + Videos = collect_video_paths(videos, videotype) if len(Videos) > 0: if "multi-animal" in dlc_cfg["dataset_type"]: for video in Videos: @@ -594,7 +595,7 @@ def analyze_videos( ################################################## # Looping over videos ################################################## - Videos = auxiliaryfunctions.get_list_of_videos(videos, videotype, in_random_order) + Videos = collect_video_paths(videos, videotype, in_random_order) if len(Videos) > 0: if "multi-animal" in dlc_cfg["dataset_type"]: from deeplabcut.pose_estimation_tensorflow.predict_multianimal import ( @@ -1651,7 +1652,7 @@ def convert_detections2tracklets( ################################################## # Looping over videos ################################################## - Videos = auxiliaryfunctions.get_list_of_videos(videos, videotype) + Videos = collect_video_paths(videos, videotype) if len(Videos) > 0: for video in Videos: print("Processing... ", video) diff --git a/deeplabcut/pose_tracking_pytorch/train_dlctransreid.py b/deeplabcut/pose_tracking_pytorch/train_dlctransreid.py index 404618a33..4d5124766 100644 --- a/deeplabcut/pose_tracking_pytorch/train_dlctransreid.py +++ b/deeplabcut/pose_tracking_pytorch/train_dlctransreid.py @@ -21,7 +21,7 @@ import numpy as np -from deeplabcut.utils import auxiliaryfunctions +from deeplabcut.utils.auxfun_videos import collect_video_paths from .config import cfg from .datasets import make_dlc_dataloader @@ -79,7 +79,7 @@ def train_tracking_transformer( destfolder=None, ): npy_list = [] - videos = auxiliaryfunctions.get_list_of_videos(videos, videotype) + videos = collect_video_paths(videos, videotype) 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 d39c3a7b8..54cd9a30a 100644 --- a/deeplabcut/post_processing/analyze_skeleton.py +++ b/deeplabcut/post_processing/analyze_skeleton.py @@ -23,6 +23,7 @@ from scipy.spatial import distance from deeplabcut.utils import auxfun_multianimal, auxiliaryfunctions +from deeplabcut.utils.auxfun_videos import collect_video_paths # utility functions @@ -261,7 +262,7 @@ def analyzeskeleton( **kwargs, ) - Videos = auxiliaryfunctions.get_list_of_videos(videos, videotype) + Videos = collect_video_paths(videos, videotype) 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 cee8ef5ff..cc527754c 100644 --- a/deeplabcut/post_processing/filtering.py +++ b/deeplabcut/post_processing/filtering.py @@ -19,6 +19,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 def columnwise_spline_interp(data, max_gap=0): @@ -212,7 +213,7 @@ def filterpredictions( modelprefix=modelprefix, **kwargs, ) - Videos = auxiliaryfunctions.get_list_of_videos(video, videotype) + Videos = collect_video_paths(video, videotype) video_to_filtered_df = {} diff --git a/deeplabcut/refine_training_dataset/outlier_frames.py b/deeplabcut/refine_training_dataset/outlier_frames.py index 32b535b1e..5ddb67913 100644 --- a/deeplabcut/refine_training_dataset/outlier_frames.py +++ b/deeplabcut/refine_training_dataset/outlier_frames.py @@ -30,7 +30,7 @@ frameselectiontools, visualization, ) -from deeplabcut.utils.auxfun_videos import VideoWriter +from deeplabcut.utils.auxfun_videos import VideoWriter, collect_video_paths def find_outliers_in_raw_data( @@ -402,7 +402,7 @@ def extract_outlier_frames( **kwargs, ) - Videos = auxiliaryfunctions.get_list_of_videos(videos, videotype) + Videos = collect_video_paths(videos, videotype) 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 86ce62b50..08d45d95a 100644 --- a/deeplabcut/refine_training_dataset/stitch.py +++ b/deeplabcut/refine_training_dataset/stitch.py @@ -35,7 +35,7 @@ calc_iou, ) from deeplabcut.utils import auxfun_multianimal, auxiliaryfunctions -from deeplabcut.utils.auxfun_videos import VideoWriter +from deeplabcut.utils.auxfun_videos import VideoWriter, collect_video_paths class Tracklet: @@ -1076,7 +1076,7 @@ def stitch_tracklets( ------- A TrackletStitcher object """ - vids = deeplabcut.utils.auxiliaryfunctions.get_list_of_videos(videos, videotype) + vids = collect_video_paths(videos, videotype) 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 d3ff79435..763fd7461 100644 --- a/deeplabcut/utils/auxiliaryfunctions.py +++ b/deeplabcut/utils/auxiliaryfunctions.py @@ -32,7 +32,9 @@ from deeplabcut.core.engine import Engine from deeplabcut.core.trackingutils import TRACK_METHODS -from deeplabcut.utils import auxfun_multianimal, auxfun_videos +from deeplabcut.utils import auxfun_multianimal +from deeplabcut.utils.auxfun_videos import collect_video_paths +from deeplabcut.utils.deprecation import deprecated def create_config_template(multianimal=False): @@ -387,66 +389,17 @@ def write_pickle(filename, data): pickle.dump(data, handle, protocol=pickle.HIGHEST_PROTOCOL) +@deprecated(replacement="deeplabcut.collect_video_paths", since="3.0.0") def get_list_of_videos( videos: list[str] | str, videotype: list[str] | str = "", in_random_order: bool = True, ) -> list[str]: - """Returns list of videos of videotype "videotype" in folder videos or for list of - videos. - - NOTE: excludes keyword videos of the form: - - *_labeled.videotype - *_full.videotype - - Args: - videos (list[str], str): List of video paths or a single path string. If string (or len() == 1 list of strings) - is a directory, - finds all videos whose extension matches ``videotype`` in the directory - - videotype (list[str], str): File extension used to filter videos. Optional if ``videos`` is a list of video - files, - and filters with common video extensions if a directory is passed in. - - in_random_order (bool): Whether or not to return a shuffled list of videos. - """ - if isinstance(videos, str): - videos = [videos] - - if [os.path.isdir(i) for i in videos] == [True]: # checks if input is a directory - """Returns all the videos in the directory.""" - if not videotype: - videotype = auxfun_videos.SUPPORTED_VIDEOS - - print("Analyzing all the videos in the directory...") - videofolder = videos[0] - - # make list of full paths - videos = [os.path.join(videofolder, fn) for fn in os.listdir(videofolder)] - - if in_random_order: - from random import shuffle - - shuffle(videos) # this is useful so multiple nets can be used to analyze simultaneously - else: - videos.sort() - - if isinstance(videotype, str): - videotype = [videotype] - if not videotype: - videotype = auxfun_videos.SUPPORTED_VIDEOS - # filter list of videos - videos = [ - v - for v in videos - if os.path.isfile(v) - and any(v.endswith(ext) for ext in videotype) - and "_labeled." not in v - and "_full." not in v - ] - - return videos + return collect_video_paths( + data_path=videos, + extensions=videotype, + shuffle=in_random_order, + ) def save_data(PredicteData, metadata, dataname, pdindex, imagenames, save_as_csv): diff --git a/deeplabcut/utils/make_labeled_video.py b/deeplabcut/utils/make_labeled_video.py index da6e1de16..b35d4de41 100644 --- a/deeplabcut/utils/make_labeled_video.py +++ b/deeplabcut/utils/make_labeled_video.py @@ -48,7 +48,7 @@ from deeplabcut.core.engine import Engine from deeplabcut.utils import auxfun_multianimal, auxiliaryfunctions, visualization -from deeplabcut.utils.auxfun_videos import VideoWriter +from deeplabcut.utils.auxfun_videos import VideoWriter, collect_video_paths from deeplabcut.utils.video_processor import ( VideoProcessorCV as vp, ) # used to CreateVideo @@ -733,7 +733,7 @@ def create_labeled_video( skeleton_color = None start_path = os.getcwd() - Videos = auxiliaryfunctions.get_list_of_videos(videos, videotype) + Videos = collect_video_paths(videos, videotype) if not Videos: return [] @@ -1225,7 +1225,7 @@ def create_video_with_all_detections( **kwargs, ) - videos = auxiliaryfunctions.get_list_of_videos(videos, videotype) + videos = collect_video_paths(videos, videotype) if not videos: return diff --git a/deeplabcut/utils/plotting.py b/deeplabcut/utils/plotting.py index 18fb92abd..58983ed98 100644 --- a/deeplabcut/utils/plotting.py +++ b/deeplabcut/utils/plotting.py @@ -36,6 +36,7 @@ from deeplabcut.core import crossvalutils from deeplabcut.utils import auxfun_multianimal, auxiliaryfunctions, visualization +from deeplabcut.utils.auxfun_videos import collect_video_paths def Histogram(vector, color, bins, ax=None, linewidth=1.0): @@ -288,7 +289,7 @@ 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 = auxiliaryfunctions.get_list_of_videos(videos, videotype) + Videos = collect_video_paths(videos, videotype) if not len(Videos): print("No videos found. Make sure you passed a list of videos and that *videotype* is right.") return From f6b33d2c424248f0f3487984cb27f351063bcebc Mon Sep 17 00:00:00 2001 From: Jaap de Ruyter van Steveninck <32810691+deruyter92@users.noreply.github.com> Date: Fri, 8 May 2026 15:37:06 +0200 Subject: [PATCH 08/24] add pytest deprecation marker `test_get_list_of_videos` --- pyproject.toml | 1 + tests/test_auxiliaryfunctions.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index fb72a1b54..ff5500ca1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -165,4 +165,5 @@ markers = [ "fmpose3d: tests for fmpose3d integration", "unittest: fast unit-level tests", "functional: functional/integration-style tests", + "deprecated: tests for deprecated APIs kept for backward-compatibility", ] diff --git a/tests/test_auxiliaryfunctions.py b/tests/test_auxiliaryfunctions.py index 30e334851..ff16dc1c1 100644 --- a/tests/test_auxiliaryfunctions.py +++ b/tests/test_auxiliaryfunctions.py @@ -48,6 +48,8 @@ def _create_fake_file(filename): auxiliaryfunctions.find_analyzed_data(fake_folder, "video" + str(ind), SCORER, filtered=True) +@pytest.mark.deprecated +@pytest.mark.filterwarnings("ignore::DeprecationWarning") def test_get_list_of_videos(tmpdir_factory): fake_folder = tmpdir_factory.mktemp("videos") n_ext = len(SUPPORTED_VIDEOS) From 659bf7dd2c18c8a03bdfb60e7157356570338505 Mon Sep 17 00:00:00 2001 From: Jaap de Ruyter van Steveninck <32810691+deruyter92@users.noreply.github.com> Date: Fri, 8 May 2026 15:44:33 +0200 Subject: [PATCH 09/24] Mark `get_video_list` as deprecated (keep for now for backward compatibility) --- deeplabcut/utils/auxiliaryfunctions.py | 1 + 1 file changed, 1 insertion(+) diff --git a/deeplabcut/utils/auxiliaryfunctions.py b/deeplabcut/utils/auxiliaryfunctions.py index 763fd7461..8a8f2368d 100644 --- a/deeplabcut/utils/auxiliaryfunctions.py +++ b/deeplabcut/utils/auxiliaryfunctions.py @@ -484,6 +484,7 @@ def filter_files_by_patterns( return matching_files +@deprecated(replacement="deeplabcut.collect_video_paths", since="3.0.0") def get_video_list(filename, videopath, videtype): """Get list of videos in a path (if filetype == all), otherwise just a specific file.""" From 983951d91fed459993c51ff9113a589bdf2c2820 Mon Sep 17 00:00:00 2001 From: Jaap de Ruyter van Steveninck <32810691+deruyter92@users.noreply.github.com> Date: Fri, 8 May 2026 15:51:41 +0200 Subject: [PATCH 10/24] fix test_collect_video_paths: rename keyword -> extensions --- tests/utils/test_collect_video_paths.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/tests/utils/test_collect_video_paths.py b/tests/utils/test_collect_video_paths.py index 39055c402..812c81497 100644 --- a/tests/utils/test_collect_video_paths.py +++ b/tests/utils/test_collect_video_paths.py @@ -40,7 +40,7 @@ def test_keeps_suffixless_files_when_explicitly_listed(tmp_path): suffixed = _touch(tmp_path / "video.mp4") hashed = _touch(tmp_path / "abcd1234") - result = collect_video_paths([suffixed, hashed], video_type=None) + result = collect_video_paths([suffixed, hashed], extensions=None) assert {p.name for p in result} == {"video.mp4", "abcd1234"} @@ -49,7 +49,7 @@ def test_accepts_path_objects_and_strings(tmp_path): suffixed = _touch(tmp_path / "video.mp4") hashed = _touch(tmp_path / "abcd1234") - result = collect_video_paths([str(suffixed), hashed], video_type=None) + result = collect_video_paths([str(suffixed), hashed], extensions=None) assert {p.name for p in result} == {"video.mp4", "abcd1234"} @@ -58,7 +58,7 @@ def test_accepts_single_path_argument(tmp_path): """A single path (not wrapped in a list) is also valid input.""" hashed = _touch(tmp_path / "abcd1234") - result = collect_video_paths(hashed, video_type=None) + result = collect_video_paths(hashed, extensions=None) assert [p.name for p in result] == ["abcd1234"] @@ -68,7 +68,7 @@ def test_explicit_video_type_filters_listed_files(tmp_path): mp4 = _touch(tmp_path / "video.mp4") avi = _touch(tmp_path / "video.avi") - result = collect_video_paths([mp4, avi], video_type="mp4") + result = collect_video_paths([mp4, avi], extensions="mp4") assert [p.name for p in result] == ["video.mp4"] @@ -77,7 +77,7 @@ def test_explicit_video_type_accepts_leading_dot(tmp_path): mp4 = _touch(tmp_path / "video.mp4") avi = _touch(tmp_path / "video.avi") - result = collect_video_paths([mp4, avi], video_type=".mp4") + result = collect_video_paths([mp4, avi], extensions=".mp4") assert [p.name for p in result] == ["video.mp4"] @@ -89,7 +89,7 @@ def test_directory_enumeration_filters_by_supported_videos(tmp_path): _touch(tmp_path / "results.h5") _touch(tmp_path / "abcd1234") # suffix-less file in a directory: not a video - result = collect_video_paths(tmp_path, video_type=None) + result = collect_video_paths(tmp_path, extensions=None) assert [p.name for p in result] == [mp4.name] @@ -100,7 +100,7 @@ def test_directory_enumeration_skips_dlc_artifacts(tmp_path): _touch(tmp_path / "video_labeled.mp4") _touch(tmp_path / "video_full.mp4") - result = collect_video_paths(tmp_path, video_type=None) + result = collect_video_paths(tmp_path, extensions=None) assert [p.name for p in result] == [mp4.name] @@ -116,7 +116,7 @@ def test_mixed_files_and_directories(tmp_path): result = collect_video_paths( [folder, explicit_mp4, explicit_hashed], - video_type=None, + extensions=None, ) assert {p.name for p in result} == { @@ -129,7 +129,7 @@ def test_mixed_files_and_directories(tmp_path): def test_duplicates_are_removed(tmp_path): mp4 = _touch(tmp_path / "video.mp4") - result = collect_video_paths([mp4, mp4, str(mp4)], video_type=None) + result = collect_video_paths([mp4, mp4, str(mp4)], extensions=None) assert len(result) == 1 assert result[0].name == "video.mp4" @@ -137,14 +137,14 @@ def test_duplicates_are_removed(tmp_path): def test_missing_path_raises(tmp_path): with pytest.raises(FileNotFoundError): - collect_video_paths([tmp_path / "does_not_exist.mp4"], video_type=None) + collect_video_paths([tmp_path / "does_not_exist.mp4"], extensions=None) @pytest.mark.parametrize("ext", SUPPORTED_VIDEOS) def test_each_supported_extension_picked_up_in_directory(tmp_path, ext): expected = _touch(tmp_path / f"clip.{ext}") - result = collect_video_paths(tmp_path, video_type=None) + result = collect_video_paths(tmp_path, extensions=None) assert [p.name for p in result] == [expected.name] @@ -156,6 +156,6 @@ def test_sorted_by_default_when_not_shuffled(tmp_path): # The resolution order in the function is dict-insertion-stable; given a # sorted input list we expect a sorted output list. - result = collect_video_paths([c, a, b], video_type=None, shuffle=False) + result = collect_video_paths([c, a, b], extensions=None, shuffle=False) assert [p.name for p in result] == ["c.mp4", "a.mp4", "b.mp4"] From 7d713061086045c118b706b7dd711de9c5dd2d00 Mon Sep 17 00:00:00 2001 From: Jaap de Ruyter van Steveninck <32810691+deruyter92@users.noreply.github.com> Date: Fri, 8 May 2026 16:32:51 +0200 Subject: [PATCH 11/24] Adjust `extensions` parameter: flexibly coerce str / list / tuple -> set of extensions. --- .../pose_estimation_pytorch/apis/utils.py | 2 +- deeplabcut/utils/auxfun_videos.py | 18 +++++++++++++----- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/deeplabcut/pose_estimation_pytorch/apis/utils.py b/deeplabcut/pose_estimation_pytorch/apis/utils.py index 82d2d974d..fc429d552 100644 --- a/deeplabcut/pose_estimation_pytorch/apis/utils.py +++ b/deeplabcut/pose_estimation_pytorch/apis/utils.py @@ -302,7 +302,7 @@ def list_videos_in_folder( ) -> list[Path]: return collect_video_paths( data_path=data_path, - extensions=video_type, + extensions=video_type or None, shuffle=shuffle, ) diff --git a/deeplabcut/utils/auxfun_videos.py b/deeplabcut/utils/auxfun_videos.py index 13bcac2e1..e8c74d130 100644 --- a/deeplabcut/utils/auxfun_videos.py +++ b/deeplabcut/utils/auxfun_videos.py @@ -649,7 +649,7 @@ def display_help(*args): def collect_video_paths( data_path: str | Path | list[str | Path], - extensions: str | None = None, + extensions: str | list[str] | None = None, shuffle: bool = False, exclude_patterns: list[str] | None = None, ) -> list[Path]: @@ -683,10 +683,18 @@ def collect_video_paths( if exclude_patterns is None: exclude_patterns = ["*_labeled.*", "*_full.*"] - if extensions: - explicit_suffixes: set[str] | None = {f".{extensions.lstrip('.').lower()}"} - else: - explicit_suffixes = None + def _coerce_extensions(extensions: str | None) -> set[str]: + """Flexible coercion of extensions to a set of suffixes""" + # NOTE @deruyter92: support legacy API, which mixed strings and iterables. + if isinstance(extensions, (list, tuple)): + explicit_suffixes = {f".{e.lstrip('.').lower()}" for e in extensions if e} or None + elif extensions: + explicit_suffixes = {f".{extensions.lstrip('.').lower()}"} + else: + explicit_suffixes = None + return explicit_suffixes + + explicit_suffixes = _coerce_extensions(extensions) implicit_suffixes = {f".{ext.lower()}" for ext in SUPPORTED_VIDEOS} videos: list[Path] = [] From adadf08e5a0d2f6cd061fb5c3b95d93a9ea737f1 Mon Sep 17 00:00:00 2001 From: Jaap de Ruyter van Steveninck <32810691+deruyter92@users.noreply.github.com> Date: Tue, 12 May 2026 13:34:09 +0200 Subject: [PATCH 12/24] `collect_video_paths`: sort videos if shuffle=False --- deeplabcut/utils/auxfun_videos.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/deeplabcut/utils/auxfun_videos.py b/deeplabcut/utils/auxfun_videos.py index e8c74d130..d411f664a 100644 --- a/deeplabcut/utils/auxfun_videos.py +++ b/deeplabcut/utils/auxfun_videos.py @@ -722,4 +722,6 @@ def _coerce_extensions(extensions: str | None) -> set[str]: unique_videos = list(dict.fromkeys(v.resolve() for v in videos)) if shuffle: random.shuffle(unique_videos) + else: + unique_videos.sort() return unique_videos From ce8bcf534fd0385adb3fdf5ae7f80bd207d29789 Mon Sep 17 00:00:00 2001 From: Jaap de Ruyter van Steveninck <32810691+deruyter92@users.noreply.github.com> Date: Tue, 12 May 2026 13:37:44 +0200 Subject: [PATCH 13/24] update docstring non-recursive directory scanning. --- deeplabcut/utils/auxfun_videos.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/deeplabcut/utils/auxfun_videos.py b/deeplabcut/utils/auxfun_videos.py index d411f664a..a7fc18f6c 100644 --- a/deeplabcut/utils/auxfun_videos.py +++ b/deeplabcut/utils/auxfun_videos.py @@ -654,7 +654,9 @@ def collect_video_paths( exclude_patterns: list[str] | None = None, ) -> list[Path]: """ - Collects video paths from a given set of data paths: directories, files or mix of both. + Collects video paths from a given set of data paths: directories, files or mix of both. Directories are + only scanned single level, non-recursively. + Optionally filters paths by extension and excludes patterns. Files and directories are treated differently: - Files are not filtered by extension by default. Set ``extensions`` if needed, to filter also supplied files. - Directory contents are filtered by ``SUPPORTED_VIDEOS`` by default. Specify custom ``extensions`` if needed. From 75a4d0aac32b7dfba252b3cbef22cf7a63403d1b Mon Sep 17 00:00:00 2001 From: Jaap de Ruyter van Steveninck <32810691+deruyter92@users.noreply.github.com> Date: Tue, 12 May 2026 14:07:05 +0200 Subject: [PATCH 14/24] update collect_video_paths: DEFAULT_EXCLUDE_PATTERNS --- deeplabcut/utils/auxfun_videos.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/deeplabcut/utils/auxfun_videos.py b/deeplabcut/utils/auxfun_videos.py index a7fc18f6c..639714e87 100644 --- a/deeplabcut/utils/auxfun_videos.py +++ b/deeplabcut/utils/auxfun_videos.py @@ -34,6 +34,7 @@ # more videos are in principle covered, as OpenCV is used and allows many formats. SUPPORTED_VIDEOS = "avi", "mp4", "mov", "mpeg", "mpg", "mpv", "mkv", "flv", "qt", "yuv" +DEFAULT_EXCLUDE_PATTERNS: tuple[str, ...] = "*_labeled.*", "*_full.*" class VideoReader: @@ -651,7 +652,7 @@ def collect_video_paths( data_path: str | Path | list[str | Path], extensions: str | list[str] | None = None, shuffle: bool = False, - exclude_patterns: list[str] | None = None, + exclude_patterns: Sequence[str] = DEFAULT_EXCLUDE_PATTERNS, ) -> list[Path]: """ Collects video paths from a given set of data paths: directories, files or mix of both. Directories are @@ -670,8 +671,8 @@ def collect_video_paths( - If ``None``, provided files are not filtered, but directory contents are filtered by ``SUPPORTED_VIDEOS``. shuffle: Whether to shuffle the order of videos. If False, videos are returned in sorted order for deterministic behavior. - exclude_patterns: Patterns to exclude from the collection. Defaults to ["*_labeled.*", "*_full.*"]. - Set to [] to exclude no patterns. + exclude_patterns: Patterns to exclude from the collection. Defaults to DEFAULT_EXCLUDE_PATTERNS. + Set to an empty sequence e.g. `[]` to exclude no patterns. Returns: The paths of videos to analyze. Duplicate paths are removed. From 481d3fac786b9db4801ee57d131c35aa6a79e860 Mon Sep 17 00:00:00 2001 From: Jaap de Ruyter van Steveninck <32810691+deruyter92@users.noreply.github.com> Date: Tue, 12 May 2026 13:34:56 +0200 Subject: [PATCH 15/24] update _coerce_extensions: only "" and None normalize to None --- deeplabcut/utils/auxfun_videos.py | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/deeplabcut/utils/auxfun_videos.py b/deeplabcut/utils/auxfun_videos.py index 639714e87..4b57e3045 100644 --- a/deeplabcut/utils/auxfun_videos.py +++ b/deeplabcut/utils/auxfun_videos.py @@ -24,6 +24,7 @@ import random import subprocess import warnings +from collections.abc import Sequence from pathlib import Path import cv2 @@ -650,7 +651,7 @@ def display_help(*args): def collect_video_paths( data_path: str | Path | list[str | Path], - extensions: str | list[str] | None = None, + extensions: str | Sequence[str] | None = None, shuffle: bool = False, exclude_patterns: Sequence[str] = DEFAULT_EXCLUDE_PATTERNS, ) -> list[Path]: @@ -666,9 +667,11 @@ def collect_video_paths( Args: data_path: Path or list of paths to folders containing videos, or individual video files. Can be a mix of directories and files. - extensions: The types of videos to filter for (e.g., ".mp4", ".avi", etc.). - - If set: filter all videos with the given extensions. Both for directory contents and supplied files. + extensions: The types of videos to select, by filtering the extension (e.g., ".mp4", ".avi", etc.). + - If set: select all videos with the given extensions. Both for directory contents and supplied files. - If ``None``, provided files are not filtered, but directory contents are filtered by ``SUPPORTED_VIDEOS``. + - An empty str "" is equivalent to None (for backwards compatibility). + - An empty sequence: select only files without extension. shuffle: Whether to shuffle the order of videos. If False, videos are returned in sorted order for deterministic behavior. exclude_patterns: Patterns to exclude from the collection. Defaults to DEFAULT_EXCLUDE_PATTERNS. @@ -683,18 +686,19 @@ def collect_video_paths( if isinstance(data_path, (str, Path)): data_path = [data_path] - if exclude_patterns is None: - exclude_patterns = ["*_labeled.*", "*_full.*"] - - def _coerce_extensions(extensions: str | None) -> set[str]: + def _coerce_extensions(extensions: str | Sequence[str] | None) -> set[str] | None: """Flexible coercion of extensions to a set of suffixes""" # NOTE @deruyter92: support legacy API, which mixed strings and iterables. - if isinstance(extensions, (list, tuple)): - explicit_suffixes = {f".{e.lstrip('.').lower()}" for e in extensions if e} or None - elif extensions: + if isinstance(extensions, (Sequence, set)): + explicit_suffixes = {f".{e.lstrip('.').lower()}" for e in extensions} + elif isinstance(extensions, str): explicit_suffixes = {f".{extensions.lstrip('.').lower()}"} else: explicit_suffixes = None + + # Normalize empty string to None + if explicit_suffixes == {""}: + explicit_suffixes = None return explicit_suffixes explicit_suffixes = _coerce_extensions(extensions) From 8ca9b495297e30ca21c103121a5a61675fc0735e Mon Sep 17 00:00:00 2001 From: Jaap de Ruyter Date: Tue, 12 May 2026 14:41:19 +0200 Subject: [PATCH 16/24] fix instance check in _coerce_video_extensions --- deeplabcut/utils/auxfun_videos.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deeplabcut/utils/auxfun_videos.py b/deeplabcut/utils/auxfun_videos.py index 4b57e3045..0a60f3f74 100644 --- a/deeplabcut/utils/auxfun_videos.py +++ b/deeplabcut/utils/auxfun_videos.py @@ -689,7 +689,7 @@ def collect_video_paths( def _coerce_extensions(extensions: str | Sequence[str] | None) -> set[str] | None: """Flexible coercion of extensions to a set of suffixes""" # NOTE @deruyter92: support legacy API, which mixed strings and iterables. - if isinstance(extensions, (Sequence, set)): + if isinstance(extensions, (list, tuple, set)): explicit_suffixes = {f".{e.lstrip('.').lower()}" for e in extensions} elif isinstance(extensions, str): explicit_suffixes = {f".{extensions.lstrip('.').lower()}"} From 39557ece722a3dc475e3a023ff01c62b8cdc8950 Mon Sep 17 00:00:00 2001 From: Jaap de Ruyter Date: Tue, 12 May 2026 14:41:48 +0200 Subject: [PATCH 17/24] fix pytest: assert alphabetic ordering if shuffle=False --- tests/utils/test_collect_video_paths.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/utils/test_collect_video_paths.py b/tests/utils/test_collect_video_paths.py index 812c81497..c64ce1c67 100644 --- a/tests/utils/test_collect_video_paths.py +++ b/tests/utils/test_collect_video_paths.py @@ -158,4 +158,4 @@ def test_sorted_by_default_when_not_shuffled(tmp_path): # sorted input list we expect a sorted output list. result = collect_video_paths([c, a, b], extensions=None, shuffle=False) - assert [p.name for p in result] == ["c.mp4", "a.mp4", "b.mp4"] + assert [p.name for p in result] == ["a.mp4", "b.mp4", "c.mp4"] From 39cfd94311191f2327c3789d0b5c85392d1d85e0 Mon Sep 17 00:00:00 2001 From: Jaap de Ruyter Date: Tue, 12 May 2026 15:35:38 +0200 Subject: [PATCH 18/24] fix empty string case in _coerce_extensions --- deeplabcut/utils/auxfun_videos.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deeplabcut/utils/auxfun_videos.py b/deeplabcut/utils/auxfun_videos.py index 0a60f3f74..8b4ed878a 100644 --- a/deeplabcut/utils/auxfun_videos.py +++ b/deeplabcut/utils/auxfun_videos.py @@ -691,7 +691,7 @@ def _coerce_extensions(extensions: str | Sequence[str] | None) -> set[str] | Non # NOTE @deruyter92: support legacy API, which mixed strings and iterables. if isinstance(extensions, (list, tuple, set)): explicit_suffixes = {f".{e.lstrip('.').lower()}" for e in extensions} - elif isinstance(extensions, str): + elif isinstance(extensions, str) and not extensions == "": explicit_suffixes = {f".{extensions.lstrip('.').lower()}"} else: explicit_suffixes = None From 77a05dc2a2734499c68a9f608ca002f08b39a818 Mon Sep 17 00:00:00 2001 From: Cyril Achard Date: Tue, 12 May 2026 15:36:50 +0200 Subject: [PATCH 19/24] Add structured deprecation info and warnings (#3326) * Add structured deprecation info and warnings Introduce a DLCDeprecationWarning and a DeprecationInfo pydantic model to standardize deprecation metadata (kind, target, replacement, since, removed_in, renamed params) with parsing and validation of versions. Revamp deprecated and renamed_parameter decorators to build messages from DeprecationInfo, emit DLCDeprecationWarning, attach metadata to wrapped callables (__deprecated_info__, __deprecated_params__), use ParamSpec/TypeVar typing for wrappers, and enforce error when both old and new kwargs are passed. Switch to packaging.version for version parsing. * Use DLCDeprecationWarning and add metadata tests Replace generic DeprecationWarning checks with DLCDeprecationWarning and import packaging.version.Version. Add tests verifying deprecated decorators attach metadata (including since/removed_in parsed as Version), validate invalid version inputs, and ensure removed_in > since. Also add tests for renamed_parameter behavior (conflicting old+new raises, metadata attachment, and invalid since handling) and small docstring/name preservation assertions. * Add packaging as core dep --- deeplabcut/utils/deprecation.py | 118 ++++++++++++++++++++++++++------ pyproject.toml | 2 +- tests/utils/test_deprecation.py | 96 +++++++++++++++++++++++--- uv.lock | 60 ++++++++-------- 4 files changed, 213 insertions(+), 63 deletions(-) diff --git a/deeplabcut/utils/deprecation.py b/deeplabcut/utils/deprecation.py index c3d98f222..8551929c5 100644 --- a/deeplabcut/utils/deprecation.py +++ b/deeplabcut/utils/deprecation.py @@ -13,13 +13,78 @@ import functools import warnings from collections.abc import Callable +from typing import Literal, ParamSpec, TypeVar + +from packaging.version import InvalidVersion, Version +from pydantic import BaseModel, ConfigDict, field_validator, model_validator + +P = ParamSpec("P") +R = TypeVar("R") + + +class DLCDeprecationWarning(DeprecationWarning): + """Project-specific deprecation warning. Helps with filtering.""" + + +class DeprecationInfo(BaseModel): + model_config = ConfigDict( + frozen=True, + arbitrary_types_allowed=True, + ) + + kind: Literal["callable", "parameter"] + target: str + replacement: str | None = None + + since: Version | None = None + removed_in: Version | None = None + + old_parameter: str | None = None + new_parameter: str | None = None + + @field_validator("since", "removed_in", mode="before") + @classmethod + def _parse_version(cls, value): + if value is None or isinstance(value, Version): + return value + try: + return Version(value) + except InvalidVersion as e: + 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: + raise ValueError(f"'removed_in' ({self.removed_in}) must be greater than 'since' ({self.since}).") + return self + + def format_message(self) -> str: + if self.kind == "callable": + parts = [f"{self.target} is deprecated"] + if self.since: + parts[0] += f" since {self.since}" + if self.replacement: + parts.append(f"Use {self.replacement} instead.") + if self.removed_in: + parts.append(f"It will be removed in {self.removed_in}.") + return " ".join(parts) + + if self.kind == "parameter": + return ( + f"Parameter '{self.old_parameter}' of {self.target} is deprecated" + + (f" since {self.since}" if self.since else "") + + f"; use '{self.new_parameter}' instead." + ) + + raise ValueError(f"Unknown deprecation kind: {self.kind}") def deprecated( + *, replacement: str | None = None, since: str | None = None, removed_in: str | None = None, -) -> Callable: +) -> Callable[[Callable[P, R]], Callable[P, R]]: """Mark a function as deprecated. Args: @@ -29,28 +94,34 @@ def deprecated( removed_in: Version in which the function will be removed. """ - def decorator(fn: Callable) -> Callable: - parts = [f"{fn.__qualname__} is deprecated"] - if since: - parts[0] += f" since {since}" - if replacement: - parts.append(f"Use {replacement} instead.") - if removed_in: - parts.append(f"It will be removed in {removed_in}.") - message = " ".join(parts) + def decorator(fn: Callable[P, R]) -> Callable[P, R]: + info = DeprecationInfo( + kind="callable", + target=fn.__qualname__, + replacement=replacement, + since=since, + removed_in=removed_in, + ) + message = info.format_message() @functools.wraps(fn) - def wrapper(*args, **kwargs): - warnings.warn(message, DeprecationWarning, stacklevel=2) + def wrapper(*args: P.args, **kwargs: P.kwargs) -> R: + warnings.warn(message, DLCDeprecationWarning, stacklevel=2) return fn(*args, **kwargs) wrapper.__doc__ = f"Deprecated. {message}\n\n" + (fn.__doc__ or "") + wrapper.__deprecated_info__ = info return wrapper return decorator -def renamed_parameter(old: str, new: str, since: str | None = None) -> Callable: +def renamed_parameter( + *, + old: str, + new: str, + since: str | None = None, +) -> Callable[[Callable[P, R]], Callable[P, R]]: """Support a renamed keyword argument while warning callers to update. Args: @@ -59,20 +130,27 @@ def renamed_parameter(old: str, new: str, since: str | None = None) -> Callable: since: Version when the rename happened. """ - def decorator(fn: Callable) -> Callable: - msg = ( - f"Parameter '{old}' of {fn.__qualname__} is deprecated" - + (f" since {since}" if since else "") - + f"; use '{new}' instead." + def decorator(fn: Callable[P, R]) -> Callable[P, R]: + info = DeprecationInfo( + kind="parameter", + target=fn.__qualname__, + since=since, + old_parameter=old, + new_parameter=new, ) + message = info.format_message() @functools.wraps(fn) - def wrapper(*args, **kwargs): + def wrapper(*args: P.args, **kwargs: P.kwargs) -> R: if old in kwargs: - warnings.warn(msg, DeprecationWarning, stacklevel=2) + if new in kwargs: + raise TypeError(f"{fn.__qualname__} received both '{old}' and '{new}'. Use only '{new}'.") + warnings.warn(message, DLCDeprecationWarning, stacklevel=2) kwargs[new] = kwargs.pop(old) return fn(*args, **kwargs) + existing = getattr(fn, "__deprecated_params__", ()) + wrapper.__deprecated_params__ = (*existing, info) return wrapper return decorator diff --git a/pyproject.toml b/pyproject.toml index f6272056a..17b761a51 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,6 +42,7 @@ dependencies = [ "networkx>=2.6", "numba>=0.54", "numpy>=1.18.5,<2", + "packaging>=26", "pandas[hdf5,performance]>=2.2,<3", "pillow>=7.1", "pycocotools", @@ -67,7 +68,6 @@ email = "alexander@deeplabcut.org" [project.optional-dependencies] gui = [ "napari-deeplabcut>=0.2.1.6", - "packaging>=26", "pyside6; platform_system!='Linux' or platform_machine!='x86_64'", # Avoid 6.10.0 only on Linux x86_64 (fails for older glib versions) "pyside6<6.10; platform_system=='Linux' and platform_machine=='x86_64'", diff --git a/tests/utils/test_deprecation.py b/tests/utils/test_deprecation.py index 7fbc65064..5938619f7 100644 --- a/tests/utils/test_deprecation.py +++ b/tests/utils/test_deprecation.py @@ -11,8 +11,13 @@ import warnings import pytest +from packaging.version import Version -from deeplabcut.utils.deprecation import deprecated, renamed_parameter +from deeplabcut.utils.deprecation import ( + DLCDeprecationWarning, + deprecated, + renamed_parameter, +) # --------------------------------------------------------------------------- # @deprecated @@ -24,7 +29,7 @@ def test_deprecated_emits_deprecation_warning(): def old_fn(): return 42 - with pytest.warns(DeprecationWarning): + with pytest.warns(DLCDeprecationWarning): result = old_fn() assert result == 42 @@ -35,7 +40,7 @@ def test_deprecated_warning_contains_function_name(): def my_old_function(): pass - with pytest.warns(DeprecationWarning, match="my_old_function"): + with pytest.warns(DLCDeprecationWarning, match="my_old_function"): my_old_function() @@ -44,7 +49,7 @@ def test_deprecated_warning_contains_replacement(): def old_fn(): pass - with pytest.warns(DeprecationWarning, match="new_module.new_fn"): + with pytest.warns(DLCDeprecationWarning, match="new_module.new_fn"): old_fn() @@ -53,7 +58,7 @@ def test_deprecated_warning_contains_since_and_removed_in(): def old_fn(): pass - with pytest.warns(DeprecationWarning, match="3.1") as record: + with pytest.warns(DLCDeprecationWarning, match="3.1") as record: old_fn() assert "4.0" in str(record[0].message) @@ -64,7 +69,7 @@ def test_deprecated_preserves_return_value_and_args(): def add(a, b): return a + b - with pytest.warns(DeprecationWarning): + with pytest.warns(DLCDeprecationWarning): assert add(2, 3) == 5 @@ -76,6 +81,44 @@ def documented_fn(): assert documented_fn.__name__ == "documented_fn" assert "Original docstring." in documented_fn.__doc__ assert "Deprecated." in documented_fn.__doc__ + assert "new_fn" in documented_fn.__doc__ + + +def test_deprecated_attaches_metadata(): + @deprecated(replacement="new_fn", since="3.1", removed_in="4.0") + def old_fn(): + pass + + info = old_fn.__deprecated_info__ + 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 + + +def test_deprecated_invalid_removed_in_raises(): + with pytest.raises(ValueError, match="Invalid version"): + + @deprecated(removed_in="definitely-not-a-version") + def old_fn(): + pass + + +def test_deprecated_removed_in_must_be_greater_than_since(): + with pytest.raises(ValueError, match="must be greater than"): + + @deprecated(since="4.0", removed_in="4.0") + def old_fn(): + pass # --------------------------------------------------------------------------- @@ -88,7 +131,7 @@ def test_renamed_parameter_old_name_emits_warning(): def fn(shuffle=False): return shuffle - with pytest.warns(DeprecationWarning): + with pytest.warns(DLCDeprecationWarning): fn(in_random_order=True) @@ -97,7 +140,7 @@ def test_renamed_parameter_old_name_is_forwarded(): def fn(shuffle=False): return shuffle - with pytest.warns(DeprecationWarning): + with pytest.warns(DLCDeprecationWarning): result = fn(in_random_order=True) assert result is True @@ -110,7 +153,7 @@ def fn(shuffle=False): # No warning should be emitted when using the current name. with warnings.catch_warnings(): - warnings.simplefilter("error", DeprecationWarning) + warnings.simplefilter("error", DLCDeprecationWarning) result = fn(shuffle=True) assert result is True @@ -121,7 +164,7 @@ def test_renamed_parameter_warning_contains_names(): def fn(extensions=None): return extensions - with pytest.warns(DeprecationWarning, match="videotype") as record: + with pytest.warns(DLCDeprecationWarning, match="videotype") as record: fn(videotype="mp4") message = str(record[0].message) @@ -135,3 +178,36 @@ def my_fn(bar=None): """Docstring.""" assert my_fn.__name__ == "my_fn" + + +def test_renamed_parameter_old_and_new_together_raise(): + @renamed_parameter(old="videotype", new="extensions") + def fn(extensions=None): + return extensions + + with pytest.raises(TypeError, match="both 'videotype' and 'extensions'"): + fn(videotype="mp4", extensions="avi") + + +def test_renamed_parameter_attaches_metadata(): + @renamed_parameter(old="videotype", new="extensions", since="3.2") + def fn(extensions=None): + return extensions + + params = fn.__deprecated_params__ + assert len(params) == 1 + + info = params[0] + assert info.kind == "parameter" + assert info.target.endswith("fn") + assert info.old_parameter == "videotype" + assert info.new_parameter == "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="extensions", since="invalid-version") + def fn(extensions=None): + return extensions diff --git a/uv.lock b/uv.lock index 5925becbc..4b72057a1 100644 --- a/uv.lock +++ b/uv.lock @@ -1013,7 +1013,7 @@ resolution-markers = [ "(python_full_version < '3.11' and platform_machine != 'aarch64' and platform_machine != 'x86_64' and sys_platform == 'linux') or (python_full_version < '3.11' and sys_platform != 'darwin' and sys_platform != 'linux')", ] dependencies = [ - { name = "cuda-pathfinder", marker = "(sys_platform != 'darwin' and extra == 'extra-10-deeplabcut-apple-mchips') or (sys_platform != 'darwin' and extra == 'extra-10-deeplabcut-fmpose3d') or (sys_platform != 'darwin' and extra == 'extra-10-deeplabcut-tf') or (extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf') or (extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-cu11') or (extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-cu12') or (extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-latest') or (extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu11') or (extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu12') or (extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-latest') or (extra != 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-fmpose3d' and extra != 'extra-10-deeplabcut-tf') or (extra != 'extra-10-deeplabcut-fmpose3d' and extra == 'extra-10-deeplabcut-tf-cu11' and extra == 'extra-10-deeplabcut-tf-cu12') or (extra != 'extra-10-deeplabcut-fmpose3d' and extra == 'extra-10-deeplabcut-tf-cu11' and extra == 'extra-10-deeplabcut-tf-latest') or (extra != 'extra-10-deeplabcut-fmpose3d' and extra == 'extra-10-deeplabcut-tf-cu12' and extra == 'extra-10-deeplabcut-tf-latest') or (extra != 'extra-10-deeplabcut-apple-mchips' and extra != 'extra-10-deeplabcut-tf' and extra != 'extra-10-deeplabcut-tf-cu11' and extra != 'extra-10-deeplabcut-tf-cu12')" }, + { name = "cuda-pathfinder", marker = "(sys_platform != 'darwin' and extra == 'extra-10-deeplabcut-apple-mchips') or (sys_platform != 'darwin' and extra == 'extra-10-deeplabcut-fmpose3d') or (sys_platform != 'darwin' and extra == 'extra-10-deeplabcut-tf') or (sys_platform != 'darwin' and extra != 'extra-10-deeplabcut-tf-cu11' and extra != 'extra-10-deeplabcut-tf-cu12') or (extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf') or (extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-cu11') or (extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-cu12') or (extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-latest') or (extra == 'extra-10-deeplabcut-fmpose3d' and extra == 'extra-10-deeplabcut-tf-cu11') or (extra == 'extra-10-deeplabcut-fmpose3d' and extra == 'extra-10-deeplabcut-tf-cu12') or (extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu11') or (extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu12') or (extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-latest') or (extra == 'extra-10-deeplabcut-tf-cu11' and extra == 'extra-10-deeplabcut-tf-cu12') or (extra == 'extra-10-deeplabcut-tf-cu11' and extra == 'extra-10-deeplabcut-tf-latest') or (extra == 'extra-10-deeplabcut-tf-cu12' and extra == 'extra-10-deeplabcut-tf-latest')" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/1a/fe/7351d7e586a8b4c9f89731bfe4cf0148223e8f9903ff09571f78b3fb0682/cuda_bindings-13.2.0-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:08b395f79cb89ce0cd8effff07c4a1e20101b873c256a1aeb286e8fd7bd0f556", size = 5744254, upload-time = "2026-03-11T00:12:29.798Z" }, @@ -1201,6 +1201,7 @@ dependencies = [ { name = "networkx", version = "3.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf') or (extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-cu11') or (extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-cu12') or (extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-latest') or (extra == 'extra-10-deeplabcut-fmpose3d' and extra == 'extra-10-deeplabcut-tf-cu11') or (extra == 'extra-10-deeplabcut-fmpose3d' and extra == 'extra-10-deeplabcut-tf-cu12') or (extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu11') or (extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu12') or (extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-latest') or (extra == 'extra-10-deeplabcut-tf-cu11' and extra == 'extra-10-deeplabcut-tf-cu12') or (extra == 'extra-10-deeplabcut-tf-cu11' and extra == 'extra-10-deeplabcut-tf-latest') or (extra == 'extra-10-deeplabcut-tf-cu12' and extra == 'extra-10-deeplabcut-tf-latest')" }, { name = "numba" }, { name = "numpy" }, + { name = "packaging" }, { name = "pandas", extra = ["hdf5", "performance"] }, { name = "pillow" }, { name = "pycocotools" }, @@ -1247,7 +1248,6 @@ fmpose3d = [ ] gui = [ { name = "napari-deeplabcut" }, - { name = "packaging" }, { name = "pyside6" }, { name = "qdarkstyle" }, ] @@ -1335,7 +1335,7 @@ requires-dist = [ { name = "numpy", specifier = ">=1.18.5,<2" }, { name = "numpydoc", marker = "extra == 'docs'" }, { name = "openvino-dev", marker = "extra == 'openvino'", specifier = "==2022.1" }, - { name = "packaging", marker = "extra == 'gui'", specifier = ">=26" }, + { name = "packaging", specifier = ">=26" }, { name = "pandas", extras = ["hdf5", "performance"], specifier = ">=2.2,<3" }, { name = "pillow", specifier = ">=7.1" }, { name = "protobuf", marker = "sys_platform == 'darwin' and extra == 'apple-mchips'", specifier = "<7" }, @@ -1861,7 +1861,7 @@ name = "h5py" version = "3.16.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numpy" }, + { name = "numpy", marker = "sys_platform == 'darwin' or extra == 'extra-10-deeplabcut-tf' or extra == 'extra-10-deeplabcut-tf-cu11' or extra == 'extra-10-deeplabcut-tf-cu12' or extra == 'extra-10-deeplabcut-tf-latest'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/db/33/acd0ce6863b6c0d7735007df01815403f5589a21ff8c2e1ee2587a38f548/h5py-3.16.0.tar.gz", hash = "sha256:a0dbaad796840ccaa67a4c144a0d0c8080073c34c76d5a6941d6818678ef2738", size = 446526, upload-time = "2026-03-06T13:49:08.07Z" } wheels = [ @@ -2929,14 +2929,14 @@ version = "0.4.1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.13' and platform_machine == 'x86_64' and sys_platform == 'linux'", - "python_full_version >= '3.13' and sys_platform == 'darwin'", - "python_full_version >= '3.13' and platform_machine == 'aarch64' and sys_platform == 'linux'", - "(python_full_version >= '3.13' and platform_machine != 'aarch64' and platform_machine != 'x86_64' and sys_platform == 'linux') or (python_full_version >= '3.13' and sys_platform != 'darwin' and sys_platform != 'linux')", "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'linux'", "python_full_version == '3.11.*' and platform_machine == 'x86_64' and sys_platform == 'linux'", "python_full_version < '3.11' and platform_machine == 'x86_64' and sys_platform == 'linux'", + "python_full_version >= '3.13' and sys_platform == 'darwin'", "python_full_version == '3.12.*' and sys_platform == 'darwin'", + "python_full_version >= '3.13' and platform_machine == 'aarch64' and sys_platform == 'linux'", "python_full_version == '3.12.*' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "(python_full_version >= '3.13' and platform_machine != 'aarch64' and platform_machine != 'x86_64' and sys_platform == 'linux') or (python_full_version >= '3.13' and sys_platform != 'darwin' and sys_platform != 'linux')", "(python_full_version == '3.12.*' and platform_machine != 'aarch64' and platform_machine != 'x86_64' and sys_platform == 'linux') or (python_full_version == '3.12.*' and sys_platform != 'darwin' and sys_platform != 'linux')", "python_full_version == '3.11.*' and sys_platform == 'darwin'", "python_full_version == '3.11.*' and platform_machine == 'aarch64' and sys_platform == 'linux'", @@ -3760,7 +3760,7 @@ name = "nvidia-cudnn-cu13" version = "9.19.0.56" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "nvidia-cublas", marker = "(sys_platform != 'darwin' and extra == 'extra-10-deeplabcut-apple-mchips') or (sys_platform != 'darwin' and extra == 'extra-10-deeplabcut-fmpose3d') or (sys_platform != 'darwin' and extra == 'extra-10-deeplabcut-tf') or (extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf') or (extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-cu11') or (extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-cu12') or (extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-latest') or (extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu11') or (extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu12') or (extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-latest') or (extra != 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-fmpose3d' and extra != 'extra-10-deeplabcut-tf') or (extra != 'extra-10-deeplabcut-fmpose3d' and extra == 'extra-10-deeplabcut-tf-cu11' and extra == 'extra-10-deeplabcut-tf-cu12') or (extra != 'extra-10-deeplabcut-fmpose3d' and extra == 'extra-10-deeplabcut-tf-cu11' and extra == 'extra-10-deeplabcut-tf-latest') or (extra != 'extra-10-deeplabcut-fmpose3d' and extra == 'extra-10-deeplabcut-tf-cu12' and extra == 'extra-10-deeplabcut-tf-latest') or (extra != 'extra-10-deeplabcut-apple-mchips' and extra != 'extra-10-deeplabcut-tf' and extra != 'extra-10-deeplabcut-tf-cu11' and extra != 'extra-10-deeplabcut-tf-cu12')" }, + { name = "nvidia-cublas", marker = "(sys_platform != 'darwin' and extra == 'extra-10-deeplabcut-apple-mchips') or (sys_platform != 'darwin' and extra == 'extra-10-deeplabcut-fmpose3d') or (sys_platform != 'darwin' and extra == 'extra-10-deeplabcut-tf') or (sys_platform != 'darwin' and extra != 'extra-10-deeplabcut-tf-cu11' and extra != 'extra-10-deeplabcut-tf-cu12') or (extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf') or (extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-cu11') or (extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-cu12') or (extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-latest') or (extra == 'extra-10-deeplabcut-fmpose3d' and extra == 'extra-10-deeplabcut-tf-cu11') or (extra == 'extra-10-deeplabcut-fmpose3d' and extra == 'extra-10-deeplabcut-tf-cu12') or (extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu11') or (extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu12') or (extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-latest') or (extra == 'extra-10-deeplabcut-tf-cu11' and extra == 'extra-10-deeplabcut-tf-cu12') or (extra == 'extra-10-deeplabcut-tf-cu11' and extra == 'extra-10-deeplabcut-tf-latest') or (extra == 'extra-10-deeplabcut-tf-cu12' and extra == 'extra-10-deeplabcut-tf-latest')" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/f1/84/26025437c1e6b61a707442184fa0c03d083b661adf3a3eecfd6d21677740/nvidia_cudnn_cu13-9.19.0.56-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:6ed29ffaee1176c612daf442e4dd6cfeb6a0caa43ddcbeb59da94953030b1be4", size = 433781201, upload-time = "2026-02-03T20:40:53.805Z" }, @@ -3773,7 +3773,7 @@ name = "nvidia-cufft" version = "12.0.0.61" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "nvidia-nvjitlink", marker = "(sys_platform != 'darwin' and extra == 'extra-10-deeplabcut-apple-mchips') or (sys_platform != 'darwin' and extra == 'extra-10-deeplabcut-fmpose3d') or (sys_platform != 'darwin' and extra == 'extra-10-deeplabcut-tf') or (extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf') or (extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-cu11') or (extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-cu12') or (extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-latest') or (extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu11') or (extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu12') or (extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-latest') or (extra != 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-fmpose3d' and extra != 'extra-10-deeplabcut-tf') or (extra != 'extra-10-deeplabcut-fmpose3d' and extra == 'extra-10-deeplabcut-tf-cu11' and extra == 'extra-10-deeplabcut-tf-cu12') or (extra != 'extra-10-deeplabcut-fmpose3d' and extra == 'extra-10-deeplabcut-tf-cu11' and extra == 'extra-10-deeplabcut-tf-latest') or (extra != 'extra-10-deeplabcut-fmpose3d' and extra == 'extra-10-deeplabcut-tf-cu12' and extra == 'extra-10-deeplabcut-tf-latest') or (extra != 'extra-10-deeplabcut-apple-mchips' and extra != 'extra-10-deeplabcut-tf' and extra != 'extra-10-deeplabcut-tf-cu11' and extra != 'extra-10-deeplabcut-tf-cu12')" }, + { name = "nvidia-nvjitlink", marker = "(sys_platform != 'darwin' and extra == 'extra-10-deeplabcut-apple-mchips') or (sys_platform != 'darwin' and extra == 'extra-10-deeplabcut-fmpose3d') or (sys_platform != 'darwin' and extra == 'extra-10-deeplabcut-tf') or (sys_platform != 'darwin' and extra != 'extra-10-deeplabcut-tf-cu11' and extra != 'extra-10-deeplabcut-tf-cu12') or (extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf') or (extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-cu11') or (extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-cu12') or (extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-latest') or (extra == 'extra-10-deeplabcut-fmpose3d' and extra == 'extra-10-deeplabcut-tf-cu11') or (extra == 'extra-10-deeplabcut-fmpose3d' and extra == 'extra-10-deeplabcut-tf-cu12') or (extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu11') or (extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu12') or (extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-latest') or (extra == 'extra-10-deeplabcut-tf-cu11' and extra == 'extra-10-deeplabcut-tf-cu12') or (extra == 'extra-10-deeplabcut-tf-cu11' and extra == 'extra-10-deeplabcut-tf-latest') or (extra == 'extra-10-deeplabcut-tf-cu12' and extra == 'extra-10-deeplabcut-tf-latest')" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/8b/ae/f417a75c0259e85c1d2f83ca4e960289a5f814ed0cea74d18c353d3e989d/nvidia_cufft-12.0.0.61-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2708c852ef8cd89d1d2068bdbece0aa188813a0c934db3779b9b1faa8442e5f5", size = 214053554, upload-time = "2025-09-04T08:31:38.196Z" }, @@ -3861,9 +3861,9 @@ name = "nvidia-cusolver" version = "12.0.4.66" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "nvidia-cublas", marker = "(sys_platform != 'darwin' and extra == 'extra-10-deeplabcut-apple-mchips') or (sys_platform != 'darwin' and extra == 'extra-10-deeplabcut-fmpose3d') or (sys_platform != 'darwin' and extra == 'extra-10-deeplabcut-tf') or (extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf') or (extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-cu11') or (extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-cu12') or (extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-latest') or (extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu11') or (extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu12') or (extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-latest') or (extra != 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-fmpose3d' and extra != 'extra-10-deeplabcut-tf') or (extra != 'extra-10-deeplabcut-fmpose3d' and extra == 'extra-10-deeplabcut-tf-cu11' and extra == 'extra-10-deeplabcut-tf-cu12') or (extra != 'extra-10-deeplabcut-fmpose3d' and extra == 'extra-10-deeplabcut-tf-cu11' and extra == 'extra-10-deeplabcut-tf-latest') or (extra != 'extra-10-deeplabcut-fmpose3d' and extra == 'extra-10-deeplabcut-tf-cu12' and extra == 'extra-10-deeplabcut-tf-latest') or (extra != 'extra-10-deeplabcut-apple-mchips' and extra != 'extra-10-deeplabcut-tf' and extra != 'extra-10-deeplabcut-tf-cu11' and extra != 'extra-10-deeplabcut-tf-cu12')" }, - { name = "nvidia-cusparse", marker = "(sys_platform != 'darwin' and extra == 'extra-10-deeplabcut-apple-mchips') or (sys_platform != 'darwin' and extra == 'extra-10-deeplabcut-fmpose3d') or (sys_platform != 'darwin' and extra == 'extra-10-deeplabcut-tf') or (extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf') or (extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-cu11') or (extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-cu12') or (extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-latest') or (extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu11') or (extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu12') or (extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-latest') or (extra != 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-fmpose3d' and extra != 'extra-10-deeplabcut-tf') or (extra != 'extra-10-deeplabcut-fmpose3d' and extra == 'extra-10-deeplabcut-tf-cu11' and extra == 'extra-10-deeplabcut-tf-cu12') or (extra != 'extra-10-deeplabcut-fmpose3d' and extra == 'extra-10-deeplabcut-tf-cu11' and extra == 'extra-10-deeplabcut-tf-latest') or (extra != 'extra-10-deeplabcut-fmpose3d' and extra == 'extra-10-deeplabcut-tf-cu12' and extra == 'extra-10-deeplabcut-tf-latest') or (extra != 'extra-10-deeplabcut-apple-mchips' and extra != 'extra-10-deeplabcut-tf' and extra != 'extra-10-deeplabcut-tf-cu11' and extra != 'extra-10-deeplabcut-tf-cu12')" }, - { name = "nvidia-nvjitlink", marker = "(sys_platform != 'darwin' and extra == 'extra-10-deeplabcut-apple-mchips') or (sys_platform != 'darwin' and extra == 'extra-10-deeplabcut-fmpose3d') or (sys_platform != 'darwin' and extra == 'extra-10-deeplabcut-tf') or (extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf') or (extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-cu11') or (extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-cu12') or (extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-latest') or (extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu11') or (extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu12') or (extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-latest') or (extra != 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-fmpose3d' and extra != 'extra-10-deeplabcut-tf') or (extra != 'extra-10-deeplabcut-fmpose3d' and extra == 'extra-10-deeplabcut-tf-cu11' and extra == 'extra-10-deeplabcut-tf-cu12') or (extra != 'extra-10-deeplabcut-fmpose3d' and extra == 'extra-10-deeplabcut-tf-cu11' and extra == 'extra-10-deeplabcut-tf-latest') or (extra != 'extra-10-deeplabcut-fmpose3d' and extra == 'extra-10-deeplabcut-tf-cu12' and extra == 'extra-10-deeplabcut-tf-latest') or (extra != 'extra-10-deeplabcut-apple-mchips' and extra != 'extra-10-deeplabcut-tf' and extra != 'extra-10-deeplabcut-tf-cu11' and extra != 'extra-10-deeplabcut-tf-cu12')" }, + { name = "nvidia-cublas", marker = "(sys_platform != 'darwin' and extra == 'extra-10-deeplabcut-apple-mchips') or (sys_platform != 'darwin' and extra == 'extra-10-deeplabcut-fmpose3d') or (sys_platform != 'darwin' and extra == 'extra-10-deeplabcut-tf') or (sys_platform != 'darwin' and extra != 'extra-10-deeplabcut-tf-cu11' and extra != 'extra-10-deeplabcut-tf-cu12') or (extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf') or (extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-cu11') or (extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-cu12') or (extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-latest') or (extra == 'extra-10-deeplabcut-fmpose3d' and extra == 'extra-10-deeplabcut-tf-cu11') or (extra == 'extra-10-deeplabcut-fmpose3d' and extra == 'extra-10-deeplabcut-tf-cu12') or (extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu11') or (extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu12') or (extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-latest') or (extra == 'extra-10-deeplabcut-tf-cu11' and extra == 'extra-10-deeplabcut-tf-cu12') or (extra == 'extra-10-deeplabcut-tf-cu11' and extra == 'extra-10-deeplabcut-tf-latest') or (extra == 'extra-10-deeplabcut-tf-cu12' and extra == 'extra-10-deeplabcut-tf-latest')" }, + { name = "nvidia-cusparse", marker = "(sys_platform != 'darwin' and extra == 'extra-10-deeplabcut-apple-mchips') or (sys_platform != 'darwin' and extra == 'extra-10-deeplabcut-fmpose3d') or (sys_platform != 'darwin' and extra == 'extra-10-deeplabcut-tf') or (sys_platform != 'darwin' and extra != 'extra-10-deeplabcut-tf-cu11' and extra != 'extra-10-deeplabcut-tf-cu12') or (extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf') or (extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-cu11') or (extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-cu12') or (extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-latest') or (extra == 'extra-10-deeplabcut-fmpose3d' and extra == 'extra-10-deeplabcut-tf-cu11') or (extra == 'extra-10-deeplabcut-fmpose3d' and extra == 'extra-10-deeplabcut-tf-cu12') or (extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu11') or (extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu12') or (extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-latest') or (extra == 'extra-10-deeplabcut-tf-cu11' and extra == 'extra-10-deeplabcut-tf-cu12') or (extra == 'extra-10-deeplabcut-tf-cu11' and extra == 'extra-10-deeplabcut-tf-latest') or (extra == 'extra-10-deeplabcut-tf-cu12' and extra == 'extra-10-deeplabcut-tf-latest')" }, + { name = "nvidia-nvjitlink", marker = "(sys_platform != 'darwin' and extra == 'extra-10-deeplabcut-apple-mchips') or (sys_platform != 'darwin' and extra == 'extra-10-deeplabcut-fmpose3d') or (sys_platform != 'darwin' and extra == 'extra-10-deeplabcut-tf') or (sys_platform != 'darwin' and extra != 'extra-10-deeplabcut-tf-cu11' and extra != 'extra-10-deeplabcut-tf-cu12') or (extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf') or (extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-cu11') or (extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-cu12') or (extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-latest') or (extra == 'extra-10-deeplabcut-fmpose3d' and extra == 'extra-10-deeplabcut-tf-cu11') or (extra == 'extra-10-deeplabcut-fmpose3d' and extra == 'extra-10-deeplabcut-tf-cu12') or (extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu11') or (extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu12') or (extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-latest') or (extra == 'extra-10-deeplabcut-tf-cu11' and extra == 'extra-10-deeplabcut-tf-cu12') or (extra == 'extra-10-deeplabcut-tf-cu11' and extra == 'extra-10-deeplabcut-tf-latest') or (extra == 'extra-10-deeplabcut-tf-cu12' and extra == 'extra-10-deeplabcut-tf-latest')" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/c8/c3/b30c9e935fc01e3da443ec0116ed1b2a009bb867f5324d3f2d7e533e776b/nvidia_cusolver-12.0.4.66-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:02c2457eaa9e39de20f880f4bd8820e6a1cfb9f9a34f820eb12a155aa5bc92d2", size = 223467760, upload-time = "2025-09-04T08:33:04.222Z" }, @@ -3904,7 +3904,7 @@ name = "nvidia-cusparse" version = "12.6.3.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "nvidia-nvjitlink", marker = "(sys_platform != 'darwin' and extra == 'extra-10-deeplabcut-apple-mchips') or (sys_platform != 'darwin' and extra == 'extra-10-deeplabcut-fmpose3d') or (sys_platform != 'darwin' and extra == 'extra-10-deeplabcut-tf') or (extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf') or (extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-cu11') or (extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-cu12') or (extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-latest') or (extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu11') or (extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu12') or (extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-latest') or (extra != 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-fmpose3d' and extra != 'extra-10-deeplabcut-tf') or (extra != 'extra-10-deeplabcut-fmpose3d' and extra == 'extra-10-deeplabcut-tf-cu11' and extra == 'extra-10-deeplabcut-tf-cu12') or (extra != 'extra-10-deeplabcut-fmpose3d' and extra == 'extra-10-deeplabcut-tf-cu11' and extra == 'extra-10-deeplabcut-tf-latest') or (extra != 'extra-10-deeplabcut-fmpose3d' and extra == 'extra-10-deeplabcut-tf-cu12' and extra == 'extra-10-deeplabcut-tf-latest') or (extra != 'extra-10-deeplabcut-apple-mchips' and extra != 'extra-10-deeplabcut-tf' and extra != 'extra-10-deeplabcut-tf-cu11' and extra != 'extra-10-deeplabcut-tf-cu12')" }, + { name = "nvidia-nvjitlink", marker = "(sys_platform != 'darwin' and extra == 'extra-10-deeplabcut-apple-mchips') or (sys_platform != 'darwin' and extra == 'extra-10-deeplabcut-fmpose3d') or (sys_platform != 'darwin' and extra == 'extra-10-deeplabcut-tf') or (sys_platform != 'darwin' and extra != 'extra-10-deeplabcut-tf-cu11' and extra != 'extra-10-deeplabcut-tf-cu12') or (extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf') or (extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-cu11') or (extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-cu12') or (extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-latest') or (extra == 'extra-10-deeplabcut-fmpose3d' and extra == 'extra-10-deeplabcut-tf-cu11') or (extra == 'extra-10-deeplabcut-fmpose3d' and extra == 'extra-10-deeplabcut-tf-cu12') or (extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu11') or (extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu12') or (extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-latest') or (extra == 'extra-10-deeplabcut-tf-cu11' and extra == 'extra-10-deeplabcut-tf-cu12') or (extra == 'extra-10-deeplabcut-tf-cu11' and extra == 'extra-10-deeplabcut-tf-latest') or (extra == 'extra-10-deeplabcut-tf-cu12' and extra == 'extra-10-deeplabcut-tf-latest')" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/f8/94/5c26f33738ae35276672f12615a64bd008ed5be6d1ebcb23579285d960a9/nvidia_cusparse-12.6.3.3-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:80bcc4662f23f1054ee334a15c72b8940402975e0eab63178fc7e670aa59472c", size = 162155568, upload-time = "2025-09-04T08:33:42.864Z" }, @@ -4600,14 +4600,14 @@ version = "5.29.6" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.13' and platform_machine == 'x86_64' and sys_platform == 'linux'", - "python_full_version >= '3.13' and sys_platform == 'darwin'", - "python_full_version >= '3.13' and platform_machine == 'aarch64' and sys_platform == 'linux'", - "(python_full_version >= '3.13' and platform_machine != 'aarch64' and platform_machine != 'x86_64' and sys_platform == 'linux') or (python_full_version >= '3.13' and sys_platform != 'darwin' and sys_platform != 'linux')", "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'linux'", "python_full_version == '3.11.*' and platform_machine == 'x86_64' and sys_platform == 'linux'", "python_full_version < '3.11' and platform_machine == 'x86_64' and sys_platform == 'linux'", + "python_full_version >= '3.13' and sys_platform == 'darwin'", "python_full_version == '3.12.*' and sys_platform == 'darwin'", + "python_full_version >= '3.13' and platform_machine == 'aarch64' and sys_platform == 'linux'", "python_full_version == '3.12.*' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "(python_full_version >= '3.13' and platform_machine != 'aarch64' and platform_machine != 'x86_64' and sys_platform == 'linux') or (python_full_version >= '3.13' and sys_platform != 'darwin' and sys_platform != 'linux')", "(python_full_version == '3.12.*' and platform_machine != 'aarch64' and platform_machine != 'x86_64' and sys_platform == 'linux') or (python_full_version == '3.12.*' and sys_platform != 'darwin' and sys_platform != 'linux')", "python_full_version == '3.11.*' and sys_platform == 'darwin'", "python_full_version == '3.11.*' and platform_machine == 'aarch64' and sys_platform == 'linux'", @@ -6863,14 +6863,14 @@ version = "2.18.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.13' and platform_machine == 'x86_64' and sys_platform == 'linux'", - "python_full_version >= '3.13' and sys_platform == 'darwin'", - "python_full_version >= '3.13' and platform_machine == 'aarch64' and sys_platform == 'linux'", - "(python_full_version >= '3.13' and platform_machine != 'aarch64' and platform_machine != 'x86_64' and sys_platform == 'linux') or (python_full_version >= '3.13' and sys_platform != 'darwin' and sys_platform != 'linux')", "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'linux'", "python_full_version == '3.11.*' and platform_machine == 'x86_64' and sys_platform == 'linux'", "python_full_version < '3.11' and platform_machine == 'x86_64' and sys_platform == 'linux'", + "python_full_version >= '3.13' and sys_platform == 'darwin'", "python_full_version == '3.12.*' and sys_platform == 'darwin'", + "python_full_version >= '3.13' and platform_machine == 'aarch64' and sys_platform == 'linux'", "python_full_version == '3.12.*' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "(python_full_version >= '3.13' and platform_machine != 'aarch64' and platform_machine != 'x86_64' and sys_platform == 'linux') or (python_full_version >= '3.13' and sys_platform != 'darwin' and sys_platform != 'linux')", "(python_full_version == '3.12.*' and platform_machine != 'aarch64' and platform_machine != 'x86_64' and sys_platform == 'linux') or (python_full_version == '3.12.*' and sys_platform != 'darwin' and sys_platform != 'linux')", "python_full_version == '3.11.*' and sys_platform == 'darwin'", "python_full_version == '3.11.*' and platform_machine == 'aarch64' and sys_platform == 'linux'", @@ -6980,8 +6980,8 @@ dependencies = [ { name = "six", marker = "(python_full_version < '3.12' and sys_platform == 'darwin' and extra == 'extra-10-deeplabcut-apple-mchips') or (python_full_version < '3.12' and extra == 'extra-10-deeplabcut-tf') or extra == 'extra-10-deeplabcut-tf-cu11' or (extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf') or (extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-cu12') or (extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-latest') or (extra == 'extra-10-deeplabcut-fmpose3d' and extra == 'extra-10-deeplabcut-tf-cu12') or (extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu12') or (extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-latest') or (extra == 'extra-10-deeplabcut-tf-cu12' and extra == 'extra-10-deeplabcut-tf-latest')" }, { name = "tensorboard", version = "2.14.1", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.12' and sys_platform == 'darwin' and extra == 'extra-10-deeplabcut-apple-mchips') or (python_full_version < '3.12' and extra == 'extra-10-deeplabcut-tf') or extra == 'extra-10-deeplabcut-tf-cu11' or (extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf') or (extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-cu12') or (extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-latest') or (extra == 'extra-10-deeplabcut-fmpose3d' and extra == 'extra-10-deeplabcut-tf-cu12') or (extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu12') or (extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-latest') or (extra == 'extra-10-deeplabcut-tf-cu12' and extra == 'extra-10-deeplabcut-tf-latest')" }, { name = "tensorflow-estimator", marker = "(python_full_version < '3.12' and sys_platform == 'darwin' and extra == 'extra-10-deeplabcut-apple-mchips') or (python_full_version < '3.12' and extra == 'extra-10-deeplabcut-tf') or extra == 'extra-10-deeplabcut-tf-cu11' or (extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf') or (extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-cu12') or (extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-latest') or (extra == 'extra-10-deeplabcut-fmpose3d' and extra == 'extra-10-deeplabcut-tf-cu12') or (extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu12') or (extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-latest') or (extra == 'extra-10-deeplabcut-tf-cu12' and extra == 'extra-10-deeplabcut-tf-latest')" }, - { name = "tensorflow-io-gcs-filesystem", version = "0.31.0", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.12' and platform_machine != 'x86_64' and sys_platform == 'linux' and extra == 'extra-10-deeplabcut-tf') or (python_full_version >= '3.12' and platform_machine != 'x86_64' and extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu11') or (python_full_version >= '3.12' and platform_machine != 'x86_64' and extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu12') or (python_full_version >= '3.12' and platform_machine != 'x86_64' and extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-latest') or (python_full_version < '3.12' and sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-10-deeplabcut-tf') or (python_full_version >= '3.12' and sys_platform != 'linux' and extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu11') or (python_full_version >= '3.12' and sys_platform != 'linux' and extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu12') or (python_full_version >= '3.12' and sys_platform != 'linux' and extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-latest') or (platform_machine == 'x86_64' and sys_platform == 'linux' and extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu11') or (platform_machine == 'x86_64' and sys_platform == 'linux' and extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu12') or (platform_machine == 'x86_64' and sys_platform == 'linux' and extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-latest') or (platform_machine != 'x86_64' and extra != 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu11') or (sys_platform == 'darwin' and extra == 'extra-10-deeplabcut-tf-cu11') or (sys_platform != 'linux' and extra != 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu11') or (sys_platform == 'darwin' and extra == 'extra-10-deeplabcut-fmpose3d' and extra == 'extra-10-deeplabcut-tf-cu12') or (sys_platform == 'darwin' and extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu12') or (sys_platform == 'darwin' and extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-latest') or (sys_platform == 'darwin' and extra == 'extra-10-deeplabcut-tf-cu12' and extra == 'extra-10-deeplabcut-tf-latest') or (extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf') or (extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-cu11') or (extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-cu12') or (extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-latest') or (extra == 'extra-10-deeplabcut-fmpose3d' and extra != 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu11') or (extra == 'extra-10-deeplabcut-fmpose3d' and extra != 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu12') or (extra != 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu11' and extra == 'extra-10-deeplabcut-tf-cu12') or (extra != 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu11' and extra == 'extra-10-deeplabcut-tf-latest') or (extra != 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu12' and extra == 'extra-10-deeplabcut-tf-latest')" }, - { name = "tensorflow-io-gcs-filesystem", version = "0.37.1", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.12' and platform_machine == 'x86_64' and sys_platform == 'linux' and extra == 'extra-10-deeplabcut-tf') or (python_full_version < '3.12' and sys_platform == 'darwin' and extra == 'extra-10-deeplabcut-apple-mchips') or (python_full_version < '3.12' and sys_platform == 'darwin' and extra == 'extra-10-deeplabcut-tf') or (python_full_version >= '3.12' and sys_platform == 'linux' and extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu11') or (python_full_version >= '3.12' and sys_platform == 'linux' and extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu12') or (python_full_version >= '3.12' and sys_platform == 'linux' and extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-latest') or (platform_machine != 'x86_64' and sys_platform == 'linux' and extra == 'extra-10-deeplabcut-fmpose3d' and extra == 'extra-10-deeplabcut-tf-cu11') or (platform_machine != 'x86_64' and sys_platform == 'linux' and extra == 'extra-10-deeplabcut-fmpose3d' and extra == 'extra-10-deeplabcut-tf-cu12') or (platform_machine != 'x86_64' and sys_platform == 'linux' and extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu11') or (platform_machine != 'x86_64' and sys_platform == 'linux' and extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu12') or (platform_machine != 'x86_64' and sys_platform == 'linux' and extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-latest') or (platform_machine != 'x86_64' and sys_platform == 'linux' and extra == 'extra-10-deeplabcut-tf-cu11' and extra == 'extra-10-deeplabcut-tf-cu12') or (platform_machine != 'x86_64' and sys_platform == 'linux' and extra == 'extra-10-deeplabcut-tf-cu11' and extra == 'extra-10-deeplabcut-tf-latest') or (platform_machine != 'x86_64' and sys_platform == 'linux' and extra == 'extra-10-deeplabcut-tf-cu12' and extra == 'extra-10-deeplabcut-tf-latest') or (platform_machine == 'x86_64' and sys_platform == 'linux' and extra != 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu11') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-cu11') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-cu12') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-latest') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-10-deeplabcut-fmpose3d' and extra == 'extra-10-deeplabcut-tf-cu11') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-10-deeplabcut-fmpose3d' and extra == 'extra-10-deeplabcut-tf-cu12') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu11') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu12') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-latest') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-10-deeplabcut-tf-cu11' and extra == 'extra-10-deeplabcut-tf-cu12') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-10-deeplabcut-tf-cu11' and extra == 'extra-10-deeplabcut-tf-latest') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-10-deeplabcut-tf-cu12' and extra == 'extra-10-deeplabcut-tf-latest') or (sys_platform == 'darwin' and extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf') or (sys_platform == 'darwin' and extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-cu11') or (sys_platform == 'darwin' and extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-cu12') or (sys_platform == 'darwin' and extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-latest') or (sys_platform == 'darwin' and extra == 'extra-10-deeplabcut-fmpose3d' and extra == 'extra-10-deeplabcut-tf-cu11') or (sys_platform == 'darwin' and extra == 'extra-10-deeplabcut-fmpose3d' and extra == 'extra-10-deeplabcut-tf-cu12') or (sys_platform == 'darwin' and extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu11') or (sys_platform == 'darwin' and extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu12') or (sys_platform == 'darwin' and extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-latest') or (sys_platform == 'darwin' and extra == 'extra-10-deeplabcut-tf-cu11' and extra == 'extra-10-deeplabcut-tf-cu12') or (sys_platform == 'darwin' and extra == 'extra-10-deeplabcut-tf-cu11' and extra == 'extra-10-deeplabcut-tf-latest') or (sys_platform == 'darwin' and extra == 'extra-10-deeplabcut-tf-cu12' and extra == 'extra-10-deeplabcut-tf-latest') or (sys_platform == 'linux' and extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf') or (sys_platform == 'linux' and extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-cu11') or (sys_platform == 'linux' and extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-cu12') or (sys_platform == 'linux' and extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-latest') or (sys_platform == 'linux' and extra == 'extra-10-deeplabcut-fmpose3d' and extra != 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu11') or (sys_platform == 'linux' and extra == 'extra-10-deeplabcut-fmpose3d' and extra != 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu12') or (sys_platform == 'linux' and extra != 'extra-10-deeplabcut-tf' and extra != 'extra-10-deeplabcut-tf-cu11' and extra == 'extra-10-deeplabcut-tf-cu12' and extra == 'extra-10-deeplabcut-tf-latest')" }, + { name = "tensorflow-io-gcs-filesystem", version = "0.31.0", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.12' and platform_machine != 'aarch64' and platform_machine != 'x86_64' and sys_platform == 'linux' and extra == 'extra-10-deeplabcut-tf') or (python_full_version >= '3.12' and platform_machine != 'aarch64' and platform_machine != 'x86_64' and extra == 'extra-10-deeplabcut-tf-cu11') or (python_full_version >= '3.12' and platform_machine != 'aarch64' and platform_machine != 'x86_64' and extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu12') or (python_full_version >= '3.12' and platform_machine != 'aarch64' and platform_machine != 'x86_64' and extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-latest') or (python_full_version >= '3.12' and platform_machine == 'aarch64' and extra == 'extra-10-deeplabcut-tf-cu11') or (python_full_version >= '3.12' and platform_machine == 'aarch64' and extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu12') or (python_full_version >= '3.12' and platform_machine == 'aarch64' and extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-latest') or (python_full_version < '3.12' and sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-10-deeplabcut-tf') or (python_full_version >= '3.12' and sys_platform != 'linux' and extra == 'extra-10-deeplabcut-tf-cu11') or (python_full_version >= '3.12' and sys_platform == 'darwin' and extra == 'extra-10-deeplabcut-tf-cu11') or (python_full_version >= '3.12' and sys_platform != 'linux' and extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu12') or (python_full_version >= '3.12' and sys_platform != 'linux' and extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-latest') or (platform_machine != 'aarch64' and platform_machine != 'x86_64' and sys_platform == 'linux' and extra != 'extra-10-deeplabcut-apple-mchips' and extra != 'extra-10-deeplabcut-fmpose3d' and extra != 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu11') or (platform_machine != 'aarch64' and platform_machine != 'x86_64' and extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf') or (platform_machine != 'aarch64' and platform_machine != 'x86_64' and extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-cu11') or (platform_machine != 'aarch64' and platform_machine != 'x86_64' and extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-cu12') or (platform_machine != 'aarch64' and platform_machine != 'x86_64' and extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-latest') or (platform_machine != 'aarch64' and platform_machine != 'x86_64' and extra == 'extra-10-deeplabcut-fmpose3d' and extra != 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu11') or (platform_machine != 'aarch64' and platform_machine != 'x86_64' and extra == 'extra-10-deeplabcut-fmpose3d' and extra != 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu12') or (platform_machine != 'aarch64' and platform_machine != 'x86_64' and extra != 'extra-10-deeplabcut-tf' and extra != 'extra-10-deeplabcut-tf-cu11' and extra == 'extra-10-deeplabcut-tf-cu12' and extra == 'extra-10-deeplabcut-tf-latest') or (platform_machine == 'x86_64' and sys_platform == 'linux' and extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu11') or (platform_machine == 'x86_64' and sys_platform == 'linux' and extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu12') or (platform_machine == 'x86_64' and sys_platform == 'linux' and extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-latest') or (platform_machine == 'x86_64' and sys_platform == 'linux' and extra == 'extra-10-deeplabcut-tf-cu11' and extra == 'extra-10-deeplabcut-tf-cu12') or (platform_machine == 'x86_64' and sys_platform == 'linux' and extra == 'extra-10-deeplabcut-tf-cu11' and extra == 'extra-10-deeplabcut-tf-latest') or (platform_machine == 'aarch64' and sys_platform == 'linux' and extra != 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu11') or (platform_machine == 'aarch64' and sys_platform == 'linux' and extra != 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu12') or (platform_machine == 'aarch64' and sys_platform == 'linux' and extra != 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-latest') or (platform_machine == 'aarch64' and sys_platform == 'linux' and extra != 'extra-10-deeplabcut-apple-mchips' and extra != 'extra-10-deeplabcut-fmpose3d' and extra == 'extra-10-deeplabcut-tf-cu11' and extra == 'extra-10-deeplabcut-tf-cu12') or (platform_machine == 'aarch64' and sys_platform == 'linux' and extra != 'extra-10-deeplabcut-apple-mchips' and extra != 'extra-10-deeplabcut-fmpose3d' and extra == 'extra-10-deeplabcut-tf-cu11' and extra == 'extra-10-deeplabcut-tf-latest') or (platform_machine == 'aarch64' and extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf') or (platform_machine == 'aarch64' and extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-cu11') or (platform_machine == 'aarch64' and extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-cu12') or (platform_machine == 'aarch64' and extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-latest') or (platform_machine == 'x86_64' and extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf') or (platform_machine == 'x86_64' and extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-cu11') or (platform_machine == 'x86_64' and extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-cu12') or (platform_machine == 'x86_64' and extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-latest') or (platform_machine == 'aarch64' and extra == 'extra-10-deeplabcut-fmpose3d' and extra != 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu11') or (platform_machine == 'aarch64' and extra == 'extra-10-deeplabcut-fmpose3d' and extra != 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu12') or (platform_machine == 'x86_64' and extra == 'extra-10-deeplabcut-fmpose3d' and extra != 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu11') or (platform_machine == 'x86_64' and extra == 'extra-10-deeplabcut-fmpose3d' and extra != 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu12') or (platform_machine == 'aarch64' and extra != 'extra-10-deeplabcut-tf' and extra != 'extra-10-deeplabcut-tf-cu11' and extra == 'extra-10-deeplabcut-tf-cu12' and extra == 'extra-10-deeplabcut-tf-latest') or (platform_machine == 'x86_64' and extra != 'extra-10-deeplabcut-tf' and extra != 'extra-10-deeplabcut-tf-cu11' and extra == 'extra-10-deeplabcut-tf-cu12' and extra == 'extra-10-deeplabcut-tf-latest') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra != 'extra-10-deeplabcut-apple-mchips' and extra != 'extra-10-deeplabcut-fmpose3d' and extra != 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu11') or (sys_platform != 'linux' and extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf') or (sys_platform != 'linux' and extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-cu11') or (sys_platform != 'linux' and extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-cu12') or (sys_platform != 'linux' and extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-latest') or (sys_platform == 'darwin' and extra == 'extra-10-deeplabcut-tf-cu11' and extra == 'extra-10-deeplabcut-tf-cu12') or (sys_platform == 'darwin' and extra == 'extra-10-deeplabcut-tf-cu11' and extra == 'extra-10-deeplabcut-tf-latest') or (sys_platform == 'darwin' and extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf') or (sys_platform == 'darwin' and extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-cu11') or (sys_platform == 'darwin' and extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-cu12') or (sys_platform == 'darwin' and extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-latest') or (sys_platform == 'darwin' and extra == 'extra-10-deeplabcut-fmpose3d' and extra == 'extra-10-deeplabcut-tf-cu11') or (sys_platform == 'darwin' and extra == 'extra-10-deeplabcut-fmpose3d' and extra == 'extra-10-deeplabcut-tf-cu12') or (sys_platform == 'darwin' and extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu11') or (sys_platform == 'darwin' and extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu12') or (sys_platform == 'darwin' and extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-latest') or (sys_platform == 'darwin' and extra == 'extra-10-deeplabcut-tf-cu12' and extra == 'extra-10-deeplabcut-tf-latest') or (sys_platform != 'linux' and extra == 'extra-10-deeplabcut-fmpose3d' and extra != 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu11') or (sys_platform != 'linux' and extra == 'extra-10-deeplabcut-fmpose3d' and extra != 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu12') or (sys_platform != 'linux' and extra != 'extra-10-deeplabcut-tf' and extra != 'extra-10-deeplabcut-tf-cu11' and extra == 'extra-10-deeplabcut-tf-cu12' and extra == 'extra-10-deeplabcut-tf-latest')" }, + { name = "tensorflow-io-gcs-filesystem", version = "0.37.1", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.12' and platform_machine == 'aarch64' and sys_platform == 'linux' and extra == 'extra-10-deeplabcut-tf') or (python_full_version < '3.12' and platform_machine == 'aarch64' and sys_platform == 'linux' and extra == 'extra-10-deeplabcut-tf-cu11') or (python_full_version < '3.12' and platform_machine == 'x86_64' and sys_platform == 'linux' and extra == 'extra-10-deeplabcut-tf') or (python_full_version < '3.12' and sys_platform == 'darwin' and extra == 'extra-10-deeplabcut-apple-mchips') or (python_full_version < '3.12' and sys_platform == 'darwin' and extra == 'extra-10-deeplabcut-tf') or (python_full_version < '3.12' and sys_platform == 'darwin' and extra != 'extra-10-deeplabcut-fmpose3d' and extra == 'extra-10-deeplabcut-tf-cu11') or (platform_machine != 'aarch64' and platform_machine != 'x86_64' and sys_platform == 'linux' and extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf') or (platform_machine != 'aarch64' and platform_machine != 'x86_64' and sys_platform == 'linux' and extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-cu11') or (platform_machine != 'aarch64' and platform_machine != 'x86_64' and sys_platform == 'linux' and extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-cu12') or (platform_machine != 'aarch64' and platform_machine != 'x86_64' and sys_platform == 'linux' and extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-latest') or (platform_machine != 'aarch64' and platform_machine != 'x86_64' and sys_platform == 'linux' and extra == 'extra-10-deeplabcut-fmpose3d' and extra == 'extra-10-deeplabcut-tf-cu11') or (platform_machine != 'aarch64' and platform_machine != 'x86_64' and sys_platform == 'linux' and extra == 'extra-10-deeplabcut-fmpose3d' and extra == 'extra-10-deeplabcut-tf-cu12') or (platform_machine != 'aarch64' and platform_machine != 'x86_64' and sys_platform == 'linux' and extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu11') or (platform_machine != 'aarch64' and platform_machine != 'x86_64' and sys_platform == 'linux' and extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu12') or (platform_machine != 'aarch64' and platform_machine != 'x86_64' and sys_platform == 'linux' and extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-latest') or (platform_machine != 'aarch64' and platform_machine != 'x86_64' and sys_platform == 'linux' and extra == 'extra-10-deeplabcut-tf-cu11' and extra == 'extra-10-deeplabcut-tf-cu12') or (platform_machine != 'aarch64' and platform_machine != 'x86_64' and sys_platform == 'linux' and extra == 'extra-10-deeplabcut-tf-cu11' and extra == 'extra-10-deeplabcut-tf-latest') or (platform_machine != 'aarch64' and platform_machine != 'x86_64' and sys_platform == 'linux' and extra == 'extra-10-deeplabcut-tf-cu12' and extra == 'extra-10-deeplabcut-tf-latest') or (platform_machine == 'x86_64' and sys_platform == 'linux' and extra == 'extra-10-deeplabcut-tf-cu11') or (platform_machine == 'aarch64' and sys_platform == 'linux' and extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf') or (platform_machine == 'aarch64' and sys_platform == 'linux' and extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-cu11') or (platform_machine == 'aarch64' and sys_platform == 'linux' and extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-cu12') or (platform_machine == 'aarch64' and sys_platform == 'linux' and extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-latest') or (platform_machine == 'aarch64' and sys_platform == 'linux' and extra == 'extra-10-deeplabcut-fmpose3d' and extra == 'extra-10-deeplabcut-tf-cu11') or (platform_machine == 'aarch64' and sys_platform == 'linux' and extra == 'extra-10-deeplabcut-fmpose3d' and extra == 'extra-10-deeplabcut-tf-cu12') or (platform_machine == 'aarch64' and sys_platform == 'linux' and extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu11') or (platform_machine == 'aarch64' and sys_platform == 'linux' and extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu12') or (platform_machine == 'aarch64' and sys_platform == 'linux' and extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-latest') or (platform_machine == 'aarch64' and sys_platform == 'linux' and extra == 'extra-10-deeplabcut-tf-cu11' and extra == 'extra-10-deeplabcut-tf-cu12') or (platform_machine == 'aarch64' and sys_platform == 'linux' and extra == 'extra-10-deeplabcut-tf-cu11' and extra == 'extra-10-deeplabcut-tf-latest') or (platform_machine == 'aarch64' and sys_platform == 'linux' and extra == 'extra-10-deeplabcut-tf-cu12' and extra == 'extra-10-deeplabcut-tf-latest') or (platform_machine == 'x86_64' and sys_platform == 'linux' and extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf') or (platform_machine == 'x86_64' and sys_platform == 'linux' and extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-cu12') or (platform_machine == 'x86_64' and sys_platform == 'linux' and extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-latest') or (platform_machine == 'x86_64' and sys_platform == 'linux' and extra == 'extra-10-deeplabcut-fmpose3d' and extra == 'extra-10-deeplabcut-tf-cu12') or (platform_machine == 'x86_64' and sys_platform == 'linux' and extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu12') or (platform_machine == 'x86_64' and sys_platform == 'linux' and extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-latest') or (platform_machine == 'x86_64' and sys_platform == 'linux' and extra == 'extra-10-deeplabcut-tf-cu12' and extra == 'extra-10-deeplabcut-tf-latest') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-cu11') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-cu12') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-latest') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-10-deeplabcut-fmpose3d' and extra == 'extra-10-deeplabcut-tf-cu11') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-10-deeplabcut-fmpose3d' and extra == 'extra-10-deeplabcut-tf-cu12') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu11') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu12') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-latest') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-10-deeplabcut-tf-cu11' and extra == 'extra-10-deeplabcut-tf-cu12') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-10-deeplabcut-tf-cu11' and extra == 'extra-10-deeplabcut-tf-latest') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-10-deeplabcut-tf-cu12' and extra == 'extra-10-deeplabcut-tf-latest') or (sys_platform == 'darwin' and extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf') or (sys_platform == 'darwin' and extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-cu11') or (sys_platform == 'darwin' and extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-cu12') or (sys_platform == 'darwin' and extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-latest') or (sys_platform == 'darwin' and extra == 'extra-10-deeplabcut-fmpose3d' and extra == 'extra-10-deeplabcut-tf-cu11') or (sys_platform == 'darwin' and extra == 'extra-10-deeplabcut-fmpose3d' and extra == 'extra-10-deeplabcut-tf-cu12') or (sys_platform == 'darwin' and extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu11') or (sys_platform == 'darwin' and extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu12') or (sys_platform == 'darwin' and extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-latest') or (sys_platform == 'darwin' and extra == 'extra-10-deeplabcut-tf-cu11' and extra == 'extra-10-deeplabcut-tf-cu12') or (sys_platform == 'darwin' and extra == 'extra-10-deeplabcut-tf-cu11' and extra == 'extra-10-deeplabcut-tf-latest') or (sys_platform == 'darwin' and extra == 'extra-10-deeplabcut-tf-cu12' and extra == 'extra-10-deeplabcut-tf-latest')" }, { name = "termcolor", marker = "(python_full_version < '3.12' and sys_platform == 'darwin' and extra == 'extra-10-deeplabcut-apple-mchips') or (python_full_version < '3.12' and extra == 'extra-10-deeplabcut-tf') or extra == 'extra-10-deeplabcut-tf-cu11' or (extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf') or (extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-cu12') or (extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-latest') or (extra == 'extra-10-deeplabcut-fmpose3d' and extra == 'extra-10-deeplabcut-tf-cu12') or (extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu12') or (extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-latest') or (extra == 'extra-10-deeplabcut-tf-cu12' and extra == 'extra-10-deeplabcut-tf-latest')" }, { name = "typing-extensions", marker = "(python_full_version < '3.12' and sys_platform == 'darwin' and extra == 'extra-10-deeplabcut-apple-mchips') or (python_full_version < '3.12' and extra == 'extra-10-deeplabcut-tf') or extra == 'extra-10-deeplabcut-tf-cu11' or (extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf') or (extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-cu12') or (extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-latest') or (extra == 'extra-10-deeplabcut-fmpose3d' and extra == 'extra-10-deeplabcut-tf-cu12') or (extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu12') or (extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-latest') or (extra == 'extra-10-deeplabcut-tf-cu12' and extra == 'extra-10-deeplabcut-tf-latest')" }, { name = "wrapt", version = "1.14.2", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.12' and sys_platform == 'darwin' and extra == 'extra-10-deeplabcut-apple-mchips') or (python_full_version < '3.12' and extra == 'extra-10-deeplabcut-tf') or extra == 'extra-10-deeplabcut-tf-cu11' or (extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf') or (extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-cu12') or (extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-latest') or (extra == 'extra-10-deeplabcut-fmpose3d' and extra == 'extra-10-deeplabcut-tf-cu12') or (extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu12') or (extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-latest') or (extra == 'extra-10-deeplabcut-tf-cu12' and extra == 'extra-10-deeplabcut-tf-latest')" }, @@ -7241,11 +7241,7 @@ resolution-markers = [ "python_full_version == '3.12.*' and platform_machine == 'aarch64' and sys_platform == 'linux'", "(python_full_version >= '3.13' and platform_machine != 'aarch64' and platform_machine != 'x86_64' and sys_platform == 'linux') or (python_full_version >= '3.13' and sys_platform != 'darwin' and sys_platform != 'linux')", "(python_full_version == '3.12.*' and platform_machine != 'aarch64' and platform_machine != 'x86_64' and sys_platform == 'linux') or (python_full_version == '3.12.*' and sys_platform != 'darwin' and sys_platform != 'linux')", - "python_full_version == '3.11.*' and sys_platform == 'darwin'", - "python_full_version == '3.11.*' and platform_machine == 'aarch64' and sys_platform == 'linux'", "(python_full_version == '3.11.*' and platform_machine != 'aarch64' and platform_machine != 'x86_64' and sys_platform == 'linux') or (python_full_version == '3.11.*' and sys_platform != 'darwin' and sys_platform != 'linux')", - "python_full_version < '3.11' and sys_platform == 'darwin'", - "python_full_version < '3.11' and platform_machine == 'aarch64' and sys_platform == 'linux'", "(python_full_version < '3.11' and platform_machine != 'aarch64' and platform_machine != 'x86_64' and sys_platform == 'linux') or (python_full_version < '3.11' and sys_platform != 'darwin' and sys_platform != 'linux')", ] wheels = [ @@ -7264,6 +7260,8 @@ name = "tensorflow-io-gcs-filesystem" version = "0.37.1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ + "python_full_version >= '3.13' and platform_machine == 'x86_64' and sys_platform == 'linux'", + "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'linux'", "python_full_version == '3.11.*' and platform_machine == 'x86_64' and sys_platform == 'linux'", "python_full_version < '3.11' and platform_machine == 'x86_64' and sys_platform == 'linux'", "python_full_version == '3.11.*' and sys_platform == 'darwin'", @@ -7272,8 +7270,6 @@ resolution-markers = [ "python_full_version < '3.11' and sys_platform == 'darwin'", "python_full_version < '3.11' and platform_machine == 'aarch64' and sys_platform == 'linux'", "(python_full_version < '3.11' and platform_machine != 'aarch64' and platform_machine != 'x86_64' and sys_platform == 'linux') or (python_full_version < '3.11' and sys_platform != 'darwin' and sys_platform != 'linux')", - "python_full_version >= '3.13' and platform_machine == 'x86_64' and sys_platform == 'linux'", - "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'linux'", ] wheels = [ { url = "https://files.pythonhosted.org/packages/e9/a3/12d7e7326a707919b321e2d6e4c88eb61596457940fd2b8ff3e9b7fac8a7/tensorflow_io_gcs_filesystem-0.37.1-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:249c12b830165841411ba71e08215d0e94277a49c551e6dd5d72aab54fe5491b", size = 2470224, upload-time = "2024-07-01T23:44:15.341Z" }, @@ -7295,8 +7291,8 @@ name = "tensorflow-metal" version = "1.2.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "six", marker = "(platform_machine != 'x86_64' and sys_platform == 'linux' and extra == 'extra-10-deeplabcut-tf-cu11') or (platform_machine != 'x86_64' and sys_platform == 'linux' and extra == 'extra-10-deeplabcut-tf-cu12') or (platform_machine != 'x86_64' and sys_platform == 'linux' and extra == 'extra-10-deeplabcut-tf-latest') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-10-deeplabcut-tf-cu11') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-10-deeplabcut-tf-cu12') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-10-deeplabcut-tf-latest') or (sys_platform == 'darwin' and extra == 'extra-10-deeplabcut-apple-mchips') or (sys_platform == 'darwin' and extra == 'extra-10-deeplabcut-tf') or (sys_platform == 'darwin' and extra == 'extra-10-deeplabcut-tf-cu11') or (sys_platform == 'darwin' and extra == 'extra-10-deeplabcut-tf-cu12') or (sys_platform == 'darwin' and extra == 'extra-10-deeplabcut-tf-latest') or (sys_platform == 'linux' and extra == 'extra-10-deeplabcut-fmpose3d' and extra == 'extra-10-deeplabcut-tf-cu11') or (sys_platform == 'linux' and extra == 'extra-10-deeplabcut-fmpose3d' and extra == 'extra-10-deeplabcut-tf-cu12') or (sys_platform == 'linux' and extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu11') or (sys_platform == 'linux' and extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu12') or (sys_platform == 'linux' and extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-latest') or (sys_platform == 'linux' and extra == 'extra-10-deeplabcut-tf-cu11' and extra == 'extra-10-deeplabcut-tf-cu12') or (sys_platform == 'linux' and extra == 'extra-10-deeplabcut-tf-cu11' and extra == 'extra-10-deeplabcut-tf-latest') or (sys_platform == 'linux' and extra == 'extra-10-deeplabcut-tf-cu12' and extra == 'extra-10-deeplabcut-tf-latest') or (extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf') or (extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-cu11') or (extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-cu12') or (extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-latest')" }, - { name = "wheel", marker = "(platform_machine != 'x86_64' and sys_platform == 'linux' and extra == 'extra-10-deeplabcut-tf-cu11') or (platform_machine != 'x86_64' and sys_platform == 'linux' and extra == 'extra-10-deeplabcut-tf-cu12') or (platform_machine != 'x86_64' and sys_platform == 'linux' and extra == 'extra-10-deeplabcut-tf-latest') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-10-deeplabcut-tf-cu11') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-10-deeplabcut-tf-cu12') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-10-deeplabcut-tf-latest') or (sys_platform == 'darwin' and extra == 'extra-10-deeplabcut-apple-mchips') or (sys_platform == 'darwin' and extra == 'extra-10-deeplabcut-tf') or (sys_platform == 'darwin' and extra == 'extra-10-deeplabcut-tf-cu11') or (sys_platform == 'darwin' and extra == 'extra-10-deeplabcut-tf-cu12') or (sys_platform == 'darwin' and extra == 'extra-10-deeplabcut-tf-latest') or (sys_platform == 'linux' and extra == 'extra-10-deeplabcut-fmpose3d' and extra == 'extra-10-deeplabcut-tf-cu11') or (sys_platform == 'linux' and extra == 'extra-10-deeplabcut-fmpose3d' and extra == 'extra-10-deeplabcut-tf-cu12') or (sys_platform == 'linux' and extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu11') or (sys_platform == 'linux' and extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-cu12') or (sys_platform == 'linux' and extra == 'extra-10-deeplabcut-tf' and extra == 'extra-10-deeplabcut-tf-latest') or (sys_platform == 'linux' and extra == 'extra-10-deeplabcut-tf-cu11' and extra == 'extra-10-deeplabcut-tf-cu12') or (sys_platform == 'linux' and extra == 'extra-10-deeplabcut-tf-cu11' and extra == 'extra-10-deeplabcut-tf-latest') or (sys_platform == 'linux' and extra == 'extra-10-deeplabcut-tf-cu12' and extra == 'extra-10-deeplabcut-tf-latest') or (extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf') or (extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-cu11') or (extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-cu12') or (extra == 'extra-10-deeplabcut-apple-mchips' and extra == 'extra-10-deeplabcut-tf-latest')" }, + { name = "six", marker = "sys_platform == 'darwin'" }, + { name = "wheel", marker = "sys_platform == 'darwin'" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/39/da/463240cc8ff13a57119db62a676e2edca86fb93905a13527872dadf1926e/tensorflow_metal-1.2.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:bc735e36c97874e41f77ec2e7421ff745d2ec36ee641141c8091e4cc2dbcc819", size = 1357400, upload-time = "2025-01-31T00:52:54.577Z" }, @@ -7388,14 +7384,14 @@ version = "2.18.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.13' and platform_machine == 'x86_64' and sys_platform == 'linux'", - "python_full_version >= '3.13' and sys_platform == 'darwin'", - "python_full_version >= '3.13' and platform_machine == 'aarch64' and sys_platform == 'linux'", - "(python_full_version >= '3.13' and platform_machine != 'aarch64' and platform_machine != 'x86_64' and sys_platform == 'linux') or (python_full_version >= '3.13' and sys_platform != 'darwin' and sys_platform != 'linux')", "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'linux'", "python_full_version == '3.11.*' and platform_machine == 'x86_64' and sys_platform == 'linux'", "python_full_version < '3.11' and platform_machine == 'x86_64' and sys_platform == 'linux'", + "python_full_version >= '3.13' and sys_platform == 'darwin'", "python_full_version == '3.12.*' and sys_platform == 'darwin'", + "python_full_version >= '3.13' and platform_machine == 'aarch64' and sys_platform == 'linux'", "python_full_version == '3.12.*' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "(python_full_version >= '3.13' and platform_machine != 'aarch64' and platform_machine != 'x86_64' and sys_platform == 'linux') or (python_full_version >= '3.13' and sys_platform != 'darwin' and sys_platform != 'linux')", "(python_full_version == '3.12.*' and platform_machine != 'aarch64' and platform_machine != 'x86_64' and sys_platform == 'linux') or (python_full_version == '3.12.*' and sys_platform != 'darwin' and sys_platform != 'linux')", "python_full_version == '3.11.*' and sys_platform == 'darwin'", "python_full_version == '3.11.*' and platform_machine == 'aarch64' and sys_platform == 'linux'", From 792d35d1d38234d6095fc3d66a0b08d42690290c Mon Sep 17 00:00:00 2001 From: Jaap de Ruyter Date: Wed, 13 May 2026 16:57:46 +0200 Subject: [PATCH 20/24] guarantee unchanged behavior for deprecated `get_list_of_videos` and `list_videos_in_folder` --- deeplabcut/pose_estimation_pytorch/apis/utils.py | 8 ++++---- deeplabcut/utils/auxiliaryfunctions.py | 8 +++++--- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/deeplabcut/pose_estimation_pytorch/apis/utils.py b/deeplabcut/pose_estimation_pytorch/apis/utils.py index fc429d552..879e449e3 100644 --- a/deeplabcut/pose_estimation_pytorch/apis/utils.py +++ b/deeplabcut/pose_estimation_pytorch/apis/utils.py @@ -11,7 +11,7 @@ from __future__ import annotations import logging -from collections.abc import Callable +from collections.abc import Callable, Sequence from pathlib import Path import albumentations as A @@ -65,7 +65,7 @@ from deeplabcut.pose_estimation_pytorch.task import Task from deeplabcut.pose_estimation_pytorch.utils import resolve_device from deeplabcut.utils import auxiliaryfunctions -from deeplabcut.utils.auxfun_videos import collect_video_paths +from deeplabcut.utils.auxfun_videos import SUPPORTED_VIDEOS, collect_video_paths from deeplabcut.utils.deprecation import deprecated @@ -297,12 +297,12 @@ def get_scorer_name( @deprecated(replacement="deeplabcut.collect_video_paths", since="3.0.0") def list_videos_in_folder( data_path: str | Path | list[str | Path], - video_type: str | None = None, + video_type: str | Sequence[str] | None = SUPPORTED_VIDEOS, shuffle: bool = False, ) -> list[Path]: return collect_video_paths( data_path=data_path, - extensions=video_type or None, + extensions=video_type, shuffle=shuffle, ) diff --git a/deeplabcut/utils/auxiliaryfunctions.py b/deeplabcut/utils/auxiliaryfunctions.py index 8a8f2368d..73b0c97cd 100644 --- a/deeplabcut/utils/auxiliaryfunctions.py +++ b/deeplabcut/utils/auxiliaryfunctions.py @@ -23,6 +23,7 @@ import os import pickle import warnings +from collections.abc import Sequence from pathlib import Path import pandas as pd @@ -33,7 +34,7 @@ from deeplabcut.core.engine import Engine from deeplabcut.core.trackingutils import TRACK_METHODS from deeplabcut.utils import auxfun_multianimal -from deeplabcut.utils.auxfun_videos import collect_video_paths +from deeplabcut.utils.auxfun_videos import SUPPORTED_VIDEOS, collect_video_paths from deeplabcut.utils.deprecation import deprecated @@ -392,14 +393,15 @@ def write_pickle(filename, data): @deprecated(replacement="deeplabcut.collect_video_paths", since="3.0.0") def get_list_of_videos( videos: list[str] | str, - videotype: list[str] | str = "", + videotype: str | Sequence[str] | None = SUPPORTED_VIDEOS, in_random_order: bool = True, ) -> list[str]: - return collect_video_paths( + video_paths = collect_video_paths( data_path=videos, extensions=videotype, shuffle=in_random_order, ) + return [str(path) for path in video_paths] def save_data(PredicteData, metadata, dataname, pdindex, imagenames, save_as_csv): From fe3815211d7665cead336bf5471569f14f154c93 Mon Sep 17 00:00:00 2001 From: Jaap de Ruyter Date: Wed, 13 May 2026 17:07:56 +0200 Subject: [PATCH 21/24] add warning if still unsupported extensions encountered --- deeplabcut/utils/auxfun_videos.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/deeplabcut/utils/auxfun_videos.py b/deeplabcut/utils/auxfun_videos.py index 8b4ed878a..14d024c70 100644 --- a/deeplabcut/utils/auxfun_videos.py +++ b/deeplabcut/utils/auxfun_videos.py @@ -731,4 +731,10 @@ def _coerce_extensions(extensions: str | Sequence[str] | None) -> set[str] | Non random.shuffle(unique_videos) else: unique_videos.sort() + + if any([fn.suffix not in SUPPORTED_VIDEOS for fn in unique_videos]): + warnings.warn( + f"Some videos have unsupported extensions: {unique_videos} \nSupported extensions are: {SUPPORTED_VIDEOS}", + stacklevel=2, + ) return unique_videos From 69981f11ad239c00852555c38e0d1959d2019344 Mon Sep 17 00:00:00 2001 From: Jaap de Ruyter Date: Fri, 15 May 2026 09:33:55 +0200 Subject: [PATCH 22/24] Consequently adjust `videotype` signatures of collect_video_path callers --- deeplabcut/compat.py | 44 ++++++++++++------- .../apis/tracking_dataset.py | 16 ++++--- .../pose_estimation_pytorch/apis/tracklets.py | 8 ++-- .../pose_estimation_pytorch/apis/videos.py | 15 ++++--- .../modelzoo/api/superanimal_inference.py | 5 ++- .../predict_videos.py | 39 +++++++++------- deeplabcut/pose_tracking_pytorch/apis.py | 17 ++++--- .../train_dlctransreid.py | 5 ++- .../post_processing/analyze_skeleton.py | 18 +++++--- deeplabcut/post_processing/filtering.py | 14 +++++- .../refine_training_dataset/outlier_frames.py | 18 +++++--- deeplabcut/refine_training_dataset/stitch.py | 17 ++++--- deeplabcut/utils/make_labeled_video.py | 35 +++++++++------ deeplabcut/utils/plotting.py | 18 +++++--- 14 files changed, 171 insertions(+), 98 deletions(-) diff --git a/deeplabcut/compat.py b/deeplabcut/compat.py index 7b8cbfeda..ab0920a11 100644 --- a/deeplabcut/compat.py +++ b/deeplabcut/compat.py @@ -12,7 +12,7 @@ from __future__ import annotations -from collections.abc import Iterable +from collections.abc import Iterable, Sequence from pathlib import Path import numpy as np @@ -664,7 +664,7 @@ def return_evaluate_network_data( def analyze_videos( config: str, videos: list[str], - videotype: str = "", + videotype: str | Sequence[str] | None = None, shuffle: int = 1, trainingsetindex: int = 0, gputouse: str | None = None, @@ -710,10 +710,14 @@ 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, optional, default="" - Checks for the extension of the video in case the input to the video is a - directory. Only videos with this extension are analyzed. If left unspecified, - videos with common extensions ('avi', 'mp4', 'mov', 'mpeg', 'mkv') are kept. + videotype : 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 + scanned for files with a recognized video extension. + - ``str`` or ``Sequence[str]`` (e.g. ``"mp4"`` or ``["mp4", "avi"]``): + both file paths and directory contents are filtered by the given + extension(s). shuffle: int, optional, default=1 An integer specifying the shuffle index of the training dataset used for @@ -986,7 +990,7 @@ def create_tracking_dataset( config: str, videos: list[str], track_method: str, - videotype: str = "", + videotype: str | Sequence[str] | None = None, shuffle: int = 1, trainingsetindex: int = 0, gputouse: int | None = None, @@ -1015,10 +1019,14 @@ def create_tracking_dataset( Specifies the tracker used to generate the pose estimation data. Must be either 'box', 'skeleton', or 'ellipse'. - videotype: str, optional, default="" - Checks for the extension of the video in case the input to the video is a - directory. Only videos with this extension are analyzed. If left unspecified, - videos with common extensions ('avi', 'mp4', 'mov', 'mpeg', 'mkv') are kept. + videotype : 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 + scanned for files with a recognized video extension. + - ``str`` or ``Sequence[str]`` (e.g. ``"mp4"`` or ``["mp4", "avi"]``): + both file paths and directory contents are filtered by the given + extension(s). shuffle: int, optional, default=1 An integer specifying the shuffle index of the training dataset used for @@ -1408,7 +1416,7 @@ def analyze_time_lapse_frames( def convert_detections2tracklets( config: str, videos: list[str], - videotype: str = "", + videotype: str | Sequence[str] | None = None, shuffle: int = 1, trainingsetindex: int = 0, overwrite: bool = False, @@ -1435,10 +1443,14 @@ 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: string, optional - Checks for the extension of the video in case the input to the video is a directory.\n - Only videos with this extension are analyzed. - If left unspecified, videos with common extensions ('avi', 'mp4', 'mov', 'mpeg', 'mkv') are kept. + videotype : 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 + scanned for files with a recognized video extension. + - ``str`` or ``Sequence[str]`` (e.g. ``"mp4"`` or ``["mp4", "avi"]``): + both file paths and directory contents are filtered by the given + extension(s). shuffle: int, optional An integer specifying the shuffle index of the training dataset used for training the network. T diff --git a/deeplabcut/pose_estimation_pytorch/apis/tracking_dataset.py b/deeplabcut/pose_estimation_pytorch/apis/tracking_dataset.py index 97a1b1702..b031e8618 100644 --- a/deeplabcut/pose_estimation_pytorch/apis/tracking_dataset.py +++ b/deeplabcut/pose_estimation_pytorch/apis/tracking_dataset.py @@ -10,6 +10,7 @@ # """Code to create tracking datasets for ReID model training.""" +from collections.abc import Sequence from pathlib import Path from tqdm import tqdm @@ -128,7 +129,7 @@ def create_tracking_dataset( config: str, videos: list[str] | list[Path], track_method: str, - videotype: str = "", + videotype: str | Sequence[str] | None = None, shuffle: int = 1, trainingsetindex: int = 0, destfolder: str | None = None, @@ -148,10 +149,13 @@ 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: Checks for the extension of the video in case the input to the video - is a directory. Only videos with this extension are analyzed. If left - unspecified, keeps videos with extensions ('avi', 'mp4', 'mov', 'mpeg', - 'mkv'). + videotype: 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. + - ``str`` or ``Sequence[str]`` (e.g. ``"mp4"`` or ``["mp4", "avi"]``): + both file paths and directory contents are filtered by the given + extension(s). shuffle: An integer specifying the shuffle index of the training dataset used for training the network. trainingsetindex: Integer specifying which TrainingsetFraction to use. @@ -241,7 +245,7 @@ def create_tracking_dataset( modelprefix=modelprefix, ) - videos = collect_video_paths(videos, videotype) + videos = collect_video_paths(videos, extensions=videotype) 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 553a96aad..121a11e1e 100644 --- a/deeplabcut/pose_estimation_pytorch/apis/tracklets.py +++ b/deeplabcut/pose_estimation_pytorch/apis/tracklets.py @@ -11,6 +11,7 @@ import os import pickle import warnings +from collections.abc import Sequence from pathlib import Path import numpy as np @@ -35,7 +36,7 @@ def convert_detections2tracklets( config: str, videos: str | list[str], - videotype: str | None = None, + videotype: str | Sequence[str] | None = None, shuffle: int = 1, trainingsetindex: int = 0, overwrite: bool = False, @@ -124,9 +125,10 @@ def convert_detections2tracklets( modelprefix=modelprefix, ) - videos = collect_video_paths(videos, videotype) + paths_input = videos + videos = collect_video_paths(videos, extensions=videotype) if len(videos) == 0: - print(f"No videos were found in {videos}") + print(f"No videos were found in {paths_input}") return for video in videos: diff --git a/deeplabcut/pose_estimation_pytorch/apis/videos.py b/deeplabcut/pose_estimation_pytorch/apis/videos.py index 7f56b5876..c41f7c775 100644 --- a/deeplabcut/pose_estimation_pytorch/apis/videos.py +++ b/deeplabcut/pose_estimation_pytorch/apis/videos.py @@ -14,6 +14,7 @@ import logging import pickle import time +from collections.abc import Sequence from pathlib import Path from typing import Any @@ -243,7 +244,7 @@ def video_inference( def analyze_videos( config: str, videos: str | list[str], - videotype: str | None = None, + videotype: str | Sequence[str] | None = None, shuffle: int = 1, trainingsetindex: int = 0, save_as_csv: bool = False, @@ -283,9 +284,13 @@ 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: checks for the extension of the video in case the input to the video - is a directory. Only videos with this extension are analyzed. If left - unspecified, keeps videos with extensions ('avi', 'mp4', 'mov', 'mpeg', 'mkv'). + videotype: 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. + - ``str`` or ``Sequence[str]`` (e.g. ``"mp4"`` or ``["mp4", "avi"]``): + both file paths and directory contents are filtered by the given + extension(s). shuffle: An integer specifying the shuffle index of the training dataset used for training the network. trainingsetindex: Integer specifying which TrainingsetFraction to use. @@ -541,7 +546,7 @@ def analyze_videos( print(f"Using scorer: {dlc_scorer}") # Reading video and init variables - videos = collect_video_paths(videos, videotype, shuffle=in_random_order) + videos = collect_video_paths(videos, extensions=videotype, shuffle=in_random_order) h5_files_created = False # Track if any .h5 files were created for video in videos: diff --git a/deeplabcut/pose_estimation_tensorflow/modelzoo/api/superanimal_inference.py b/deeplabcut/pose_estimation_tensorflow/modelzoo/api/superanimal_inference.py index 705653807..9d0386490 100644 --- a/deeplabcut/pose_estimation_tensorflow/modelzoo/api/superanimal_inference.py +++ b/deeplabcut/pose_estimation_tensorflow/modelzoo/api/superanimal_inference.py @@ -14,6 +14,7 @@ import pickle import time import warnings +from collections.abc import Sequence from pathlib import Path import imgaug.augmenters as iaa @@ -247,7 +248,7 @@ def video_inference( project_name, model_name, scale_list=None, - videotype="avi", + videotype: str | Sequence[str] | None = None, destfolder=None, batchsize=1, robust_nframes=False, @@ -306,7 +307,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, videotype) + videos = collect_video_paths(videos, extensions=videotype) datafiles = [] for video in videos: diff --git a/deeplabcut/pose_estimation_tensorflow/predict_videos.py b/deeplabcut/pose_estimation_tensorflow/predict_videos.py index 39607aa8a..d40fa68d1 100644 --- a/deeplabcut/pose_estimation_tensorflow/predict_videos.py +++ b/deeplabcut/pose_estimation_tensorflow/predict_videos.py @@ -21,6 +21,7 @@ import re import time import warnings +from collections.abc import Sequence from pathlib import Path import cv2 @@ -51,7 +52,7 @@ def create_tracking_dataset( config, videos, track_method, - videotype="", + videotype: str | Sequence[str] | None = None, shuffle=1, trainingsetindex=0, gputouse=None, @@ -199,7 +200,7 @@ def create_tracking_dataset( ################################################## # Looping over videos ################################################## - Videos = collect_video_paths(videos, videotype) + Videos = collect_video_paths(videos, extensions=videotype) if len(Videos) > 0: if "multi-animal" in dlc_cfg["dataset_type"]: for video in Videos: @@ -255,7 +256,7 @@ def create_tracking_dataset( def analyze_videos( config, videos, - videotype="", + videotype: str | Sequence[str] | None = None, shuffle=1, trainingsetindex=0, gputouse=None, @@ -299,10 +300,14 @@ 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, optional, default="" - Checks for the extension of the video in case the input to the video is a - directory. Only videos with this extension are analyzed. If left unspecified, - videos with common extensions ('avi', 'mp4', 'mov', 'mpeg', 'mkv') are kept. + videotype : 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 + scanned for files with a recognized video extension. + - ``str`` or ``Sequence[str]`` (e.g. ``"mp4"`` or ``["mp4", "avi"]``): + both file paths and directory contents are filtered by the given + extension(s). shuffle: int, optional, default=1 An integer specifying the shuffle index of the training dataset used for @@ -595,7 +600,7 @@ def analyze_videos( ################################################## # Looping over videos ################################################## - Videos = collect_video_paths(videos, videotype, in_random_order) + Videos = collect_video_paths(videos, extensions=videotype, shuffle=in_random_order) if len(Videos) > 0: if "multi-animal" in dlc_cfg["dataset_type"]: from deeplabcut.pose_estimation_tensorflow.predict_multianimal import ( @@ -681,7 +686,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 'video_type'.") + print("No video(s) were found. Please check your paths and/or 'videotype'.") return DLCscorer @@ -1479,7 +1484,7 @@ def _convert_detections_to_tracklets( def convert_detections2tracklets( config, videos, - videotype="", + videotype: str | Sequence[str] | None = None, shuffle=1, trainingsetindex=0, overwrite=False, @@ -1505,10 +1510,14 @@ 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: string, optional - Checks for the extension of the video in case the input to the video is a directory.\n - Only videos with this extension are analyzed. - If left unspecified, videos with common extensions ('avi', 'mp4', 'mov', 'mpeg', 'mkv') are kept. + videotype : 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 + scanned for files with a recognized video extension. + - ``str`` or ``Sequence[str]`` (e.g. ``"mp4"`` or ``["mp4", "avi"]``): + both file paths and directory contents are filtered by the given + extension(s). shuffle: int, optional An integer specifying the shuffle index of the training dataset used for training the network. @@ -1652,7 +1661,7 @@ def convert_detections2tracklets( ################################################## # Looping over videos ################################################## - Videos = collect_video_paths(videos, videotype) + Videos = collect_video_paths(videos, extensions=videotype) 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 627eaf7d1..166e0509c 100644 --- a/deeplabcut/pose_tracking_pytorch/apis.py +++ b/deeplabcut/pose_tracking_pytorch/apis.py @@ -9,11 +9,13 @@ # Licensed under GNU Lesser General Public License v3.0 # +from collections.abc import Sequence + def transformer_reID( config: str, videos: list[str], - videotype: str = "", + videotype: str | Sequence[str] | None = None, shuffle: int = 1, trainingsetindex: int = 0, track_method: str = "ellipse", @@ -44,11 +46,14 @@ 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: string, optional - Checks for the extension of the video in case the input to the video is a - directory. Only videos with this extension are analyzed. - If left unspecified, videos with common extensions ('avi', 'mp4', 'mov', 'mpeg', - 'mkv') are kept. + videotype : 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 + scanned for files with a recognized video extension. + - ``str`` or ``Sequence[str]`` (e.g. ``"mp4"`` or ``["mp4", "avi"]``): + both file paths and directory contents are filtered by the given + extension(s). shuffle : int, optional which shuffle to use diff --git a/deeplabcut/pose_tracking_pytorch/train_dlctransreid.py b/deeplabcut/pose_tracking_pytorch/train_dlctransreid.py index 4d5124766..b1145dc93 100644 --- a/deeplabcut/pose_tracking_pytorch/train_dlctransreid.py +++ b/deeplabcut/pose_tracking_pytorch/train_dlctransreid.py @@ -17,6 +17,7 @@ raise ModuleNotFoundError("Unsupervised identity learning requires PyTorch. Please run `pip install torch`.") from e import glob import os +from collections.abc import Sequence from pathlib import Path import numpy as np @@ -70,7 +71,7 @@ def train_tracking_transformer( path_config_file, dlcscorer, videos, - videotype="", + videotype: str | Sequence[str] | None = None, train_frac=0.8, modelprefix="", train_epochs=100, @@ -79,7 +80,7 @@ def train_tracking_transformer( destfolder=None, ): npy_list = [] - videos = collect_video_paths(videos, videotype) + videos = collect_video_paths(videos, extensions=videotype) 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 54cd9a30a..14d5ab50d 100644 --- a/deeplabcut/post_processing/analyze_skeleton.py +++ b/deeplabcut/post_processing/analyze_skeleton.py @@ -15,6 +15,7 @@ import argparse import os +from collections.abc import Sequence from math import atan2, degrees from pathlib import Path @@ -168,7 +169,7 @@ def analyzebone(bp1, bp2): def analyzeskeleton( config, videos, - videotype="", + videotype: str | Sequence[str] | None = None, shuffle=1, trainingsetindex=0, filtered=False, @@ -192,11 +193,14 @@ 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, optional, default="" - Checks for the extension of the video in case the input to the video is a - directory. Only videos with this extension are analyzed. - If left unspecified, videos with common extensions - ('avi', 'mp4', 'mov', 'mpeg', 'mkv') are kept. + videotype : 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 + scanned for files with a recognized video extension. + - ``str`` or ``Sequence[str]`` (e.g. ``"mp4"`` or ``["mp4", "avi"]``): + both file paths and directory contents are filtered by the given + extension(s). shuffle : int, optional, default=1 The shuffle index of training dataset. The extracted frames will be stored in @@ -262,7 +266,7 @@ def analyzeskeleton( **kwargs, ) - Videos = collect_video_paths(videos, videotype) + Videos = collect_video_paths(videos, extensions=videotype) 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 cc527754c..f0ed95595 100644 --- a/deeplabcut/post_processing/filtering.py +++ b/deeplabcut/post_processing/filtering.py @@ -10,6 +10,7 @@ # import argparse +from collections.abc import Sequence from pathlib import Path import numpy as np @@ -66,7 +67,7 @@ def columnwise_spline_interp(data, max_gap=0): def filterpredictions( config, video, - videotype="", + videotype: str | Sequence[str] | None = None, shuffle=1, trainingsetindex=0, filtertype="median", @@ -96,6 +97,15 @@ 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 + 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. + - ``str`` or ``Sequence[str]`` (e.g. ``"mp4"`` or ``["mp4", "avi"]``): + both file paths and directory contents are filtered by the given + extension(s). + shuffle : int, optional, default=1 The shuffle index of training dataset. The extracted frames will be stored in the labeled-dataset for the corresponding shuffle of training dataset. @@ -213,7 +223,7 @@ def filterpredictions( modelprefix=modelprefix, **kwargs, ) - Videos = collect_video_paths(video, videotype) + Videos = collect_video_paths(video, extensions=videotype) video_to_filtered_df = {} diff --git a/deeplabcut/refine_training_dataset/outlier_frames.py b/deeplabcut/refine_training_dataset/outlier_frames.py index a360ae9bf..fbaa7d663 100644 --- a/deeplabcut/refine_training_dataset/outlier_frames.py +++ b/deeplabcut/refine_training_dataset/outlier_frames.py @@ -14,6 +14,7 @@ import os import pickle import re +from collections.abc import Sequence from pathlib import Path import matplotlib.pyplot as plt @@ -199,7 +200,7 @@ def _read_video_specific_cropping_margins(config: str | Path | dict, video_path: def extract_outlier_frames( config, videos, - videotype="", + videotype: str | Sequence[str] | None = None, shuffle=1, trainingsetindex=0, outlieralgorithm="jump", @@ -239,11 +240,14 @@ 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, optional, default="" - Checks for the extension of the video in case the input to the video is a - directory. Only videos with this extension are analyzed. - If left unspecified, videos with common extensions - ('avi', 'mp4', 'mov', 'mpeg', 'mkv') are kept. + videotype : 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 + scanned for files with a recognized video extension. + - ``str`` or ``Sequence[str]`` (e.g. ``"mp4"`` or ``["mp4", "avi"]``): + both file paths and directory contents are filtered by the given + extension(s). shuffle : int, optional, default=1 The shuffle index of training dataset. The extracted frames will be stored in @@ -402,7 +406,7 @@ def extract_outlier_frames( **kwargs, ) - Videos = collect_video_paths(videos, videotype) + Videos = collect_video_paths(videos, extensions=videotype) 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 08d45d95a..049c72d2b 100644 --- a/deeplabcut/refine_training_dataset/stitch.py +++ b/deeplabcut/refine_training_dataset/stitch.py @@ -14,6 +14,7 @@ import shelve import warnings from collections import defaultdict +from collections.abc import Sequence from functools import partial from itertools import combinations, cycle from pathlib import Path @@ -960,7 +961,7 @@ def reconstruct_path(self, source): def stitch_tracklets( config_path, videos, - videotype="", + videotype: str | Sequence[str] | None = None, shuffle=1, trainingsetindex=0, n_tracks=None, @@ -990,10 +991,14 @@ 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: string, optional - Checks for the extension of the video in case the input to the video is a directory.\n Only videos with this - extension are analyzed. - If left unspecified, videos with common extensions ('avi', 'mp4', 'mov', 'mpeg', 'mkv') are kept. + videotype : 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 + scanned for files with a recognized video extension. + - ``str`` or ``Sequence[str]`` (e.g. ``"mp4"`` or ``["mp4", "avi"]``): + both file paths and directory contents are filtered by the given + extension(s). shuffle: int, optional An integer specifying the shuffle index of the training dataset used for training the network. The default is 1. @@ -1076,7 +1081,7 @@ def stitch_tracklets( ------- A TrackletStitcher object """ - vids = collect_video_paths(videos, videotype) + vids = collect_video_paths(videos, extensions=videotype) if not vids: print("No video(s) found. Please check your path!") return diff --git a/deeplabcut/utils/make_labeled_video.py b/deeplabcut/utils/make_labeled_video.py index b35d4de41..c83778ba0 100644 --- a/deeplabcut/utils/make_labeled_video.py +++ b/deeplabcut/utils/make_labeled_video.py @@ -30,7 +30,7 @@ # Dependencies #################################################### import os.path -from collections.abc import Callable, Iterable +from collections.abc import Callable, Iterable, Sequence from functools import partial from multiprocessing import Pool, get_start_method from pathlib import Path @@ -393,7 +393,7 @@ def CreateVideoSlow( def create_labeled_video( config: str, videos: list[str], - videotype: str = "", + videotype: str | Sequence[str] | None = None, shuffle: int = 1, trainingsetindex: int = 0, filtered: bool = False, @@ -441,11 +441,14 @@ 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, optional, default="" - Checks for the extension of the video in case the input to the video is a - directory. Only videos with this extension are analyzed. - If left unspecified, videos with common extensions - ('avi', 'mp4', 'mov', 'mpeg', 'mkv') are kept. + videotype : 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 + scanned for files with a recognized video extension. + - ``str`` or ``Sequence[str]`` (e.g. ``"mp4"`` or ``["mp4", "avi"]``): + both file paths and directory contents are filtered by the given + extension(s). shuffle : int, optional, default=1 Number of shuffles of training dataset. @@ -733,7 +736,7 @@ def create_labeled_video( skeleton_color = None start_path = os.getcwd() - Videos = collect_video_paths(videos, videotype) + Videos = collect_video_paths(videos, extensions=videotype) if not Videos: return [] @@ -1147,7 +1150,7 @@ def create_video_with_keypoints_only( def create_video_with_all_detections( config, videos, - videotype="", + videotype: str | Sequence[str] | None = None, shuffle=1, trainingsetindex=0, displayedbodyparts="all", @@ -1169,10 +1172,14 @@ 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: string, optional - Checks for the extension of the video in case the input to the video is a directory.\n - Only videos with this extension are analyzed. - If left unspecified, videos with common extensions ('avi', 'mp4', 'mov', 'mpeg', 'mkv') are kept. + videotype : 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 + scanned for files with a recognized video extension. + - ``str`` or ``Sequence[str]`` (e.g. ``"mp4"`` or ``["mp4", "avi"]``): + both file paths and directory contents are filtered by the given + extension(s). shuffle : int, optional Number of shuffles of training dataset. Default is set to 1. @@ -1225,7 +1232,7 @@ def create_video_with_all_detections( **kwargs, ) - videos = collect_video_paths(videos, videotype) + videos = collect_video_paths(videos, extensions=videotype) if not videos: return diff --git a/deeplabcut/utils/plotting.py b/deeplabcut/utils/plotting.py index 58983ed98..51a8b575e 100644 --- a/deeplabcut/utils/plotting.py +++ b/deeplabcut/utils/plotting.py @@ -28,6 +28,7 @@ #################################################### import os.path import pickle +from collections.abc import Sequence from pathlib import Path import matplotlib.pyplot as plt @@ -171,7 +172,7 @@ def PlottingResults( def plot_trajectories( config, videos, - videotype="", + videotype: str | Sequence[str] | None = None, shuffle=1, trainingsetindex=0, filtered=False, @@ -198,11 +199,14 @@ 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, optional, default="" - Checks for the extension of the video in case the input to the video is a - directory. Only videos with this extension are analyzed. - If left unspecified, videos with common extensions - ('avi', 'mp4', 'mov', 'mpeg', 'mkv') are kept. + videotype : 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 + scanned for files with a recognized video extension. + - ``str`` or ``Sequence[str]`` (e.g. ``"mp4"`` or ``["mp4", "avi"]``): + both file paths and directory contents are filtered by the given + extension(s). shuffle: int, optional, default=1 Integer specifying the shuffle index of the training dataset. @@ -289,7 +293,7 @@ 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, videotype) + Videos = collect_video_paths(videos, extensions=videotype) if not len(Videos): print("No videos found. Make sure you passed a list of videos and that *videotype* is right.") return From 096973ebdbac1f8b4df3a710ceb97df8568e6ada Mon Sep 17 00:00:00 2001 From: Jaap de Ruyter Date: Fri, 15 May 2026 10:16:17 +0200 Subject: [PATCH 23/24] refactor collect_video_paths: add warnings, raise for empty sequence. --- deeplabcut/utils/auxfun_videos.py | 74 +++++++++++++++++++------------ 1 file changed, 45 insertions(+), 29 deletions(-) diff --git a/deeplabcut/utils/auxfun_videos.py b/deeplabcut/utils/auxfun_videos.py index 14d024c70..a0089226b 100644 --- a/deeplabcut/utils/auxfun_videos.py +++ b/deeplabcut/utils/auxfun_videos.py @@ -33,6 +33,8 @@ from skimage import io from skimage.util import img_as_ubyte +from deeplabcut.utils.deprecation import DLCDeprecationWarning + # more videos are in principle covered, as OpenCV is used and allows many formats. SUPPORTED_VIDEOS = "avi", "mp4", "mov", "mpeg", "mpg", "mpv", "mkv", "flv", "qt", "yuv" DEFAULT_EXCLUDE_PATTERNS: tuple[str, ...] = "*_labeled.*", "*_full.*" @@ -656,50 +658,64 @@ def collect_video_paths( exclude_patterns: Sequence[str] = DEFAULT_EXCLUDE_PATTERNS, ) -> list[Path]: """ - Collects video paths from a given set of data paths: directories, files or mix of both. Directories are - only scanned single level, non-recursively. + Collects video paths from a given set of data paths: directories, files, or a mix + of both. Directories are scanned one level deep (non-recursively). - Optionally filters paths by extension and excludes patterns. Files and directories are treated differently: - - Files are not filtered by extension by default. Set ``extensions`` if needed, to filter also supplied files. - - Directory contents are filtered by ``SUPPORTED_VIDEOS`` by default. Specify custom ``extensions`` if needed. - - exclude patterns are ALWAYS applied for directory contents and supplied files. Set to `[]` to exclude no patterns. + Files and directories are treated differently with respect to extension filtering: + - File paths are accepted as-is when ``extensions`` is ``None``; only filtered when + ``extensions`` is explicitly set. + - Directory contents are always filtered by extension: by ``SUPPORTED_VIDEOS`` when + ``extensions`` is ``None``, or by the given value(s) otherwise. + - ``exclude_patterns`` are always applied to both files and directory contents. Args: data_path: Path or list of paths to folders containing videos, or individual video files. Can be a mix of directories and files. - extensions: The types of videos to select, by filtering the extension (e.g., ".mp4", ".avi", etc.). - - If set: select all videos with the given extensions. Both for directory contents and supplied files. - - If ``None``, provided files are not filtered, but directory contents are filtered by ``SUPPORTED_VIDEOS``. - - An empty str "" is equivalent to None (for backwards compatibility). - - An empty sequence: select only files without extension. - shuffle: Whether to shuffle the order of videos. If False, videos are returned - in sorted order for deterministic behavior. - exclude_patterns: Patterns to exclude from the collection. Defaults to DEFAULT_EXCLUDE_PATTERNS. - Set to an empty sequence e.g. `[]` to exclude no patterns. + extensions: Controls extension filtering for collected video files. + - ``None`` (default): file paths are accepted without extension filtering; + directories are scanned for files with a recognized video extension. + - ``str`` or ``Sequence[str]`` (e.g. ``"mp4"`` or ``["mp4", "avi"]``): + both file paths and directory contents are filtered to only include files + matching the given extension(s). + - Empty ``str`` ``""`` is treated as ``None`` (deprecated, keep for backwards + compatibility). + shuffle: Whether to shuffle the order of videos. If ``False``, videos are + returned in sorted order for deterministic behavior. + exclude_patterns: Patterns to exclude from the collection. Defaults to + ``DEFAULT_EXCLUDE_PATTERNS``. Set to ``[]`` to disable pattern exclusion. Returns: The paths of videos to analyze. Duplicate paths are removed. Raises: - FileNotFoundError: If any path in data_path does not exist. + FileNotFoundError: If any path in ``data_path`` does not exist. + ValueError: If ``extensions`` is an empty sequence. """ if isinstance(data_path, (str, Path)): data_path = [data_path] def _coerce_extensions(extensions: str | Sequence[str] | None) -> set[str] | None: - """Flexible coercion of extensions to a set of suffixes""" - # NOTE @deruyter92: support legacy API, which mixed strings and iterables. - if isinstance(extensions, (list, tuple, set)): - explicit_suffixes = {f".{e.lstrip('.').lower()}" for e in extensions} - elif isinstance(extensions, str) and not extensions == "": - explicit_suffixes = {f".{extensions.lstrip('.').lower()}"} - else: - explicit_suffixes = None + """Coerce the extensions argument to a set of dot-prefixed suffixes, or None.""" + if extensions is None: + return None + + if extensions in ["", ("",), [""], {""}]: + warnings.warn( + "Passing an empty string for filtering video type extensions is deprecated; pass None instead.", + DLCDeprecationWarning, + stacklevel=3, + ) + return None + + if isinstance(extensions, str): + return {f".{extensions.lstrip('.').lower()}"} + + if not isinstance(extensions, Sequence): + raise TypeError(f"extensions must be a string, a sequence or None, got {type(extensions)}") - # Normalize empty string to None - if explicit_suffixes == {""}: - explicit_suffixes = None - return explicit_suffixes + if len(extensions) == 0: + raise ValueError("Video type extensions filter needs to be an non-empty sequence.") + return {f".{e.lstrip('.').lower()}" for e in extensions} explicit_suffixes = _coerce_extensions(extensions) implicit_suffixes = {f".{ext.lower()}" for ext in SUPPORTED_VIDEOS} @@ -732,7 +748,7 @@ def _coerce_extensions(extensions: str | Sequence[str] | None) -> set[str] | Non else: unique_videos.sort() - if any([fn.suffix not in SUPPORTED_VIDEOS for fn in unique_videos]): + if any(fn.suffix.lower().lstrip(".") not in SUPPORTED_VIDEOS for fn in unique_videos if fn.suffix): warnings.warn( f"Some videos have unsupported extensions: {unique_videos} \nSupported extensions are: {SUPPORTED_VIDEOS}", stacklevel=2, From 0929e7b803019fe25b22067500800aafb10f24ec Mon Sep 17 00:00:00 2001 From: Jaap de Ruyter Date: Fri, 15 May 2026 11:04:06 +0200 Subject: [PATCH 24/24] update tests for collect_video_paths --- tests/utils/test_collect_video_paths.py | 58 ++++++++++++++++++++++--- 1 file changed, 52 insertions(+), 6 deletions(-) diff --git a/tests/utils/test_collect_video_paths.py b/tests/utils/test_collect_video_paths.py index c64ce1c67..251778c14 100644 --- a/tests/utils/test_collect_video_paths.py +++ b/tests/utils/test_collect_video_paths.py @@ -26,6 +26,7 @@ import pytest from deeplabcut.utils.auxfun_videos import SUPPORTED_VIDEOS, collect_video_paths +from deeplabcut.utils.deprecation import DLCDeprecationWarning def _touch(path: Path) -> Path: @@ -64,13 +65,13 @@ def test_accepts_single_path_argument(tmp_path): def test_explicit_video_type_filters_listed_files(tmp_path): - """When ``video_type`` is set, it filters explicitly-supplied files too.""" + """When ``extensions`` is set, it filters explicitly-supplied files too.""" mp4 = _touch(tmp_path / "video.mp4") avi = _touch(tmp_path / "video.avi") result = collect_video_paths([mp4, avi], extensions="mp4") - assert [p.name for p in result] == ["video.mp4"] + assert {p.name for p in result} == {"video.mp4"} def test_explicit_video_type_accepts_leading_dot(tmp_path): @@ -79,7 +80,28 @@ def test_explicit_video_type_accepts_leading_dot(tmp_path): result = collect_video_paths([mp4, avi], extensions=".mp4") - assert [p.name for p in result] == ["video.mp4"] + assert {p.name for p in result} == {"video.mp4"} + + +def test_explicit_video_type_case_insensitive(tmp_path): + """Extension matching must be case-insensitive.""" + mp4 = _touch(tmp_path / "video.mp4") + avi = _touch(tmp_path / "video.avi") + + result = collect_video_paths([mp4, avi], extensions="MP4") + + assert {p.name for p in result} == {"video.mp4"} + + +def test_multiple_extensions_filter_directory(tmp_path): + """A sequence of extensions filters directory contents to only matching files.""" + mp4 = _touch(tmp_path / "video.mp4") + avi = _touch(tmp_path / "video.avi") + _touch(tmp_path / "video.mkv") + + result = collect_video_paths(tmp_path, extensions=["mp4", "avi"]) + + assert {p.name for p in result} == {mp4.name, avi.name} def test_directory_enumeration_filters_by_supported_videos(tmp_path): @@ -102,7 +124,18 @@ def test_directory_enumeration_skips_dlc_artifacts(tmp_path): result = collect_video_paths(tmp_path, extensions=None) - assert [p.name for p in result] == [mp4.name] + assert {p.name for p in result} == {mp4.name} + + +def test_disable_exclude_patterns_includes_dlc_artifacts(tmp_path): + """Setting ``exclude_patterns=[]`` disables all pattern exclusion.""" + mp4 = _touch(tmp_path / "video.mp4") + labeled = _touch(tmp_path / "video_labeled.mp4") + full = _touch(tmp_path / "video_full.mp4") + + result = collect_video_paths(tmp_path, extensions=None, exclude_patterns=[]) + + assert {p.name for p in result} == {mp4.name, labeled.name, full.name} def test_mixed_files_and_directories(tmp_path): @@ -154,8 +187,21 @@ def test_sorted_by_default_when_not_shuffled(tmp_path): b = _touch(tmp_path / "b.mp4") c = _touch(tmp_path / "c.mp4") - # The resolution order in the function is dict-insertion-stable; given a - # sorted input list we expect a sorted output list. result = collect_video_paths([c, a, b], extensions=None, shuffle=False) assert [p.name for p in result] == ["a.mp4", "b.mp4", "c.mp4"] + + +@pytest.mark.parametrize("deprecated_value", ["", [""], ("",), {""}]) +def test_deprecated_empty_extensions_warns(tmp_path, deprecated_value): + """Empty / blank extension values are deprecated and should emit a warning.""" + _touch(tmp_path / "video.mp4") + + with pytest.warns(DLCDeprecationWarning): + collect_video_paths(tmp_path, extensions=deprecated_value) + + +def test_empty_sequence_raises(tmp_path): + """An empty sequence is not a valid filter; callers must pass None instead.""" + with pytest.raises(ValueError): + collect_video_paths(tmp_path, extensions=[])