Commit eb286c33 authored by Tiago de Freitas Pereira's avatar Tiago de Freitas Pereira

Merge branch 'frame-container-load-subset' into 'master'

Improve FrameContainer and FrameSelector to be more memory efficient

See merge request !40
parents fe242495 b014e4a5
Pipeline #36898 passed with stages
in 12 minutes and 43 seconds
...@@ -9,7 +9,7 @@ class Base(bob.bio.base.annotator.Annotator): ...@@ -9,7 +9,7 @@ class Base(bob.bio.base.annotator.Annotator):
---------- ----------
frame_selector : :any:`bob.bio.video.FrameSelector` frame_selector : :any:`bob.bio.video.FrameSelector`
A frame selector class to define, which frames of the video to use. A frame selector class to define, which frames of the video to use.
read_original_data : callable read_original_data : ``callable``
A function with the signature of A function with the signature of
``data = read_original_data(biofile, directory, extension)`` ``data = read_original_data(biofile, directory, extension)``
that will be used to load the data from biofiles. By default the that will be used to load the data from biofiles. By default the
......
...@@ -28,7 +28,7 @@ class FailSafeVideo(Base): ...@@ -28,7 +28,7 @@ class FailSafeVideo(Base):
The maximum number of frames that an annotation is valid for next frames. The maximum number of frames that an annotation is valid for next frames.
This value should be positive. If you want to set max_age to infinite, This value should be positive. If you want to set max_age to infinite,
then you can use the :any:`bob.bio.video.annotator.Wrapper` instead. then you can use the :any:`bob.bio.video.annotator.Wrapper` instead.
validator : callable validator : ``callable``
A function that takes the annotations of a frame and validates it. A function that takes the annotations of a frame and validates it.
......
...@@ -10,7 +10,7 @@ def normalize_annotations(annotations, validator, max_age=-1): ...@@ -10,7 +10,7 @@ def normalize_annotations(annotations, validator, max_age=-1):
strings (starting from 0). The inside dicts contain annotations for that strings (starting from 0). The inside dicts contain annotations for that
frame. The dictionary needs to be an ordered dict in order for this to frame. The dictionary needs to be an ordered dict in order for this to
work. work.
validator : callable validator : ``callable``
Takes a dict (annotations) and returns True if the annotations are valid. Takes a dict (annotations) and returns True if the annotations are valid.
This can be a check based on minimal face size for example: see This can be a check based on minimal face size for example: see
:any:`bob.bio.face.annotator.min_face_size_validator`. :any:`bob.bio.face.annotator.min_face_size_validator`.
......
...@@ -41,6 +41,12 @@ def test_frame_container(): ...@@ -41,6 +41,12 @@ def test_frame_container():
# test as_array method # test as_array method
assert numpy.allclose(read.as_array(), test_data) assert numpy.allclose(read.as_array(), test_data)
# check loading only a part of the hdf5
with bob.io.base.HDF5File(filename) as f:
# load only a subset of the FrameContainer
fc = bob.bio.video.FrameContainer().load(f, selection_style="spread", max_number_of_frames=10)
assert len(fc) == 10, len(fc)
finally: finally:
if os.path.exists(filename): if os.path.exists(filename):
os.remove(filename) os.remove(filename)
...@@ -113,3 +119,9 @@ def test_frame_selector(): ...@@ -113,3 +119,9 @@ def test_frame_selector():
assert frames[1][0] == '6' assert frames[1][0] == '6'
assert numpy.allclose(frames[1][1], video_data[6]) assert numpy.allclose(frames[1][1], video_data[6])
assert frames[1][2] is None assert frames[1][2] is None
# test bob.io.video.reader support
path = bob.io.base.test_utils.datafile("testvideo.avi", __name__)
fs = bob.bio.video.FrameSelector(selection_style="spread", max_number_of_frames=20)
fc = fs(bob.io.video.reader(path)) # only loads 20 frames into memory
assert len(fc) == 20, len(fc)
This diff is collapsed.
...@@ -10,84 +10,93 @@ import os ...@@ -10,84 +10,93 @@ import os
import six import six
import logging import logging
logger = logging.getLogger("bob.bio.video")
from .FrameContainer import FrameContainer logger = logging.getLogger(__name__)
from .FrameContainer import FrameContainer, select_frames
class FrameSelector: class FrameSelector:
"""A class for selecting frames from videos. """A class for selecting frames from videos.
In total, up to ``max_number_of_frames`` is selected (unless selection style is ``all`` In total, up to ``max_number_of_frames`` is selected (unless selection style is ``all``
Different selection styles are supported: Different selection styles are supported:
* first : The first frames are selected * first : The first frames are selected
* spread : Frames are selected to be taken from the whole video * spread : Frames are selected to be taken from the whole video
* step : Frames are selected every ``step_size`` indices, starting at ``step_size/2`` **Think twice if you want to have that when giving FrameContainer data!** * step : Frames are selected every ``step_size`` indices, starting at ``step_size/2`` **Think twice if you want to have that when giving FrameContainer data!**
* all : All frames are stored unconditionally * all : All frames are stored unconditionally
* quality (only valid for FrameContainer data) : Select the frames based on the highest internally stored quality value * quality (only valid for FrameContainer data) : Select the frames based on the highest internally stored quality value
"""
def __init__(self,
max_number_of_frames = 20,
selection_style = "spread",
step_size = 10
):
if selection_style not in ('first', 'spread', 'step', 'all'):
raise ValueError("Unknown selection style '%s', choose one of ('first', 'spread', 'step', 'all')" % selection_style)
self.selection = selection_style
self.max_frames = max_number_of_frames
self.step = step_size
def __call__(self, data, load_function = bob.io.base.load):
"""Selects frames and returns them in a FrameContainer.
Different ``data`` parameters are accepted:
* :py:class:`FrameContainer` : frames are selected from the given frame container
* ``str`` : A video file to read and select frames from
* ``[str]`` : A list of image names to select from
* ``numpy.array`` (3D or 4D): A video to select frames from
When giving ``str`` or ``[str]`` data, the given ``load_function`` is used to read the data from file.
""" """
# if given a string, first load the video
if isinstance(data, six.string_types): def __init__(self, max_number_of_frames=20, selection_style="spread", step_size=10):
logger.debug("Loading video file '%s'", data) if selection_style not in ("first", "spread", "step", "all"):
data = load_function(data) raise ValueError(
"Unknown selection style '%s', choose one of ('first', 'spread', 'step', 'all')"
# first, get the indices % selection_style
count = len(data) )
if self.selection == 'first': self.selection = selection_style
# get the first frames (limited by all frames) self.max_frames = max_number_of_frames
indices = range(0, min(count, self.max_frames)) self.step = step_size
elif self.selection == 'spread':
# get frames lineraly spread over all frames def __call__(self, data, load_function=bob.io.base.load):
indices = bob.bio.base.selected_indices(count, self.max_frames) """Selects frames and returns them in a FrameContainer.
elif self.selection == 'step': Different ``data`` parameters are accepted:
indices = range(self.step//2, count, self.step)[:self.max_frames]
elif self.selection == 'all': * :py:class:`FrameContainer` : frames are selected from the given frame container
indices = range(0, count) * ``str`` : A video file to read and select frames from
* ``[str]`` : A list of image names to select from
# now, iterate through the data * ``numpy.array`` (3D or 4D): A video to select frames from
fc = FrameContainer() * ``bob.io.video.reader`` : An instance of bob.io.video.reader.
if isinstance(data, FrameContainer):
indices = set(indices) When giving ``str`` or ``[str]`` data, the given ``load_function`` is used to read the data from file.
# frame container data, just copy """
for i, frame in enumerate(data): # if given a string, first load the video
if i in indices: if isinstance(data, six.string_types):
fc.add(*frame) logger.debug("Loading video file '%s'", data)
elif isinstance(data, numpy.ndarray): data = load_function(data)
# select video frames
for i in indices: # first, get the indices
fc.add(i, data[i]) if isinstance(data, bob.io.video.reader):
elif isinstance(data, list): count = data.number_of_frames
for i in indices: else:
# load image count = len(data)
image = load_function(data[i])
# save image name as well indices = select_frames(
fc.add(os.path.basename(data[i]), image) count=count,
max_number_of_frames=self.max_frames,
return fc selection_style=self.selection,
step_size=self.step,
def __str__(self): )
"""Writes the parameters of the FrameSelector as a string."""
return "FrameSelector(max_number_of_frames=%d, selection_style='%s', step_size=%d)" % (self.max_frames, self.selection, self.step) # now, iterate through the data
fc = FrameContainer()
if isinstance(data, FrameContainer):
indices = set(indices)
# frame container data, just copy
for i, frame in enumerate(data):
if i in indices:
fc.add(*frame)
elif isinstance(data, bob.io.video.reader):
for i, frame in enumerate(data):
if i in indices:
fc.add(i, frame)
elif isinstance(data, numpy.ndarray):
# select video frames
for i in indices:
fc.add(i, data[i])
elif isinstance(data, list):
for i in indices:
# load image
image = load_function(data[i])
# save image name as well
fc.add(os.path.basename(data[i]), image)
return fc
def __str__(self):
"""Writes the parameters of the FrameSelector as a string."""
return (
"FrameSelector(max_number_of_frames=%d, selection_style='%s', step_size=%d)"
% (self.max_frames, self.selection, self.step)
)
from .FrameContainer import FrameContainer, load_compressed, save_compressed from .FrameContainer import FrameContainer, load_compressed, save_compressed, select_frames
from .FrameSelector import FrameSelector from .FrameSelector import FrameSelector
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment