Skip to content

[WIP] UCF101 prototype with utilities for video loading #4838

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

Open
wants to merge 52 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
9c9b27e
stash
bjuncek Oct 21, 2021
e00c095
Merge branch 'pytorch:main' into bkorbar/prototypes/ucf101
bjuncek Oct 21, 2021
914380f
base implementation
Oct 31, 2021
9dd6786
Format and add documentation to the video utilities
Nov 2, 2021
7ad8357
simple driver for Philip to play with
Nov 2, 2021
dc205e9
format ucf101 and lint stuff
Nov 2, 2021
711adf3
Update torchvision/prototype/datasets/_builtin/ucf101.py
bjuncek Nov 3, 2021
56c1779
Update torchvision/prototype/datasets/_builtin/ucf101.py
bjuncek Nov 3, 2021
65f3c64
Update torchvision/prototype/datasets/video_utils.py
bjuncek Nov 3, 2021
017e9b9
Merge branch 'main' into bkorbar/prototypes/ucf101
bjuncek Nov 3, 2021
666ca6e
Merge branch 'main' into bkorbar/prototypes/ucf101
bjuncek Nov 3, 2021
acc0e54
address https://github.com/pytorch/vision/pull/4838#pullrequestreview…
Nov 10, 2021
c209153
Update torchvision/prototype/datasets/_builtin/ucf101.py
bjuncek Nov 10, 2021
31c0eb7
Merge branch 'bkorbar/prototypes/ucf101' of https://github.com/bjunce…
Nov 10, 2021
f5eb8fd
use internal utils
Nov 10, 2021
0a66ff0
remove transform antipattern
Nov 10, 2021
d29d22b
change return/pop stuff
Nov 10, 2021
cf4f354
remove unnecessary and uncalled methods
Nov 10, 2021
52b2b67
make changes to catch up with the master
Nov 12, 2021
5e2f15d
minor flake
Nov 12, 2021
b608f6d
lint
Nov 12, 2021
4f281c4
add video default decoder
Nov 12, 2021
ab6a2b8
revert changes to the decoder
bjuncek Nov 12, 2021
9800f8e
Apply suggestions from code review
bjuncek Nov 12, 2021
64b644f
apply suggestions from code review
Nov 12, 2021
7557931
Merge branch 'bkorbar/prototypes/ucf101' of https://github.com/bjunce…
Nov 12, 2021
18eb9c0
Merge branch 'main' into bkorbar/prototypes/ucf101
pmeier Nov 12, 2021
587723e
Update torchvision/prototype/datasets/_builtin/ucf101.py
bjuncek Nov 28, 2021
a3737ab
addressing comments 1
Nov 28, 2021
8fce5ff
remove shuffler comment
Nov 28, 2021
a10a3a0
remove main.py
Nov 28, 2021
697fdfd
clange and flake being mad at me
Nov 28, 2021
ebef4f2
Merge branch 'main' into bkorbar/prototypes/ucf101
bjuncek Nov 28, 2021
62078b6
Merge branch 'main' into bkorbar/prototypes/ucf101
bjuncek Dec 1, 2021
a574089
addig type annotations
Dec 1, 2021
d809cb9
pass flake8
Dec 1, 2021
8f57ee6
Decoder typing change
Dec 1, 2021
8f21f0e
remove unused parameters
Dec 1, 2021
8dbda84
fixing _api with decoder changes
Dec 1, 2021
788d82a
build errors
Dec 1, 2021
31a8929
remove unused
Dec 1, 2021
84cdecb
Merge branch 'main' into bkorbar/prototypes/ucf101
pmeier Dec 2, 2021
4386c48
fix python lint
pmeier Dec 2, 2021
97bd457
cleanup decoder
pmeier Dec 2, 2021
4609783
mypy fix
Dec 2, 2021
1c77e6f
Merge branch 'bkorbar/prototypes/ucf101' of https://github.com/bjunce…
Dec 2, 2021
6019ce7
[DIRTY] Merge branch 'main' into bkorbar/prototypes/ucf101
pmeier Dec 16, 2021
25c3668
revert decoder changes
pmeier Dec 16, 2021
08a616c
add categories and fix data loading
pmeier Dec 16, 2021
0675649
cleanup
pmeier Dec 16, 2021
381f70e
Merge branch 'main' into bkorbar/prototypes/ucf101
pmeier Dec 19, 2021
f1a69e0
use shuffling hint
pmeier Dec 19, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from torchvision.prototype import datasets
from torchvision.prototype.datasets.video_utils import AVKeyframeReader, AVRandomFrameReader, AVClipReader



