def noisy_align(source, target, noise_std=0.04, rotation=False):
    r"""
    Constructs and perturbs the optimal similarity transform between source
    to the target by adding white noise to its weights.

    Parameters
    ----------
    source: :class:`pybug.shape.PointCloud`
        The source pointcloud instance used in the alignment

    target: :class:`pybug.shape.PointCloud`
        The target pointcloud instance used in the alignment

    noise_std: float
        The standard deviation of the white noise

        Default: 0.04
    rotation: boolean
        If False the second parameter of the Similarity,
        which captures captures inplane rotations, is set to 0.

        Default:False

    Returns
    -------
    noisy_transform : :class: `pybug.transform.Similarity`
        The noisy Similarity Transform
    """
    transform = AlignmentSimilarity(source, target, rotation=rotation)
    parameters = transform.as_vector()
    parameter_range = np.hstack((parameters[:2], target.range()))
    noise = (parameter_range * noise_std *
             np.random.randn(transform.n_parameters))
    parameters += noise
    return Similarity.identity(source.n_dims).from_vector(parameters) def build_sampling_grid(grid_size):
    r"""
    """
    patch_size = np.array(grid_size)
    patch_half_size = (np.round(patch_size / 2)).astype(int)
    start = -patch_half_size
    end = patch_half_size + 1
    sampling_grid = np.mgrid[start[0]:end[0], start[1]:end[1]]
    return sampling_grid.swapaxes(0, 2).swapaxes(0, 1) def extract_local_patches(image, shape, sampling_grid):
    r"""
    """
    max_x = image.shape[0] - 1
    max_y = image.shape[1] - 1

    patches = []
    for point in shape.points:
        patch_grid = (sampling_grid +
                      np.round(point[None, None, ...]).astype(int))
        x = patch_grid[:, :, 0]
        y = patch_grid[:, :, 1]

        # deal with boundaries
        x[x > max_x] = max_x
        y[y > max_y] = max_y
        x[x < 0] = 0
        y[y < 0] = 0

        patch_data = image.pixels[x, y, :]
        patch_img = image.__class__(patch_data)
        patches.append(patch_img)

    return patches def compute_features(image, feature_type):
    r"""
    Computes a particular feature representation of the given images.

    Parameters
    ----------
    image: :class:`menpo.image.MaskedNDImage`
        The original image from which the features will be computed.
    feature_type: string or closure
        If None, no feature representation will be computed from the
        original image.
        If string or closure, the feature representation will be computed
        in the following way:
            If string, the feature representation will be extracted by
            executing:

                feature_image = eval('img.features.' + feature_type + '()')

            For this to work properly feature_type needs to be one of
            Menpo's standard image feature methods. Note that, in this case,
            the feature computation will be carried out using its default
            options.

            Non-default feature options and new experimental feature can be
            used by defining a closure. In this case, the closure must define a
            function that receives as an input an image and returns a
            particular feature representation of that image. For example:

                def igo_double_from_std_normalized_intensities(image)
                    image = deepcopy(image)
                    image.normalize_std_inplace()
                    return image.feature_type.igo(double_angles=True)

            See `menpo.image.feature.py` for details more details on
            Menpo's standard image features and feature options.

    Returns
    -------
    feature_image: :class:`menpo.image.MaskedNDImage`
        The resulting feature image.
    """
    if feature_type is not None:
        if type(feature_type) is str:
            image = eval('image.features.' + feature_type + '()')
        elif hasattr(feature_type, '__call__'):
            image = feature_type(image)
        else:
            raise ValueError("feature_type can only be: (1) None, "
                             "(2) a string defining one of Menpo's standard "
                             "image feature_type ('hog', 'igo', etc) "
                             "or (3) a closure defining a non-standard "
                             "feature computation")
    return image def sparse_hog(image):
    return image.features.hog(mode='sparse') class NonParametricFittingResult(FittingResult):
    r"""
    """

    def __init__(self, image, fitter, shape=None, costs=None,
                 gt_shape=None, error_type='me_norm'):
        super(NonParametricFittingResult, self).__init__(
            image, fitter, gt_shape=gt_shape, error_type=error_type)
        self.parameters = shape
        self.costs = costs

    @FittingResult.fitted.setter
    def fitted(self, value):
        r"""
        Setter for the fitted property.
        """
        if value and type(value) is bool:
            if len(self.parameters) < 2:
               # and len(self.parameters) is not len(self.cost):
                raise ValueError("Lists containing weights and costs "
                                 "must have the same length")
            self._fitted = value
        else:
            raise ValueError("Fitted can only be set to True")

    @property
    def n_iters(self):
        return len(self.shapes()) - 1

    def shapes(self, as_points=False):
        if as_points:
            return [deepcopy(s.points) for s in self.parameters]

        else:
            return deepcopy(self.parameters)

    @property
    def final_shape(self):
        return deepcopy(self.parameters[-1])

    @property
    def initial_shape(self):
        return deepcopy(self.parameters[0])

    @FittingResult.gt_shape.setter
    def gt_shape(self, value):
        r"""
        Setter for the ground truth shape associated to the image.
        """
        if type(value) is PointCloud:
            self._gt_shape = value
        else:
            raise ValueError("Accepted values for gt_shape setter are "
                             "`menpo.shape.PointClouds`.")

    @property
    def final_cost(self):
        r"""
        Returns the value of the cost function at the final iteration.
        """
        return self.costs[-1]

    @property
    def initial_cost(self):
        r"""
        Returns the value of the cost function at the first iteration.
        """
        return self.costs[0]

    def plot_cost(self, figure_id=None, new_figure=False, **kwargs):
        r"""
        Plots the cost evolution throughout the fitting.
        """
        legend = self.algorithm
        x_label = 'Number of iterations'
        y_label = 'Normalized cost'
        return GraphPlotter(figure_id, new_figure, range(0, self.n_iters+1),
                            [self.costs], legend=legend, x_label=x_label,
                            y_label=y_label).render(**kwargs) class SemiParametricFittingResult(NonParametricFittingResult):
    r"""
    """
    def __init__(self, image, fitter, parameters=None, costs=None,
                 gt_shape=None, error_type='me_norm'):
        super(SemiParametricFittingResult, self).__init__(
            image, fitter, gt_shape=gt_shape, error_type=error_type)
        self.parameters = parameters
        self.costs = costs

    @FittingResult.fitted.setter
    def fitted(self, value):
        r"""
        Setter for the fitted property.
        """
        if value and type(value) is bool:
            if len(self.parameters) < 2:
               # and len(self.parameters) is not len(self.cost):
                raise ValueError("Lists containing weights and costs "
                                 "must have the same length")
            self._fitted = value
        else:
            raise ValueError("Fitted can only be set to True")

    @property
    def residual(self):
        r"""
        Returns the type of residual used by the basic fitter.
        """
        return self.fitter.residual.type

    @property
    def transforms(self):
        r"""
        Generates a list containing the transforms obtained at each fitting
        iteration.
        """
        return [self.fitter.transform.from_vector(p) for p in self.parameters]

    def shapes(self, as_points=False):
        if as_points:
            return [self.fitter.transform.from_vector(p).target.points
                    for p in self.parameters]

        else:
            return [self.fitter.transform.from_vector(p).target
                    for p in self.parameters]

    @property
    def final_transform(self):
        r"""
        Returns the final transform.
        """
        return self.fitter.transform.from_vector(self.parameters[-1])

    @property
    def initial_transform(self):
        r"""
        Returns the initial transform from which the fitting started.
        """
        return self.fitter.transform.from_vector(self.parameters[0])

    @property
    def final_shape(self):
        return self.final_transform.target

    @property
    def initial_shape(self):
        return self.initial_transform.target

    @FittingResult.gt_shape.setter
    def gt_shape(self, value):
        r"""
        Setter for the ground truth shape associated to the image.
        """
        if type(value) is PointCloud:
            self._gt_shape = value
        elif type(value) is list and value[0] is float:
            transform = self.fitter.transform.from_vector(value)
            self._gt_shape = transform.target
        else:
            raise ValueError("Accepted values for gt_shape setter are "
                             "`menpo.shape.PointClouds` or float lists"
                             "specifying transform parameters.") class ParametricFittingResult(SemiParametricFittingResult):
    r"""
    Object that holds the state of a lucas-kanade object before, during
    and after it has fitted a particular image.

    Parameters
    -----------
    image: :class:`menpo.image.masked.MaskedImage`
        The fitted image.

    lk: :class:`menpo.aam.fitter.BasicFitter`
        The fitter object used to fitter the image.

    weights: ndarray list
        A list containing the weights of the lk object transform per
        fitting iteration.

        Default: None

    weights: ndarray list
        A list containing the weights of the lk appearance model
        per fitting iteration. Note that, some lk objects do not explicitly
        recover the weights of the appearance model; in this cases
        weights is None.

        Default: None

    costs: ndarray list
        A list containing the values of the cost function optimized by the
        lk object per fitting iteration.

        Default: None

    error_type: 'me_norm', 'me' or 'rmse', optional.
        Specifies the way in which the error between the fitted and
        ground truth shapes is to be computed.

        Default: 'me_norm'
    """

    def __init__(self, image, lk, parameters=None, weights=None, costs=None,
                 gt_shape=None, error_type='me_norm'):
        super(ParametricFittingResult, self).__init__(
            image, lk, gt_shape=gt_shape, error_type=error_type)
        self.parameters = parameters
        self.weights = weights
        self.costs = costs

    @FittingResult.fitted.setter
    def fitted(self, value):
        r"""
        Setter for the fitted property.
        """
        if value and type(value) is bool:
            if len(self.parameters) < 2 and \
               len(self.parameters): #is not len(self.cost):
                raise ValueError("Lists containing weights and costs "
                                 "must have the same length")
            if self.weights and \
               (len(self.parameters) is not len(self.weights)):
                raise ValueError("Lists containing weights, costs and "
                                 "weights must have the same length")
            self._fitted = value
        else:
            raise ValueError("Fitted can only be set to True")

    def warped_images(self, as_pixels=False):
        r"""
        Generates a list containing the warped images obtained at each fitting
        iteration.

        Parameters
        -----------
        as_pixels: boolean, optional
            Whether the result is returned as a list of Images or ndarrays.

            Default: False

        Returns
        -------
        warped_images: :class:`menpo.image.masked.MaskedImage` or ndarray list
            A list containing the warped images obtained at each fitting
            iteration.
        """
        mask = self.fitter.template.mask
        transform = self.fitter.transform
        interpolator = self.fitter.interpolator
        if as_pixels:
            return [self.image.warp_to(mask, transform.from_vector(p),
                                       interpolator=interpolator).pixels
                    for p in self.parameters]
        else:
            return [self.image.warp_to(mask, transform.from_vector(p),
                                       interpolator=interpolator)
                    for p in self.parameters]

    def appearance_reconstructions(self, as_pixels=False):
        r"""
        Generates a list containing the appearance reconstruction obtained at
        each fitting iteration.

        Parameters
        -----------
        as_pixels: boolean, optional
            Whether the result is returned as a list of Images or ndarrays.

            Default: False

        Returns
        -------
        appearance_reconstructions: :class:`menpo.image.masked.MaskedImage`
                                    or ndarray list
            A list containing the appearance reconstructions obtained at each
            fitting iteration.
        """
        if self.weights:
            if as_pixels:
                return [self.fitter.appearance_model.instance(w).pixels
                        for w in self.weights]
            else:
                return [self.fitter.appearance_model.instance(w)
                        for w in self.weights]
        else:
            if as_pixels:
                return [self.fitter.template.pixels for _ in self.parameters]
            else:
                return [self.fitter.template for _ in self.parameters]

    def view_warped_images(self, figure_id=None, new_figure=False,
                           channels=None, **kwargs):
        r"""
        Displays the warped images.
        """
        pixels_list = self.warped_images(as_pixels=True)
        return MultipleImageViewer(figure_id, new_figure,
                                   self.image.n_dims, pixels_list,
                                   channels=channels).render(**kwargs)

    def view_appearance_reconstructions(self, figure_id=None,
                                        new_figure=False, channels=None,
                                        **kwargs):
        r"""
        Displays the appearance recontructions.
        """
        pixels_list = self.appearance_reconstructions(as_pixels=True)
        return MultipleImageViewer(figure_id, new_figure,
                                   self.image.n_dims, pixels_list,
                                   channels=channels).render(**kwargs)

    def view_error_images(self, figure_id=None, new_figure=False,
                          channels=None, **kwargs):
        r"""
        Displays the error images.
        """
        warped_images = self.warped_images(as_pixels=True)
        appearances = self.appearance_reconstructions(as_pixels=True)
        pixels_list = [a - i for a, i in zip(appearances, warped_images)]
        return MultipleImageViewer(figure_id, new_figure,
                                   self.image.n_dims, pixels_list,
                                   channels=channels).render(**kwargs) class NonParametricRegressor(Regressor):

    def __init__(self, regressor, features):
        super(NonParametricRegressor, self).__init__(
            regressor, features)

    @property
    def algorithm(self):
        return "Non-Parametric"

    def _create_fitting(self, image, shape, gt_shape=None):
        return NonParametricFittingResult(image, self, shape=[shape],
                                          gt_shape=gt_shape)

    def update(self, delta_shape, initial_shape):
        fitted_shape = initial_shape.from_vector(
            initial_shape.as_vector() + delta_shape)
        return fitted_shape, fitted_shape class SemiParametricRegressor(Regressor):

    def __init__(self, regressor, features, transform, update='composition'):
        super(SemiParametricRegressor, self).__init__(
            regressor, features)
        self.transform = transform
        self._update = self._select_update(update)

    @property
    def algorithm(self):
        return "SemiParametric"

    def _create_fitting(self, image, shape, gt_shape=None):
        self.transform.set_target(shape)
        return SemiParametricFittingResult(
            image, self, parameters=[self.transform.as_vector()],
            gt_shape=gt_shape)

    def _select_update(self, update):
        if update is 'additive':
            return self._additive
        elif update is 'compositional':
            return self._compositional
        else:
            raise ValueError('Unknown update string selected. Valid'
                             'options are: additive, compositional')

    def _additive(self, delta_p):
        parameters = self.transform.as_vector() + delta_p
        self.transform.from_vector_inplace(parameters)

    def _compositional(self, delta_p):
        self.transform.compose_after_from_vector_inplace(delta_p)

    def update(self, delta_p, initial_shape):
        self._update(delta_p)
        return self.transform.target, self.transform.as_vector() class ParametricRegressor(SemiParametricRegressor):

    def __init__(self, regressor, features, appearance_model, transform,
                 update='composition'):
        super(ParametricRegressor, self).__init__(
            regressor, features, transform, update=update)
        self.appearance_model = appearance_model
        self.template = appearance_model.mean

    @property
    def algorithm(self):
        return "Parametric"

    def _create_fitting(self, image, shape, gt_shape=None):
        self.transform.set_target(shape)
        return ParametricFittingResult(
            image, self, parameters=[self.transform.as_vector()],
            gt_shape=gt_shape) def weights(appearance_model, warped_image):
    r"""
    """
    return appearance_model.project(warped_image) def regression(X, T, regression_type, **kwargs):
    r"""
    """
    if hasattr(regression_type, '__call__'):
        regression_closure = regression_type(X, T, **kwargs)

        # note that, self is required as this closure is assigned to an object
        def regression_object_method(x):
            return regression_closure(x)

        return regression_closure
    else:
        raise ValueError("regression_type can only be: a closure defining "
                         "a particular regression technique. Several examples"
                         " of such closures can be found in "
                         "`menpo.sdm.functions` (mlr, pcr, pls, ccr, ...).") def mlr(X, T):
    r"""
    Multivariate Linear Regression
    """
    XX = np.dot(X.T, X)
    XX = (XX + XX.T) / 2
    XT = np.dot(X.T, T)
    R = np.linalg.solve(XX, XT)

    def mlr_fitting(x):
        return np.dot(x, R)

    return mlr_fitting
