Skip to content

Commit

Permalink
Merge pull request #591 from patricksnape/scale_around_centre
Browse files Browse the repository at this point in the history
Scale around centre
  • Loading branch information
jabooth committed Jun 25, 2015
2 parents 2e7d053 + 2a4e911 commit c576197
Show file tree
Hide file tree
Showing 7 changed files with 85 additions and 106 deletions.
18 changes: 7 additions & 11 deletions menpo/image/base.py
Expand Up @@ -9,8 +9,8 @@
from menpo.shape import PointCloud
from menpo.landmark import Landmarkable
from menpo.transform import (Translation, NonUniformScale,
AlignmentUniformScale, Affine, Rotation,
UniformScale)
AlignmentUniformScale, Affine, scale_about_centre,
rotate_ccw_about_centre)
from menpo.visualize.base import ImageViewer, LandmarkableViewable, Viewable
from .interpolation import scipy_interpolation, cython_interpolation
from .extract_patches import extract_patches
Expand Down Expand Up @@ -1397,14 +1397,15 @@ def warp_to_shape(self, template_shape, transform, warp_landmarks=False,
batch_size=batch_size)
sampled = self.sample(points_to_sample,
order=order, mode=mode, cval=cval)

# set any nan values to 0
sampled[np.isnan(sampled)] = 0
# build a warped version of the image
warped_pixels = sampled.reshape(
(self.n_channels,) + tuple(template_shape))

return self._build_warp_to_shape(warped_pixels, transform,
warp_landmarks)
warp_landmarks)

