From 8c5bac3176ce145c5497c5b2bb6bf916702801a5 Mon Sep 17 00:00:00 2001 From: Talmo Pereira Date: Sat, 28 Sep 2024 16:47:29 -0700 Subject: [PATCH] Add explicit control of video backend opening --- sleap_io/io/slp.py | 8 ++++++-- sleap_io/model/video.py | 22 ++++++++++++++++++++-- tests/io/test_slp.py | 9 +++++++++ 3 files changed, 35 insertions(+), 4 deletions(-) diff --git a/sleap_io/io/slp.py b/sleap_io/io/slp.py index 262ee3d0..208c6fc4 100644 --- a/sleap_io/io/slp.py +++ b/sleap_io/io/slp.py @@ -109,6 +109,9 @@ def make_video( # This is an ImageVideo. # TODO: Path resolution. video_path = backend_metadata["filenames"] + video_path = [ + Path(Path(p).as_posix().replace("\\", "/")) for p in video_path + ] try: backend = VideoBackend.from_filename( @@ -117,7 +120,7 @@ def make_video( grayscale=backend_metadata.get("grayscale", None), input_format=backend_metadata.get("input_format", None), ) - except: + except Exception: backend = None return Video( @@ -125,10 +128,11 @@ def make_video( backend=backend, backend_metadata=backend_metadata, source_video=source_video, + open_backend=open_backend, ) -def read_videos(labels_path: str, open_backend: bool = False) -> list[Video]: +def read_videos(labels_path: str, open_backend: bool = True) -> list[Video]: """Read `Video` dataset in a SLEAP labels file. Args: diff --git a/sleap_io/model/video.py b/sleap_io/model/video.py index edd49489..8239cbb6 100644 --- a/sleap_io/model/video.py +++ b/sleap_io/model/video.py @@ -34,6 +34,11 @@ class Video: information) without having access to the video file itself. source_video: The source video object if this is a proxy video. This is present when the video contains an embedded subset of frames from another video. + open_backend: Whether to open the backend when the video is available. If `True` + (the default), the backend will be automatically opened if the video exists. + Set this to `False` when you want to manually open the backend, or when the + you know the video file does not exist and you want to avoid trying to open + the file. Notes: Instances of this class are hashed by identity, not by value. This means that @@ -47,12 +52,13 @@ class Video: backend: Optional[VideoBackend] = None backend_metadata: dict[str, any] = attrs.field(factory=dict) source_video: Optional[Video] = None + open_backend: bool = True EXTS = MediaVideo.EXTS + HDF5Video.EXTS + ImageVideo.EXTS def __attrs_post_init__(self): """Post init syntactic sugar.""" - if self.backend is None and self.exists(): + if self.open_backend and self.backend is None and self.exists(): self.open() @classmethod @@ -181,7 +187,13 @@ def __getitem__(self, inds: int | list[int] | slice) -> np.ndarray: See also: VideoBackend.get_frame, VideoBackend.get_frames """ if not self.is_open: - self.open() + if self.open_backend: + self.open() + else: + raise ValueError( + "Video backend is not open. Call video.open() or set " + "video.open_backend to True to do automatically on frame read." + ) return self.backend[inds] def exists(self, check_all: bool = False) -> bool: @@ -208,6 +220,7 @@ def is_open(self) -> bool: def open( self, + filename: Optional[str] = None, dataset: Optional[str] = None, grayscale: Optional[str] = None, keep_open: bool = True, @@ -215,6 +228,8 @@ def open( """Open the video backend for reading. Args: + filename: Filename to open. If not specified, will use the filename set on + the video object. dataset: Name of dataset in HDF5 file. grayscale: Whether to force grayscale. If None, autodetect on first frame load. @@ -231,6 +246,9 @@ def open( Values for the HDF5 dataset and grayscale will be remembered if not specified. """ + if self.filename: + self.replace_filename(filename, open=False) + if not self.exists(): raise FileNotFoundError(f"Video file not found: {self.filename}") diff --git a/tests/io/test_slp.py b/tests/io/test_slp.py index cd37b9a1..18c7d2ed 100644 --- a/tests/io/test_slp.py +++ b/tests/io/test_slp.py @@ -354,3 +354,12 @@ def test_embed_two_rounds(tmpdir, slp_real_data): == "tests/data/videos/centered_pair_low_quality.mp4" ) assert type(labels3.video.backend) == MediaVideo + + +def test_lazy_video_read(slp_real_data): + labels = read_labels(slp_real_data) + assert type(labels.video.backend) == MediaVideo + assert labels.video.exists() + + labels = read_labels(slp_real_data, open_videos=False) + assert labels.video.backend is None