import abc
import numpy as np
from menpo.fitmultilevel.functions import (noisy_align, build_sampling_grid,
                                           extract_local_patches)
from menpo.fitmultilevel.featurefunctions import compute_features, sparse_hog
from menpo.fit.fittingresult import (NonParametricFittingResult,
                                     SemiParametricFittingResult,
                                     ParametricFittingResult)
from .base import (NonParametricRegressor, SemiParametricRegressor,
                   ParametricRegressor)
from .parametricfeatures import weights
from .regressionfunctions import regression, mlr
                images, shapes, perturbed_shapes)

        else:
            raise ValueError("The number of perturbed shapes must be "
                             "equal or multiple to the number of images.")

        print('- performing regression')
        regressor = regression(features, delta_ps, self.regression_type,
                               **kwargs)

        print('- computing regression rmse')
        estimated_delta_ps = regressor(features)
        error = np.sqrt(np.mean(np.sum((delta_ps - estimated_delta_ps) ** 2,
                                       axis=1)))
        print(' - error = {}'.format(error))

        return self._build_regressor(regressor, self.features)

    def perturb_shapes(self, gt_shape):
        return [[self._perturb_shape(s) for _ in range(self.n_perturbations)]
                for s in gt_shape]

    def _perturb_shape(self, gt_shape):
        return noisy_align(self.reference_shape, gt_shape,
                           noise_std=self.noise_std
                           ).apply(self.reference_shape)

    @abc.abstractmethod
    def _build_regressor(self, regressor, features):
        pass