def _build_warp_to_shape(self, warped_pixels, transform, warp_landmarks):
# factored out common logic from the different paths we can take in
Expand Down Expand Up @@ -1667,10 +1668,7 @@ def zoom(self, scale, cval=0.0):
cval : ``float``, optional
The value to be set outside the rotated image boundaries.
"""
centre = Translation(-self.centre())
t = (centre.compose_before(UniformScale(1.0 / scale, self.n_dims))
.compose_before(centre.pseudoinverse()))

t = scale_about_centre(self, 1.0 / scale)
return self.warp_to_shape(self.shape, t, cval=cval)

def rotate_ccw_about_centre(self, theta, degrees=True, cval=0.0):
Expand All @@ -1695,10 +1693,8 @@ def rotate_ccw_about_centre(self, theta, degrees=True, cval=0.0):
if self.n_dims != 2:
raise ValueError('Image rotation is presently only supported on '
'2D images')
# create a translation that moves the centre of the image to the origin
t = Translation(self.centre)
r = Rotation.init_from_2d_ccw_angle(theta, degrees=degrees)
r_about_centre = t.pseudoinverse().compose_before(r).compose_before(t)

r_about_centre = rotate_ccw_about_centre(self, theta, degrees=degrees)
return self.warp_to_shape(self.shape, r_about_centre.pseudoinverse(),
warp_landmarks=True, cval=cval)

Expand Down
1 change: 0 additions & 1 deletion menpo/image/test/image_warp_test.py
Expand Up @@ -199,4 +199,3 @@ def test_zoom_booleanimage():

zim = im.zoom(1.2)
assert np.all(zim.pixels)

1 change: 1 addition & 0 deletions menpo/transform/__init__.py
Expand Up @@ -4,3 +4,4 @@
from .piecewiseaffine import PiecewiseAffine
from .rbf import R2LogR2RBF, R2LogRRBF
from .groupalign.procrustes import GeneralizedProcrustesAnalysis
from .compositions import scale_about_centre, rotate_ccw_about_centre
62 changes: 62 additions & 0 deletions menpo/transform/compositions.py
@@ -0,0 +1,62 @@
from .homogeneous import Translation, UniformScale, Rotation, Similarity


def scale_about_centre(obj, scale):
r"""
Return a Homogeneous Transform that implements scaling an object about
its centre. The given object must be transformable and must implement
a method to provide the object centre.
Parameters
----------
obj : :map:`Transformable`
A transformable object that has the ``centre`` method.
scale : `float` or ``(n_dims,)`` `ndarray`
The scale factor as defined in the :map:`Scale` documentation.
Returns
-------
transform : :map:`Homogeneous`
A homogeneous transform that implements the scaling.
"""
rescale = Similarity.init_identity(obj.n_dims)

s = UniformScale(scale, obj.n_dims, skip_checks=True)
t = Translation(-obj.centre(), skip_checks=True)
# Translate to origin, scale, then translate back
rescale.compose_before_inplace(t)
rescale.compose_before_inplace(s)
rescale.compose_before_inplace(t.pseudoinverse())
return rescale


def rotate_ccw_about_centre(obj, theta, degrees=True):
r"""
Return a Homogeneous Transform that implements rotating an object
counter-clockwise about its centre. The given object must be transformable
and must implement a method to provide the object centre.
Parameters
----------
obj : :map:`Transformable`
A transformable object that has the ``centre`` method.
theta : `float`
The angle of rotation clockwise about the origin.
degrees : `bool`, optional
If ``True`` theta is interpreted as degrees. If ``False``, theta is
interpreted as radians.
Returns
-------
transform : :map:`Homogeneous`
A homogeneous transform that implements the rotation.
"""
rotate_ccw = Similarity.init_identity(obj.n_dims)

r = Rotation.init_from_2d_ccw_angle(theta, degrees=degrees)
t = Translation(-obj.centre(), skip_checks=True)
# Translate to origin, rotate counter-clockwise, then translate back
rotate_ccw.compose_before_inplace(t)
rotate_ccw.compose_before_inplace(r)
rotate_ccw.compose_before_inplace(t.pseudoinverse())
return rotate_ccw
28 changes: 13 additions & 15 deletions menpo/transform/groupalign/procrustes.py
@@ -1,11 +1,13 @@
import numpy as np

from menpo.transform import AlignmentSimilarity, UniformScale, Translation
from ..homogeneous import AlignmentSimilarity
from .base import MultipleAlignment

mean_pointcloud = None # to avoid circular imports
PointCloud = None # to avoid circular imports
Similarity = None # to avoid circular imports

avoid_circular = None # to avoid circular imports
mean_pointcloud = None # to avoid circular imports
PointCloud = None # to avoid circular imports
scale_about_centre = None # to avoid circular imports


class GeneralizedProcrustesAnalysis(MultipleAlignment):
Expand Down Expand Up @@ -45,25 +47,21 @@ def _recursive_procrustes(self):
r"""
Recursively calculates a procrustes alignment.
"""
global mean_pointcloud, PointCloud, Similarity
if mean_pointcloud is None or PointCloud is None or Similarity is None:
global mean_pointcloud, PointCloud, scale_about_centre, avoid_circular
if avoid_circular is None:
from menpo.shape import mean_pointcloud, PointCloud
from menpo.transform import Similarity
from ..compositions import scale_about_centre
avoid_circular = True

if self.n_iterations > self.max_iterations:
return False
new_tgt = mean_pointcloud([PointCloud(t.aligned_source().points,
copy=False)
for t in self.transforms])
# rescale the new_target to be the same size as the original about
# it's centre
rescale = Similarity.init_identity(new_tgt.n_dims)

s = UniformScale(self.initial_target_scale / new_tgt.norm(),
self.n_dims, skip_checks=True)
t = Translation(-new_tgt.centre(), skip_checks=True)
rescale.compose_before_inplace(t)
rescale.compose_before_inplace(s)
rescale.compose_before_inplace(t.pseudoinverse())
rescale = scale_about_centre(new_tgt,
self.initial_target_scale / new_tgt.norm())
rescale.apply_inplace(new_tgt)
# check to see if we have converged yet
delta_target = np.linalg.norm(self.target.points - new_tgt.points)
Expand Down
3 changes: 2 additions & 1 deletion menpo/transform/homogeneous/rotation.py
Expand Up @@ -87,7 +87,8 @@ def init_from_2d_ccw_angle(cls, theta, degrees=True):
# convert to radians
theta = theta * np.pi / 180.0
return Rotation(np.array([[np.cos(theta), -np.sin(theta)],
[np.sin(theta), np.cos(theta)]]))
[np.sin(theta), np.cos(theta)]]),
skip_checks=True)

@property
def rotation_matrix(self):
Expand Down
78 changes: 0 additions & 78 deletions menpo/transform/image.py

This file was deleted.

0 comments on commit c576197

Please sign in to comment.