print("\n \n KEYFRAMES \n \n")
ct = 0
dataset = AVKeyframeReader(datasets.load("ucf101"))
for i in dataset:
print(i)
ct += 1
if ct > 5:
break


print("\n \n RANDOM FRAMES")
ct = 0
dataset = AVRandomFrameReader(datasets.load("ucf101"), num_samples=3)
for i in dataset:
print(i)
ct += 1
if ct > 5:
break

print("\n \n CLIPS ")
ct = 0
dataset = AVClipReader(datasets.load("ucf101"), num_frames_per_clip=16, num_clips_per_video=8)
for i in dataset:
print(i['path'], i["range"])
ct += 1
if ct > 5:
break
1 change: 1 addition & 0 deletions torchvision/prototype/datasets/_builtin/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from .caltech import Caltech101, Caltech256
from .celeba import CelebA
from .cifar import Cifar10, Cifar100
from .ucf101 import ucf101
from .coco import Coco
from .imagenet import ImageNet
from .mnist import MNIST, FashionMNIST, KMNIST, EMNIST, QMNIST
Expand Down
94 changes: 94 additions & 0 deletions torchvision/prototype/datasets/_builtin/ucf101.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import io
from pathlib import Path
from typing import Any, Callable, Dict, List, Optional, Tuple

from torchvision.prototype.datasets.utils._internal import RarArchiveReader, INFINITE_BUFFER_SIZE

import numpy as np
import torch
from torchdata.datapipes.iter import CSVParser, KeyZipper
from torch.utils.data import IterDataPipe
from torch.utils.data.datapipes.iter import (
Filter,
Mapper,
ZipArchiveReader,
Shuffler,
)
from torchvision.prototype.datasets.decoder import raw
from torchvision.prototype.datasets.utils import (
Dataset,
DatasetConfig,
DatasetInfo,
HttpResource,
OnlineResource,
DatasetType,
)


class ucf101(Dataset):
"""This is a base datapipe that returns a file handler of the video.
What we want to do is implement either several decoder options or additional
datapipe extensions to make this work.
"""
@property
def info(self) -> DatasetInfo:
return DatasetInfo(
"ucf101",
type=DatasetType.VIDEO,
valid_options={'split': ["train", "test"], 'fold': ["1", "2", "3"]},
# categories=HERE / "ucf101.categories",
homepage="https://www.crcv.ucf.edu/data/UCF101.php",
)

def resources(self, config: DatasetConfig) -> List[OnlineResource]:
return [
HttpResource(
"https://www.crcv.ucf.edu/data/UCF101/UCF101TrainTestSplits-RecognitionTask.zip",
sha256="",
),
HttpResource(
"https://www.crcv.ucf.edu/data/UCF101/UCF101.rar",
sha256="",
)
]

def _collate_and_decode(
self,
data: Tuple[np.ndarray, int],
*,
decoder: Optional[Callable[[io.IOBase], Dict[str, Any]]],
) -> Dict[str, Any]:
annotations_d, file_d = data

label = annotations_d[1]
_path, file_handle = file_d
return {"path": _path, "file": file_handle, "target": label}

def _filtername(self, data, *, tgt):
return Path(data[0]).name == tgt

def _getname(self, data):
return Path(data[0]).name

def _make_datapipe(
self,
resource_dps: List[IterDataPipe],
*,
config: DatasetConfig,
decoder: Optional[Callable[[io.IOBase], torch.Tensor]],
) -> IterDataPipe[Dict[str, Any]]:

annotations = resource_dps[0]
files = resource_dps[1]

annotations_dp = ZipArchiveReader(annotations)
annotations_dp = Filter(annotations_dp,
self._filtername,
fn_kwargs=dict(tgt=f"{config.split}list0{config.fold}.txt"))
annotations_dp = CSVParser(annotations_dp, delimiter=" ")
# COMMENT OUT FOR TESTING
Copy link
Collaborator

Choose a reason for hiding this comment

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

True, but should be removed before merge.

Copy link
Member

Choose a reason for hiding this comment

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

I would really want our datasets to be deterministic outside of a DataLoader though. Making it stochastic will make it much harder to debug.