#TODO: document me
class NonParametricRegressorTrainer(RegressorTrainer):
    r"""
    """
    def __init__(self, reference_shape, regression_type=mlr,
                 regression_features=sparse_hog, patch_shape=(16, 16),
                 noise_std=0.04, rotation=False, n_perturbations=10):
        super(NonParametricRegressorTrainer, self).__init__(
            reference_shape, regression_type=regression_type,
            regression_features=regression_features, noise_std=noise_std,
            rotation=rotation, n_perturbations=n_perturbations)
        self.patch_shape = patch_shape
        self.sampling_grid = build_sampling_grid(patch_shape)

    def _create_fitting(self, image, shapes, gt_shape=None):
        return NonParametricFittingResult(image, self, shapes=[shapes],
                                          gt_shape=gt_shape)

    def features(self, image, shape):
        patches = extract_local_patches(image, shape, self.sampling_grid)
        features = [compute_features(p, self.regression_features).pixels.ravel()
                    for p in patches]
        return np.hstack((np.asarray(features).ravel(), 1))

    def delta_ps(self, gt_shape, perturbed_shape):
        return (gt_shape.as_vector() -
                perturbed_shape.as_vector())

    def _build_regressor(self, regressor, features):
        return NonParametricRegressor(regressor, features)


#TODO: Document me
class SemiParametricRegressorTrainer(NonParametricRegressorTrainer):
    r"""
    """
    def __init__(self, transform, reference_shape, regression_type=mlr,
                 regression_features=sparse_hog, patch_shape=(16, 16),
                 update='compositional', noise_std=0.04, rotation=False,
                 n_perturbations=10):
        super(SemiParametricRegressorTrainer, self).__init__(
            reference_shape, regression_type=regression_type,
            regression_features=regression_features, patch_shape=patch_shape,
            noise_std=noise_std, rotation=rotation,
            n_perturbations=n_perturbations)
        self.transform = transform
        self.update = update

    @property
    def algorithm(self):
        return "SemiParametric"

    def _create_fitting(self, image, shapes, gt_shape=None):
        return SemiParametricFittingResult(image, self, parameters=[shapes],
                                           gt_shape=gt_shape)

    def delta_ps(self, gt_shape, perturbed_shape):
        self.transform.target = gt_shape
        gt_ps = self.transform.as_vector()
        self.transform.target = perturbed_shape
        perturbed_ps = self.transform.as_vector()
        return gt_ps - perturbed_ps

    def _build_regressor(self, regressor, features, ):
        return SemiParametricRegressor(regressor, features, self.transform,
                                       self.update)


#TODO: Document me
class ParametricRegressorTrainer(RegressorTrainer):
    r"""
    """
    def __init__(self, appearance_model, transform, reference_shape,
                 regression_type=mlr, regression_features=weights,
                 update='compositional', noise_std=0.04, rotation=False,
                 n_perturbations=10, interpolator='scipy'):
        super(ParametricRegressorTrainer, self).__init__(
            reference_shape, regression_type=regression_type,
            regression_features=regression_features, noise_std=noise_std,
            rotation=rotation, n_perturbations=n_perturbations)
        self.appearance_model = appearance_model
        self.template = appearance_model.mean
        self.regression_features = regression_features
        self.transform = transform
        self.update = update
        self.interpolator = interpolator

    def _create_fitting(self, image, shapes, gt_shape=None):

        return ParametricFittingResult(image, self, parameters=[shapes],