|
From: <md...@us...> - 2007-09-12 19:48:00
|
Revision: 3842
http://matplotlib.svn.sourceforge.net/matplotlib/?rev=3842&view=rev
Author: mdboom
Date: 2007-09-12 12:47:56 -0700 (Wed, 12 Sep 2007)
Log Message:
-----------
More progress. Zooming and panning working thanks to John's patch.
Modified Paths:
--------------
branches/transforms/lib/matplotlib/affine.py
branches/transforms/lib/matplotlib/artist.py
branches/transforms/lib/matplotlib/axes.py
branches/transforms/lib/matplotlib/backend_bases.py
branches/transforms/lib/matplotlib/backends/backend_agg.py
branches/transforms/lib/matplotlib/backends/backend_tkagg.py
Modified: branches/transforms/lib/matplotlib/affine.py
===================================================================
--- branches/transforms/lib/matplotlib/affine.py 2007-09-12 18:22:24 UTC (rev 3841)
+++ branches/transforms/lib/matplotlib/affine.py 2007-09-12 19:47:56 UTC (rev 3842)
@@ -8,9 +8,16 @@
from numpy.linalg import inv
from sets import Set
+# MGDTODO: The name of this module is bad, since it deals with
+# non-affine transformations as well. It should probably just be
+# "transforms", but we already had one of those... ;)
+
# MGDTODO: This creates a ton of cyclical references. We may want to
# consider using weak references
+# MGDTODO: deep copying is probably incorrect wrt the parent/child
+# relationships
+
class TransformNode(object):
def __init__(self):
self._parents = Set()
@@ -48,29 +55,31 @@
points = N.array(args, dtype=N.float_).reshape(2, 2)
return Bbox(points)
from_lbrt = staticmethod(from_lbrt)
+
+ def __copy__(self):
+ return Bbox(self._points.copy())
+ def __deepcopy__(self, memo):
+ return Bbox(self._points.copy())
+
def __cmp__(self, other):
# MGDTODO: Totally suboptimal
- if isinstance(other, Bbox):
- if (self._points == other._points).all():
- return 0
+ if isinstance(other, Bbox) and (self._points == other._points).all():
+ return 0
return -1
-
+
+ def __repr__(self):
+ return 'Bbox(%s)' % repr(self._points)
+ __str__ = __repr__
+
# JDH: the update method will update the box limits from the
# existing limits and the new data; it appears here you are just
# using the new data. We use an "ignore" flag to specify whether
# you want to include the existing data or not in the update
- def update_from_data(self, x, y):
+ def update_from_data(self, x, y, ignore=True):
self._points = N.array([[x.min(), y.min()], [x.max(), y.max()]], N.float_)
self.invalidate()
-
- def copy(self):
- return Bbox(self._points.copy())
- def __repr__(self):
- return 'Bbox(%s)' % repr(self._points)
- __str__ = __repr__
-
# MGDTODO: Probably a more efficient ways to do this...
def _get_xmin(self):
return self._points[0, 0]
@@ -136,19 +145,24 @@
return self.ymax - self.ymin
height = property(_get_height)
+ def _get_bounds(self):
+ return (self.xmin, self.ymin,
+ self.xmax - self.xmin, self.ymax - self.ymin)
+ def _set_bounds(self, bounds):
+ l,b,w,h = bounds
+ self._points = N.array([[l, b], [l+w, b+h]], N.float_)
+ self.invalidate()
+ bounds = property(_get_bounds, _set_bounds)
+
def transformed(self, transform):
return Bbox(transform(self._points))
def inverse_transformed(self, transform):
return Bbox(transform.inverted()(self._points))
- def get_bounds(self):
- return (self.xmin, self.ymin,
- self.xmax - self.xmin, self.ymax - self.ymin)
-
def expanded(self, sw, sh):
- width = self.width()
- height = self.height()
+ width = self.width
+ height = self.height
deltaw = (sw * width - width) / 2.0
deltah = (sh * height - height) / 2.0
a = N.array([[-deltaw, -deltah], [deltaw, deltah]])
@@ -199,6 +213,9 @@
if isinstance(other, Transform):
return composite_transform_factory(other, self)
raise TypeError("Can not add Transform to object of type '%s'" % type(other))
+
+ def transform_point(self, point):
+ return self.__call__([point])[0]
def has_inverse(self):
raise NotImplementedError()
@@ -211,49 +228,27 @@
def is_affine(self):
return False
-
-class Affine2D(Transform):
+
+# MGDTODO: Separate out Affine2DBase / Affine2DConcrete so BlendedAffine and CompositeAffine don't have translate/scale/rotate members
+
+class Affine2DBase(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
- """
+ def __init__(self):
Transform.__init__(self)
- if matrix is None:
- matrix = N.identity(3)
- else:
- assert matrix.shape == (3, 3)
- self._mtx = matrix
self._inverted = None
- def __repr__(self):
- return "Affine2D(%s)" % repr(self._mtx)
- __str__ = __repr__
-
- def __cmp__(self, other):
- # MGDTODO: We need to decide if we want deferred transforms
- # to be equal to this one
- if isinstance(other, Affine2D):
- if (self.get_matrix() == other.get_matrix()).all():
- return 0
- return -1
-
def _do_invalidation(self):
result = self._inverted is None
self._inverted = None
return result
+
+ #@staticmethod
+ def _concat(a, b):
+ return N.dot(b, a)
+ _concat = staticmethod(_concat)
- #@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)
-
def to_values(self):
mtx = self.get_matrix()
return tuple(mtx[:2].swapaxes(0, 1).flatten())
@@ -268,7 +263,7 @@
matrix_from_values = staticmethod(matrix_from_values)
def get_matrix(self):
- return self._mtx
+ raise NotImplementedError()
def __call__(self, points):
"""
@@ -278,26 +273,74 @@
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 easier for now, however, since we can just keep a
- # regular affine matrix around
- # MGDTODO: Trap cases where this isn't an array and fix there
+ # MGDTODO: The major speed trap here is just converting to
+ # the points to an array in the first place. If we can use
+ # more arrays upstream, that should help here.
mtx = self.get_matrix()
points = N.asarray(points, N.float_)
- new_points = points.swapaxes(0, 1)
- new_points = N.vstack((new_points, N.ones((1, points.shape[0]))))
- result = N.dot(mtx, new_points)[:2]
- return result.swapaxes(0, 1)
+ points = points.transpose()
+ points = N.dot(mtx[0:2, 0:2], points)
+ points = points + mtx[0:2, 2:]
+ return points.transpose()
+ def inverted(self):
+ if self._inverted is None:
+ mtx = self.get_matrix()
+ self._inverted = Affine2D(inv(mtx))
+ return self._inverted
+
+ def is_separable(self):
+ mtx = self.get_matrix()
+ return mtx[0, 1] == 0.0 and mtx[1, 0] == 0.0
+
+ def is_affine(self):
+ return True
+
+
+class Affine2D(Affine2DBase):
+ 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
+ """
+ Affine2DBase.__init__(self)
+ if matrix is None:
+ matrix = N.identity(3)
+ else:
+ assert matrix.shape == (3, 3)
+ self._mtx = matrix
+ self._inverted = None
+
+ def __repr__(self):
+ return "Affine2D(%s)" % repr(self._mtx)
+ __str__ = __repr__
+
+ def __cmp__(self, other):
+ if (isinstance(other, Affine2D) and
+ (self.get_matrix() == other.get_matrix()).all()):
+ return 0
+ return -1
+
+ def __copy__(self):
+ return Affine2D(self._mtx.copy())
+
+ def __deepcopy__(self, memo):
+ return Affine2D(self._mtx.copy())
+
#@staticmethod
- def _concat(a, b):
- return N.dot(b, a)
- _concat = staticmethod(_concat)
+ 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)
+ def get_matrix(self):
+ return self._mtx
+
#@staticmethod
def concat(a, b):
return Affine2D(Affine2D._concat(a._mtx, b._mtx))
@@ -346,19 +389,18 @@
def is_affine(self):
return True
-class BlendedAffine2D(Affine2D):
+class BlendedAffine2D(Affine2DBase):
def __init__(self, x_transform, y_transform):
assert x_transform.is_affine()
assert y_transform.is_affine()
assert x_transform.is_separable()
assert y_transform.is_separable()
- Transform.__init__(self)
+ Affine2DBase.__init__(self)
self.add_children([x_transform, y_transform])
self._x = x_transform
self._y = y_transform
self._mtx = None
- self._inverted = None
def __repr__(self):
return "BlendedAffine2D(%s,%s)" % (self._x, self._y)
@@ -367,7 +409,7 @@
def _do_invalidation(self):
if self._mtx is not None:
self._mtx = None
- Affine2D._do_invalidation(self)
+ Affine2DBase._do_invalidation(self)
return False
return True
@@ -376,8 +418,9 @@
x_mtx = self._x.get_matrix()
y_mtx = self._y.get_matrix()
# This works because we already know the transforms are
- # separable
- self._mtx = N.vstack([x_mtx[0], y_mtx[1], [0.0, 0.0, 1.0]])
+ # separable, though normally one would want to set b and
+ # c to zero.
+ self._mtx = N.vstack((x_mtx[0], y_mtx[1], [0.0, 0.0, 1.0]))
def is_separable(self):
return True
@@ -397,20 +440,22 @@
self._y = y_transform
def __call__(self, points):
- # MGDTODO: Implement me
- pass
+ x_points = self._x(points)
+ y_points = self._y(points)
+ # This works because we already know the transforms are
+ # separable
+ return N.hstack((x_points[:, 0:1], y_points[:, 1:2]))
-class CompositeAffine2D(Affine2D):
+class CompositeAffine2D(Affine2DBase):
def __init__(self, a, b):
assert a.is_affine()
assert b.is_affine()
- Transform.__init__(self)
+ Affine2DBase.__init__(self)
self.add_children([a, b])
self._a = a
self._b = b
self._mtx = None
- self._inverted = None
def __repr__(self):
return "CompositeAffine2D(%s, %s)" % (self._a, self._b)
@@ -418,7 +463,7 @@
def _do_invalidation(self):
self._mtx = None
- Affine2D._do_invalidation(self)
+ Affine2DBase._do_invalidation(self)
def _make__mtx(self):
if self._mtx is None:
@@ -433,22 +478,23 @@
class CompositeTransform(Transform):
def __init__(self, a, b):
assert a.output_dims == b.input_dims
-
+ self.input_dims = a.input_dims
+ self.output_dims = b.output_dims
+
Transform.__init__(self)
self.add_children([a, b])
self._a = a
self._b = b
def __call__(self, points):
- # MGDTODO: Optimize here by concatenating affines if possible
return self._b(self._a(points))
-class BboxTransform(Affine2D):
+class BboxTransform(Affine2DBase):
def __init__(self, boxin, boxout):
assert isinstance(boxin, Bbox)
assert isinstance(boxout, Bbox)
- Transform.__init__(self)
+ Affine2DBase.__init__(self)
self.add_children([boxin, boxout])
self._boxin = boxin
self._boxout = boxout
@@ -462,7 +508,7 @@
def _do_invalidation(self):
if self._mtx is not None:
self._mtx = None
- Affine2D._do_invalidation(self)
+ Affine2DBase._do_invalidation(self)
return False
return True
@@ -481,14 +527,6 @@
self._mtx = affine._mtx
- def __call__(self, points):
- self._make__mtx()
- return Affine2D.__call__(self, points)
-
- def inverted(self):
- self._make__mtx()
- return Affine2D.inverted(self)
-
def is_separable(self):
return True
@@ -541,6 +579,9 @@
return interval[0] < val and interval[1] > val
if __name__ == '__main__':
+ from random import random
+ import timeit
+
bbox = Bbox.from_lbrt(10., 15., 20., 25.)
assert bbox.xmin == 10
assert bbox.ymin == 15
@@ -589,7 +630,9 @@
scale = Affine2D().scale(10, 20)
assert scale.to_values() == (10, 0, 0, 20, 0, 0)
rotation = Affine2D().rotate_deg(30)
- print rotation.to_values() == (0.86602540378443871, 0.49999999999999994, -0.49999999999999994, 0.86602540378443871, 0.0, 0.0)
+ print rotation.to_values() == (0.86602540378443871, 0.49999999999999994,
+ -0.49999999999999994, 0.86602540378443871,
+ 0.0, 0.0)
points = N.array([[1,2],[3,4],[5,6],[7,8]], N.float_)
translated_points = translation(points)
@@ -600,11 +643,20 @@
print rotated_points
tpoints1 = rotation(translation(scale(points)))
- trans_sum = rotation + translation + scale
+ trans_sum = scale + translation + rotation
tpoints2 = trans_sum(points)
print tpoints1, tpoints2
print tpoints1 == tpoints2
# Need to do some sort of fuzzy comparison here?
# assert (tpoints1 == tpoints2).all()
+
+ # Here are some timing tests
+ points = [(random(), random()) for i in xrange(10000)]
+ t = timeit.Timer("trans_sum(points)", "from __main__ import trans_sum, points")
+ print "Time to transform 10000 x 10 points as tuples:", t.timeit(10)
+
+ points2 = N.asarray(points)
+ t = timeit.Timer("trans_sum(points2)", "from __main__ import trans_sum, points2")
+ print "Time to transform 10000 x 10 points as numpy array:", t.timeit(10)
__all__ = ['Transform', 'Affine2D']
Modified: branches/transforms/lib/matplotlib/artist.py
===================================================================
--- branches/transforms/lib/matplotlib/artist.py 2007-09-12 18:22:24 UTC (rev 3841)
+++ branches/transforms/lib/matplotlib/artist.py 2007-09-12 19:47:56 UTC (rev 3842)
@@ -337,7 +337,7 @@
def _set_gc_clip(self, gc):
'set the clip properly for the gc'
if self.clipbox is not None:
- gc.set_clip_rectangle(self.clipbox.get_bounds())
+ gc.set_clip_rectangle(self.clipbox.bounds)
gc.set_clip_path(self._clippath)
def draw(self, renderer, *args, **kwargs):
Modified: branches/transforms/lib/matplotlib/axes.py
===================================================================
--- branches/transforms/lib/matplotlib/axes.py 2007-09-12 18:22:24 UTC (rev 3841)
+++ branches/transforms/lib/matplotlib/axes.py 2007-09-12 19:47:56 UTC (rev 3842)
@@ -1,5 +1,5 @@
from __future__ import division, generators
-import math, sys, warnings
+import math, sys, warnings, copy
import numpy as npy
@@ -483,7 +483,7 @@
"""
martist.Artist.__init__(self)
self._position = maffine.Bbox.from_lbwh(*rect)
- self._originalPosition = self._position.copy()
+ self._originalPosition = copy.deepcopy(self._position)
self.set_axes(self)
self.set_aspect('auto')
self.set_adjustable('box')
@@ -613,7 +613,7 @@
"""
martist.Artist.set_figure(self, fig)
- l, b, w, h = self._position.get_bounds()
+ l, b, w, h = self._position.bounds
xmin = fig.bbox.xmin
xmax = fig.bbox.xmax
ymin = fig.bbox.ymin
@@ -669,9 +669,9 @@
def get_position(self, original=False):
'Return the axes rectangle left, bottom, width, height'
if original:
- return self._originalPosition[:]
+ return self._originalPosition.bounds
else:
- return self._position[:]
+ return self._position.bounds
# return [val.get() for val in self._position]
def set_position(self, pos, which='both'):
@@ -694,10 +694,10 @@
# # Change values within self._position--don't replace it.
# for num,val in zip(pos, self._position):
# val.set(num)
- self._position = pos
+ self._position.bounds = pos.bounds
# MGDTODO: side-effects
if which in ('both', 'original'):
- self._originalPosition = pos
+ self._originalPosition.bounds = pos.bounds
def _set_artist_props(self, a):
@@ -1547,7 +1547,9 @@
def get_xscale(self):
'return the xaxis scale string: log or linear'
- return self.scaled[self.transData.get_funcx().get_type()]
+ # MGDTODO
+ # return self.scaled[self.transData.get_funcx().get_type()]
+ return 'linear'
def set_xscale(self, value, basex = 10, subsx=None):
"""
@@ -1671,7 +1673,8 @@
def get_yscale(self):
'return the yaxis scale string: log or linear'
- return self.scaled[self.transData.get_funcy().get_type()]
+ # return self.scaled[self.transData.get_funcy().get_type()]
+ return 'linear'
def set_yscale(self, value, basey=10, subsy=None):
"""
Modified: branches/transforms/lib/matplotlib/backend_bases.py
===================================================================
--- branches/transforms/lib/matplotlib/backend_bases.py 2007-09-12 18:22:24 UTC (rev 3841)
+++ branches/transforms/lib/matplotlib/backend_bases.py 2007-09-12 19:47:56 UTC (rev 3842)
@@ -4,7 +4,7 @@
"""
from __future__ import division
-import os, sys, warnings
+import os, sys, warnings, copy
import numpy as npy
import matplotlib.numerix.npyma as ma
@@ -1070,7 +1070,7 @@
def get_width_height(self):
"""return the figure width and height in points or pixels
(depending on the backend), truncated to integers"""
- return int(self.figure.bbox.width()), int(self.figure.bbox.height())
+ return int(self.figure.bbox.width), int(self.figure.bbox.height)
filetypes = {
'emf': 'Enhanced Metafile',
@@ -1544,7 +1544,7 @@
xmin, xmax = a.get_xlim()
ymin, ymax = a.get_ylim()
lim = xmin, xmax, ymin, ymax
- self._xypress.append((x, y, a, i, lim,a.transData.deepcopy()))
+ self._xypress.append((x, y, a, i, lim, copy.deepcopy(a.transData)))
self.canvas.mpl_disconnect(self._idDrag)
self._idDrag=self.canvas.mpl_connect('motion_notify_event', self.drag_pan)
@@ -1571,7 +1571,7 @@
xmin, xmax = a.get_xlim()
ymin, ymax = a.get_ylim()
lim = xmin, xmax, ymin, ymax
- self._xypress.append(( x, y, a, i, lim, a.transData.deepcopy() ))
+ self._xypress.append(( x, y, a, i, lim, copy.deepcopy(a.transData) ))
self.press(event)
@@ -1637,8 +1637,9 @@
#safer to use the recorded button at the press than current button:
#multiple button can get pressed during motion...
if self._button_pressed==1:
- lastx, lasty = trans.inverse_xy_tup( (lastx, lasty) )
- x, y = trans.inverse_xy_tup( (event.x, event.y) )
+ inverse = trans.inverted()
+ lastx, lasty = inverse.transform_point((lastx, lasty))
+ x, y = inverse.transform_point( (event.x, event.y) )
if a.get_xscale()=='log':
dx=1-lastx/x
else:
@@ -1664,15 +1665,16 @@
ymax -= dy
elif self._button_pressed==3:
try:
- dx=(lastx-event.x)/float(a.bbox.width())
- dy=(lasty-event.y)/float(a.bbox.height())
+ dx=(lastx-event.x)/float(a.bbox.width)
+ dy=(lasty-event.y)/float(a.bbox.height)
dx,dy=format_deltas(event,dx,dy)
if a.get_aspect() != 'auto':
dx = 0.5*(dx + dy)
dy = dx
alphax = pow(10.0,dx)
alphay = pow(10.0,dy)#use logscaling, avoid singularities and smother scaling...
- lastx, lasty = trans.inverse_xy_tup( (lastx, lasty) )
+ inverse = trans.inverted()
+ lastx, lasty = inverse.transform_point( (lastx, lasty) )
if a.get_xscale()=='log':
xmin = lastx*(xmin/lastx)**alphax
xmax = lastx*(xmax/lastx)**alphax
@@ -1710,8 +1712,9 @@
xmin, ymin, xmax, ymax = lim
# zoom to rect
- lastx, lasty = a.transData.inverse_xy_tup( (lastx, lasty) )
- x, y = a.transData.inverse_xy_tup( (x, y) )
+ inverse = a.transData.inverted()
+ lastx, lasty = inverse.transform_point( (lastx, lasty) )
+ x, y = inverse.transform_point( (x, y) )
Xmin,Xmax=a.get_xlim()
Ymin,Ymax=a.get_ylim()
Modified: branches/transforms/lib/matplotlib/backends/backend_agg.py
===================================================================
--- branches/transforms/lib/matplotlib/backends/backend_agg.py 2007-09-12 18:22:24 UTC (rev 3841)
+++ branches/transforms/lib/matplotlib/backends/backend_agg.py 2007-09-12 19:47:56 UTC (rev 3842)
@@ -398,7 +398,7 @@
self.figure.draw(self.renderer)
def get_renderer(self):
- l,b,w,h = self.figure.bbox.get_bounds()
+ l,b,w,h = self.figure.bbox.bounds
# MGDTODO
# key = w, h, self.figure.dpi.get()
key = w, h, self.figure.dpi
Modified: branches/transforms/lib/matplotlib/backends/backend_tkagg.py
===================================================================
--- branches/transforms/lib/matplotlib/backends/backend_tkagg.py 2007-09-12 18:22:24 UTC (rev 3841)
+++ branches/transforms/lib/matplotlib/backends/backend_tkagg.py 2007-09-12 19:47:56 UTC (rev 3842)
@@ -147,7 +147,7 @@
def __init__(self, figure, master=None, resize_callback=None):
FigureCanvasAgg.__init__(self, figure)
self._idle = True
- t1,t2,w,h = self.figure.bbox.get_bounds()
+ t1,t2,w,h = self.figure.bbox.bounds
w, h = int(w), int(h)
self._tkcanvas = Tk.Canvas(
master=master, width=w, height=h, borderwidth=4)
@@ -288,7 +288,7 @@
self.window.wm_title("Figure %d" % num)
self.canvas = canvas
self._num = num
- t1,t2,w,h = canvas.figure.bbox.get_bounds()
+ t1,t2,w,h = canvas.figure.bbox.bounds
w, h = int(w), int(h)
self.window.minsize(int(w*3/4),int(h*3/4))
if matplotlib.rcParams['toolbar']=='classic':
@@ -436,7 +436,7 @@
self.canvas = canvas
self.window = window
- xmin, xmax = canvas.figure.bbox.intervalx().get_bounds()
+ xmin, xmax = canvas.figure.bbox.intervalx
height, width = 50, xmax-xmin
Tk.Frame.__init__(self, master=self.window,
width=width, height=height,
@@ -582,7 +582,7 @@
self.message.set(s)
def draw_rubberband(self, event, x0, y0, x1, y1):
- height = self.canvas.figure.bbox.height()
+ height = self.canvas.figure.bbox.height
y0 = height-y0
y1 = height-y1
try: self.lastrect
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|