Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions deeplabcut/gui/tabs/create_videos.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
_create_label_widget,
_create_vertical_layout,
)
from deeplabcut.utils.auxfun_videos import collect_video_paths


class CreateVideos(DefaultTab):
Expand Down Expand Up @@ -272,10 +273,15 @@ def create_videos(self):
color_by=color_by,
overwrite=self.overwrite_videos.isChecked(),
)
if all(videos_created):
analyzed_videos = collect_video_paths(videos)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this filter by extension in case the folder has more than one supported video type?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

videos comes from the GUI file picker and is an explicit file list rather than a directory path.

So here collect_video_paths(videos) just normalizes the selected files in the same way that the create_labeled_videos() backend does. (Both use the default SUPPORTED_VIDEO_EXTENSIONS) So I don’t think extra filtering is helpful here. Unless I'm overlooking something or didn't understand your comment correctly.

if not analyzed_videos:
self.root.writer.write("No videos found to label.")
elif all(videos_created):
self.root.writer.write("Labeled videos created.")
else:
failed_videos = [video for success, video in zip(videos_created, videos, strict=False) if not success]
failed_videos = [
video for success, video in zip(videos_created, analyzed_videos, strict=True) if not success
]
failed_videos_str = ", ".join(str(video) for video in failed_videos)
self.root.writer.write(f"Failed to create videos from {failed_videos_str}.")