Maybe what we should do instead is have a new DataLoaderOnlyShuffler which is a no-op in general, and inside the dataloader it can be activated if shuffle is true.

Thoughts?

cc @VitalyFedyunin @ejguan

Copy link
Collaborator

Choose a reason for hiding this comment

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

annotations_dp = Shuffler(annotations_dp, buffer_size=INFINITE_BUFFER_SIZE)

files_dp = RarArchiveReader(files)
dp = KeyZipper(annotations_dp, files_dp, self._getname, self._getname)
return Mapper(dp, self._collate_and_decode, fn_kwargs=dict(decoder=decoder))
17 changes: 17 additions & 0 deletions torchvision/prototype/datasets/decoder.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import io
import unittest.mock
from typing import Dict, Any

import av
import PIL.Image
import torch
from torchvision.transforms.functional import pil_to_tensor
Expand All @@ -13,3 +16,17 @@ def raw(buffer: io.IOBase) -> torch.Tensor:

def pil(buffer: io.IOBase, mode: str = "RGB") -> torch.Tensor:
return pil_to_tensor(PIL.Image.open(buffer).convert(mode.upper()))


def av_kf(buffer: io.IOBase, **read_video_kwargs: Any) -> Dict[str, Any]:
with unittest.mock.patch("torchvision.io.video.os.path.exists", return_value=True):
keyframes, pts = [], []
with av.open(buffer) as container:
stream = container.streams.video[0]
stream.codec_context.skip_frame = 'NONKEY'
for frame in container.decode(stream):
keyframes.append(frame.to_image())
# TODO: convert to seconds
pts.append(frame.pts)

return {"keyframes": keyframes, "pts": pts}
5 changes: 3 additions & 2 deletions torchvision/prototype/datasets/utils/_dataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
class DatasetType(enum.Enum):
RAW = enum.auto()
IMAGE = enum.auto()
VIDEO = enum.auto()


class DatasetConfig(FrozenBunch):
Expand Down Expand Up @@ -148,7 +149,7 @@ def _make_datapipe(
resource_dps: List[IterDataPipe],
*,
config: DatasetConfig,
decoder: Optional[Callable[[io.IOBase], torch.Tensor]],
decoder: Optional[Callable[[io.IOBase], Dict[str, Any]]],
) -> IterDataPipe[Dict[str, Any]]:
pass

Expand All @@ -157,7 +158,7 @@ def to_datapipe(
root: Union[str, pathlib.Path],
*,
config: Optional[DatasetConfig] = None,
decoder: Optional[Callable[[io.IOBase], torch.Tensor]] = None,
decoder: Optional[Callable[[io.IOBase], Dict[str, Any]]] = None,
) -> IterDataPipe[Dict[str, Any]]:
if not config:
config = self.info.default_config
Expand Down
35 changes: 35 additions & 0 deletions torchvision/prototype/datasets/utils/_internal.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
"path_accessor",
"path_comparator",
"Decompressor",
"RarArchiveReader",
]

K = TypeVar("K")
Expand Down Expand Up @@ -277,3 +278,37 @@ def __iter__(self) -> Iterator[Tuple[str, io.IOBase]]:
type = self._detect_compression_type(path)
decompressor = self._DECOMPRESSORS[type]
yield path, decompressor(file)


class RarArchiveReader(IterDataPipe[Tuple[str, io.BufferedIOBase]]):
def __init__(self, datapipe: IterDataPipe[Tuple[str, io.BufferedIOBase]]):
self._rarfile = self._verify_dependencies()
super().__init__()
self.datapipe = datapipe

@staticmethod
def _verify_dependencies():
try:
import rarfile
except ImportError as error:
raise ModuleNotFoundError(
"Package `rarfile` is required to be installed to use this datapipe. "
"Please use `pip install rarfile` or `conda -c conda-forge install rarfile` to install it."
) from error

# check if at least one system library for reading rar archives is available to be used by rarfile
rarfile.tool_setup()

return rarfile

def __iter__(self) -> Iterator[Tuple[str, io.BufferedIOBase]]:
for path, stream in self.datapipe:
rar = self._rarfile.RarFile(stream)
for info in rar.infolist():
if info.filename.endswith("/"):
continue

inner_path = os.path.join(path, info.filename)
file_obj = rar.open(info)

yield inner_path, file_obj
Loading