Skip to content

Refactor VideoProcessor class - #3358

Merged
MMathisLab merged 22 commits into
mainfrom
cy/fix-video-reader-set-error
Jun 25, 2026
Merged

Refactor VideoProcessor class#3358
MMathisLab merged 22 commits into
mainfrom
cy/fix-video-reader-set-error

Conversation

@C-Achard

@C-Achard C-Achard commented Jun 5, 2026

Copy link
Copy Markdown
Collaborator

Motivation

Fixes #3357.

The utils/make_labeled_video file uses the provided video as-is, but video may now be provided as a Path, which may not always be supported by OpenCV calls.
Otherwise, the GUI stores selected videos in self.files, which is backed by a set to avoid duplicates.
However, labeled video creation expects a list-like sequence of video path strings.

Fix

  • Convert all uses of video to str in utils/make_labeled_video
  • Convert the selected videos to a sorted list[str] before calling deeplabcut.create_labeled_video
  • VideoProcessor:
    • Is now a proper abstract class
    • Properties are used where relevant, and callers updated accordingly
      • Coercion to str is now immediate at the property level for fname and sname
      • Other validation is not yet implemented for compatibility, like raising on missing input file
      • NOTE: this could break any external site still calling methods instead of attributes. In this codebase this is not a concern as only utils/make_labeled_video uses it. Due to how properties are implemented I'm not sure this is the best solution IF we are worried about potential other external callers.
    • Updated docstrings
  • Added minimal tests for VideoProcessorCV

C-Achard added 2 commits June 5, 2026 10:03
Convert entries in self.files to strings and sort them before use. This ensures a deterministic, predictable ordering of videos (e.g., when files are Path objects or come from an unordered collection) and avoids nondeterministic processing order.
Cast video and output path arguments to str before passing them to vp to ensure pathlib.Path or other path-like objects don't cause errors. Updated calls in proc_video, create_video, create_video_with_all_detections (and related CreateVideoSlow usage) to use str(video) and str(outputname/output_path) for improved compatibility with Path inputs.
@C-Achard
C-Achard requested a review from Copilot June 5, 2026 08:23
@C-Achard C-Achard self-assigned this Jun 5, 2026
@C-Achard C-Achard added GUI issues relating to GUI bug fix! fix for a real buggy one... labels Jun 5, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes labeled-video creation failures when GUI-selected videos are stored as a set and/or when video paths are provided as Path objects that OpenCV cannot consume directly. It ensures deterministic, list-like ordering for GUI video selection and normalizes video path inputs to str before passing them into the OpenCV video processor.

Changes:

  • Convert video arguments to str before constructing VideoProcessorCV instances in labeled-video generation paths.
  • Convert GUI-selected videos from a set to a sorted list[str] before calling deeplabcut.create_labeled_video.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
deeplabcut/utils/make_labeled_video.py Normalizes video/outputname to str when initializing the OpenCV video processor to avoid OpenCV type errors.
deeplabcut/gui/tabs/create_videos.py Passes a deterministic, sorted list[str] of videos (instead of a set) into deeplabcut.create_labeled_video.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread deeplabcut/utils/make_labeled_video.py
@C-Achard
C-Achard marked this pull request as ready for review June 5, 2026 08:28
@C-Achard
C-Achard requested a review from deruyter92 June 5, 2026 08:28

@deruyter92 deruyter92 left a comment

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.

@C-Achard, thanks for picking this up. A few remarks:

The GUI commit seems a bit misplaced to me, since collect_video_paths sorts as Paths right after.

More general, to keep a clear internal policy (i.e. prefere Path over str), I would prefer to solve it at the boundaries of our own package rather than in intermediate modules like create_videos.py and make_labeled_video.py. Let me know if you agree.

I think a good alternative would be to solve this in the VideoProcessor base class:

self.fname = str(fname) if fname else fname
self.sname = str(sname) if sname else sname

Same for VideoReader and VideoWriter

