from functools import wraps, partial
import numpy as np
from menpo.shape import PointCloud
def pointcloud_to_points(wrapped):
@wraps(wrapped)
def wrapper(*args, **kwargs):
args = list(args)
for index, arg in enumerate(args):
if isinstance(arg, PointCloud):
args[index] = arg.points
for key in kwargs:
if isinstance(kwargs[key], PointCloud):
kwargs[key] = kwargs[key].points
return wrapped(*args, **kwargs)
return wrapper
# BOUNDING BOX NORMALISERS
[docs]def bb_area(shape):
r"""
Computes the area of the bounding box of the provided shape,
i.e.
.. math::
h w
where :math:`h` and :math:`w` are the height and width of the bounding box.
Parameters
----------
shape : `menpo.shape.PointCloud` or `subclass`
The input shape.
Returns
-------
bb_area : `float`
The area of the bounding box.
"""
# Area = w * h
height, width = np.max(shape, axis=0) - np.min(shape, axis=0)
return height * width
[docs]def bb_perimeter(shape):
r"""
Computes the perimeter of the bounding box of the provided shape, i.e.
.. math::
2(h + w)
where :math:`h` and :math:`w` are the height and width of the bounding box.
Parameters
----------
shape : `menpo.shape.PointCloud` or `subclass`
The input shape.
Returns
-------
bb_perimeter : `float`
The perimeter of the bounding box.
"""
# Area = 2(w + h)
height, width = np.max(shape, axis=0) - np.min(shape, axis=0)
return 2 * (height + width)
[docs]def bb_avg_edge_length(shape):
r"""
Computes the average edge length of the bounding box of the provided shape,
i.e.
.. math::
\frac{h + w}{2} = \frac{2h + 2w}{4}
where :math:`h` and :math:`w` are the height and width of the bounding box.
Parameters
----------
shape : `menpo.shape.PointCloud` or `subclass`
The input shape.
Returns
-------
bb_avg_edge_length : `float`
The average edge length of the bounding box.
"""
# 0.5(w + h) = (2w + 2h) / 4
height, width = np.max(shape, axis=0) - np.min(shape, axis=0)
return 0.5 * (height + width)
[docs]def bb_diagonal(shape):
r"""
Computes the diagonal of the bounding box of the provided shape, i.e.
.. math::
\sqrt{h^2 + w^2}
where :math:`h` and :math:`w` are the height and width of the bounding box.
Parameters
----------
shape : `menpo.shape.PointCloud` or `subclass`
The input shape.
Returns
-------
bb_diagonal : `float`
The diagonal of the bounding box.
"""
# sqrt(w**2 + h**2)
height, width = np.max(shape, axis=0) - np.min(shape, axis=0)
return np.sqrt(width ** 2 + height ** 2)
bb_norm_types = {
'avg_edge_length': bb_avg_edge_length,
'perimeter': bb_perimeter,
'diagonal': bb_diagonal,
'area': bb_area
}
# EUCLIDEAN AND ROOT MEAN SQUARE ERRORS
[docs]@pointcloud_to_points
def root_mean_square_error(shape, gt_shape):
r"""
Computes the root mean square error between two shapes, i.e.
.. math::
\sqrt{\frac{1}{N}\sum_{i=1}^N(s_i-s^*_i)^2}
where :math:`s_i` and :math:`s^*_i` are the coordinates of the :math:`i`'th
point of the final and ground truth shapes, and :math:`N` is the total
number of points.
Parameters
----------
shape : `menpo.shape.PointCloud`
The input shape (e.g. the final shape of a fitting procedure).
gt_shape : `menpo.shape.PointCloud`
The ground truth shape.
Returns
-------
root_mean_square_error : `float`
The root mean square error.
"""
return np.sqrt(np.mean((shape.ravel() - gt_shape.ravel()) ** 2))
[docs]@pointcloud_to_points
def euclidean_error(shape, gt_shape):
r"""
Computes the Euclidean error between two shapes, i.e.
.. math::
\frac{1}{N}\sum_{i=1}^N\sqrt{(s_{i,x}-s^*_{i,x})^2 + (s_{i,y}-s^*_{i,y})^2}
where :math:`(s_{i,x}, s_{i,y})` are the `x` and `y` coordinates of the
:math:`i`'th point of the final shape, :math:`(s^*_{i,x}, s^*_{i,y})`
are the `x` and `y` coordinates of the :math:`i`'th point of the ground
truth shape and :math:`N` is the total number of points.
Parameters
----------
shape : `menpo.shape.PointCloud`
The input shape (e.g. the final shape of a fitting procedure).
gt_shape : `menpo.shape.PointCloud`
The ground truth shape.
Returns
-------
root_mean_square_error : `float`
The Euclidean error.
"""
return np.mean(np.sqrt(np.sum((shape - gt_shape) ** 2, axis=-1)))
# DISTANCE NORMALISER
[docs]def distance_two_indices(index1, index2, shape):
r"""
Computes the Euclidean distance between two points of a shape, i.e.
.. math::
\sqrt{(s_{i,x}-s_{j,x})^2 + (s_{i,y}-s_{j,y})^2}
where :math:`s_{i,x}`, :math:`s_{i,y}` are the `x` and `y` coordinates of
the :math:`i`'th point (`index1`) and :math:`s_{j,x}`, :math:`s_{j,y}` are
the `x` and `y` coordinates of the :math:`j`'th point (`index2`).
Parameters
----------
index1 : `int`
The index of the first point.
index2 : `int`
The index of the second point.
shape : `menpo.shape.PointCloud`
The input shape.
Returns
-------
distance_two_indices : `float`
The Euclidean distance between the points.
"""
return euclidean_error(shape[index1], shape[index2])
# GENERIC NORMALISED ERROR FUNCTIONS
@pointcloud_to_points
def bb_normalised_error(shape_error_f, shape, gt_shape,
norm_shape=None, norm_type='avg_edge_length'):
r"""
Computes an error normalised by a measure based on the shape's bounding
box, i.e.
.. math::
\frac{\mathcal{F}(s,s^*)}{\mathcal{N}(s^*)}
where :math:`\mathcal{F}(s,s^*)` is an error metric function between the
final shape :math:`s` and the ground truth shape :math:`s^*` and
:math:`\mathcal{N}(s^*)` is a normalising function that returns a measure
based on the ground truth shape's bounding box.
Parameters
----------
shape_error_f : `callable`
The function to be used for computing the error.
shape : `menpo.shape.PointCloud`
The input shape (e.g. the final shape of a fitting procedure).
gt_shape : `menpo.shape.PointCloud`
The ground truth shape.
norm_shape : `menpo.shape.PointCloud` or ``None``, optional
The shape to be used to compute the normaliser. If ``None``, then the
ground truth shape is used.
norm_type : ``{'area', 'perimeter', 'avg_edge_length', 'diagonal'}``, optional
The type of the normaliser. Possible options are:
========================= ==========================================
Method Description
========================= ==========================================
:map:`bb_area` Area of `norm_shape`'s bounding box
:map:`bb_perimeter` Perimeter of `norm_shape`'s bounding box
:map:`bb_avg_edge_length` Average edge length of `norm_shape`'s bbox
:map:`bb_diagonal` Diagonal of `norm_shape`'s bounding box
========================= ==========================================
Returns
-------
normalised_error : `float`
The computed normalised error.
"""
if norm_type not in bb_norm_types:
raise ValueError('norm_type must be one of '
'{avg_edge_length, perimeter, diagonal, area}.')
if norm_shape is None:
norm_shape = gt_shape
return (shape_error_f(shape, gt_shape) /
bb_norm_types[norm_type](norm_shape))
@pointcloud_to_points
def distance_normalised_error(shape_error_f, distance_norm_f, shape, gt_shape):
r"""
Computes an error normalised by a distance measure between two shapes, i.e.
.. math::
\frac{\mathcal{F}(s,s^*)}{\mathcal{N}(s,s^*)}
where :math:`\mathcal{F}(s,s^*)` is an error metric function between the
final shape :math:`s` and the ground truth shape :math:`s^*` and
:math:`\mathcal{N}(s,s^*)` is a normalising function based on a distance
metric between the two shapes.
Parameters
----------
shape_error_f : `callable`
The function to be used for computing the error.
distance_norm_f : `callable`
The function to be used for computing the normalisation distance metric.
shape : `menpo.shape.PointCloud`
The input shape (e.g. the final shape of a fitting procedure).
gt_shape : `menpo.shape.PointCloud`
The ground truth shape.
Returns
-------
normalised_error : `float`
The computed normalised error.
"""
return shape_error_f(shape, gt_shape) / distance_norm_f(shape, gt_shape)
@pointcloud_to_points
def distance_indexed_normalised_error(shape_error_f, index1, index2, shape,
gt_shape):
r"""
Computes an error normalised by the distance measure between two points
of the ground truth shape, i.e.
.. math::
\frac{\mathcal{F}(s,s^*)}{\mathcal{N}(s^*)}
where :math:`\mathcal{F}(s,s^*)` is an error metric function between the
final shape :math:`s` and the ground truth shape :math:`s^*` and
:math:`\mathcal{N}(s^*)` is a normalising function that returns the
distance between two points of the ground truth shape.
Parameters
----------
shape_error_f : `callable`
The function to be used for computing the error.
index1 : `int`
The index of the first point.
index2 : `int`
The index of the second point.
shape : `menpo.shape.PointCloud`
The input shape (e.g. the final shape of a fitting procedure).
gt_shape : `menpo.shape.PointCloud`
The ground truth shape.
Returns
-------
normalised_error : `float`
The computed normalised error.
"""
return shape_error_f(shape, gt_shape) / distance_two_indices(index1, index2,
gt_shape)
# EUCLIDEAN AND ROOT MEAN SQUARE NORMALISED ERRORS
[docs]def root_mean_square_bb_normalised_error(shape, gt_shape, norm_shape=None,
norm_type='avg_edge_length'):
r"""
Computes the root mean square error between two shapes normalised by a
measure based on the ground truth shape's bounding box, i.e.
.. math::
\frac{\mathcal{F}(s,s^*)}{\mathcal{N}(s^*)}
where
.. math::
\mathcal{F}(s,s^*) = \sqrt{\frac{1}{N}\sum_{i=1}^N(s_i-s^*_i)^2}
where :math:`s` and :math:`s^*` are the final and ground truth shapes,
respectively. :math:`s_i` and :math:`s^*_i` are the coordinates of the
:math:`i`'th point of the final and ground truth shapes, and :math:`N` is
the total number of points. Finally, :math:`\mathcal{N}(s^*)` is a
normalising function that returns a measure based on the ground truth
shape's bounding box.
Parameters
----------
shape : `menpo.shape.PointCloud`
The input shape (e.g. the final shape of a fitting procedure).
gt_shape : `menpo.shape.PointCloud`
The ground truth shape.
norm_shape : `menpo.shape.PointCloud` or ``None``, optional
The shape to be used to compute the normaliser. If ``None``, then the
ground truth shape is used.
norm_type : ``{'area', 'perimeter', 'avg_edge_length', 'diagonal'}``, optional
The type of the normaliser. Possible options are:
========================= ==========================================
Method Description
========================= ==========================================
:map:`bb_area` Area of `norm_shape`'s bounding box
:map:`bb_perimeter` Perimeter of `norm_shape`'s bounding box
:map:`bb_avg_edge_length` Average edge length of `norm_shape`'s bbox
:map:`bb_diagonal` Diagonal of `norm_shape`'s bounding box
========================= ==========================================
Returns
-------
error : `float`
The computed root mean square normalised error.
"""
return bb_normalised_error(shape_error_f=root_mean_square_error,
shape=shape, gt_shape=gt_shape,
norm_shape=norm_shape, norm_type=norm_type)
[docs]def root_mean_square_distance_normalised_error(shape, gt_shape,
distance_norm_f):
r"""
Computes the root mean square error between two shapes normalised by a
distance measure between two shapes, i.e.
.. math::
\frac{\mathcal{F}(s,s^*)}{\mathcal{N}(s,s^*)}
where
.. math::
\mathcal{F}(s,s^*) = \sqrt{\frac{1}{N}\sum_{i=1}^N(s_i-s^*_i)^2}
where :math:`s` and :math:`s^*` are the final and ground truth shapes,
respectively. :math:`s_i` and :math:`s^*_i` are the coordinates of the
:math:`i`'th point of the final and ground truth shapes, and :math:`N` is
the total number of points. Finally, :math:`\mathcal{N}(s,s^*)` is a
normalising function based on a distance metric between the two shapes.
Parameters
----------
shape : `menpo.shape.PointCloud`
The input shape (e.g. the final shape of a fitting procedure).
gt_shape : `menpo.shape.PointCloud`
The ground truth shape.
distance_norm_f : `callable`
The function to be used for computing the normalisation distance metric.
Returns
-------
error : `float`
The computed root mean square normalised error.
"""
return distance_normalised_error(shape_error_f=root_mean_square_error,
distance_norm_f=distance_norm_f,
shape=shape, gt_shape=gt_shape)
[docs]def root_mean_square_distance_indexed_normalised_error(shape, gt_shape,
index1, index2):
r"""
Computes the root mean square error between two shapes normalised by the
distance measure between two points of the ground truth shape, i.e.
.. math::
\frac{\mathcal{F}(s,s^*)}{\mathcal{N}(s^*)}
where
.. math::
\mathcal{F}(s,s^*) = \sqrt{\frac{1}{N}\sum_{i=1}^N(s_i-s^*_i)^2}
where :math:`s` and :math:`s^*` are the final and ground truth shapes,
respectively. :math:`s_i` and :math:`s^*_i` are the coordinates of the
:math:`i`'th point of the final and ground truth shapes, and :math:`N` is
the total number of points. Finally, :math:`\mathcal{N}(s^*)` is a
normalising function that returns the distance between two points of the
ground truth shape.
Parameters
----------
shape : `menpo.shape.PointCloud`
The input shape (e.g. the final shape of a fitting procedure).
gt_shape : `menpo.shape.PointCloud`
The ground truth shape.
index1 : `int`
The index of the first point.
index2 : `int`
The index of the second point.
Returns
-------
error : `float`
The computed root mean square normalised error.
"""
return distance_indexed_normalised_error(
shape_error_f=root_mean_square_error, index1=index1, index2=index2,
shape=shape, gt_shape=gt_shape)
[docs]def euclidean_bb_normalised_error(shape, gt_shape, norm_shape=None,
norm_type='avg_edge_length'):
r"""
Computes the Euclidean error between two shapes normalised by a measure
based on the ground truth shape's bounding box, i.e.
.. math::
\frac{\mathcal{F}(s,s^*)}{\mathcal{N}(s^*)}
where
.. math::
\mathcal{F}(s,s^*) = \frac{1}{N}\sum_{i=1}^N\sqrt{(s_{i,x}-s^*_{i,x})^2 + (s_{i,y}-s^*_{i,y})^2}
where :math:`s` and :math:`s^*` are the final and ground truth shapes,
respectively. :math:`(s_{i,x}, s_{i,y})` are the `x` and `y` coordinates of
the :math:`i`'th point of the final shape, :math:`(s^*_{i,x}, s^*_{i,y})`
are the `x` and `y` coordinates of the :math:`i`'th point of the ground
truth shape and :math:`N` is the total number of points. Finally,
:math:`\mathcal{N}(s^*)` is a normalising function that returns a measure
based on the ground truth shape's bounding box.
Parameters
----------
shape : `menpo.shape.PointCloud`
The input shape (e.g. the final shape of a fitting procedure).
gt_shape : `menpo.shape.PointCloud`
The ground truth shape.
norm_shape : `menpo.shape.PointCloud` or ``None``, optional
The shape to be used to compute the normaliser. If ``None``, then the
ground truth shape is used.
norm_type : ``{'area', 'perimeter', 'avg_edge_length', 'diagonal'}``, optional
The type of the normaliser. Possible options are:
========================= ==========================================
Method Description
========================= ==========================================
:map:`bb_area` Area of `norm_shape`'s bounding box
:map:`bb_perimeter` Perimeter of `norm_shape`'s bounding box
:map:`bb_avg_edge_length` Average edge length of `norm_shape`'s bbox
:map:`bb_diagonal` Diagonal of `norm_shape`'s bounding box
========================= ==========================================
Returns
-------
error : `float`
The computed Euclidean normalised error.
"""
return bb_normalised_error(shape_error_f=euclidean_error,
shape=shape, gt_shape=gt_shape,
norm_shape=norm_shape, norm_type=norm_type)
[docs]def euclidean_distance_normalised_error(shape, gt_shape, distance_norm_f):
r"""
Computes the Euclidean error between two shapes normalised by a distance
measure between two shapes, i.e.
.. math::
\frac{\mathcal{F}(s,s^*)}{\mathcal{N}(s,s^*)}
where
.. math::
\mathcal{F}(s,s^*) = \frac{1}{N}\sum_{i=1}^N\sqrt{(s_{i,x}-s^*_{i,x})^2 + (s_{i,y}-s^*_{i,y})^2}
where :math:`s` and :math:`s^*` are the final and ground truth shapes,
respectively. :math:`(s_{i,x}, s_{i,y})` are the `x` and `y` coordinates of
the :math:`i`'th point of the final shape, :math:`(s^*_{i,x}, s^*_{i,y})`
are the `x` and `y` coordinates of the :math:`i`'th point of the ground
truth shape and :math:`N` is the total number of points. Finally,
:math:`\mathcal{N}(s,s^*)` is a normalising function based on a distance
metric between the two shapes.
Parameters
----------
shape : `menpo.shape.PointCloud`
The input shape (e.g. the final shape of a fitting procedure).
gt_shape : `menpo.shape.PointCloud`
The ground truth shape.
distance_norm_f : `callable`
The function to be used for computing the normalisation distance metric.
Returns
-------
error : `float`
The computed Euclidean normalised error.
"""
return distance_normalised_error(shape_error_f=euclidean_error,
distance_norm_f=distance_norm_f,
shape=shape, gt_shape=gt_shape)
[docs]def euclidean_distance_indexed_normalised_error(shape, gt_shape, index1,
index2):
r"""
Computes the Euclidean error between two shapes normalised by the
distance measure between two points of the ground truth shape, i.e.
.. math::
\frac{\mathcal{F}(s,s^*)}{\mathcal{N}(s^*)}
where
.. math::
\mathcal{F}(s,s^*) = \frac{1}{N}\sum_{i=1}^N\sqrt{(s_{i,x}-s^*_{i,x})^2 + (s_{i,y}-s^*_{i,y})^2}
where :math:`s` and :math:`s^*` are the final and ground truth shapes,
respectively. :math:`(s_{i,x}, s_{i,y})` are the `x` and `y` coordinates of
the :math:`i`'th point of the final shape, :math:`(s^*_{i,x}, s^*_{i,y})`
are the `x` and `y` coordinates of the :math:`i`'th point of the ground
truth shape and :math:`N` is the total number of points. Finally,
:math:`\mathcal{N}(s^*)` is a normalising function that returns the
distance between two points of the ground truth shape.
Parameters
----------
shape : `menpo.shape.PointCloud`
The input shape (e.g. the final shape of a fitting procedure).
gt_shape : `menpo.shape.PointCloud`
The ground truth shape.
index1 : `int`
The index of the first point.
index2 : `int`
The index of the second point.
Returns
-------
error : `float`
The computed Euclidean normalised error.
"""
return distance_indexed_normalised_error(
shape_error_f=euclidean_error, index1=index1, index2=index2,
shape=shape, gt_shape=gt_shape)