From 86ea7f3077bf4cead039a6e0ae2e4cf635680b22 Mon Sep 17 00:00:00 2001 From: Talmo Pereira Date: Mon, 24 Jun 2024 18:26:35 -0700 Subject: [PATCH 1/3] Initial implementation --- sleap_io/io/video.py | 10 +++++++--- sleap_io/model/video.py | 10 +++++++++- tests/model/test_video.py | 9 +++++++++ 3 files changed, 25 insertions(+), 4 deletions(-) diff --git a/sleap_io/io/video.py b/sleap_io/io/video.py index dae8e5e8..23163f76 100644 --- a/sleap_io/io/video.py +++ b/sleap_io/io/video.py @@ -160,7 +160,11 @@ def num_frames(self) -> int: @property def img_shape(self) -> Tuple[int, int, int]: """Shape of a single frame in the video.""" - height, width, channels = self.get_frame(0).shape + height, width, channels = self.read_test_frame().shape + if self.grayscale is False: + channels = 3 + elif self.grayscale is True: + channels = 1 return int(height), int(width), int(channels) @property @@ -578,7 +582,7 @@ def read_test_frame(self) -> np.ndarray: frame_idx = list(self.frame_map.keys())[0] else: frame_idx = 0 - return self.read_frame(frame_idx) + return self._read_frame(frame_idx) @property def has_embedded_images(self) -> bool: @@ -699,7 +703,7 @@ class ImageVideo(VideoBackend): This backend supports reading videos stored as a list of images. Attributes: - filename: Path to video files. + filename: Path to image files. grayscale: Whether to force grayscale. If None, autodetect on first frame load. """ diff --git a/sleap_io/model/video.py b/sleap_io/model/video.py index 049963ba..69895220 100644 --- a/sleap_io/model/video.py +++ b/sleap_io/model/video.py @@ -120,7 +120,7 @@ def _get_shape(self) -> Tuple[int, int, int, int] | None: return self.backend_metadata["shape"] return None - @property + @property.getter def grayscale(self) -> bool | None: """Return whether the video is grayscale. @@ -135,6 +135,14 @@ def grayscale(self) -> bool | None: return self.backend_metadata["grayscale"] return None + @property.setter + def grayscale(self, value: bool): + """Set the grayscale value and adjust the backend.""" + self.backend.grayscale = value + self.backend._cached_shape = None + if "grayscale" in self.backend_metadata: + self.backend_metadata["grayscale"] = value + def __len__(self) -> int: """Return the length of the video as the number of frames.""" shape = self.shape diff --git a/tests/model/test_video.py b/tests/model/test_video.py index 03a0bdba..55641930 100644 --- a/tests/model/test_video.py +++ b/tests/model/test_video.py @@ -122,3 +122,12 @@ def test_video_replace_filename( video.replace_filename(centered_pair_frame_paths) assert type(video.backend) == ImageVideo assert video.exists(check_all=True) is True + + +def test_grayscale(centered_pair_low_quality_path): + video = Video.from_filename(centered_pair_low_quality_path) + assert video.grayscale == True + assert video.shape[-1] == 1 + + video.grayscale = False + assert video.shape[-1] == 3 From 96412ffb2e23cd17ac0affb6c142743a19b661c4 Mon Sep 17 00:00:00 2001 From: Talmo Pereira Date: Mon, 24 Jun 2024 19:30:34 -0700 Subject: [PATCH 2/3] Fix basic python and edge cases --- sleap_io/model/video.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/sleap_io/model/video.py b/sleap_io/model/video.py index 69895220..edd49489 100644 --- a/sleap_io/model/video.py +++ b/sleap_io/model/video.py @@ -120,7 +120,7 @@ def _get_shape(self) -> Tuple[int, int, int, int] | None: return self.backend_metadata["shape"] return None - @property.getter + @property def grayscale(self) -> bool | None: """Return whether the video is grayscale. @@ -131,17 +131,16 @@ def grayscale(self) -> bool | None: if shape is not None: return shape[-1] == 1 else: - if "grayscale" in self.backend_metadata: - return self.backend_metadata["grayscale"] - return None + return self.backend_metadata.get("grayscale", None) - @property.setter + @grayscale.setter def grayscale(self, value: bool): """Set the grayscale value and adjust the backend.""" - self.backend.grayscale = value - self.backend._cached_shape = None - if "grayscale" in self.backend_metadata: - self.backend_metadata["grayscale"] = value + if self.backend is not None: + self.backend.grayscale = value + self.backend._cached_shape = None + + self.backend_metadata["grayscale"] = value def __len__(self) -> int: """Return the length of the video as the number of frames.""" From 99403ca4eb10de96f9371868ddc2b1a9ece38624 Mon Sep 17 00:00:00 2001 From: Talmo Pereira Date: Sun, 30 Jun 2024 09:51:15 -0700 Subject: [PATCH 3/3] Fix auto-detection --- sleap_io/io/video.py | 2 ++ tests/model/test_video.py | 11 +++++++++++ 2 files changed, 13 insertions(+) diff --git a/sleap_io/io/video.py b/sleap_io/io/video.py index 23163f76..25ccc0d0 100644 --- a/sleap_io/io/video.py +++ b/sleap_io/io/video.py @@ -161,6 +161,8 @@ def num_frames(self) -> int: def img_shape(self) -> Tuple[int, int, int]: """Shape of a single frame in the video.""" height, width, channels = self.read_test_frame().shape + if self.grayscale is None: + self.detect_grayscale() if self.grayscale is False: channels = 3 elif self.grayscale is True: diff --git a/tests/model/test_video.py b/tests/model/test_video.py index 55641930..cc87f5a3 100644 --- a/tests/model/test_video.py +++ b/tests/model/test_video.py @@ -131,3 +131,14 @@ def test_grayscale(centered_pair_low_quality_path): video.grayscale = False assert video.shape[-1] == 3 + + video.close() + video.open() + assert video.grayscale == False + assert video.shape[-1] == 3 + + video.grayscale = True + video.close() + video.open() + assert video.grayscale == True + assert video.shape[-1] == 1