Expand Down
20 changes: 15 additions & 5 deletions deeplabcut/utils/auxfun_videos.py
Original file line number Diff line number Diff line change
Expand Up @@ -630,6 +630,8 @@ def collect_video_paths(
extensions: str | Sequence[str] | None = None,
shuffle: bool = False,
exclude_patterns: Sequence[str] = DEFAULT_EXCLUDE_PATTERNS,
*,
warn_on_unsupported_ext: bool = True,
) -> list[Path]:
"""
Collects video paths from a given set of data paths: directories, files, or a mix
Expand Down Expand Up @@ -657,6 +659,9 @@ def collect_video_paths(
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.
warn_on_unsupported_ext: Whether to warn when collected files have an extension
outside ``SUPPORTED_VIDEOS``. Set to ``False`` when deliberately collecting
non-video files (e.g. ``extensions=".h5"``), where the warning is misleading.

Returns:
The paths of videos to analyze. Duplicate paths are removed.
Expand Down Expand Up @@ -722,9 +727,14 @@ def _coerce_extensions(extensions: str | Sequence[str] | None) -> set[str] | Non
else:
unique_videos.sort()

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,
)
if warn_on_unsupported_ext:
unsupported = [
fn for fn in unique_videos if fn.suffix and fn.suffix.lower().lstrip(".") not in SUPPORTED_VIDEOS
]
if unsupported:
warnings.warn(
f"Some videos have unsupported extensions: {unsupported} \n"
f"Supported extensions are: {SUPPORTED_VIDEOS}",
stacklevel=2,
)
return unique_videos
4 changes: 2 additions & 2 deletions deeplabcut/utils/auxiliaryfunctions.py
Original file line number Diff line number Diff line change
Expand Up @@ -559,7 +559,7 @@ def check_if_post_processing(folder, vname, DLCscorer, DLCscorerlegacy, suffix="


def check_if_not_analyzed(destfolder, vname, DLCscorer, DLCscorerlegacy, flag="video"):
h5files = collect_video_paths(destfolder, extensions=".h5")
h5files = collect_video_paths(destfolder, extensions=".h5", warn_on_unsupported_ext=False)
if not len(h5files):
dataname = Path(destfolder) / (vname + DLCscorer + ".h5")
return True, dataname, DLCscorer
Expand Down Expand Up @@ -640,7 +640,7 @@ def find_analyzed_data(folder, videoname: str, scorer: str, filtered=False, trac
tracker = TRACK_METHODS.get(track_method, "")

candidates = []
for file in collect_video_paths(folder, extensions=".h5"):
for file in collect_video_paths(folder, extensions=".h5", warn_on_unsupported_ext=False):
stem = file.stem.replace("_filtered", "")
starts_by_scorer = file.name.startswith((videoname + scorer, videoname + scorer_legacy))
if tracker:
Expand Down
38 changes: 26 additions & 12 deletions deeplabcut/utils/conversioncode.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ def adapt_labeled_data_to_new_project(


# TODO: @deruyter92 2026-05-20: this function uses videotype instead of video_extensions.
def analyze_videos_converth5_to_csv(video_folder, videotype=".mp4", listofvideos=False):
def analyze_videos_converth5_to_csv(video_folder, videotype=None, listofvideos=False):
"""By default the output poses (when running analyze_videos) are stored as
MultiIndex Pandas Array, which contains the name of the network, body part name, (x,
y) label position in pixels, and the likelihood for each frame per body part.
Expand All @@ -221,7 +221,8 @@ def analyze_videos_converth5_to_csv(video_folder, videotype=".mp4", listofvideos

Args:
video_folder (string): Absolute path of a folder containing videos and the corresponding h5 data files.
videotype (string, optional): Only videos with this extension are screened. Defaults to .mp4.
videotype (string, optional): Only videos with this extension are screened.
Defaults to None, i.e. every supported video extension is screened.

Examples:
Converts all pose-output files belonging to mp4 videos in the folder
Expand All @@ -233,13 +234,13 @@ def analyze_videos_converth5_to_csv(video_folder, videotype=".mp4", listofvideos
)
"""
if listofvideos: # can also be called with a list of videos (from GUI)
videos = video_folder # GUI gives a list of videos
videos = [Path(video) for video in video_folder] # GUI gives a list of videos
if len(videos) > 0:
h5_files = collect_video_paths(Path(videos[0]).parent, extensions=".h5")
h5_files = collect_video_paths(videos[0].parent, extensions=".h5", warn_on_unsupported_ext=False)
else:
h5_files = []
else:
h5_files = collect_video_paths(video_folder, extensions=".h5")
h5_files = collect_video_paths(video_folder, extensions=".h5", warn_on_unsupported_ext=False)
videos = collect_video_paths(video_folder, extensions=videotype)

_convert_h5_files_to("csv", None, h5_files, videos)
Expand All @@ -249,15 +250,16 @@ def analyze_videos_converth5_to_csv(video_folder, videotype=".mp4", listofvideos
def analyze_videos_converth5_to_nwb(
config: str | Path,
video_folder: str | Path,
videotype=".mp4",
videotype=None,
listofvideos=False,
):
"""Convert all h5 output data files in `video_folder` to NWB format.

Args:
config (string): Absolute path to the project YAML config file.
video_folder (string): Absolute path of a folder containing videos and the corresponding h5 data files.
videotype (string, optional): Only videos with this extension are screened. Defaults to .mp4.
videotype (string, optional): Only videos with this extension are screened.
Defaults to None, i.e. every supported video extension is screened.

Examples:
Converts all pose-output files belonging to mp4 videos in the folder
Expand All @@ -270,13 +272,13 @@ def analyze_videos_converth5_to_nwb(
)
"""
if listofvideos: # can also be called with a list of videos (from GUI)
videos = video_folder # GUI gives a list of videos
videos = [Path(video) for video in video_folder] # GUI gives a list of videos
if len(videos) > 0:
h5_files = collect_video_paths(Path(videos[0]).parent, extensions=".h5")
h5_files = collect_video_paths(videos[0].parent, extensions=".h5", warn_on_unsupported_ext=False)
else:
h5_files = []
else:
h5_files = collect_video_paths(video_folder, extensions=".h5")
h5_files = collect_video_paths(video_folder, extensions=".h5", warn_on_unsupported_ext=False)
videos = collect_video_paths(video_folder, extensions=videotype)

_convert_h5_files_to("nwb", config, h5_files, videos)
Expand All @@ -296,6 +298,7 @@ def _convert_h5_files_to(filetype, config, h5_files, videos):
except ImportError as e:
raise ImportError("The package `dlc2nwb` is missing. Please run `pip install dlc2nwb`.") from e

converted = set()
for video in videos:
if "_labeled" in video.name:
continue
Expand All @@ -311,8 +314,19 @@ def _convert_h5_files_to(filetype, config, h5_files, videos):
df.to_csv(file.with_suffix(".csv"))
else:
convert_h5_to_nwb(config, file)

print(f"All H5 files were converted to {filetype.upper()}.")
converted.add(file)

if converted:
print(f"{len(converted)} H5 file(s) were converted to {filetype.upper()}.")
elif h5_files:
# Reporting success here would hide the most common cause: the videos the H5
# files belong to were filtered out by `videotype`, so nothing was screened.
print(
f"No H5 files were converted to {filetype.upper()}: none of the {len(h5_files)} H5 file(s) "
f"found belong to any of the {len(videos)} screened video(s)."
)
else:
print(f"No H5 files were found to convert to {filetype.upper()}.")


def merge_windowsannotationdataONlinuxsystem(cfg):
Expand Down
Loading