Commit 8358ea0a authored by Vincent POLLET's avatar Vincent POLLET

[DOC] Documentation of utils.py, unit tests for utils.py

parent 5ea323d0
Pipeline #43890 passed with stage
in 13 minutes and 45 seconds
#!/usr/bin/env python
# vim: set fileencoding=utf-8 :
"""
Test Units
"""
# ==============================================================================
import numpy as np
from bob.io.stream.utils import get_axis_size, get_index_list, rotate_data
# ==============================================================================
def test_rotate_data():
"""Unit tests for `~bob.io.stream.utils.rotate_data`."""
# 2D
test_array = np.array([[1, 2], [3, 4]])
assert np.array_equal(rotate_data(test_array, 90), np.array([[3, 1], [4, 2]]))
assert np.array_equal(rotate_data(test_array, 180), np.array([[2, 1], [4, 3]]))
assert np.array_equal(rotate_data(test_array, -90), np.array([[2, 4], [1, 3]]))
assert np.array_equal(rotate_data(test_array, 270), np.array([[2, 4], [1, 3]]))
assert np.array_equal(rotate_data(test_array, 0), test_array)
# 3D
test_array = np.arange(12).reshape(2, 3, 2)
assert np.array_equal(rotate_data(test_array, 90), np.array([[[4, 2, 0], [5, 3, 1]], [[10, 8, 6], [11, 9, 7]]]))
assert np.array_equal(
rotate_data(test_array, 180), np.array([[[1, 0], [3, 2], [5, 4]], [[7, 6], [9, 8], [11, 10]]])
)
assert np.array_equal(rotate_data(test_array, -90), np.array([[[1, 3, 5], [0, 2, 4]], [[7, 9, 11], [6, 8, 10]]]))
assert np.array_equal(rotate_data(test_array, 270), np.array([[[1, 3, 5], [0, 2, 4]], [[7, 9, 11], [6, 8, 10]]]))
assert np.array_equal(rotate_data(test_array, 0), test_array)
def test_get_axis_size():
"""Unit tests for `~bob.io.stream.utils.get_axis_size`."""
test_array = np.arange(15000).reshape(10, 3, 50, 10)
assert test_array[:].shape[0] == get_axis_size(test_array.shape, 0, None)
assert 1 == get_axis_size(test_array.shape, 0, 0)
assert test_array[::3].shape[0] == get_axis_size(test_array.shape, 0, slice(None, None, 3))
assert test_array[3::3].shape[0] == get_axis_size(test_array.shape, 0, slice(3, None, 3))
assert test_array[3:9:3].shape[0] == get_axis_size(test_array.shape, 0, slice(3, 9, 3))
assert test_array[9:3].shape[0] == get_axis_size(test_array.shape, 0, slice(9, 3, None))
assert test_array[-6:-3:2].shape[0] == get_axis_size(test_array.shape, 0, slice(-6, -3, 2))
assert test_array[-3:-6:2].shape[0] == get_axis_size(test_array.shape, 0, slice(-3, -6, 2))
assert test_array[3:12].shape[0] == get_axis_size(test_array.shape, 0, slice(3, 12, None))
assert test_array[3:12].shape[0] == get_axis_size(test_array.shape, 0, slice(3, 12, None))
assert test_array[:, :, 3:12, :].shape[2] == get_axis_size(test_array.shape, 2, slice(3, 12, None))
assert test_array[:, :, -24:-1:5, :].shape[2] == get_axis_size(test_array.shape, 2, slice(-24, -1, 5))
assert test_array[:, :, 3:12:4, :].shape[2] == get_axis_size(test_array.shape, 2, slice(3, 12, 4))
assert test_array[:, :, :, 3:37:3].shape[3] == get_axis_size(test_array.shape, 3, slice(3, 37, 3))
assert test_array[:, :, :, -16::3].shape[3] == get_axis_size(test_array.shape, 3, slice(-16, None, 3))
assert test_array[:, :, :, -16:-16:3].shape[3] == get_axis_size(test_array.shape, 3, slice(-16, -16, 3))
def test_get_index_list():
"""Unit tests for `~bob.io.stream.utils.get_index_list`."""
test_array = np.arange(15000).reshape(10, 3, 50, 10)
assert np.array_equal(test_array[:], test_array[get_index_list(None, test_array.shape[0])])
assert [3] == get_index_list(3, test_array.shape[0])
assert [2], get_index_list(2, test_array.shape[1])
assert np.array_equal(
test_array[..., 2:6], test_array[..., get_index_list(slice(2, 6, None), test_array.shape[-1])]
)
assert np.array_equal(test_array[..., 2:9:3], test_array[..., get_index_list(slice(2, 9, 3), test_array.shape[-1])])
assert np.array_equal(
test_array[..., -8:-2], test_array[..., get_index_list(slice(-8, -2, None), test_array.shape[-1])]
)
assert np.array_equal(
test_array[..., 12:14], test_array[..., get_index_list(slice(12, 14, None), test_array.shape[-1])]
)
assert np.array_equal(
test_array[..., -2:-3], test_array[..., get_index_list(slice(-2, -3, None), test_array.shape[-1])]
)
assert np.array_equal(
test_array[..., -9:-3:-2], test_array[..., get_index_list(slice(-9, -3, -2), test_array.shape[-1])]
)
assert np.array_equal(
test_array[..., -12:-10:-2], test_array[..., get_index_list(slice(-12, -10, -2), test_array.shape[-1])]
)
assert np.array_equal(test_array[..., [1, 2, 3]], test_array[..., get_index_list([1, 2, 3], test_array.shape[-1])])
import numpy as np
import cv2 as cv
# convert from OpenCV's <h, w, BGR> to bob's <RGB, h, w> and vice versa
def convert_bob_to_cv(img):
assert len(img.shape) == 3
assert img.shape[0] == 3
"""Convert image from Bob's <RGB, h, w> format to OpenCV's <h, w, BGR> format.
Parameters
----------
img : :obj:`numpy.ndarray`
Image in Bob's format <RGB, h, w>.
Returns
-------
:obj:`numpy.ndarray`
Image in OpenCV's format <h, w, BGR>.
Raises
------
ValueError
If the input image has the wrong number of dimensions. (Must be 3).
ValueError
If the input image does not have 3 channels in first dimension.
"""
if len(img.shape) != 3:
raise ValueError("Expected image to be 3D object, but got shape " + str(img.shape))
if img.shape[0] != 3:
raise ValueError("Expected image to have 3 color channels, but got " + str(img.shape[0]) + " channels.")
img = np.rollaxis(img, 2)
img = np.flip(img, axis=2)
return img
def convert_cv_to_bob(img):
assert len(img.shape) == 3
assert img.shape[2] == 3
"""Convert image from OpenCV's <h, w, BGR> format to Bob's <RGB, h, w> format.
Parameters
----------
img : :obj:`numpy.ndarray`
Image in OpenCV's <h, w, BGR> format.
Returns
-------
:obj:`numpy.ndarray`
Image in Bob's <RGB, h, w> format.
Raises
------
ValueError
If the input image has the wrong number of dimension. (Must be 3).
ValueError
If the input image does not have 3 channels in the last dimension.
"""
if len(img.shape) != 3:
raise ValueError("Expected image to have 3 dimension, but got shape " + str(img.shape))
if img.shape[2] != 3:
raise ValueError("Expected image to have 3 color channels, but got " + str(img.shape[2]) + " channels.")
img = np.rollaxis(img, 2)
img = np.flip(img, axis=0)
return img
# rotate
def rotate_data(data, angle):
if angle == 90:
data = data.swapaxes(-2,-1)[...,::-1]
"""Rotate the `data` array by `angle`, where `angle` is a multiple of a square angle.
`data` must at least have 2 dimension. If it has more, the rotation operates on the last dimensions.
Parameters
----------
data : :obj:`numpy.ndarray`
Array to rotate.
angle : int
Angle by which to rotate `data`.
Returns
-------
:obj:`numpy.ndarray`
Rotated array.
Raises
------
ValueError
If `angle` is not a supported multiple of a square angle.
"""
if angle not in (-90, 0, 90, 180, 270):
raise ValueError("angle must be a multiple of a square angle. Accepted values: -90, 0, 90, 180, 270.")
if angle == 0:
pass
elif angle == 90:
data = data.swapaxes(-2, -1)[..., ::-1]
elif angle == 180:
data = data[...,::-1]
data = data[..., ::-1]
elif angle in (-90, 270):
data = data.swapaxes(-2,-1)[...,::-1,:]
else:
pass
data = data.swapaxes(-2, -1)[..., ::-1, :]
return data
def _crop(video, mask, crop):
"""Crops each image in `video` and associated `mask` to region defined by `crop`.
Parameters
----------
video : :obj:`numpy.ndarray`
Video to crop.
mask : :obj:`numpy.ndarray`
Mask associated with`video`.
crop : tuple
(start_x, start_y, width, height): the two first elements are the starting coordinates of the region to keep in
`video`. The 2 last elements are the width and heigth of the region to keep.
Returns
-------
:obj:`tuple` of :obj:`numpy.ndarray`
Cropped video and associated mask.
"""
startx = crop[0]
starty = crop[1]
width = crop[2]
height = crop[3]
if len(video.shape) == 4:
video = video[:, :, starty:starty + height, startx:startx + width]
else:
video = video[:, starty:starty + height, startx:startx + width]
mask = mask[:, starty:starty + height, startx:startx + width]
video = video[..., starty : starty + height, startx : startx + width]
mask = mask[..., starty : starty + height, startx : startx + width]
return video, mask
################################################################################
# return a list of indices given an index (int / slice / list / None) and a size
# TODO: check all boundary cases
def get_index_list(index, size):
# None index is equivalent to [:] i.e. slice(None, None, None)
"""From an indexing value of type int, slice, list or None, generates the equivalent list of indices in a 1d array.
Parameters
----------
index : int or slice or list or None
Indexing value in an array. Eg: 2 for array[2], slice(None, None, 2) for array[::2], ...
Only 1d index are supported.
size : int
Size of the array that is indexed.
Returns
-------
list of int
Equivalent list of indices to `index`.
Raises
------
ValueError
If `index` is not of a supported type.
"""
# None index is equivalent to [:] i.e. slice(None, None, None)
if index is None:
index = slice(None, None, None)
index = slice(None, None, None)
# frame index transform to list
if isinstance(index, int):
indices = [index]
......@@ -61,7 +160,7 @@ def get_index_list(index, size):
start = index.start
# boundary case
if start >= size:
start = size - 1
start = size - 1
else:
start = 0
# stop value: handle None and negative
......@@ -72,10 +171,10 @@ def get_index_list(index, size):
stop = index.stop
# boundary case
if stop >= size:
stop = size - 1
stop = size - 1
else:
stop = size
# step value: handle None
# step value: handle None
if index.step is not None:
step = index.step
else:
......@@ -86,44 +185,84 @@ def get_index_list(index, size):
elif isinstance(index, list):
indices = index
else:
raise Exception("index can only be None, int, slice or list")
return indices
raise ValueError("index can only be None, int, slice or list, but got " + str(type(index)))
return indices
################################################################################
# get size of a given (sliced) axis
def get_axis_size(shape, axis, _slice=None):
if _slice is None:
def get_axis_size(shape, axis, indices=None):
"""Given the `shape` of an array, returns the dimension along `axis` of that array after `indices` are taken into
the array along `axis`.
Parameters
----------
shape : tuple of int
Shape of an array.
axis : int
Axis on which `indices` operate.
indices : int or slice or None
The indices taken in an array with shape `shape`, on axis `axis`, by default None
Returns
-------
int
Size of an array along `axis` after `indices` are taken.
Raises
------
ValueError
If `indices` does not have a supported type.
Examples
--------
This function is used to know the size of an array after slicing into it, which is usefull when the actual slicing
operation is delayed as sometimes in the `~bob.io.stream.Stream` class.
>>> a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
>>> a[...,1:].shape[1]
2
>>> get_axis_size(a.shape, 1, slice(1, None, None))
2
"""
if indices is None:
return shape[axis]
else:
if isinstance(_slice, int):
if isinstance(indices, int):
return 1
elif isinstance(_slice, slice):
# TODO check this works always
return len(range(*_slice.indices(shape[axis])))
elif isinstance(indices, slice):
return len(range(*indices.indices(shape[axis])))
else:
raise Exception('_slice can be None, int or slice')
raise ValueError("`indices` can only be None, int or slice, but got " + str(type(indices)))
################################################################################
class StreamArray():
class StreamArray:
"""Class to associate data to a `~bob.io.stream.Stream`, for instance bounding boxes to a video stream.
This class allows to set the value of the data array (eg the bounding box at some or each frame of a stream) without
having to care about the shape of the stream.
If the data is not initialized, it will return None.
"""
def __init__(self, stream):
"""Set link to stream to access its shape.
Parameters
----------
stream : :obj:`~bob.io.stream.Stream`
The stream to which this array of data is associated.
"""
self.__stream = stream
self.__data = None
def __getitem__(self, index):
# TODO handle slices too
assert isinstance(index, int)
# no value is array not initialised
# no value is array not initialised
if self.__data is None:
return None
else:
return self.__data[index]
def __setitem__(self, index, data):
# TODO handle slices too
assert isinstance(index, int)
# initialise array if needed
if self.__data is None:
self.__data = [None for i in range(self.__stream.shape[0])]
assert len(self.__data) == self.__stream.shape[0]
self.__data[index] = data
\ No newline at end of file
self.__data[index] = data
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