|
From: <md...@us...> - 2007-09-10 17:40:51
|
Revision: 3823
http://matplotlib.svn.sourceforge.net/matplotlib/?rev=3823&view=rev
Author: mdboom
Date: 2007-09-10 10:40:47 -0700 (Mon, 10 Sep 2007)
Log Message:
-----------
Adding new files that will eventually replace transforms.py/cpp
Added Paths:
-----------
branches/transforms/lib/matplotlib/affine.py
branches/transforms/lib/matplotlib/bbox.py
Added: branches/transforms/lib/matplotlib/affine.py
===================================================================
--- branches/transforms/lib/matplotlib/affine.py (rev 0)
+++ branches/transforms/lib/matplotlib/affine.py 2007-09-10 17:40:47 UTC (rev 3823)
@@ -0,0 +1,207 @@
+"""
+A set of classes to handle transformations.
+
+2007 Michael Droettboom
+"""
+
+import numpy as N
+from numpy.linalg import inv
+
+class Transform(object):
+ def __call__(self, points):
+ raise NotImplementedError()
+
+ def __add__(self, other):
+ if isinstance(other, Transform):
+ return CompositeTransform(self, other)
+ raise TypeError("Can not add Transform to object of type '%s'" % type(other))
+
+ def __radd__(self, other):
+ if isinstance(other, Transform):
+ return CompositeTransform(other, self)
+ raise TypeError("Can not add Transform to object of type '%s'" % type(other))
+
+ def has_inverse(self):
+ raise NotImplementedError()
+
+ def inverted(self):
+ raise NotImplementedError()
+
+ def is_separable(self):
+ return False
+
+class CompositeTransform(Transform):
+ def __init__(self, a, b):
+ assert a.output_dims == b.input_dims
+ self.a = a
+ self.b = b
+
+ def __call__(self, points):
+ return self.b(self.a(points))
+
+class Affine2D(Transform):
+ input_dims = 2
+ output_dims = 2
+
+ def __init__(self, matrix = None):
+ """
+ Initialize an Affine transform from a 3x3 numpy float array.
+
+ a c e
+ b d f
+ 0 0 1
+ """
+ if matrix is None:
+ matrix = N.identity(3)
+ else:
+ assert matrix.shape == (3, 3)
+ self.mtx = matrix
+
+ def __repr__(self):
+ return repr(self.mtx)
+
+ def __str__(self):
+ return str(self.mtx)
+
+ #@staticmethod
+ def from_values(a, b, c, d, e, f):
+ return Affine2D(Affine2D.matrix_from_values(a, b, c, d, e, f))
+ from_values = staticmethod(from_values)
+
+ #@staticmethod
+ def matrix_from_values(a, b, c, d, e, f):
+ affine = N.zeros((3,3), N.float_)
+ affine[0,] = a, c, e
+ affine[1,] = b, d, f
+ affine[2,2] = 1
+ return affine
+ matrix_from_values = staticmethod(matrix_from_values)
+
+ def __call__(self, points):
+ """
+ Applies the transformation to a set of 2D points and
+ returns the result.
+
+ points must be a numpy array of shape (N, 2), where N is the
+ number of points.
+ """
+ # MGDTODO: This involves a copy. We may need to do something like
+ # http://neuroimaging.scipy.org/svn/ni/ni/trunk/neuroimaging/core/reference/mapping.py
+ # to separate the matrix out into the translation and scale components
+ # and apply each separately (which is still sub-optimal)
+
+ # This is nicer for now, however, since we can just keep a
+ # regular affine matrix around
+ new_points = points.swapaxes(0, 1)
+ new_points = N.vstack((new_points, N.ones((1, points.shape[0]))))
+ result = N.dot(self.mtx, new_points)[:2]
+ result.swapaxes(0, 1)
+ return result
+
+ #@staticmethod
+ def _concat(a, b):
+ return N.dot(b, a)
+ _concat = staticmethod(_concat)
+
+ def concat(a, b):
+ return Affine2D(Affine2D._concat(a.mtx, b.mtx))
+ concat = staticmethod(concat)
+
+ #@staticmethod
+ def identity():
+ return Affine2D(N.identity(3))
+ identity = staticmethod(identity)
+
+ def __add__(self, other):
+ if isinstance(other, Affine2D):
+ return Affine2D.concat(self, other)
+ return Transform.__add__(self, other)
+
+ def __radd__(self, other):
+ if isinstance(other, Affine2D):
+ return Affine2D.concat(other, self)
+ return Transform.__radd__(self, other)
+
+ def rotated(self, theta):
+ a = N.cos(theta)
+ b = N.sin(theta)
+ rotate_mtx = self.matrix_from_values(a, b, -b, a, 0, 0)
+ return Affine2D(self._concat(self.mtx, rotate_mtx))
+
+ def rotated_deg(self, degrees):
+ return self.rotated(degrees*N.pi/180.)
+
+ def translated(self, tx, ty):
+ translate_mtx = self.matrix_from_values(1., 0., 0., 1., tx, ty)
+ return Affine2D(self._concat(self.mtx, translate_mtx))
+
+ def scaled(self, sx, sy=None):
+ if sy is None:
+ sy = sx
+ scale_mtx = self.matrix_from_values(sx, 0., 0., sy, 0., 0.)
+ return Affine2D(self._concat(self.mtx, scale_mtx))
+
+ def inverted(self):
+ # MGDTODO: We may want to optimize by storing the inverse
+ # of the transform with every transform
+ return Affine2D(inv(self.mtx))
+
+ def is_separable(self):
+ mtx = self.mtx
+ return mtx[0, 1] == 0.0 and mtx[1, 0] == 0.0
+
+class BlendedAffine2D(Affine2D):
+ def __init__(self, x_transform, y_transform):
+ assert isinstance(x_transform, Affine2D)
+ assert isinstance(y_transform, Affine2D)
+ assert x_transform.is_separable()
+ assert y_transform.is_separable()
+ x_mtx = x_transform.mtx
+ y_mtx = y_transform.mtx
+ self.mtx = self.matrix_from_values(
+ x_mtx[0,0], 0.0, 0.0, y_mtx[1,1], x_mtx[0,2], y_mtx[1,2])
+
+# This is a placeholder since eventually we may need to handle the
+# more general case of two transforms that aren't affines
+BlendedTransform = BlendedAffine2D
+
+def blend_xy_sep_transform(x_transform, y_transform):
+ return BlendedAffine2D(x_transform, y_transform)
+
+def get_bbox_transform(boxin, boxout):
+ x_scale = boxout.width() / boxin.width()
+ y_scale = boxout.height() / boxin.height()
+
+ # MGDTODO: Optimize
+ return Affine2D() \
+ .translated(-boxin.xmin(), -boxin.ymin()) \
+ .scaled(x_scale, y_scale) \
+ .translated(boxout.xmin(), boxout.ymin())
+
+if __name__ == '__main__':
+ print Affine2D.from_values(1., 0, 0, 1, 0, 0)
+
+ print "translated", Affine2D.identity().translated(5, 4)
+ print "rotated", Affine2D.identity().rotated_deg(30)
+ print "scaled", Affine2D.identity().scaled(5, 4)
+
+ transform = Affine2D.identity().rotated_deg(30).translated(5, 4)
+
+ points = N.array([[1, 2], [3, 4], [5, 6]])
+
+ print inv(transform.mtx)
+
+ print transform(points)
+
+ transform = Affine2D.identity().scaled(5., 1.).translated(10, 0)
+ print transform
+ print transform.inverted()
+
+ from bbox import Bbox
+ boxin = Bbox([[10, 10], [320, 240]])
+ boxout = Bbox([[25, 25], [640, 400]])
+ trans = bbox_transform(boxin, boxout)
+ print trans
+ print trans(N.array([[10, 10], [320, 240]]))
+
+__all__ = ['Transform', 'Affine2D']
Added: branches/transforms/lib/matplotlib/bbox.py
===================================================================
--- branches/transforms/lib/matplotlib/bbox.py (rev 0)
+++ branches/transforms/lib/matplotlib/bbox.py 2007-09-10 17:40:47 UTC (rev 3823)
@@ -0,0 +1,108 @@
+"""
+A convenience class for handling bounding boxes
+
+2007 Michael Droettboom
+"""
+
+import numpy as N
+
+class Bbox:
+ def __init__(self, points):
+ self._points = N.array(points)
+
+ #@staticmethod
+ def unit():
+ return Bbox([[0,0], [1,1]])
+ unit = staticmethod(unit)
+
+ #@staticmethod
+ def from_lbwh(left, bottom, width, height):
+ return Bbox([[left, bottom], [left + width, bottom + height]])
+ from_lbwh = staticmethod(from_lbwh)
+
+ #@staticmethod
+ def from_lbrt(left, bottom, right, top):
+ return Bbox([[left, bottom], [right, top]])
+ from_lbwh = staticmethod(from_lbwh)
+
+ # MGDTODO: Probably a more efficient ways to do this...
+ def xmin(self):
+ return self._points[0,0]
+
+ def ymin(self):
+ return self._points[0,1]
+
+ def xmax(self):
+ return self._points[1,0]
+
+ def ymax(self):
+ return self._points[1,1]
+
+ def width(self):
+ return self.xmax() - self.xmin()
+
+ def height(self):
+ return self.ymax() - self.ymin()
+
+ def transform(self, transform):
+ return Bbox(transform(points))
+
+ def inverse_transform(self, transform):
+ return Bbox(transform.inverted()(points))
+
+ def get_bounds(self):
+ return (self.xmin(), self.ymin(),
+ self.xmax() - self.xmin(), self.ymax() - self.ymin())
+
+def lbwh_to_bbox(left, bottom, width, height):
+ return Bbox([[left, bottom], [left + width, bottom + height]])
+
+def bbox_union(bboxes):
+ """
+ Return the Bbox that bounds all bboxes
+ """
+ assert(len(bboxes))
+
+ if len(bboxes) == 1:
+ return bboxes[0]
+
+ bbox = bboxes[0]
+ xmin = bbox.xmin
+ ymin = bbox.ymin
+ xmax = bbox.xmax
+ ymax = bbox.ymax
+
+ for bbox in bboxes[1:]:
+ xmin = min(xmin, bbox.xmin)
+ ymin = min(ymin, bbox.ymin)
+ xmax = max(xmax, bbox.xmax)
+ ymax = max(ymax, bbox.ymax)
+
+ return Bbox(xmin, ymin, xmax, ymax)
+
+# MGDTODO: There's probably a better place for this
+def nonsingular(vmin, vmax, expander=0.001, tiny=1e-15, increasing=True):
+ '''
+ Ensure the endpoints of a range are not too close together.
+
+ "too close" means the interval is smaller than 'tiny' times
+ the maximum absolute value.
+
+ If they are too close, each will be moved by the 'expander'.
+ If 'increasing' is True and vmin > vmax, they will be swapped,
+ regardless of whether they are too close.
+ '''
+ swapped = False
+ if vmax < vmin:
+ vmin, vmax = vmax, vmin
+ swapped = True
+ if vmax - vmin <= max(abs(vmin), abs(vmax)) * tiny:
+ if vmin==0.0:
+ vmin = -expander
+ vmax = expander
+ else:
+ vmin -= expander*abs(vmin)
+ vmax += expander*abs(vmax)
+ if swapped and not increasing:
+ vmin, vmax = vmax, vmin
+ return vmin, vmax
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|