Skip to content
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

Video importing warnings #698

Merged
merged 6 commits into from May 20, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion conda/meta.yaml
Expand Up @@ -26,7 +26,7 @@ requirements:
- matplotlib >=1.4,<2.0

# Test dependencies
- mock 1.3.*
- mock
- nose

test:
Expand Down
2 changes: 2 additions & 0 deletions docs/.gitignore
@@ -1 +1,3 @@
_build/*
source/_build/*
source/api/generated/*
24 changes: 11 additions & 13 deletions docs/rtd_environment.yml
Expand Up @@ -4,20 +4,18 @@ channels:
dependencies:
- python
- setuptools
- numpy>=1.10,<1.11
- cython=0.23.*
- cython>=0.23
- pathlib=1.0

- numpy>=1.10,<1.11
- scipy=0.17.*
- pillow=3.0.*
- cyvlfeat>=0.4.2,<0.5
- imageio>=1.5.0,<1.6.0
- matplotlib>=1.4,<1.6
- mock=1.3.*
- numpy>=1.10,<2.0
- scipy>=0.16,<1.0
- pillow>=3.0,<4.0
- imageio>=1.5,<2.0
- cyvlfeat>=0.4.3,<0.5
- matplotlib>=1.4,<2.0
- mock
- nose
- pip:
- sphinx==1.3.1
- sphinx_rtd_theme==0.1.7
- sphinxmapxrefrole==0.2.1

- sphinx
- sphinx_rtd_theme
- sphinxmapxrefrole>=0.2
7 changes: 7 additions & 0 deletions docs/source/api/menpo/io/import_video.rst
@@ -0,0 +1,7 @@
.. _menpo-io-import_video:

.. currentmodule:: menpo.io

import_video
============
.. autofunction:: import_video
7 changes: 7 additions & 0 deletions docs/source/api/menpo/io/import_videos.rst
@@ -0,0 +1,7 @@
.. _menpo-io-import_videos:

.. currentmodule:: menpo.io

import_videos
=============
.. autofunction:: import_videos
4 changes: 4 additions & 0 deletions docs/source/api/menpo/io/index.rst
Expand Up @@ -11,6 +11,8 @@ Input

import_image
import_images
import_video
import_videos
import_landmark_file
import_landmark_files
import_pickle
Expand All @@ -37,6 +39,8 @@ Path Operations

image_paths
landmark_file_paths
pickle_paths
video_paths
data_path_to
data_dir_path
ls_builtin_assets
7 changes: 7 additions & 0 deletions docs/source/api/menpo/io/pickle_paths.rst
@@ -0,0 +1,7 @@
.. _menpo-io-pickle_paths:

.. currentmodule:: menpo.io

pickle_paths
============
.. autofunction:: pickle_paths
7 changes: 7 additions & 0 deletions docs/source/api/menpo/io/video_paths.rst
@@ -0,0 +1,7 @@
.. _menpo-io-video_paths:

.. currentmodule:: menpo.io

video_paths
===========
.. autofunction:: video_paths
2 changes: 1 addition & 1 deletion docs/sphinxext/__init__.py
@@ -1 +1 @@
import ref_prettify
from . import ref_prettify
3 changes: 2 additions & 1 deletion docs/xref_map.py
Expand Up @@ -69,5 +69,6 @@
'Viewable': ('class', 'menpo.visualize.Viewable'),
'view_patches': ('function', 'menpo.visualize.view_patches'),
'VComposable': ('class', 'menpo.transform.VComposable'),
'VInvertible': ('class', 'menpo.transform.VInvertible')
'VInvertible': ('class', 'menpo.transform.VInvertible'),
'video_paths': ('function', 'menpo.io.video_paths'),
}
32 changes: 28 additions & 4 deletions menpo/io/input/base.py
Expand Up @@ -119,6 +119,15 @@ def import_video(filepath, landmark_resolver=same_name_video, normalise=True,
extension of the landmark file appended with the frame number, although this
behavior can be customised (see `landmark_resolver`).

.. warning::

This method currently uses imageio to perform the importing in
conjunction with the ffmpeg plugin. As of this release, and the release
of imageio at the time of writing (1.5.0), the per-frame computation
is not very accurate. This may cause errors when importing frames
that do not actually map to valid timestamps within the image.
Therefore, use this method at your own risk.

Parameters
----------
filepath : `pathlib.Path` or `str`
Expand Down Expand Up @@ -147,6 +156,12 @@ def import_video(filepath, landmark_resolver=same_name_video, normalise=True,
An lazy list of :map:`Image` or subclass thereof which wraps the frames
of the video. This list can be treated as a normal list, but the frame
is only read when the video is indexed or iterated.

Examples
--------
>>> video = menpo.io.import_video('video.avi')
>>> # Lazily load the 100th frame without reading the entire video
>>> frame100 = video[100]
"""
kwargs = {'normalise': normalise}

Expand Down Expand Up @@ -269,10 +284,10 @@ def import_images(pattern, max_images=None, shuffle=False,
--------
Import images at 20% scale from a huge collection:

>>> images = []
>>> for img in menpo.io.import_images('./massive_image_db/*'):
>>> # rescale to a sensible size as we go
>>> images.append(img.rescale(0.2))
>>> rescale_20p = lambda x: x.rescale(0.2)
>>> images = menpo.io.import_images('./massive_image_db/*') # Returns immediately
>>> images.map(rescale_20p) # Returns immediately
>>> images[0] # Get the first image, resize, lazily loaded
"""
kwargs = {'normalise': normalise}
return _import_glob_lazy_list(
Expand Down Expand Up @@ -302,6 +317,15 @@ def import_videos(pattern, max_videos=None, shuffle=False,
will load an image at run time. If all images should be loaded, then simply
wrap the returned :map:`LazyList` in a Python `list`.

.. warning::

This method currently uses imageio to perform the importing in
conjunction with the ffmpeg plugin. As of this release, and the release
of imageio at the time of writing (1.5.0), the per-frame computation
is not very accurate. This may cause errors when importing frames
that do not actually map to valid timestamps within the image.
Therefore, use this method at your own risk.

Parameters
----------
pattern : `str`
Expand Down
42 changes: 42 additions & 0 deletions menpo/io/input/video.py
@@ -1,3 +1,4 @@
import warnings
from functools import partial

from menpo.image.base import normalize_pixels_range, channels_to_front
Expand Down Expand Up @@ -55,4 +56,45 @@ def imageio_to_menpo(imio_reader, index):
ll = LazyList.init_from_index_callable(index_callable,
reader.get_length())
ll.fps = reader.get_meta_data()['fps']

if len(ll) != 0:
# TODO: Remove when imageio fixes the ffmpeg importer duration/start
# This is a bit grim but the frame->timestamp logic in imageio at
# the moment is not very accurate and so we really need to ensure
# that the user is returned a list they can actually index into. So
# we just remove all the frames that we can't actually index into.
# Remove from the front
for start in range(len(ll)):
if start > 10: # Arbitrary but probably safe
warnings.warn('Highly inaccurate frame->timestamp mapping '
'returned by imageio - many frames are being '
'dropped and thus importing may be very slow.'
' Please see the documentation.')
try:
ll[start]
break
except:
pass
else:
# If we never broke out then ALL frames raised exceptions
ll = LazyList([])
# Only take the frames after the initial broken ones
ll = ll[start:]

if len(ll) != 0:
n_frames = len(ll) - 1
for end in range(n_frames, -1, -1):
if end < n_frames - 10: # Arbitrary but probably safe
warnings.warn('Highly inaccurate frame->timestamp mapping '
'returned by imageio - many frames are being '
'dropped and thus importing may be very slow.'
' Please see the documentation.')
try:
ll[end]
break
except:
pass
# Only take the frames before the broken ones
ll = ll[:end+1]

return ll
97 changes: 97 additions & 0 deletions menpo/io/test/io_import_test.py
Expand Up @@ -511,6 +511,103 @@ def test_importing_pickles_as_generator(is_file, glob, mock_open, mock_pickle):
assert objs[1]['test'] == 1


# TODO: Remove when imageio is fixed
@patch('imageio.get_reader')
@patch('menpo.io.input.base.Path.is_file')
def test_importing_imageio_ffmpeg_bad_frames(is_file, mock_reader):
def fake_get_data(index):
if index not in [1, 2]:
raise ValueError()
f = np.ones((10, 10, 3)) * -1
f[0, 0, 0] = index
return f

mock_reader.return_value.get_length.return_value = 4
mock_reader.return_value.get_data.side_effect = fake_get_data
is_file.return_value = True

ll = mio.import_video('fake_image_being_mocked.avi', normalise=False)
assert len(ll) == 2

im = ll[0]
assert im.shape == (10, 10)
assert im.pixels[0, 0, 0] == 1


# TODO: Remove when imageio is fixed
@patch('imageio.get_reader')
@patch('menpo.io.input.base.Path.is_file')
def test_importing_imageio_ffmpeg_many_bad_frames_warning_start(is_file, mock_reader):
def fake_get_data(index):
if index < 11:
raise ValueError()
f = np.ones((10, 10, 3)) * -1
f[0, 0, 0] = index
return f

mock_reader.return_value.get_length.return_value = 15
mock_reader.return_value.get_data.side_effect = fake_get_data
is_file.return_value = True

with warnings.catch_warnings(record=True) as w:
ll = mio.import_video('fake_image_being_mocked.avi', normalise=False)
assert len(w) == 1
assert len(ll) == 4

im = ll[0]
assert im.shape == (10, 10)
assert im.pixels[0, 0, 0] == 11


# TODO: Remove when imageio is fixed
@patch('imageio.get_reader')
@patch('menpo.io.input.base.Path.is_file')
def test_importing_imageio_ffmpeg_many_bad_frames_warning_end(is_file, mock_reader):
def fake_get_data(index):
if index > 3:
raise ValueError()
f = np.ones((10, 10, 3)) * -1
f[0, 0, 0] = index
return f

mock_reader.return_value.get_length.return_value = 15
mock_reader.return_value.get_data.side_effect = fake_get_data
is_file.return_value = True

with warnings.catch_warnings(record=True) as w:
ll = mio.import_video('fake_image_being_mocked.avi', normalise=False)
assert len(w) == 1
assert len(ll) == 4

assert ll[0].pixels[0, 0, 0] == 0
assert ll[-1].pixels[0, 0, 0] == 3


# TODO: Remove when imageio is fixed
@patch('imageio.get_reader')
@patch('menpo.io.input.base.Path.is_file')
def test_importing_imageio_ffmpeg_all_bad_frames(is_file, mock_reader):
def fake_get_data(_):
raise ValueError()

mock_reader.return_value.get_length.return_value = 2
mock_reader.return_value.get_data.side_effect = fake_get_data
is_file.return_value = True

ll = mio.import_video('fake_image_being_mocked.avi')
assert len(ll) == 0


@patch('imageio.get_reader')
@patch('menpo.io.input.base.Path.is_file')
def test_importing_imageio_avi_no_frames(is_file, mock_image):
mock_image.return_value.get_length.return_value = 0
is_file.return_value = True

ll = mio.import_video('fake_image_being_mocked.avi')
assert len(ll) == 0


@patch('imageio.get_reader')
@patch('menpo.io.input.base.Path.is_file')
def test_importing_imageio_avi_normalise(is_file, mock_image):
Expand Down
9 changes: 7 additions & 2 deletions menpo/transform/homogeneous/affine.py
Expand Up @@ -109,9 +109,12 @@ def decompose(self):
Returns
-------
transforms : `list` of :map:`DiscreteAffine`
Equivalent to this affine transform, such that::
Equivalent to this affine transform, such that

.. code-block:: python

reduce(lambda x, y: x.chain(y), self.decompose()) == self

reduce(lambda x,y: x.chain(y), self.decompose()) == self
"""
from .rotation import Rotation
from .translation import Translation
Expand Down Expand Up @@ -168,11 +171,13 @@ def n_parameters(self):
[p1, p3, p5]
[p2, p4, p6]


3D Affine: 12 parameters::

[p1, p4, p7, p10]
[p2, p5, p8, p11]
[p3, p6, p9, p12]

"""
return self.n_dims * (self.n_dims + 1)

Expand Down
5 changes: 3 additions & 2 deletions menpo/transform/homogeneous/similarity.py
Expand Up @@ -55,8 +55,9 @@ def _transform_str(self):

@property
def n_parameters(self):
r"""
2D Similarity: 4 parameters::
r"""Number of parameters of Similarity

2D Similarity - 4 parameters ::

[(1 + a), -b, tx]
[b, (1 + a), ty]
Expand Down