Source code for menpo.landmark.labels.base

from collections import OrderedDict
from functools import wraps
import numpy as np

from menpo.base import name_of_callable
from menpo.landmark.exceptions import LabellingError

def connectivity_from_array(array, close_loop=False):
    Build the connectivity over a given array. For example, given ::

        array = [(0, 3, 1, 2 )]

    Generate the connectivity of ::

        [(0, 3), (3, 1), (1, 2)]

    If ``close_loop`` is true, add an extra connection from the last point to
    the first.
    # zip is a generator - need a list in this case
    conn = list(zip(array, array[1:]))
    if close_loop:
        conn.append((array[-1], array[0]))
    return np.asarray(conn)

def connectivity_from_range(range_tuple, close_loop=False):
    Build the connectivity over a range. For example, given ::

        range_array = np.arange(3)

    Generate the connectivity of ::

        [(0, 1), (1, 2), (2, 3)]

    If ``close_loop`` is true, add an extra connection from the last point to
    the first.
    return connectivity_from_array(np.arange(*range_tuple), close_loop=close_loop)

def validate_input(pcloud, n_expected_points):
    Ensure that the input matches the number of expected points.

    pcloud : :map:`PointCloud`
        Input Pointcloud to validate
    n_expected_points : `int`
        Number of expected points

        If the number of expected points doesn't match the number of given
    n_actual_points = pcloud.n_points
    if n_actual_points != n_expected_points:
        msg = (
            "Label expects exactly {} "
            "points. However, the given pointcloud "
            "has {} points".format(n_expected_points, n_actual_points)
        raise LabellingError(msg)

def pcloud_and_lgroup_from_ranges(pointcloud, labels_to_ranges):
    Label the given pointcloud according to the given ordered dictionary
    of labels to ranges. This assumes that you can semantically label the group
    by using ranges in to the existing points e.g ::

        labels_to_ranges = {'jaw': (0, 17, False)}

    The third element of the range tuple is whether the range is a closed loop
    or not. For example, for an eye landmark this would be ``True``, as you
    do want to create a closed loop for an eye.

    pointcloud : :map:`PointCloud`
        The pointcloud to apply semantic labels to.
    labels_to_ranges : `ordereddict` {`str` -> (`int`, `int`, `bool`)}
        Ordered dictionary of string labels to range tuples.

    new_pcloud : :map:`PointCloud`
        New pointcloud with specific connectivity information applied.
    mapping : `ordereddict` {`str` -> `int ndarray`}
        For each label, the indices in to the pointcloud that belong to the
    from menpo.shape import LabelledPointUndirectedGraph

    mapping = OrderedDict()
    all_connectivity = []
    for label, tup in labels_to_ranges.items():
        range_tuple = tup[:-1]
        close_loop = tup[-1]

        connectivity = connectivity_from_range(range_tuple, close_loop=close_loop)
        mapping[label] = np.arange(*range_tuple)
    all_connectivity = np.vstack(all_connectivity)

    new_pcloud = LabelledPointUndirectedGraph.init_from_indices_mapping(
        pointcloud.points, all_connectivity, mapping

    return new_pcloud, mapping

_labeller_docs = r"""
    x : :map:`LabelledPointUndirectedGraph` or :map:`PointCloud` or `ndarray`
        The input labelled point graph, pointcloud, subclass of those or
        array to label. If a pointcloud is passed, then only the connectivity
        information is propagated to the pointcloud (a subclass of
        :map:`PointCloud` may be returned).
    return_mapping : `bool`, optional
        Only applicable if a :map:`PointCloud` or `ndarray` is passed. Returns
        the mapping dictionary which maps labels to indices into the resulting
        :map:`PointCloud` (which is then used to for building a
        :map:`LabelledPointUndirectedGraph`. This parameter is only provided
        for internal use so that other labellers can piggyback off one another.

    x_labelled : :map:`LabelledPointUndirectedGraph` or :map:`PointCloud`
        If a :map:`LabelledPointUndirectedGraph` was passed, a
        :map:`LabelledPointUndirectedGraph` is returned. This labelled
        pointgraph will contain specific labels and these labels may refer to
        sub-pointclouds with specific connectivity information.

        If a :map:`PointCloud` was passed, a :map:`PointCloud` is returned. Only
        the connectivity information is propagated to the pointcloud
        (a subclass of :map:`PointCloud` may be returned).
    mapping_dict : `ordereddict` {`str` -> `int ndarray`}, optional
        Only returned if ``return_mapping==True``. Used for building

    : :map:`LabellingError`
        If the given labelled point graph/pointcloud contains less than the
        expected number of points.

def labeller_func(group_label=None):
    Decorator for labelling functions. Labelling functions should return
    a template pointcloud and a mapping dictionary (from labels to indices).
    This decorator then takes that output and returns the correctly labelled
    object depending on if a landmark group, pointcloud or numpy array is
    passed. See the docstrings of the labelling methods where this
    has been made clear.

    Note that we duck type the group label (usually just the name of the
    labelling function) on to the function itself for the labeller method

    def decorator(labelling_method):
        # Shadowing parent scope variables inside a nested function
        # kills the scope of the parent variable, so we need a unique alias
        # for the group name
        gl = (
            if group_label is not None
            else name_of_callable(labelling_method)
        # Duck type group label onto method itself
        labelling_method.group_label = gl
        # Set up the global docs
        labelling_method.__doc__ += _labeller_docs

        def wrapper(x, return_mapping=False):
            from menpo.shape import PointCloud

            # Accepts PointCloud subclass or ndarray
            if isinstance(x, np.ndarray):
                x = PointCloud(x, copy=False)

            new_pcloud, mapping = labelling_method(x)
            if return_mapping:
                return new_pcloud, mapping
                return new_pcloud

        return wrapper

    return decorator

[docs]def labeller(landmarkable, group, label_func): """ Re-label an existing landmark group on a :map:`Landmarkable` object with a new label set. Parameters ---------- landmarkable: :map:`Landmarkable` :map:`Landmarkable` that will have it's :map:`LandmarkManager` augmented with a new :map:`LabelledPointUndirectedGraph` or :map:`PointCloud` group: `str` The group label of the existing pointcloud that should be re-labelled. A copy of this group will be attached to it's landmark manager with new labels. The group label of this new group and the labels it will have is determined by ``label_func`` label_func: `func` -> `(str, LabelledPointUndirectedGraph)` A labelling function taken from this module. Takes as input a :map:`PointCloud` or :map:`LabelledPointUndirectedGraph` or subclass and returns a tuple of (new group label, new LabelledPointUndirectedGraph with semantic labels applied). Returns ------- landmarkable : :map:`Landmarkable` Augmented ``landmarkable`` (this is just for convenience, the object will actually be modified in place) """ new_group = label_func(landmarkable.landmarks[group]) landmarkable.landmarks[label_func.group_label] = new_group return landmarkable