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)
+    
+    [EMAIL PROTECTED]
+    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)
+        
+    [EMAIL PROTECTED]
+    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
+    
+    [EMAIL PROTECTED]
+    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)
+    
+    [EMAIL PROTECTED]
+    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)
+
+    [EMAIL PROTECTED]
+    def unit():
+       return Bbox([[0,0], [1,1]])
+    unit = staticmethod(unit)
+
+    [EMAIL PROTECTED]
+    def from_lbwh(left, bottom, width, height):
+       return Bbox([[left, bottom], [left + width, bottom + height]])
+    from_lbwh = staticmethod(from_lbwh)
+
+    [EMAIL PROTECTED]
+    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.

-------------------------------------------------------------------------
This SF.net email is sponsored by: Microsoft
Defy all challenges. Microsoft(R) Visual Studio 2005.
http://clk.atdmt.com/MRT/go/vse0120000070mrt/direct/01/
_______________________________________________
Matplotlib-checkins mailing list
Matplotlib-checkins@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/matplotlib-checkins

Reply via email to