-
Notifications
You must be signed in to change notification settings - Fork 11
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add ImageVideo
backend
#88
Merged
Merged
Changes from 5 commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -1,6 +1,7 @@ | ||||||
"""Backends for reading and writing videos.""" | ||||||
|
||||||
from __future__ import annotations | ||||||
from pathlib import Path | ||||||
|
||||||
import simplejson as json | ||||||
import sys | ||||||
|
@@ -42,15 +43,15 @@ class VideoBackend: | |||||
constructor to create a backend instance. | ||||||
|
||||||
Attributes: | ||||||
filename: Path to video file. | ||||||
filename: Path to video file(s). | ||||||
grayscale: Whether to force grayscale. If None, autodetect on first frame load. | ||||||
keep_open: Whether to keep the video reader open between calls to read frames. | ||||||
If False, will close the reader after each call. If True (the default), it | ||||||
will keep the reader open and cache it for subsequent calls which may | ||||||
enhance the performance of reading multiple frames. | ||||||
""" | ||||||
|
||||||
filename: str | ||||||
filename: str | Path | list[str] | list[Path] | ||||||
grayscale: Optional[bool] = None | ||||||
keep_open: bool = True | ||||||
_cached_shape: Optional[Tuple[int, int, int, int]] = None | ||||||
|
@@ -59,7 +60,7 @@ class VideoBackend: | |||||
@classmethod | ||||||
def from_filename( | ||||||
cls, | ||||||
filename: str, | ||||||
filename: str | list[str], | ||||||
dataset: Optional[str] = None, | ||||||
grayscale: Optional[bool] = None, | ||||||
keep_open: bool = True, | ||||||
|
@@ -68,7 +69,7 @@ def from_filename( | |||||
"""Create a VideoBackend from a filename. | ||||||
|
||||||
Args: | ||||||
filename: Path to video file. | ||||||
filename: Path to video file(s). | ||||||
dataset: Name of dataset in HDF5 file. | ||||||
grayscale: Whether to force grayscale. If None, autodetect on first frame | ||||||
load. | ||||||
|
@@ -80,10 +81,22 @@ def from_filename( | |||||
Returns: | ||||||
VideoBackend subclass instance. | ||||||
""" | ||||||
if type(filename) != str: | ||||||
filename = str(filename) | ||||||
if isinstance(filename, Path): | ||||||
filename = filename.as_posix() | ||||||
|
||||||
if type(filename) == str and Path(filename).is_dir(): | ||||||
filename = ImageVideo.find_images(filename) | ||||||
|
||||||
if filename.endswith(MediaVideo.EXTS): | ||||||
if type(filename) == list: | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use - if type(filename) == list:
+ if isinstance(filename, list): Committable suggestion
Suggested change
|
||||||
filename = [Path(f).as_posix() for f in filename] | ||||||
return ImageVideo( | ||||||
filename, grayscale=grayscale, **_get_valid_kwargs(ImageVideo, kwargs) | ||||||
) | ||||||
elif filename.endswith(ImageVideo.EXTS): | ||||||
return ImageVideo( | ||||||
[filename], grayscale=grayscale, **_get_valid_kwargs(ImageVideo, kwargs) | ||||||
) | ||||||
elif filename.endswith(MediaVideo.EXTS): | ||||||
return MediaVideo( | ||||||
filename, | ||||||
grayscale=grayscale, | ||||||
|
@@ -106,8 +119,8 @@ def _read_frame(self, frame_idx: int) -> np.ndarray: | |||||
raise NotImplementedError | ||||||
|
||||||
def _read_frames(self, frame_inds: list) -> np.ndarray: | ||||||
"""Read a list of frames from the video. Must be implemented in subclasses.""" | ||||||
return np.stack([self._read_frame(i) for i in frame_inds], axis=0) | ||||||
"""Read a list of frames from the video.""" | ||||||
return np.stack([self.get_frame(i) for i in frame_inds], axis=0) | ||||||
|
||||||
def read_test_frame(self) -> np.ndarray: | ||||||
"""Read a single frame from the video to test for grayscale. | ||||||
|
@@ -146,7 +159,7 @@ def num_frames(self) -> int: | |||||
|
||||||
@property | ||||||
def img_shape(self) -> Tuple[int, int, int]: | ||||||
"""Shape of a single frame in the video. Must be implemented in subclasses.""" | ||||||
"""Shape of a single frame in the video.""" | ||||||
return self.get_frame(0).shape | ||||||
|
||||||
@property | ||||||
|
@@ -668,3 +681,48 @@ def _read_frames(self, frame_inds: list) -> np.ndarray: | |||||
f.close() | ||||||
|
||||||
return imgs | ||||||
|
||||||
|
||||||
@attrs.define | ||||||
class ImageVideo(VideoBackend): | ||||||
"""Video backend for reading videos stored as image files. | ||||||
|
||||||
This backend supports reading videos stored as a list of images. | ||||||
|
||||||
Attributes: | ||||||
filename: Path to video files. | ||||||
grayscale: Whether to force grayscale. If None, autodetect on first frame load. | ||||||
""" | ||||||
|
||||||
EXTS = ("png", "jpg", "jpeg", "tif", "tiff", "bmp") | ||||||
|
||||||
@staticmethod | ||||||
def find_images(folder: str) -> list[str]: | ||||||
"""Find images in a folder and return a list of filenames.""" | ||||||
folder = Path(folder) | ||||||
return sorted( | ||||||
[f.as_posix() for f in folder.glob("*") if f.suffix[1:] in ImageVideo.EXTS] | ||||||
) | ||||||
|
||||||
@property | ||||||
def num_frames(self) -> int: | ||||||
"""Number of frames in the video.""" | ||||||
return len(self.filename) | ||||||
|
||||||
def _read_frame(self, frame_idx: int) -> np.ndarray: | ||||||
"""Read a single frame from the video. | ||||||
|
||||||
Args: | ||||||
frame_idx: Index of frame to read. | ||||||
|
||||||
Returns: | ||||||
The frame as a numpy array of shape `(height, width, channels)`. | ||||||
|
||||||
Notes: | ||||||
This does not apply grayscale conversion. It is recommended to use the | ||||||
`get_frame` method of the `VideoBackend` class instead. | ||||||
""" | ||||||
img = iio.imread(self.filename[frame_idx]) | ||||||
if img.ndim == 2: | ||||||
img = np.expand_dims(img, axis=-1) | ||||||
return img |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use
isinstance()
instead of type comparison for checking iffilename
is a string.Committable suggestion