Skip to content

Commit

Permalink
Merge pull request #55 from menpo/sdm_pickle
Browse files Browse the repository at this point in the history
Fix pickling SDMs
  • Loading branch information
Patrick Snape committed Jul 16, 2015
2 parents 15ef00a + 0a1e97d commit a4fcc16
Show file tree
Hide file tree
Showing 7 changed files with 104 additions and 35 deletions.
7 changes: 3 additions & 4 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,11 @@ env:
- PYTHON_VERSION: 3.4

install:
- wget https://raw.githubusercontent.com/menpo/condaci/v0.3.0/condaci.py -O condaci.py
- python condaci.py setup $PYTHON_VERSION --channel $BINSTAR_USER
- export PATH=$HOME/miniconda/bin:$PATH
- wget https://raw.githubusercontent.com/menpo/condaci/v0.4.1/condaci.py -O condaci.py
- python condaci.py setup

script:
- python condaci.py auto ./conda --binstaruser $BINSTAR_USER --binstarkey $BINSTAR_KEY
- ~/miniconda/bin/python condaci.py build ./conda

notifications:
slack:
Expand Down
10 changes: 5 additions & 5 deletions appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@ matrix:
fast_finish: true

platform:
- x86
- x64
- x86
- x64

init:
- ps: Start-FileDownload 'https://raw.githubusercontent.com/menpo/condaci/v0.3.0/condaci.py' C:\\condaci.py; echo "Done"
- cmd: python C:\\condaci.py setup %PYTHON_VERSION% --channel %BINSTAR_USER%
- ps: Start-FileDownload 'https://raw.githubusercontent.com/menpo/condaci/v0.4.1/condaci.py' C:\\condaci.py; echo "Done"
- cmd: python C:\\condaci.py setup

install:
- cmd: C:\\Miniconda\\python C:\\condaci.py auto ./conda --binstaruser %BINSTAR_USER% --binstarkey %BINSTAR_KEY%
- cmd: C:\\Miniconda\\python C:\\condaci.py build ./conda

notifications:
- provider: Slack
Expand Down
2 changes: 1 addition & 1 deletion conda/meta.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ test:
requires:
- coverage
- nose
- mock
- mock 1.0.1

imports:
- menpofit
Expand Down
54 changes: 54 additions & 0 deletions menpofit/regression/parametricfeatures.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import numpy as np


def extract_parametric_features(appearance_model, warped_image,
rergession_features):
r"""
Expand Down Expand Up @@ -120,3 +123,54 @@ def project_out(appearance_model, warped_image):
"""
diff = warped_image.as_vector() - appearance_model.mean().as_vector()
return appearance_model.distance_to_subspace_vector(diff).ravel()


class nonparametric_regression_features(object):

def __init__(self, patch_shape, feature_patch_length, regression_features):
self.patch_shape = patch_shape
self.feature_patch_length = feature_patch_length
self.regression_features = regression_features

def __call__(self, image, shape):
# extract patches
patches = image.extract_patches(shape, patch_size=self.patch_shape)

features = np.zeros((shape.n_points, self.feature_patch_length))
for j, patch in enumerate(patches):
# compute features
features[j, ...] = self.regression_features(patch).as_vector()

return np.hstack((features.ravel(), 1))


class parametric_regression_features(object):

def __init__(self, transform, template, appearance_model,
regression_features):
self.transform = transform
self.template = template
self.appearance_model = appearance_model
self.regression_features = regression_features

def __call__(self, image, shape):
self.transform.set_target(shape)
# TODO should the template be a mask or a shape? warp_to_shape here
warped_image = image.warp_to_mask(self.template.mask, self.transform,
warp_landmarks=False)
features = extract_parametric_features(
self.appearance_model, warped_image, self.regression_features)
return np.hstack((features, 1))


class semiparametric_classifier_regression_features(object):

def __init__(self, patch_shape, classifiers):
self.patch_shape = patch_shape
self.classifiers = classifiers

