From d65e1362e852a3127df5337f2eabb7b390c40868 Mon Sep 17 00:00:00 2001 From: Juan Cobos Date: Mon, 12 Jan 2026 10:16:30 +0100 Subject: [PATCH 1/7] refactored list_videos_in_folder --- .../pose_estimation_pytorch/apis/utils.py | 32 ++++++++----------- 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/deeplabcut/pose_estimation_pytorch/apis/utils.py b/deeplabcut/pose_estimation_pytorch/apis/utils.py index 58ed371d13..50fa40b360 100644 --- a/deeplabcut/pose_estimation_pytorch/apis/utils.py +++ b/deeplabcut/pose_estimation_pytorch/apis/utils.py @@ -9,6 +9,7 @@ # Licensed under GNU Lesser General Public License v3.0 # from __future__ import annotations +from testscript_cli import video import logging import random @@ -305,8 +306,8 @@ def get_scorer_name( def list_videos_in_folder( - data_path: str | list[str], - video_type: str | None, + data_path: str | Path | list[str | Path], + video_type: str | None = None, shuffle: bool = False, ) -> list[Path]: """ @@ -321,27 +322,22 @@ def list_videos_in_folder( """ if not isinstance(data_path, list): data_path = [data_path] - video_paths = [Path(p) for p in data_path] + if not video_type: + video_suffixes = {f".{ext}" for ext in auxfun_videos.SUPPORTED_VIDEOS} + else: + video_suffixes = {f".{video_type.lstrip('.')}"} + videos = [] - for path in video_paths: + for path in map(Path, data_path): + assert path.exists(), f"Could not find: {path}. Check access rights." if path.is_dir(): - if not video_type: - video_suffixes = ["." + ext for ext in auxfun_videos.SUPPORTED_VIDEOS] - else: - video_suffixes = [video_type] - - suffixes = [s if s.startswith(".") else "." + s for s in video_suffixes] - videos_in_dir = [file for file in path.iterdir() if file.suffix in suffixes] - if shuffle: - random.shuffle(videos_in_dir) - videos += videos_in_dir - else: - assert ( - path.exists() - ), f"Could not find the video: {path}. Check access rights." + videos.extend(f for f in path.iterdir() if f.suffix in video_suffixes) + elif path.is_file() and path.suffix in video_suffixes: videos.append(path) + if shuffle: + random.shuffle(videos) return videos From 999465b709d8c2999594092d775bcb977836fbe1 Mon Sep 17 00:00:00 2001 From: Juan Cobos Date: Mon, 12 Jan 2026 10:20:07 +0100 Subject: [PATCH 2/7] remove accidental import --- deeplabcut/pose_estimation_pytorch/apis/utils.py | 1 - 1 file changed, 1 deletion(-) diff --git a/deeplabcut/pose_estimation_pytorch/apis/utils.py b/deeplabcut/pose_estimation_pytorch/apis/utils.py index 50fa40b360..51312d78a5 100644 --- a/deeplabcut/pose_estimation_pytorch/apis/utils.py +++ b/deeplabcut/pose_estimation_pytorch/apis/utils.py @@ -9,7 +9,6 @@ # Licensed under GNU Lesser General Public License v3.0 # from __future__ import annotations -from testscript_cli import video import logging import random From 4f9bf15219e57d0c679d3c8103d846a46b8e3e79 Mon Sep 17 00:00:00 2001 From: Jaap de Ruyter Date: Thu, 15 Jan 2026 12:00:18 +0100 Subject: [PATCH 3/7] Refactor `list_videos_in_folder` - Case handling: extensions are normalized to lowercase for matching - Raise FileNotFoundError instead of assertion error - Updated docstrings --- .../pose_estimation_pytorch/apis/utils.py | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/deeplabcut/pose_estimation_pytorch/apis/utils.py b/deeplabcut/pose_estimation_pytorch/apis/utils.py index 3dffee299f..4782ddd865 100644 --- a/deeplabcut/pose_estimation_pytorch/apis/utils.py +++ b/deeplabcut/pose_estimation_pytorch/apis/utils.py @@ -311,28 +311,36 @@ def list_videos_in_folder( ) -> list[Path]: """ Args: - data_path: Path or list of paths to folders containing videos - video_type: The type of video to filter for - shuffle: If the paths point to directories, whether to shuffle the order of - videos in the directory. + 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. + 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. + + Raises: + FileNotFoundError: If any path in data_path does not exist. """ if not isinstance(data_path, list): data_path = [data_path] if not video_type: - video_suffixes = {f".{ext}" for ext in auxfun_videos.SUPPORTED_VIDEOS} + video_suffixes = {f".{ext.lower()}" for ext in auxfun_videos.SUPPORTED_VIDEOS} else: - video_suffixes = {f".{video_type.lstrip('.')}"} + video_suffixes = {f".{video_type.lstrip('.').lower()}"} videos = [] for path in map(Path, data_path): - assert path.exists(), f"Could not find: {path}. Check access rights." + 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.suffix in video_suffixes) - elif path.is_file() and path.suffix in video_suffixes: + 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) if shuffle: From 746282ea1a8e79c3b73f22c926d7341bd88bc423 Mon Sep 17 00:00:00 2001 From: Jaap de Ruyter Date: Thu, 15 Jan 2026 12:01:13 +0100 Subject: [PATCH 4/7] Remove duplacate paths in `list_videos_in_folder` --- deeplabcut/pose_estimation_pytorch/apis/utils.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/deeplabcut/pose_estimation_pytorch/apis/utils.py b/deeplabcut/pose_estimation_pytorch/apis/utils.py index 4782ddd865..d1b7b11121 100644 --- a/deeplabcut/pose_estimation_pytorch/apis/utils.py +++ b/deeplabcut/pose_estimation_pytorch/apis/utils.py @@ -319,7 +319,7 @@ def list_videos_in_folder( in sorted order for deterministic behavior. Returns: - The paths of videos to analyze. + The paths of videos to analyze. Duplicate paths are removed. Raises: FileNotFoundError: If any path in data_path does not exist. @@ -343,6 +343,9 @@ def list_videos_in_folder( elif path.is_file() and path.suffix.lower() in video_suffixes: videos.append(path) + # Remove duplicates while preserving order (using resolved paths to handle symlinks) + videos = list({v.resolve(): v for v in videos}.values()) + if shuffle: random.shuffle(videos) return videos From da1ccd7275322e736496ab344f8c0f67055e0e2b Mon Sep 17 00:00:00 2001 From: Jaap de Ruyter van Steveninck <32810691+deruyter92@users.noreply.github.com> Date: Thu, 15 Jan 2026 12:50:00 +0100 Subject: [PATCH 5/7] refactor deduplication in `list_videos_in_folder` Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- deeplabcut/pose_estimation_pytorch/apis/utils.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/deeplabcut/pose_estimation_pytorch/apis/utils.py b/deeplabcut/pose_estimation_pytorch/apis/utils.py index d1b7b11121..26f6baf9bd 100644 --- a/deeplabcut/pose_estimation_pytorch/apis/utils.py +++ b/deeplabcut/pose_estimation_pytorch/apis/utils.py @@ -344,7 +344,15 @@ def list_videos_in_folder( videos.append(path) # Remove duplicates while preserving order (using resolved paths to handle symlinks) - videos = list({v.resolve(): v for v in videos}.values()) + seen = set() + unique_videos: list[Path] = [] + for v in videos: + resolved = v.resolve() + if resolved in seen: + continue + seen.add(resolved) + unique_videos.append(v) + videos = unique_videos if shuffle: random.shuffle(videos) From 3c3d0c4f689a64b1af063215e09cf5aa2d9c83ee Mon Sep 17 00:00:00 2001 From: Juan Cobos Date: Thu, 15 Jan 2026 14:54:38 +0100 Subject: [PATCH 6/7] resolve video paths and remove duplicates --- .../pose_estimation_pytorch/apis/utils.py | 20 ++++++------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/deeplabcut/pose_estimation_pytorch/apis/utils.py b/deeplabcut/pose_estimation_pytorch/apis/utils.py index 26f6baf9bd..d98766f6c0 100644 --- a/deeplabcut/pose_estimation_pytorch/apis/utils.py +++ b/deeplabcut/pose_estimation_pytorch/apis/utils.py @@ -331,32 +331,24 @@ def list_videos_in_folder( video_suffixes = {f".{ext.lower()}" for ext in auxfun_videos.SUPPORTED_VIDEOS} else: video_suffixes = {f".{video_type.lstrip('.').lower()}"} - + videos = [] 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) - # Remove duplicates while preserving order (using resolved paths to handle symlinks) - seen = set() - unique_videos: list[Path] = [] - for v in videos: - resolved = v.resolve() - if resolved in seen: - continue - seen.add(resolved) - unique_videos.append(v) - videos = unique_videos - + # Resolve video paths and remove duplicates + unique_videos = list(dict.fromkeys(v.resolve() for v in videos)) if shuffle: - random.shuffle(videos) - return videos + random.shuffle(unique_videos) + return unique_videos def ensure_multianimal_df_format(df_predictions: pd.DataFrame) -> pd.DataFrame: From 859713517af3fc4639881032cb8ca6a8f4512dcc Mon Sep 17 00:00:00 2001 From: Juan Cobos Date: Thu, 15 Jan 2026 14:56:08 +0100 Subject: [PATCH 7/7] check for (str, Path) to handle any sequence --- deeplabcut/pose_estimation_pytorch/apis/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deeplabcut/pose_estimation_pytorch/apis/utils.py b/deeplabcut/pose_estimation_pytorch/apis/utils.py index d98766f6c0..e363ab8c19 100644 --- a/deeplabcut/pose_estimation_pytorch/apis/utils.py +++ b/deeplabcut/pose_estimation_pytorch/apis/utils.py @@ -324,7 +324,7 @@ def list_videos_in_folder( Raises: FileNotFoundError: If any path in data_path does not exist. """ - if not isinstance(data_path, list): + if isinstance(data_path, (str, Path)): data_path = [data_path] if not video_type: