|
|
|
"""Contains utility functions used for formatting.""" |
|
|
|
import cv2 |
|
import numpy as np |
|
|
|
__all__ = [ |
|
'format_time', 'format_range', 'format_image_size', 'format_image', |
|
'raw_label_to_one_hot', 'one_hot_to_raw_label' |
|
] |
|
|
|
|
|
def format_time(seconds): |
|
"""Formats seconds to readable time string. |
|
|
|
Args: |
|
seconds: Number of seconds to format. |
|
|
|
Returns: |
|
The formatted time string. |
|
|
|
Raises: |
|
ValueError: If the input `seconds` is less than 0. |
|
""" |
|
if seconds < 0: |
|
raise ValueError(f'Input `seconds` should be greater than or equal to ' |
|
f'0, but `{seconds}` is received!') |
|
|
|
|
|
if seconds < 10: |
|
return f'{seconds:7.3f} s' |
|
if seconds < 60: |
|
return f'{seconds:7.2f} s' |
|
|
|
seconds = int(seconds + 0.5) |
|
days, seconds = divmod(seconds, 86400) |
|
hours, seconds = divmod(seconds, 3600) |
|
minutes, seconds = divmod(seconds, 60) |
|
if days: |
|
return f'{days:2d} d {hours:02d} h' |
|
if hours: |
|
return f'{hours:2d} h {minutes:02d} m' |
|
return f'{minutes:2d} m {seconds:02d} s' |
|
|
|
|
|
def format_range(obj, min_val=None, max_val=None): |
|
"""Formats the given object to a valid range. |
|
|
|
If `min_val` or `max_val` is provided, both the starting value and the end |
|
value will be clamped to range `[min_val, max_val]`. |
|
|
|
NOTE: (a, b) is regarded as a valid range if and only if `a <= b`. |
|
|
|
Args: |
|
obj: The input object to format. |
|
min_val: The minimum value to cut off the input range. If not provided, |
|
the default minimum value is negative infinity. (default: None) |
|
max_val: The maximum value to cut off the input range. If not provided, |
|
the default maximum value is infinity. (default: None) |
|
|
|
Returns: |
|
A two-elements tuple, indicating the start and the end of the range. |
|
|
|
Raises: |
|
ValueError: If the input object is an invalid range. |
|
""" |
|
if not isinstance(obj, (tuple, list)): |
|
raise ValueError(f'Input object must be a tuple or a list, ' |
|
f'but `{type(obj)}` received!') |
|
if len(obj) != 2: |
|
raise ValueError(f'Input object is expected to contain two elements, ' |
|
f'but `{len(obj)}` received!') |
|
if obj[0] > obj[1]: |
|
raise ValueError(f'The second element is expected to be equal to or ' |
|
f'greater than the first one, ' |
|
f'but `({obj[0]}, {obj[1]})` received!') |
|
|
|
obj = list(obj) |
|
if min_val is not None: |
|
obj[0] = max(obj[0], min_val) |
|
obj[1] = max(obj[1], min_val) |
|
if max_val is not None: |
|
obj[0] = min(obj[0], max_val) |
|
obj[1] = min(obj[1], max_val) |
|
return tuple(obj) |
|
|
|
|
|
def format_image_size(size): |
|
"""Formats the given image size to a two-element tuple. |
|
|
|
A valid image size can be an integer, indicating both the height and the |
|
width, OR can be a two-element list or tuple. Both height and width are |
|
assumed to be positive integer. |
|
|
|
Args: |
|
size: The input size to format. |
|
|
|
Returns: |
|
A two-elements tuple, indicating the height and the width, respectively. |
|
|
|
Raises: |
|
ValueError: If the input size is invalid. |
|
""" |
|
if not isinstance(size, (int, tuple, list)): |
|
raise ValueError(f'Input size must be an integer, a tuple, or a list, ' |
|
f'but `{type(size)}` received!') |
|
if isinstance(size, int): |
|
size = (size, size) |
|
else: |
|
if len(size) == 1: |
|
size = (size[0], size[0]) |
|
if not len(size) == 2: |
|
raise ValueError(f'Input size is expected to have two numbers at ' |
|
f'most, but `{len(size)}` numbers received!') |
|
if not isinstance(size[0], int) or size[0] < 0: |
|
raise ValueError(f'The height is expected to be a non-negative ' |
|
f'integer, but `{size[0]}` received!') |
|
if not isinstance(size[1], int) or size[1] < 0: |
|
raise ValueError(f'The width is expected to be a non-negative ' |
|
f'integer, but `{size[1]}` received!') |
|
return tuple(size) |
|
|
|
|
|
def format_image(image): |
|
"""Formats an image read from `cv2`. |
|
|
|
NOTE: This function will always return a 3-dimensional image (i.e., with |
|
shape [H, W, C]) in pixel range [0, 255]. For color images, the channel |
|
order of the input is expected to be with `BGR` or `BGRA`, which is the |
|
raw image decoded by `cv2`; while the channel order of the output is set to |
|
`RGB` or `RGBA` by default. |
|
|
|
Args: |
|
image: `np.ndarray`, an image read by `cv2.imread()` or |
|
`cv2.imdecode()`. |
|
|
|
Returns: |
|
An image with shape [H, W, C] (where `C = 1` for grayscale image). |
|
""" |
|
if image.ndim == 2: |
|
image = image[:, :, np.newaxis] |
|
|
|
assert isinstance(image, np.ndarray) |
|
assert image.dtype == np.uint8 |
|
assert image.ndim == 3 and image.shape[2] in [1, 3, 4] |
|
|
|
if image.shape[2] == 3: |
|
return cv2.cvtColor(image, cv2.COLOR_BGR2RGB) |
|
if image.shape[2] == 4: |
|
return cv2.cvtColor(image, cv2.COLOR_BGRA2RGBA) |
|
return image |
|
|
|
|
|
def raw_label_to_one_hot(raw_label, num_classes): |
|
"""Converts a single label into one-hot vector. |
|
|
|
Args: |
|
raw_label: The raw label. |
|
num_classes: Total number of classes. |
|
|
|
Returns: |
|
one-hot vector of the given raw label. |
|
""" |
|
one_hot = np.zeros(num_classes, dtype=np.float32) |
|
one_hot[raw_label] = 1.0 |
|
return one_hot |
|
|
|
|
|
def one_hot_to_raw_label(one_hot): |
|
"""Converts a one-hot vector to a single value label. |
|
|
|
Args: |
|
one_hot: `np.ndarray`, a one-hot encoded vector. |
|
|
|
Returns: |
|
A single integer to represent the category. |
|
""" |
|
return np.argmax(one_hot) |
|
|