|
From: <md...@us...> - 2007-09-14 13:03:33
|
Revision: 3851
http://matplotlib.svn.sourceforge.net/matplotlib/?rev=3851&view=rev
Author: mdboom
Date: 2007-09-14 06:03:31 -0700 (Fri, 14 Sep 2007)
Log Message:
-----------
Removed transforms on the C++ side -- removed many methods that depend
on it in backend_agg in preparation for path generalization.
Lots of general renaming...
Modified Paths:
--------------
branches/transforms/lib/matplotlib/artist.py
branches/transforms/lib/matplotlib/axes.py
branches/transforms/lib/matplotlib/axis.py
branches/transforms/lib/matplotlib/backends/backend_agg.py
branches/transforms/lib/matplotlib/figure.py
branches/transforms/lib/matplotlib/finance.py
branches/transforms/lib/matplotlib/legend.py
branches/transforms/lib/matplotlib/lines.py
branches/transforms/lib/matplotlib/table.py
branches/transforms/lib/matplotlib/text.py
branches/transforms/lib/matplotlib/ticker.py
branches/transforms/lib/matplotlib/widgets.py
branches/transforms/setup.py
branches/transforms/setupext.py
branches/transforms/src/_backend_agg.cpp
branches/transforms/src/_backend_agg.h
branches/transforms/src/_gtkagg.cpp
branches/transforms/src/_tkagg.cpp
Added Paths:
-----------
branches/transforms/lib/matplotlib/transforms.py
Removed Paths:
-------------
branches/transforms/lib/matplotlib/affine.py
branches/transforms/src/_transforms.cpp
branches/transforms/src/_transforms.h
Deleted: branches/transforms/lib/matplotlib/affine.py
===================================================================
--- branches/transforms/lib/matplotlib/affine.py 2007-09-14 12:24:20 UTC (rev 3850)
+++ branches/transforms/lib/matplotlib/affine.py 2007-09-14 13:03:31 UTC (rev 3851)
@@ -1,854 +0,0 @@
-"""
-A set of classes to handle transformations.
-
-2007 Michael Droettboom
-"""
-
-import numpy as npy
-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()
-
- def invalidate(self):
- if not self._do_invalidation():
- for parent in self._parents:
- parent.invalidate()
-
- def _do_invalidation(self):
- return False
-
- def set_children(self, children):
- for child in children:
- getattr(self, child)._parents.add(self)
- self._children = children
-
-# def replace_child(self, index, child):
-# children = self._children
-# getattr(self, children[index])._parents.remove(self)
-# setattr(self, children[index], child)
-# # We have to reset children in case two or more
-# # of the children are the same
-# for child in children:
-# getattr(self, child)._parents.add(self)
-# self.invalidate()
-
-class BboxBase(TransformNode):
- '''
- This is the read-only part of a bounding-box
- '''
-
- def __init__(self):
- TransformNode.__init__(self)
-
- def __array__(self):
- return self.get_points()
-
- # MGDTODO: Probably a more efficient ways to do this...
- def _get_xmin(self):
- return self.get_points()[0, 0]
- xmin = property(_get_xmin)
-
- def _get_ymin(self):
- return self.get_points()[0, 1]
- ymin = property(_get_ymin)
-
- def _get_xmax(self):
- return self.get_points()[1, 0]
- xmax = property(_get_xmax)
-
- def _get_ymax(self):
- return self.get_points()[1, 1]
- ymax = property(_get_ymax)
-
- def _get_min(self):
- return self.get_points()[0]
- min = property(_get_min)
-
- def _get_max(self):
- return self.get_points()[1]
- max = property(_get_max)
-
- def _get_intervalx(self):
- return self.get_points()[:, 0]
- intervalx = property(_get_intervalx)
-
- def _get_intervaly(self):
- return self.get_points()[:, 1]
- intervaly = property(_get_intervaly)
-
- def _get_width(self):
- return self.xmax - self.xmin
- width = property(_get_width)
-
- def _get_height(self):
- 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)
- bounds = property(_get_bounds)
-
- def get_points(self):
- return NotImplementedError()
-
- # MGDTODO: Optimize
- def containsx(self, x):
- return x >= self.xmin and x <= self.xmax
-
- def containsy(self, y):
- return y >= self.ymin and y <= self.ymax
-
- def contains(self, x, y):
- return self.containsx(x) and self.containsy(y)
-
- def overlapsx(self, other):
- return self.containsx(other.xmin) \
- or self.containsx(other.xmax)
-
- def overlapsy(self, other):
- return self.containsy(other.ymin) \
- or self.containsx(other.ymax)
-
- def overlaps(self, other):
- return self.overlapsx(other) \
- and self.overlapsy(other)
-
- def fully_containsx(self, x):
- return x > self.xmin and x < self.xmax
-
- def fully_containsy(self, y):
- return y > self.ymin and y < self.ymax
-
- def fully_contains(self, x, y):
- return self.fully_containsx(x) \
- and self.fully_containsy(y)
-
- def fully_overlapsx(self, other):
- return self.fully_containsx(other.xmin) \
- or self.fully_containsx(other.xmax)
-
- def fully_overlapsy(self, other):
- return self.fully_containsy(other.ymin) \
- or self.fully_containsx(other.ymax)
-
- def fully_overlaps(self, other):
- return self.fully_overlapsx(other) and \
- self.fully_overlapsy(other)
-
-
-class Bbox(BboxBase):
- def __init__(self, points):
- BboxBase.__init__(self)
- self._points = npy.asarray(points, npy.float_)
-
- #@staticmethod
- def unit():
- return Bbox.from_lbrt(0., 0., 1., 1.)
- unit = staticmethod(unit)
-
- #@staticmethod
- def from_lbwh(left, bottom, width, height):
- return Bbox.from_lbrt(left, bottom, left + width, bottom + height)
- from_lbwh = staticmethod(from_lbwh)
-
- #@staticmethod
- def from_lbrt(*args):
- points = npy.array(args, dtype=npy.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) 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, ignore=True):
- self._points = npy.array(
- [[x.min(), y.min()], [x.max(), y.max()]],
- npy.float_)
- self.invalidate()
-
- # MGDTODO: Probably a more efficient ways to do this...
- def _set_xmin(self, val):
- self._points[0, 0] = val
- self.invalidate()
- xmin = property(BboxBase._get_xmin, _set_xmin)
-
- def _set_ymin(self, val):
- self._points[0, 1] = val
- self.invalidate()
- ymin = property(BboxBase._get_ymin, _set_ymin)
-
- def _set_xmax(self, val):
- self._points[1, 0] = val
- self.invalidate()
- xmax = property(BboxBase._get_xmax, _set_xmax)
-
- def _set_ymax(self, val):
- self._points[1, 1] = val
- self.invalidate()
- ymax = property(BboxBase._get_ymax, _set_ymax)
-
- def _set_min(self, val):
- self._points[0] = val
- self.invalidate()
- min = property(BboxBase._get_min, _set_min)
-
- def _set_max(self, val):
- self._points[1] = val
- self.invalidate()
- max = property(BboxBase._get_max, _set_max)
-
- def _set_intervalx(self, interval):
- self._points[:, 0] = interval
- self.invalidate()
- intervalx = property(BboxBase._get_intervalx, _set_intervalx)
-
- def _set_intervaly(self, interval):
- self._points[:, 1] = interval
- self.invalidate()
- intervaly = property(BboxBase._get_intervaly, _set_intervaly)
-
- def _set_bounds(self, bounds):
- l,b,w,h = bounds
- self._points = npy.array([[l, b], [l+w, b+h]], npy.float_)
- self.invalidate()
- bounds = property(BboxBase._get_bounds, _set_bounds)
-
- def get_points(self):
- return self._points
-
- def set_points(self, points):
- self._points = points
- self.invalidate()
-
- def set(self, other):
- self._points = other.get_points()
- self.invalidate()
-
- def transformed(self, transform):
- return Bbox(transform(self._points))
-
- def inverse_transformed(self, transform):
- return Bbox(transform.inverted()(self._points))
-
- def expanded(self, sw, sh):
- width = self.width
- height = self.height
- deltaw = (sw * width - width) / 2.0
- deltah = (sh * height - height) / 2.0
- a = npy.array([[-deltaw, -deltah], [deltaw, deltah]])
- return Bbox(self._points + a)
-
- #@staticmethod
- def 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.from_lbrt(xmin, ymin, xmax, ymax)
- union = staticmethod(union)
-
-class TransformedBbox(BboxBase):
- def __init__(self, bbox, transform):
- assert isinstance(bbox, Bbox)
- assert isinstance(transform, Transform)
-
- BboxBase.__init__(self)
- self.bbox = bbox
- self.transform = transform
- self.set_children(['bbox', 'transform'])
- self._points = None
-
- def __repr__(self):
- return "TransformedBbox(%s, %s)" % (self.bbox, self.transform)
- __str__ = __repr__
-
- def _do_invalidation(self):
- self._points = None
-
- def get_points(self):
- if self._points is None:
- self._points = self.transform(self.bbox.get_points())
- return self._points
-
-# MGDTODO: This code probably works, but I don't think it's a good idea
-# (from a code clarity perspective)
-# class BlendedBbox(BboxBase):
-# def __init__(self, bbox_x, bbox_y):
-# assert isinstance(bbox_x, BboxBase)
-# assert isinstance(bbox_y, BboxBase)
-
-# BboxBase.__init__(self)
-# self._x = bbox_x
-# self._y = bbox_y
-# self.set_children(['_x', '_y'])
-# self._points = None
-
-# def __repr__(self):
-# return "TransformedBbox(%s, %s)" % (self.bbox, self.transform)
-# __str__ = __repr__
-
-# def _do_invalidation(self):
-# self._points = None
-
-# def get_points(self):
-# if self._points is None:
-# # MGDTODO: Optimize
-# if self._x == self._y:
-# self._points = self._x.get_points()
-# else:
-# x_points = self._x.get_points()
-# y_points = self._y.get_points()
-# self._points = npy.array(
-# [[x_points[0,0], y_points[0,1]],
-# [x_points[1,0], y_points[1,1]]],
-# npy.float_)
-# return self._points
-
-# def _set_intervalx(self, pair):
-# # MGDTODO: Optimize
-# bbox = Bbox([[pair[0], 0.0], [pair[1], 0.0]])
-# self.replace_child(0, bbox)
-# intervalx = property(BboxBase._get_intervalx, _set_intervalx)
-
-# def _set_intervaly(self, pair):
-# # MGDTODO: Optimize
-# bbox = Bbox([[0.0, pair[0]], [0.0, pair[1]]])
-# self.replace_child(1, bbox)
-# intervaly = property(BboxBase._get_intervaly, _set_intervaly)
-
-class Transform(TransformNode):
- def __init__(self):
- TransformNode.__init__(self)
-
- def __call__(self, points):
- raise NotImplementedError()
-
- def __add__(self, other):
- if isinstance(other, Transform):
- return composite_transform_factory(self, other)
- raise TypeError(
- "Can not add Transform to object of type '%s'" % type(other))
-
- def __radd__(self, other):
- 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__(npy.asarray([point]))[0]
-
- def has_inverse(self):
- raise NotImplementedError()
-
- def inverted(self):
- raise NotImplementedError()
-
- def is_separable(self):
- return False
-
- def is_affine(self):
- return False
-
-class Affine2DBase(Transform):
- input_dims = 2
- output_dims = 2
-
- def __init__(self):
- Transform.__init__(self)
- self._inverted = None
-
- def _do_invalidation(self):
- result = self._inverted is None
- self._inverted = None
- return result
-
- #@staticmethod
- def _concat(a, b):
- return npy.dot(b, a)
- _concat = staticmethod(_concat)
-
- #@staticmethod
- def concat(a, b):
- return Affine2D(Affine2D._concat(a.get_matrix(), b.get_matrix()))
- concat = staticmethod(concat)
-
- def to_values(self):
- mtx = self.get_matrix()
- return tuple(mtx[:2].swapaxes(0, 1).flatten())
-
- #@staticmethod
- def matrix_from_values(a, b, c, d, e, f):
- affine = npy.zeros((3, 3), npy.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 get_matrix(self):
- raise NotImplementedError()
-
- def __call__(self, points):
- """
- Applies the transformation to an array 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: 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.
- if not isinstance(points, npy.ndarray):
- import traceback
- print '-' * 60
- print 'A non-numpy array was passed in for transformation. Please '
- print 'correct this.'
- print "".join(traceback.format_stack())
- print points
- mtx = self.get_matrix()
- points = npy.asarray(points, npy.float_)
- points = points.transpose()
- points = npy.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 = npy.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 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
-
- def set_matrix(self, mtx):
- self._mtx = mtx
- self.invalidate()
-
- def set(self, other):
- self._mtx = other.get_matrix()
- self.invalidate()
-
- #@staticmethod
- def identity():
- return Affine2D(npy.identity(3))
- identity = staticmethod(identity)
-
- def clear(self):
- self._mtx = npy.identity(3)
- self.invalidate()
- return self
-
- def rotate(self, theta):
- a = npy.cos(theta)
- b = npy.sin(theta)
- rotate_mtx = self.matrix_from_values(a, b, -b, a, 0, 0)
- self._mtx = self._concat(self._mtx, rotate_mtx)
- self.invalidate()
- return self
-
- def rotate_deg(self, degrees):
- return self.rotate(degrees*npy.pi/180.)
-
- def rotate_around(self, x, y, theta):
- return self.translate(-x, -y).rotate(theta).translate(x, y)
-
- def rotate_deg_around(self, x, y, degrees):
- return self.translate(-x, -y).rotate_deg(degrees).translate(x, y)
-
- def translate(self, tx, ty):
- translate_mtx = self.matrix_from_values(1., 0., 0., 1., tx, ty)
- self._mtx = self._concat(self._mtx, translate_mtx)
- self.invalidate()
- return self
-
- def scale(self, sx, sy=None):
- if sy is None:
- sy = sx
- scale_mtx = self.matrix_from_values(sx, 0., 0., sy, 0., 0.)
- self._mtx = self._concat(self._mtx, scale_mtx)
- self.invalidate()
- return self
-
- 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 BlendedTransform(Transform):
- def __init__(self, x_transform, y_transform):
- assert x_transform.is_separable()
- assert y_transform.is_separable()
-
- Transform.__init__(self)
- self._x = x_transform
- self._y = y_transform
- self.set_children(['_x', '_y'])
-
- def __call__(self, points):
- if self._x == self._y:
- return self._x(points)
-
- x_points = self._x(points)
- y_points = self._y(points)
- # This works because we already know the transforms are
- # separable
- return npy.hstack((x_points[:, 0:1], y_points[:, 1:2]))
-
-# def set_x_transform(self, x_transform):
-# self.replace_child(0, x_transform)
-
-# def set_y_transform(self, y_transform):
-# self.replace_child(1, y_transform)
-
-class BlendedAffine2D(Affine2DBase, BlendedTransform):
- 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()
- BlendedTransform.__init__(self, x_transform, y_transform)
-
- Affine2DBase.__init__(self)
- self._mtx = None
-
- def __repr__(self):
- return "BlendedAffine2D(%s,%s)" % (self._x, self._y)
- __str__ = __repr__
-
- def _do_invalidation(self):
- if self._mtx is not None:
- self._mtx = None
- Affine2DBase._do_invalidation(self)
- return False
- return True
-
- def is_separable(self):
- return True
-
- def get_matrix(self):
- if self._mtx is None:
- if self._x == self._y:
- self._mtx = self._x.get_matrix()
- else:
- x_mtx = self._x.get_matrix()
- y_mtx = self._y.get_matrix()
- # This works because we already know the transforms are
- # separable, though normally one would want to set b and
- # c to zero.
- self._mtx = npy.vstack((x_mtx[0], y_mtx[1], [0.0, 0.0, 1.0]))
- return self._mtx
-
-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._a = a
- self._b = b
- self.set_children(['_a', '_b'])
-
- def __call__(self, points):
- return self._b(self._a(points))
-
-class CompositeAffine2D(Affine2DBase):
- def __init__(self, a, b):
- assert a.is_affine()
- assert b.is_affine()
-
- Affine2DBase.__init__(self)
- self._a = a
- self._b = b
- self.set_children(['_a', '_b'])
- self._mtx = None
-
- def __repr__(self):
- return "CompositeAffine2D(%s, %s)" % (self._a, self._b)
- __str__ = __repr__
-
- def _do_invalidation(self):
- self._mtx = None
- Affine2DBase._do_invalidation(self)
-
- def get_matrix(self):
- if self._mtx is None:
- self._mtx = self._concat(
- self._a.get_matrix(),
- self._b.get_matrix())
- return self._mtx
-
-class BboxTransform(Affine2DBase):
- def __init__(self, boxin, boxout):
- assert isinstance(boxin, BboxBase)
- assert isinstance(boxout, BboxBase)
-
- Affine2DBase.__init__(self)
- self._boxin = boxin
- self._boxout = boxout
- self.set_children(['_boxin', '_boxout'])
- self._mtx = None
- self._inverted = None
-
- def __repr__(self):
- return "BboxTransform(%s, %s)" % (self._boxin, self._boxout)
- __str__ = __repr__
-
- def _do_invalidation(self):
- if self._mtx is not None:
- self._mtx = None
- Affine2DBase._do_invalidation(self)
- return False
- return True
-
- def is_separable(self):
- return True
-
- def get_matrix(self):
- if self._mtx is None:
- boxin = self._boxin
- boxout = self._boxout
- x_scale = boxout.width / boxin.width
- y_scale = boxout.height / boxin.height
-
- # MGDTODO: Optimize
- affine = Affine2D() \
- .translate(-boxin.xmin, -boxin.ymin) \
- .scale(x_scale, y_scale) \
- .translate(boxout.xmin, boxout.ymin)
-
- self._mtx = affine._mtx
- return self._mtx
-
-def blend_xy_sep_transform(x_transform, y_transform):
- if x_transform.is_affine() and y_transform.is_affine():
- return BlendedAffine2D(x_transform, y_transform)
- return BlendedTransform(x_transform, y_transform)
-
-def composite_transform_factory(a, b):
- if a.is_affine() and b.is_affine():
- return CompositeAffine2D(a, b)
- return CompositeTransform(a, b)
-
-# 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
-
-# MGDTODO: Optimize
-def interval_contains(interval, val):
- return interval[0] <= val and interval[1] >= val
-
-def interval_contains_open(interval, val):
- return interval[0] < val and interval[1] > val
-
-if __name__ == '__main__':
- import copy
- from random import random
- import timeit
-
- bbox = Bbox.from_lbrt(10., 15., 20., 25.)
- assert bbox.xmin == 10
- assert bbox.ymin == 15
- assert bbox.xmax == 20
- assert bbox.ymax == 25
-
- assert npy.all(bbox.min == [10, 15])
- assert npy.all(bbox.max == [20, 25])
- assert npy.all(bbox.intervalx == (10, 20))
- assert npy.all(bbox.intervaly == (15, 25))
-
- assert bbox.width == 10
- assert bbox.height == 10
-
- assert bbox.bounds == (10, 15, 10, 10)
-
- print npy.asarray(bbox)
-
- bbox.intervalx = (11, 21)
- bbox.intervaly = (16, 26)
-
- assert bbox.bounds == (11, 16, 10, 10)
-
- bbox.xmin = 12
- bbox.ymin = 17
- bbox.xmax = 22
- bbox.ymax = 27
-
- assert bbox.bounds == (12, 17, 10, 10)
-
- bbox = Bbox.from_lbwh(10, 11, 12, 13)
- assert bbox.bounds == (10, 11, 12, 13)
-
- bbox_copy = copy.copy(bbox)
- assert bbox == bbox_copy
- bbox_copy.max = (14, 15)
- assert bbox.bounds == (10, 11, 12, 13)
- assert bbox_copy.bounds == (10, 11, 4, 4)
-
- bbox1 = Bbox([[10., 15.], [20., 25.]])
- bbox2 = Bbox([[30., 35.], [40., 45.]])
- trans = BboxTransform(bbox1, bbox2)
- bbox3 = bbox1.transformed(trans)
- assert bbox3 == bbox2
-
- translation = Affine2D().translate(10, 20)
- assert translation.to_values() == (1, 0, 0, 1, 10, 20)
- 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)
-
- points = npy.array([[1,2],[3,4],[5,6],[7,8]], npy.float_)
- translated_points = translation(points)
- assert (translated_points == [[11., 22.], [13., 24.], [15., 26.], [17., 28.]]).all()
- scaled_points = scale(points)
- print scaled_points
- rotated_points = rotation(points)
- print rotated_points
-
- tpoints1 = rotation(translation(scale(points)))
- 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 = npy.asarray([(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:", t.timeit(10)
-
-__all__ = ['Transform', 'Affine2D']
Modified: branches/transforms/lib/matplotlib/artist.py
===================================================================
--- branches/transforms/lib/matplotlib/artist.py 2007-09-14 12:24:20 UTC (rev 3850)
+++ branches/transforms/lib/matplotlib/artist.py 2007-09-14 13:03:31 UTC (rev 3851)
@@ -1,7 +1,7 @@
from __future__ import division
import sys, re
from cbook import iterable, flatten
-from affine import Affine2D
+from transforms import Affine2D
import matplotlib.units as units
## Note, matplotlib artists use the doc strings for set and get
Modified: branches/transforms/lib/matplotlib/axes.py
===================================================================
--- branches/transforms/lib/matplotlib/axes.py 2007-09-14 12:24:20 UTC (rev 3850)
+++ branches/transforms/lib/matplotlib/axes.py 2007-09-14 13:03:31 UTC (rev 3851)
@@ -9,7 +9,6 @@
rcParams = matplotlib.rcParams
from matplotlib import artist as martist
-from matplotlib import affine as maffine
from matplotlib import agg
from matplotlib import axis as maxis
from matplotlib import cbook
@@ -29,6 +28,7 @@
from matplotlib import table as mtable
from matplotlib import text as mtext
from matplotlib import ticker as mticker
+from matplotlib import transforms as mtransforms
iterable = cbook.iterable
is_string_like = cbook.is_string_like
@@ -481,7 +481,7 @@
"""
martist.Artist.__init__(self)
- self._position = maffine.Bbox.from_lbwh(*rect)
+ self._position = mtransforms.Bbox.from_lbwh(*rect)
self._originalPosition = copy.deepcopy(self._position)
self.set_axes(self)
self.set_aspect('auto')
@@ -612,7 +612,7 @@
"""
martist.Artist.set_figure(self, fig)
- self.bbox = maffine.TransformedBbox(self._position, fig.transFigure)
+ self.bbox = mtransforms.TransformedBbox(self._position, fig.transFigure)
#these will be updated later as data is added
self._set_lim_and_transforms()
@@ -631,7 +631,7 @@
set the dataLim and viewLim BBox attributes and the
transData and transAxes Transformation attributes
"""
- Bbox = maffine.Bbox
+ Bbox = mtransforms.Bbox
self.viewLim = Bbox.unit()
if self._sharex is not None:
@@ -647,20 +647,11 @@
self.dataLim = Bbox.unit()
- self.transAxes = maffine.BboxTransform(
+ self.transAxes = mtransforms.BboxTransform(
Bbox.unit(), self.bbox)
- localTransData = maffine.BboxTransform(
+ self.transData = mtransforms.BboxTransform(
self.viewLim, self.bbox)
- if self._sharex:
- transDataX = self._sharex.transData
- else:
- transDataX = localTransData
- if self._sharey:
- transDataY = self._sharey.transData
- else:
- transDataY = localTransData
- self.transData = localTransData # maffine.blend_xy_sep_transform(transDataX, transDataY)
def get_position(self, original=False):
@@ -1538,7 +1529,7 @@
# and min(xmin, xmax)<=0):
# raise ValueError('Cannot set nonpositive limits with log transform')
- xmin, xmax = maffine.nonsingular(xmin, xmax, increasing=False)
+ xmin, xmax = mtransforms.nonsingular(xmin, xmax, increasing=False)
self.viewLim.intervalx = (xmin, xmax)
if emit:
@@ -1670,7 +1661,7 @@
# and min(ymin, ymax)<=0):
# raise ValueError('Cannot set nonpositive limits with log transform')
- ymin, ymax = maffine.nonsingular(ymin, ymax, increasing=False)
+ ymin, ymax = mtransforms.nonsingular(ymin, ymax, increasing=False)
self.viewLim.intervaly = (ymin, ymax)
if emit:
self.callbacks.process('ylim_changed', self)
@@ -2167,7 +2158,7 @@
%(Annotation)s
"""
a = mtext.Annotation(*args, **kwargs)
- a.set_transform(maffine.Affine2D())
+ a.set_transform(mtransforms.Affine2D())
self._set_artist_props(a)
if kwargs.has_key('clip_on'): a.set_clip_box(self.bbox)
self.texts.append(a)
@@ -2206,7 +2197,7 @@
%(Line2D)s
"""
- trans = maffine.blend_xy_sep_transform(self.transAxes, self.transData)
+ trans = mtransforms.blend_xy_sep_transform(self.transAxes, self.transData)
l, = self.plot([xmin,xmax], [y,y], transform=trans, scalex=False, **kwargs)
return l
@@ -2242,7 +2233,7 @@
%(Line2D)s
"""
- trans = maffine.blend_xy_sep_transform( self.transData, self.transAxes )
+ trans = mtransforms.blend_xy_sep_transform( self.transData, self.transAxes )
l, = self.plot([x,x], [ymin,ymax] , transform=trans, scaley=False, **kwargs)
return l
@@ -2281,7 +2272,7 @@
%(Polygon)s
"""
# convert y axis units
- trans = maffine.blend_xy_sep_transform( self.transAxes, self.transData)
+ trans = mtransforms.blend_xy_sep_transform( self.transAxes, self.transData)
verts = (xmin, ymin), (xmin, ymax), (xmax, ymax), (xmax, ymin)
p = mpatches.Polygon(verts, **kwargs)
p.set_transform(trans)
@@ -2321,7 +2312,7 @@
%(Polygon)s
"""
# convert x axis units
- trans = maffine.blend_xy_sep_transform(self.transData, self.transAxes)
+ trans = mtransforms.blend_xy_sep_transform(self.transData, self.transAxes)
verts = [(xmin, ymin), (xmin, ymax), (xmax, ymax), (xmax, ymin)]
p = mpatches.Polygon(verts, **kwargs)
p.set_transform(trans)
@@ -4104,7 +4095,7 @@
offsets = zip(x,y),
transOffset = self.transData,
)
- collection.set_transform(maffine.Affine2D())
+ collection.set_transform(mtransforms.Affine2D())
collection.set_alpha(alpha)
collection.update(kwargs)
Modified: branches/transforms/lib/matplotlib/axis.py
===================================================================
--- branches/transforms/lib/matplotlib/axis.py 2007-09-14 12:24:20 UTC (rev 3850)
+++ branches/transforms/lib/matplotlib/axis.py 2007-09-14 13:03:31 UTC (rev 3851)
@@ -15,10 +15,10 @@
from ticker import NullFormatter, FixedFormatter, ScalarFormatter, LogFormatter
from ticker import NullLocator, FixedLocator, LinearLocator, LogLocator, AutoLocator
-from affine import Affine2D, Bbox, blend_xy_sep_transform, interval_contains, \
- interval_contains_open
from font_manager import FontProperties
from text import Text, TextWithDash, _process_text_args
+from transforms import Affine2D, Bbox, blended_transform_factory, interval_contains, \
+ interval_contains_open
from patches import bbox_artist
import matplotlib.units as units
@@ -236,7 +236,7 @@
xaxis=True,
)
- trans = blend_xy_sep_transform(
+ trans = blended_transform_factory(
self.axes.transData, self.axes.transAxes)
#offset the text downward with a post transformation
trans = trans + Affine2D().translate(0, -1 * self._padPixels)
@@ -261,7 +261,7 @@
horizontalalignment='center',
)
- trans = blend_xy_sep_transform(
+ trans = blended_transform_factory(
self.axes.transData, self.axes.transAxes)
# offset the text upward with a post transformation
trans = trans + Affine2D().translate(0, self._padPixels)
@@ -279,7 +279,7 @@
marker = self._xtickmarkers[0],
markersize=self._size,
)
- l.set_transform(blend_xy_sep_transform(
+ l.set_transform(blended_transform_factory(
self.axes.transData, self.axes.transAxes) )
self._set_artist_props(l)
return l
@@ -295,7 +295,7 @@
markersize=self._size,
)
- l.set_transform(blend_xy_sep_transform(
+ l.set_transform(blended_transform_factory(
self.axes.transData, self.axes.transAxes) )
self._set_artist_props(l)
return l
@@ -310,7 +310,7 @@
antialiased=False,
)
l.set_transform(
- blend_xy_sep_transform(
+ blended_transform_factory(
self.axes.transData, self.axes.transAxes))
l.set_clip_box(self.axes.bbox)
self._set_artist_props(l)
@@ -359,7 +359,7 @@
dashdirection=0,
xaxis=False,
)
- trans = blend_xy_sep_transform(
+ trans = blended_transform_factory(
self.axes.transAxes, self.axes.transData)
# offset the text leftward with a post transformation
trans = trans + Affine2D().translate(-1 * self._padPixels, 0)
@@ -382,7 +382,7 @@
xaxis=False,
horizontalalignment='left',
)
- trans = blend_xy_sep_transform(
+ trans = blended_transform_factory(
self.axes.transAxes, self.axes.transData)
# offset the text rightward with a post transformation
trans = trans + Affine2D().translate(self._padPixels, 0)
@@ -401,7 +401,7 @@
markersize=self._size,
)
l.set_transform(
- blend_xy_sep_transform(
+ blended_transform_factory(
self.axes.transAxes, self.axes.transData))
self._set_artist_props(l)
return l
@@ -417,7 +417,7 @@
)
l.set_transform(
- blend_xy_sep_transform(
+ blended_transform_factory(
self.axes.transAxes, self.axes.transData))
self._set_artist_props(l)
return l
@@ -432,7 +432,7 @@
antialiased=False,
)
- l.set_transform( blend_xy_sep_transform(
+ l.set_transform( blended_transform_factory(
self.axes.transAxes, self.axes.transData) )
l.set_clip_box(self.axes.bbox)
@@ -979,7 +979,7 @@
verticalalignment='top',
horizontalalignment='center',
)
- label.set_transform( blend_xy_sep_transform(
+ label.set_transform( blended_transform_factory(
self.axes.transAxes, Affine2D() ))
self._set_artist_props(label)
@@ -994,7 +994,7 @@
verticalalignment='top',
horizontalalignment='right',
)
- offsetText.set_transform( blend_xy_sep_transform(
+ offsetText.set_transform( blended_transform_factory(
self.axes.transAxes, Affine2D() ))
self._set_artist_props(offsetText)
self.offset_text_position='bottom'
@@ -1169,7 +1169,7 @@
horizontalalignment='right',
rotation='vertical',
)
- label.set_transform( blend_xy_sep_transform(
+ label.set_transform( blended_transform_factory(
Affine2D(), self.axes.transAxes) )
self._set_artist_props(label)
@@ -1184,7 +1184,7 @@
verticalalignment = 'bottom',
horizontalalignment = 'left',
)
- offsetText.set_transform(blend_xy_sep_transform(
+ offsetText.set_transform(blended_transform_factory(
self.axes.transAxes, Affine2D()) )
self._set_artist_props(offsetText)
self.offset_text_position='left'
Modified: branches/transforms/lib/matplotlib/backends/backend_agg.py
===================================================================
--- branches/transforms/lib/matplotlib/backends/backend_agg.py 2007-09-14 12:24:20 UTC (rev 3850)
+++ branches/transforms/lib/matplotlib/backends/backend_agg.py 2007-09-14 13:03:31 UTC (rev 3851)
@@ -84,7 +84,7 @@
from matplotlib.font_manager import findfont
from matplotlib.ft2font import FT2Font, LOAD_DEFAULT
from matplotlib.mathtext import MathTextParser
-from matplotlib.affine import Bbox
+from matplotlib.transforms import Bbox
from _backend_agg import RendererAgg as _RendererAgg
@@ -115,15 +115,10 @@
'debug-annoying')
# self.draw_polygon = self._renderer.draw_polygon
self.draw_rectangle = self._renderer.draw_rectangle
- self.draw_path = self._renderer.draw_path
# MGDTODO -- remove these lines
# self.draw_lines = self._renderer.draw_lines
# self.draw_markers = self._renderer.draw_markers
self.draw_image = self._renderer.draw_image
- self.draw_line_collection = self._renderer.draw_line_collection
- self.draw_quad_mesh = self._renderer.draw_quad_mesh
- self.draw_poly_collection = self._renderer.draw_poly_collection
- self.draw_regpoly_collection = self._renderer.draw_regpoly_collection
self.copy_from_bbox = self._renderer.copy_from_bbox
self.restore_region = self._renderer.restore_region
Modified: branches/transforms/lib/matplotlib/figure.py
===================================================================
--- branches/transforms/lib/matplotlib/figure.py 2007-09-14 12:24:20 UTC (rev 3850)
+++ branches/transforms/lib/matplotlib/figure.py 2007-09-14 13:03:31 UTC (rev 3851)
@@ -18,8 +18,8 @@
from text import Text, _process_text_args
from legend import Legend
-from affine import Affine2D, Bbox, BboxTransform, TransformedBbox
from ticker import FormatStrFormatter
+from transforms import Affine2D, Bbox, BboxTransform, TransformedBbox
from cm import ScalarMappable
from contour import ContourSet
import warnings
Modified: branches/transforms/lib/matplotlib/finance.py
===================================================================
--- branches/transforms/lib/matplotlib/finance.py 2007-09-14 12:24:20 UTC (rev 3850)
+++ branches/transforms/lib/matplotlib/finance.py 2007-09-14 13:03:31 UTC (rev 3851)
@@ -22,7 +22,7 @@
from matplotlib.colors import colorConverter
from lines import Line2D, TICKLEFT, TICKRIGHT
from patches import Rectangle
-from matplotlib.affine import Affine2D
+from matplotlib.transforms import Affine2D
Modified: branches/transforms/lib/matplotlib/legend.py
===================================================================
--- branches/transforms/lib/matplotlib/legend.py 2007-09-14 12:24:20 UTC (rev 3850)
+++ branches/transforms/lib/matplotlib/legend.py 2007-09-14 13:03:31 UTC (rev 3851)
@@ -34,7 +34,7 @@
from patches import Patch, Rectangle, RegularPolygon, Shadow, bbox_artist, draw_bbox
from collections import LineCollection, RegularPolyCollection, PatchCollection
from text import Text
-from affine import Bbox, BboxTransform
+from transforms import Bbox, BboxTransform
def line_cuts_bbox(line, bbox):
""" Return True if and only if line cuts bbox. """
Modified: branches/transforms/lib/matplotlib/lines.py
===================================================================
--- branches/transforms/lib/matplotlib/lines.py 2007-09-14 12:24:20 UTC (rev 3850)
+++ branches/transforms/lib/matplotlib/lines.py 2007-09-14 13:03:31 UTC (rev 3851)
@@ -14,10 +14,10 @@
import numerix.ma as ma
from matplotlib import verbose
import artist
-from affine import Bbox
from artist import Artist, setp
from cbook import iterable, is_string_like, is_numlike
from colors import colorConverter
+from transforms import Bbox
from matplotlib import rcParams
Modified: branches/transforms/lib/matplotlib/table.py
===================================================================
--- branches/transforms/lib/matplotlib/table.py 2007-09-14 12:24:20 UTC (rev 3850)
+++ branches/transforms/lib/matplotlib/table.py 2007-09-14 13:03:31 UTC (rev 3851)
@@ -29,7 +29,7 @@
from patches import Rectangle
from cbook import enumerate, is_string_like, flatten
from text import Text
-from affine import Bbox
+from transforms import Bbox
Modified: branches/transforms/lib/matplotlib/text.py
===================================================================
--- branches/transforms/lib/matplotlib/text.py 2007-09-14 12:24:20 UTC (rev 3850)
+++ branches/transforms/lib/matplotlib/text.py 2007-09-14 13:03:31 UTC (rev 3851)
@@ -15,7 +15,7 @@
from cbook import enumerate, is_string_like, maxdict, is_numlike
from font_manager import FontProperties
from patches import bbox_artist, YAArrow
-from affine import Affine2D, Bbox
+from transforms import Affine2D, Bbox
from lines import Line2D
import matplotlib.nxutils as nxutils
Modified: branches/transforms/lib/matplotlib/ticker.py
===================================================================
--- branches/transforms/lib/matplotlib/ticker.py 2007-09-14 12:24:20 UTC (rev 3850)
+++ branches/transforms/lib/matplotlib/ticker.py 2007-09-14 13:03:31 UTC (rev 3851)
@@ -109,7 +109,7 @@
import matplotlib as mpl
from matplotlib import verbose, rcParams
from matplotlib import cbook
-from matplotlib import affine as maffine
+from matplotlib import transforms as mtransforms
@@ -540,7 +540,7 @@
def autoscale(self):
'autoscale the view limits'
self.verify_intervals()
- return maffine.nonsingular(*self.dataInterval.get_bounds())
+ return mtransforms.nonsingular(*self.dataInterval.get_bounds())
def pan(self, numsteps):
'Pan numticks (can be positive or negative)'
@@ -682,7 +682,7 @@
vmin = math.floor(scale*vmin)/scale
vmax = math.ceil(scale*vmax)/scale
- return maffine.nonsingular(vmin, vmax)
+ return mtransforms.nonsingular(vmin, vmax)
def closeto(x,y):
@@ -766,7 +766,7 @@
vmin -=1
vmax +=1
- return maffine.nonsingular(vmin, vmax)
+ return mtransforms.nonsingular(vmin, vmax)
def scale_range(vmin, vmax, n = 1, threshold=100):
dv = abs(vmax - vmin)
@@ -833,12 +833,12 @@
def __call__(self):
vmin, vmax = self.axis.get_view_interval()
- vmin, vmax = maffine.nonsingular(vmin, vmax, expander = 0.05)
+ vmin, vmax = mtransforms.nonsingular(vmin, vmax, expander = 0.05)
return self.bin_boundaries(vmin, vmax)
def autoscale(self):
dmin, dmax = self.axis.get_data_interval()
- dmin, dmax = maffine.nonsingular(dmin, dmax, expander = 0.05)
+ dmin, dmax = mtransforms.nonsingular(dmin, dmax, expander = 0.05)
return npy.take(self.bin_boundaries(dmin, dmax), [0,-1])
@@ -939,7 +939,7 @@
if vmin==vmax:
vmin = decade_down(vmin,self._base)
vmax = decade_up(vmax,self._base)
- return maffine.nonsingular(vmin, vmax)
+ return mtransforms.nonsingular(vmin, vmax)
class AutoLocator(MaxNLocator):
def __init__(self):
Copied: branches/transforms/lib/matplotlib/transforms.py (from rev 3849, branches/transforms/lib/matplotlib/affine.py)
===================================================================
--- branches/transforms/lib/matplotlib/transforms.py (rev 0)
+++ branches/transforms/lib/matplotlib/transforms.py 2007-09-14 13:03:31 UTC (rev 3851)
@@ -0,0 +1,852 @@
+"""
+A set of classes to handle transformations.
+
+2007 Michael Droettboom
+"""
+
+import numpy as npy
+from numpy.linalg import inv
+from sets import Set
+
+# 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()
+
+ def invalidate(self):
+ if not self._do_invalidation():
+ for parent in self._parents:
+ parent.invalidate()
+
+ def _do_invalidation(self):
+ return False
+
+ def set_children(self, children):
+ for child in children:
+ getattr(self, child)._parents.add(self)
+ self._children = children
+
+ # MGDTODO: decide whether we need this in-place updating and
+ # remove if not
+# def replace_child(self, index, child):
+# children = self._children
+# getattr(self, children[index])._parents.remove(self)
+# setattr(self, children[index], child)
+# # We have to reset children in case two or more
+# # of the children are the same
+# for child in children:
+# getattr(self, child)._parents.add(self)
+# self.invalidate()
+
+class BboxBase(TransformNode):
+ '''
+ This is the read-only part of a bounding-box
+ '''
+
+ def __init__(self):
+ TransformNode.__init__(self)
+
+ def __array__(self):
+ return self.get_points()
+
+ # MGDTODO: Probably a more efficient ways to do this...
+ def _get_xmin(self):
+ return self.get_points()[0, 0]
+ xmin = property(_get_xmin)
+
+ def _get_ymin(self):
+ return self.get_points()[0, 1]
+ ymin = property(_get_ymin)
+
+ def _get_xmax(self):
+ return self.get_points()[1, 0]
+ xmax = property(_get_xmax)
+
+ def _get_ymax(self):
+ return self.get_points()[1, 1]
+ ymax = property(_get_ymax)
+
+ def _get_min(self):
+ return self.get_points()[0]
+ min = property(_get_min)
+
+ def _get_max(self):
+ return self.get_points()[1]
+ max = property(_get_max)
+
+ def _get_intervalx(self):
+ return self.get_points()[:, 0]
+ intervalx = property(_get_intervalx)
+
+ def _get_intervaly(self):
+ return self.get_points()[:, 1]
+ intervaly = property(_get_intervaly)
+
+ def _get_width(self):
+ return self.xmax - self.xmin
+ width = property(_get_width)
+
+ def _get_height(self):
+ 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)
+ bounds = property(_get_bounds)
+
+ def get_points(self):
+ return NotImplementedError()
+
+ # MGDTODO: Optimize
+ def containsx(self, x):
+ return x >= self.xmin and x <= self.xmax
+
+ def containsy(self, y):
+ return y >= self.ymin and y <= self.ymax
+
+ def contains(self, x, y):
+ return self.containsx(x) and self.containsy(y)
+
+ def overlapsx(self, other):
+ return self.containsx(other.xmin) \
+ or self.containsx(other.xmax)
+
+ def overlapsy(self, other):
+ return self.containsy(other.ymin) \
+ or self.containsx(other.ymax)
+
+ def overlaps(self, other):
+ return self.overlapsx(other) \
+ and self.overlapsy(other)
+
+ def fully_containsx(self, x):
+ return x > self.xmin and x < self.xmax
+
+ def fully_containsy(self, y):
+ return y > self.ymin and y < self.ymax
+
+ def fully_contains(self, x, y):
+ return self.fully_containsx(x) \
+ and self.fully_containsy(y)
+
+ def fully_overlapsx(self, other):
+ return self.fully_containsx(other.xmin) \
+ or self.fully_containsx(other.xmax)
+
+ def fully_overlapsy(self, other):
+ return self.fully_containsy(other.ymin) \
+ or self.fully_containsx(other.ymax)
+
+ def fully_overlaps(self, other):
+ return self.fully_overlapsx(other) and \
+ self.fully_overlapsy(other)
+
+
+class Bbox(BboxBase):
+ def __init__(self, points):
+ BboxBase.__init__(self)
+ self._points = npy.asarray(points, npy.float_)
+
+ #@staticmethod
+ def unit():
+ return Bbox.from_lbrt(0., 0., 1., 1.)
+ unit = staticmethod(unit)
+
+ #@staticmethod
+ def from_lbwh(left, bottom, width, height):
+ return Bbox.from_lbrt(left, bottom, left + width, bottom + height)
+ from_lbwh = staticmethod(from_lbwh)
+
+ #@staticmethod
+ def from_lbrt(*args):
+ points = npy.array(args, dtype=npy.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) 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, ignore=True):
+ self._points = npy.array(
+ [[x.min(), y.min()], [x.max(), y.max()]],
+ npy.float_)
+ self.invalidate()
+
+ # MGDTODO: Probably a more efficient ways to do this...
+ def _set_xmin(self, val):
+ self._points[0, 0] = val
+ self.invalidate()
+ xmin = property(BboxBase._get_xmin, _set_xmin)
+
+ def _set_ymin(self, val):
+ self._points[0, 1] = val
+ self.invalidate()
+ ymin = property(BboxBase._get_ymin, _set_ymin)
+
+ def _set_xmax(self, val):
+ self._points[1, 0] = val
+ self.invalidate()
+ xmax = property(BboxBase._get_xmax, _set_xmax)
+
+ def _set_ymax(self, val):
+ self._points[1, 1] = val
+ self.invalidate()
+ ymax = property(BboxBase._get_ymax, _set_ymax)
+
+ def _set_min(self, val):
+ self._points[0] = val
+ self.invalidate()
+ min = property(BboxBase._get_min, _set_min)
+
+ def _set_max(self, val):
+ self._points[1] = val
+ self.invalidate()
+ max = property(BboxBase._get_max, _set_max)
+
+ def _set_intervalx(self, interval):
+ self._points[:, 0] = interval
+ self.invalidate()
+ intervalx = property(BboxBase._get_intervalx, _set_intervalx)
+
+ def _set_intervaly(self, interval):
+ self._points[:, 1] = interval
+ self.invalidate()
+ intervaly = property(BboxBase._get_intervaly, _set_intervaly)
+
+ def _set_bounds(self, bounds):
+ l,b,w,h = bounds
+ self._points = npy.array([[l, b], [l+w, b+h]], npy.float_)
+ self.invalidate()
+ bounds = property(BboxBase._get_bounds, _set_bounds)
+
+ def get_points(self):
+ return self._points
+
+ def set_points(self, points):
+ self._points = points
+ self.invalidate()
+
+ def set(self, other):
+ self._points = other.get_points()
+ self.invalidate()
+
+ def transformed(self, transform):
+ return Bbox(transform(self._points))
+
+ def inverse_transformed(self, transform):
+ return Bbox(transform.inverted()(self._points))
+
+ def expanded(self, sw, sh):
+ width = self.width
+ height = self.height
+ deltaw = (sw * width - width) / 2.0
+ deltah = (sh * height - height) / 2.0
+ a = npy.array([[-deltaw, -deltah], [deltaw, deltah]])
+ return Bbox(self._points + a)
+
+ #@staticmethod
+ def 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.from_lbrt(xmin, ymin, xmax, ymax)
+ union = staticmethod(union)
+
+class TransformedBbox(BboxBase):
+ def __init__(self, bbox, transform):
+ assert isinstance(bbox, Bbox)
+ assert isinstance(transform, Transform)
+
+ BboxBase.__init__(self)
+ self.bbox = bbox
+ self.transform = transform
+ self.set_children(['bbox', 'transform'])
+ self._points = None
+
+ def __repr__(self):
+ return "TransformedBbox(%s, %s)" % (self.bbox, self.transform)
+ __str__ = __repr__
+
+ def _do_invalidation(self):
+ self._points = None
+
+ def get_points(self):
+ if self._points is None:
+ self._points = self.transform(self.bbox.get_points())
+ return self._points
+
+# MGDTODO: This code probably works, but I don't think it's a good idea
+# (from a code clarity perspective)
+# class BlendedBbox(BboxBase):
+# def __init__(self, bbox_x, bbox_y):
+# assert isinstance(bbox_x, BboxBase)
+# assert isinstance(bbox_y, BboxBase)
+
+# BboxBase.__init__(self)
+# self._x = bbox_x
+# self._y = bbox_y
+# self.set_children(['_x', '_y'])
+# self._points = None
+
+# def __repr__(self):
+# return "TransformedBbox(%s, %s)" % (self.bbox, self.transform)
+# __str__ = __repr__
+
+# def _do_invalidation(self):
+# self._points = None
+
+# def get_points(self):
+# if self._points is None:
+# # MGDTODO: Optimize
+# if self._x == self._y:
+# self._points = self._x.get_points()
+# else:
+# x_points = self._x.get_points()
+# y_points = self._y.get_points()
+# self._points = npy.array(
+# [[x_points[0,0], y_points[0,1]],
+# [x_points[1,0], y_points[1,1]]],
+# npy.float_)
+# return self._points
+
+# def _set_intervalx(self, pair):
+# # MGDTODO: Optimize
+# bbox = Bbox([[pair[0], 0.0], [pair[1], 0.0]])
+# self.replace_child(0, bbox)
+# intervalx = property(BboxBase._get_intervalx, _set_intervalx)
+
+# def _set_intervaly(self, pair):
+# # MGDTODO: Optimize
+# bbox = Bbox([[0.0, pair[0]], [0.0, pair[1]]])
+# self.replace_child(1, bbox)
+# intervaly = property(BboxBase._get_intervaly, _set_intervaly)
+
+class Transform(TransformNode):
+ def __init__(self):
+ TransformNode.__init__(self)
+
+ def __call__(self, points):
+ raise NotImplementedError()
+
+ def __add__(self, other):
+ if isinstance(other, Transform):
+ return composite_transform_factory(self, other)
+ raise TypeError(
+ "Can not add Transform to object of type '%s'" % type(other))
+
+ def __radd__(self, other):
+ 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__(npy.asarray([point]))[0]
+
+ def has_inverse(self):
+ raise NotImplementedError()
+
+ def inverted(self):
+ raise NotImplementedError()
+
+ def is_separable(self):
+ return False
+
+ def is_affine(self):
+ return False
+
+class Affine2DBase(Transform):
+ input_dims = 2
+ output_dims = 2
+
+ def __init__(self):
+ Transform.__init__(self)
+ self._inverted = None
+
+ def _do_invalidation(self):
+ result = self._inverted is None
+ self._inverted = None
+ return result
+
+ #@staticmethod
+ def _concat(a, b):
+ return npy.dot(b, a)
+ _concat = staticmethod(_concat)
+
+ #@staticmethod
+ def concat(a, b):
+ return Affine2D(Affine2D._concat(a.get_matrix(), b.get_matrix()))
+ concat = staticmethod(concat)
+
+ def to_values(self):
+ mtx = self.get_matrix()
+ return tuple(mtx[:2].swapaxes(0, 1).flatten())
+
+ #@staticmethod
+ def matrix_from_values(a, b, c, d, e, f):
+ affine = npy.zeros((3, 3), npy.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 get_matrix(self):
+ raise NotImplementedError()
+
+ def __call__(self, points):
+ """
+ Applies the transformation to an array 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: 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.
+ if not isinstance(points, npy.ndarray):
+ import traceback
+ print '-' * 60
+ print 'A non-numpy array was passed in for transformation. Please '
+ print 'correct this.'
+ print "".join(traceback.format_stack())
+ print points
+ mtx = self.get_matrix()...
[truncated message content] |