This keeps the solution to small isolated boundaries, where it is expected to fail (i.e. OpenCV can't handle pathlib, pass str to OpenCV, use Path elsewere).

Also let's add a test to pick this up in CI.

@deruyter92

Copy link
Copy Markdown
Collaborator

BTW, let me know if you prefer me to implement these suggestions directly in the PR

@C-Achard

C-Achard commented Jun 9, 2026

Copy link
Copy Markdown
Collaborator Author

@deruyter92 I think you're right, the fix is a bit too minimal and does not go well with your recent efforts for internal standardization to pathlib.

I think I will instead spend a bit of time in the VideoProcessor class, I think it could use a careful overhaul and likely some tests.
I will add some docstrings as well, the class is a bit light on that currently.
Thanks!

C-Achard added 5 commits June 9, 2026 11:19
Introduce tests/utils/test_video_processor.py which adds comprehensive unit tests for deeplabcut.utils.video_processor.VideoProcessorCV. Includes a helper _make_test_video to produce temporary OpenCV videos and tests for metadata reading, frame loading and EOF behavior, nframes cap and -1 handling, fps override, video writing with default and explicit dimensions, idempotent close, context-manager cleanup, and invalid-input error handling.
Make VideoProcessor an abstract base class by importing ABC and abstractmethod and inheriting from ABC. Replace placeholder pass methods with @abstractmethod-decorated methods that raise NotImplementedError for get_video, get_info, create_video, _read_frame, save_frame, and close. Expose internal attributes via @Property for height, width, fps, counter, and frame_count. These changes formalize the interface and enforce implementation in subclasses.
Replace calls to clip.height(), clip.width(), and clip.fps() with attribute access (clip.height, clip.width, clip.fps) in make_labeled_video.py. This updates CreateVideo, CreateVideoSlow, proc_video, and create_video_with_all_detections to match the video object API and avoid method-call errors when these values are provided as properties.
Introduce fname/sname properties on VideoProcessor (with _fname/_sname initialization) to ensure string coercion and clearer attribute access. Expand VideoProcessorCV with comprehensive docstrings, clarify RGB<->BGR conversion behavior, add safety checks when reading frames, and make close() null out released OpenCV handles. Update tests: remove the context-manager close test and mark the invalid-input-video test as xfail for backwards compatibility.
Remove redundant str() conversions when creating vp clips in make_labeled_video.py now that the property coerces. Pass video and outputname/path variables directly (e.g. vp(fname=video), vp(video), vp(fname=video, sname=outputname)) to improve compatibility with pathlib.Path and avoid potential type issues. Updates applied in proc_video, create_video, and create_video_with_all_detections.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

Comment thread deeplabcut/utils/video_processor.py Outdated
Comment thread deeplabcut/utils/video_processor.py
Comment thread deeplabcut/utils/video_processor.py Outdated
In deeplabcut/utils/video_processor.py initialize commonly used instance attributes (FPS, vid, svid, sh, sw) in the constructor to avoid missing-attribute errors. Replace a stray print() in the exception handler with a module logger and add logging import/logger setup. Also add a brief note about FPS handling to clarify behavior when overriding video FPS.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

Comment thread deeplabcut/utils/video_processor.py
Comment thread deeplabcut/utils/video_processor.py Outdated
Comment thread tests/utils/test_video_processor.py
@C-Achard C-Achard changed the title Fix: Sort and convert video set to list Refactor VideoProcessor class Jun 9, 2026
Add a unit test that creates a temporary video file and passes a pathlib.Path to VideoProcessorCV. The test asserts that no exception is raised, that clip.fname is stored as a string, and ensures the clip is closed. This prevents regressions where Path objects might not be handled or converted to str by the processor.
Introduce Python type hints to the VideoProcessor.__init__ signature: fname: str, sname: str, nframes: int, fps: float, codec: str, sh: int, sw: int. This improves code clarity and static typing.

@deruyter92 deruyter92 left a comment

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.

Overall great improvements and good fix to the issue!

Some suggestions about consistency for types + private/public attributes

If those are addressed looks good to me!

Comment thread deeplabcut/utils/video_processor.py Outdated
Comment thread deeplabcut/utils/video_processor.py Outdated
Comment thread deeplabcut/utils/video_processor.py Outdated
Comment thread deeplabcut/utils/video_processor.py
Comment thread deeplabcut/utils/video_processor.py Outdated
Comment thread deeplabcut/utils/make_labeled_video.py
Comment thread deeplabcut/utils/video_processor.py
@deruyter92

deruyter92 commented Jun 10, 2026

Copy link
Copy Markdown
Collaborator

@C-Achard, regarding the API breakage, maybe it would actually be a good idea to keep transitional backward compatibility, since it is relatively cheap to implement, e.g.:

use properties with a new name (e.g. h) and keep method accessors as they were

@property
def h(self):
    return self._h

@deprecated(replacement="VideoProcessor.h", since="3.1")
def height(self):
    return self._h    

C-Achard added 2 commits June 10, 2026 10:27
Annotate fps, sh, and sw with None-aware union types and change sh/sw defaults from empty strings to None. Update logic to check for None rather than empty strings, and make fname/sname setters convert None or empty input to an empty string to avoid storing literal 'None'. Also guard frame writes by ensuring svid is set before calling write (prevents attempts to write when output video is not initialized). These changes improve type correctness and robustness when optional values are omitted.
Introduce a clearer VideoProcessor API: replace legacy FPS/width/height/frame_count attributes with video_fps, h, w, and nframes, add setters/getters and legacy compatibility methods (fps(), height(), width(), frame_count(), FPS). Improve docstrings and typing (Literal import), tighten handling of empty output sizes, and ensure video writers use video_fps. Update VideoProcessorCV to set/read video_fps from OpenCV and to warn when writing without an open writer; avoid writing None frames. Update make_labeled_video calls to use clip.video_fps and clip.h/clip.w, and adjust tests accordingly to reflect the new attribute names.
@C-Achard

Copy link
Copy Markdown
Collaborator Author

@C-Achard, regarding the API breakage, maybe it would actually be a good idea to keep transitional backward compatibility, since it is relatively cheap to implement, e.g.:

use properties with a new name (e.g. h) and keep method accessors as they were

@property
def h(self):
    return self._h

@deprecated(replacement="VideoProcessor.h", since="3.1")
def height(self):
    return self._h    

It should now be more of a middle ground. I'm not sure we really gain clarity from it but it is compatible.

C-Achard added 2 commits June 10, 2026 11:01
Add deprecation wrappers to legacy VideoProcessor methods and properties (counter, height, width, fps, FPS getter/setter) so callers are warned to migrate. Each decorator provides a replacement hint (i, h, w, video_fps) and a since="3.1" note. Also import the deprecated helper from deeplabcut.utils.deprecation to support these annotations. This preserves backward compatibility while guiding users toward the new attributes.
Comment thread deeplabcut/utils/video_processor.py Outdated
@C-Achard
C-Achard requested a review from deruyter92 June 10, 2026 16:08
@deruyter92

Copy link
Copy Markdown
Collaborator

Thanks for addressing all the comments. Looks good to me!

It should now be more of a middle ground. I'm not sure we really gain clarity from it but it is compatible.

Yes, for me this is also still a difficult balance - to what extent backward compatibility is favored over clarity. I think the middle ground you took is both clear and backward compatible. If you think we are overdoing it in this case, feel free to revert to your VideoProcessor.width and VideoProcessor.height properties, which I think are also a clear and viable option (and relatively very safe still). Whichever you prefer.

@C-Achard

C-Achard commented Jun 11, 2026

Copy link
Copy Markdown
Collaborator Author

I think here problems from external use are less likely to occur compared to a higher level API. This is not really meant to be user facing, so I think I would favor maintainability for the more private API. I will re-adapt this a bit, as discussed, to be more towards what I think is the (c)lean side.

Replace the legacy video_fps attribute with a slimmer fps property across the codebase. Updated VideoProcessor to expose fps (getter/setter) and changed VideoProcessorCV to initialize and use fps for reading and writing; callers in make_labeled_video now reference clip.fps. Removed several legacy/deprecated compatibility methods related to old accessors (counter, height, width, FPS, frame_count, etc.). Tests were adjusted to assert clip.fps instead of clip.video_fps. This consolidates FPS handling and simplifies the VideoProcessor API.
@deruyter92

deruyter92 commented Jun 12, 2026

Copy link
Copy Markdown
Collaborator

yes good consideration. I agree. Looks good now! Feel free to name the properties height / width as you originally intended, or keep them h and w as they are now.

C-Achard added 2 commits June 12, 2026 10:08
Replace ambiguous 'h' and 'w' properties with explicit 'height' and 'width' on VideoProcessor and its subclasses. Update all internal references (e.g. VideoProcessorCV initialization and make_labeled_video bbox/cropping logic) and adjust tests to assert clip.width and clip.height. Improves readability and consistency of video dimension access across the codebase.
@C-Achard
C-Achard requested review from AlexEMG and MMathisLab June 25, 2026 15:29
@MMathisLab
MMathisLab merged commit 95c630b into main Jun 25, 2026
4 of 5 checks passed
@MMathisLab
MMathisLab deleted the cy/fix-video-reader-set-error branch June 25, 2026 19:45
@C-Achard C-Achard added this to the v3.0.1 milestone Jun 26, 2026
@deruyter92 deruyter92 mentioned this pull request Jul 20, 2026
9 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug fix! fix for a real buggy one... GUI issues relating to GUI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

'VideoProcessorCV' object has no attribute 'FPS'

4 participants