-
Notifications
You must be signed in to change notification settings - Fork 34.1k
Chat template: return vectorized output in processors #34275
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
Changes from 34 commits
d66a928
2bff795
3c24aff
710edd1
1bf58f3
76d24ae
eb588d1
3de67e0
bcf3dac
87205d7
6282694
690c314
9049d64
243b4c3
899d20d
47272f8
fc8ba58
8b0ddd7
d2d27fb
1adfbca
d0209e2
cde21be
34ee690
3cd24ac
91057e4
5edb363
04080ea
eb450f8
9cc74a4
39724ef
72368f7
376e808
de58cb0
71a82b5
4e62720
45289f3
503b153
2b54a52
3c3441e
4600728
39875be
db2ec0c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,6 +15,7 @@ | |
|
|
||
| import base64 | ||
| import os | ||
| from contextlib import redirect_stdout | ||
| from io import BytesIO | ||
| from typing import TYPE_CHECKING, Dict, Iterable, List, Optional, Tuple, Union | ||
|
|
||
|
|
@@ -25,13 +26,17 @@ | |
| from .utils import ( | ||
| ExplicitEnum, | ||
| TensorType, | ||
| is_av_available, | ||
| is_cv2_available, | ||
| is_decord_available, | ||
| is_jax_tensor, | ||
| is_numpy_array, | ||
| is_tf_tensor, | ||
| is_torch_available, | ||
| is_torch_tensor, | ||
| is_torchvision_available, | ||
| is_vision_available, | ||
| is_yt_dlp_available, | ||
| logging, | ||
| requires_backends, | ||
| to_numpy, | ||
|
|
@@ -56,6 +61,7 @@ | |
| PILImageResampling = PIL.Image | ||
|
|
||
| if is_torchvision_available(): | ||
| from torchvision import io as torchvision_io | ||
| from torchvision.transforms import InterpolationMode | ||
|
|
||
| pil_torch_interpolation_mapping = { | ||
|
|
@@ -67,6 +73,17 @@ | |
| PILImageResampling.LANCZOS: InterpolationMode.LANCZOS, | ||
| } | ||
|
|
||
| if is_decord_available(): | ||
| from decord import VideoReader, cpu | ||
|
|
||
| if is_av_available(): | ||
| import av | ||
|
|
||
| if is_cv2_available(): | ||
| import cv2 | ||
|
|
||
| if is_yt_dlp_available(): | ||
| from yt_dlp import YoutubeDL | ||
|
Comment on lines
+76
to
+86
Member
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. This block breaks lazy importing of Would it be possible to delay this import? This would be preferable to lazily importing |
||
|
|
||
| if TYPE_CHECKING: | ||
| if is_torch_available(): | ||
|
|
@@ -386,6 +403,204 @@ def load_image(image: Union[str, "PIL.Image.Image"], timeout: Optional[float] = | |
| return image | ||
|
|
||
|
|
||
| def get_uniform_frame_indices(total_num_frames: int, num_frames: Optional[int] = None): | ||
| """ | ||
| Creates a numpy array for uniform sampling of `num_frame` frames from `total_num_frames` | ||
| when loading a video. | ||
|
|
||
| Args: | ||
| total_num_frames (`int`): | ||
| Total number of frames that a video has. | ||
| num_frames (`int`, *optional*): | ||
| Number of frames to sample uniformly. If not specified, all frames are sampled. | ||
|
|
||
| Returns: | ||
| np.ndarray: np array of frame indices that will be sampled. | ||
| """ | ||
| if num_frames is not None: | ||
| indices = np.arange(0, total_num_frames, total_num_frames / num_frames).astype(int) | ||
| else: | ||
| indices = np.arange(0, total_num_frames).astype(int) | ||
|
zucchini-nlp marked this conversation as resolved.
|
||
| return indices | ||
|
|
||
|
|
||
| def read_video_opencv(video_path: str, num_frames: Optional[int] = None): | ||
| """ | ||
| Decode the video with open-cv decoder. | ||
|
|
||
| Args: | ||
| video_path (`str`): | ||
| Path to the video file. | ||
| num_frames (`int`, *optional*): | ||
| Number of frames to sample uniformly. If not specified, all frames are sampled. | ||
|
|
||
| Returns: | ||
| np.ndarray: np array of decoded frames of shape (num_frames, height, width, 3). | ||
| """ | ||
| video = cv2.VideoCapture(video_path) | ||
| total_num_frames = int(video.get(cv2.CAP_PROP_FRAME_COUNT)) | ||
| indices = get_uniform_frame_indices(total_num_frames, num_frames=num_frames) | ||
|
|
||
| index = 0 | ||
| frames = [] | ||
| while video.isOpened(): | ||
| success, frame = video.read() | ||
| if index in indices: | ||
| height, width, channel = frame.shape | ||
| frames.append(frame[0:height, 0:width, 0:channel]) | ||
| if success: | ||
| index += 1 | ||
| if index >= total_num_frames: | ||
| break | ||
|
|
||
| video.release() | ||
| return np.stack(frames) | ||
|
|
||
|
|
||
| def read_video_decord(video_path: str, num_frames: Optional[int] = None): | ||
| """ | ||
| Decode the video with Decord decoder. | ||
|
|
||
| Args: | ||
| video_path (`str`): | ||
| Path to the video file. | ||
| num_frames (`int`, *optional*): | ||
| Number of frames to sample uniformly. If not specified, all frames are sampled. | ||
|
|
||
| Returns: | ||
| np.ndarray: np array of decoded frames of shape (num_frames, height, width, 3). | ||
| """ | ||
| vr = VideoReader(uri=video_path, ctx=cpu(0)) # decord has problems with gpu | ||
| indices = get_uniform_frame_indices(total_num_frames=len(vr), num_frames=num_frames) | ||
| frames = vr.get_batch(indices).asnumpy() | ||
| return frames | ||
|
|
||
|
|
||
| def read_video_pyav(video_path: str, num_frames: Optional[int] = None): | ||
| """ | ||
| Decode the video with PyAV decoder. | ||
|
|
||
| Args: | ||
| video_path (`str`): | ||
| Path to the video file. | ||
| num_frames (`int`, *optional*): | ||
| Number of frames to sample uniformly. If not specified, all frames are sampled. | ||
|
|
||
| Returns: | ||
| np.ndarray: np array of decoded frames of shape (num_frames, height, width, 3). | ||
| """ | ||
| container = av.open(video_path) | ||
|
|
||
| # sample uniformly "num_frames" frames from the video | ||
| total_num_frames = container.streams.video[0].frames | ||
| indices = get_uniform_frame_indices(total_num_frames, num_frames=num_frames) | ||
|
|
||
| frames = [] | ||
| container.seek(0) | ||
| end_index = indices[-1] | ||
| for i, frame in enumerate(container.decode(video=0)): | ||
| if i > end_index: | ||
| break | ||
| if i >= 0 and i in indices: | ||
| frames.append(frame) | ||
| return np.stack([x.to_ndarray(format="rgb24") for x in frames]) | ||
|
|
||
|
|
||
| def read_video_torchvision(video_path: str, num_frames: Optional[int] = None): | ||
| """ | ||
| Decode the video with torchvision decoder. | ||
|
|
||
| Args: | ||
| video_path (`str`): | ||
| Path to the video file. | ||
| num_frames (`int`, *optional*): | ||
| Number of frames to sample uniformly. If not specified, all frames are sampled. | ||
|
|
||
| Returns: | ||
| np.ndarray: np array of decoded frames of shape (num_frames, height, width, 3). | ||
| """ | ||
| video, _, info = torchvision_io.read_video( | ||
| video_path, | ||
| start_pts=0.0, | ||
| end_pts=None, | ||
| pts_unit="sec", | ||
| output_format="TCHW", | ||
| ) | ||
|
|
||
| if num_frames is not None: | ||
| idx = torch.linspace(0, video.size(0) - 1, num_frames, dtype=torch.int64) | ||
| return video[idx] | ||
|
|
||
| return video | ||
|
|
||
|
|
||
| VIDEO_DECODERS = { | ||
| "decord": read_video_decord, | ||
| "opencv": read_video_opencv, | ||
| "pyav": read_video_pyav, | ||
| "torchvision": read_video_torchvision, | ||
| } | ||
|
|
||
|
|
||
| def load_video(video: Union[str, "VideoInput"], num_frames: Optional[int] = None, backend: str = "opencv") -> np.array: | ||
| """ | ||
| Loads `video` to a numpy array. | ||
|
|
||
| Args: | ||
| video (`str` or `VideoInput`): | ||
| The video to convert to the numpy array format. Can be a link to video or local path. | ||
| num_frames (`int`, *optional*): | ||
| Number of frames to sample uniformly. If not passed, the whole video is loaded. | ||
| backend (`str`, *optional*, defaults to `"opencv"`): | ||
| The backend to use when loading the video. Can be any of ["decord", "pyav", "opencv", "torchvision"]. Defaults to "opencv". | ||
|
|
||
| Returns: | ||
| `np.array`: A numpy array of shape (num_frames, channels, height, width). | ||
| """ | ||
| if video.startswith("https://www.youtube.com") or video.startswith("http://www.youtube.com"): | ||
| if not is_yt_dlp_available(): | ||
| raise ImportError("To load a video from YouTube url you have to install `yt_dlp` first.") | ||
| buffer = BytesIO() | ||
| with redirect_stdout(buffer), YoutubeDL() as f: | ||
| f.download([video]) | ||
| bytes_obj = buffer.getvalue() | ||
| file_obj = BytesIO(bytes_obj) | ||
| elif video.startswith("http://") or video.startswith("https://"): | ||
| file_obj = BytesIO(requests.get(video).content) | ||
|
Comment on lines
+560
to
+569
Contributor
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. Some additional kwargs might be required here, e.g. timeout, but probably fine for now |
||
| elif os.path.isfile(video): | ||
| file_obj = video | ||
| elif is_valid_image(video) or (isinstance(video, (list, tuple) and is_valid_image(video[0]))): | ||
| file_obj = None | ||
| else: | ||
| raise TypeError("Incorrect format used for video. Should be an url linking to an video or a local path.") | ||
|
|
||
| # can also load with decord, but not cv2/torchvision | ||
| # both will fail in case of url links | ||
| video_is_url = video.startswith("http://") or video.startswith("https://") | ||
| if video_is_url and backend in ["opencv", "torchvision"]: | ||
| raise ValueError( | ||
| "If you are trying to load a video from URL, you can decode the video only with `pyav` or `decord` as backend" | ||
| ) | ||
|
|
||
| if file_obj is None: | ||
| return video | ||
|
|
||
| if ( | ||
| (not is_decord_available() and backend == "decord") | ||
| or (not is_av_available() and backend == "pyav") | ||
| or (not is_cv2_available() and backend == "opencv") | ||
| or (not is_torchvision_available() and backend == "torchvision") | ||
| ): | ||
| raise ImportError( | ||
| f"You chose backend={backend} for loading the video but the required library is not found in your environment " | ||
| f"Make sure to install {backend} before loading the video." | ||
| ) | ||
|
|
||
| video_decoder = VIDEO_DECODERS[backend] | ||
|
zucchini-nlp marked this conversation as resolved.
|
||
| video = video_decoder(file_obj) | ||
| return video | ||
|
|
||
|
|
||
| def load_images( | ||
| images: Union[List, Tuple, str, "PIL.Image.Image"], timeout: Optional[float] = None | ||
| ) -> Union["PIL.Image.Image", List["PIL.Image.Image"], List[List["PIL.Image.Image"]]]: | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.