def __call__(self, image, shape):
patches = image.extract_patches(shape, patch_size=self.patch_shape)
features = [clf(patch.as_vector(keep_channels=True))
for (clf, patch) in zip(self.classifiers, patches)]
return np.hstack((np.asarray(features).ravel(), 1))
5 changes: 3 additions & 2 deletions menpofit/regression/regressors.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@ class mlr(object):
T: numpy.array
The shapes differential that denote the dependent variable.
"""
def __init__(self, X, T):
def __init__(self, X, T, lmda=0):
XX = np.dot(X.T, X)
XX = (XX + XX.T) / 2
if lmda > 0:
np.fill_diagonal(XX, lmda + np.diag(XX))
XT = np.dot(X.T, T)
self.R = np.linalg.solve(XX, XT)

Expand Down
59 changes: 37 additions & 22 deletions menpofit/regression/trainer.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@
ParametricFittingResult)
from .base import (NonParametricRegressor, SemiParametricRegressor,
ParametricRegressor)
from .parametricfeatures import extract_parametric_features, weights
from .parametricfeatures import extract_parametric_features, weights, \
nonparametric_regression_features, parametric_regression_features, \
semiparametric_classifier_regression_features
from .regressors import mlr


Expand Down Expand Up @@ -101,6 +103,22 @@ def features(self, image, shape):
"""
pass

@abc.abstractmethod
def get_features_function(self):
r"""
Abstract method to return the function that computes the features for
the regression.
Parameters
----------
image : :map:`MaskedImage`
The current image.
shape : :map:`PointCloud`
The current shape.
"""
pass

@abc.abstractmethod
def delta_ps(self, gt_shape, perturbed_shape):
r"""
Expand Down Expand Up @@ -178,7 +196,7 @@ def train(self, images, shapes, perturbed_shapes=None, verbose=False,
axis=1)))
if verbose:
print_dynamic('- Regression RMSE is {0:.5f}.\n'.format(error))
return self._build_regressor(regressor, self.features)
return self._build_regressor(regressor, self.get_features_function())

def perturb_shapes(self, gt_shape):
r"""
Expand Down Expand Up @@ -293,6 +311,11 @@ def _create_fitting(self, image, shapes, gt_shape=None):
return NonParametricFittingResult(image, self, parameters=[shapes],
gt_shape=gt_shape)

def get_features_function(self):
return nonparametric_regression_features(self.patch_shape,
self._feature_patch_length,
self.regression_features)

def features(self, image, shape):
r"""
Method that extracts the features for the regression, which in this
Expand All @@ -306,15 +329,7 @@ def features(self, image, shape):
shape : :map:`PointCloud`
The current shape.
"""
# extract patches
patches = image.extract_patches(shape, patch_size=self.patch_shape)

features = np.zeros((shape.n_points, self._feature_patch_length))
for j, patch in enumerate(patches):
# compute features
features[j, ...] = self.regression_features(patch).as_vector()

return np.hstack((features.ravel(), 1))
return self.get_features_function()(image, shape)

def delta_ps(self, gt_shape, perturbed_shape):
r"""
Expand Down Expand Up @@ -520,6 +535,11 @@ def _create_fitting(self, image, shapes, gt_shape=None):
return ParametricFittingResult(image, self, parameters=[shapes],
gt_shape=gt_shape)

def get_features_function(self):
return parametric_regression_features(self.transform, self.template,
self.appearance_model,
self.regression_features)

def features(self, image, shape):
r"""
Method that extracts the features for the regression, which in this
Expand All @@ -533,13 +553,7 @@ def features(self, image, shape):
shape : :map:`PointCloud`
The current shape.
"""
self.transform.set_target(shape)
# TODO should the template be a mask or a shape? warp_to_shape here
warped_image = image.warp_to_mask(self.template.mask, self.transform,
warp_landmarks=False)
features = extract_parametric_features(
self.appearance_model, warped_image, self.regression_features)
return np.hstack((features, 1))
return self.get_features_function()(image, shape)

def delta_ps(self, gt_shape, perturbed_shape):
r"""
Expand Down Expand Up @@ -615,6 +629,10 @@ def _set_up(self):
# set up sampling grid
self.sampling_grid = build_sampling_grid(self.patch_shape)

def get_features_function(self):
return semiparametric_classifier_regression_features(self.patch_shape,
self.classifiers)

def features(self, image, shape):
r"""
Method that extracts the features for the regression, which in this
Expand All @@ -628,7 +646,4 @@ def features(self, image, shape):
shape : :map:`PointCloud`
The current shape.
"""
patches = image.extract_patches(shape, patch_size=self.patch_shape)
features = [clf(patch.as_vector(keep_channels=True))
for (clf, patch) in zip(self.classifiers, patches)]
return np.hstack((np.asarray(features).ravel(), 1))
return self.get_features_function()(image, shape)
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@
packages=find_packages(),
install_requires=['menpo>=0.5.1,<0.6',
'scikit-learn>=0.16,<0.17'],
tests_require=['nose', 'mock']
tests_require=['nose', 'mock==1.0.1']
)

0 comments on commit a4fcc16

Please sign in to comment.