From: <md...@us...> - 2007-11-07 15:31:40
|
Revision: 4142 http://matplotlib.svn.sourceforge.net/matplotlib/?rev=4142&view=rev Author: mdboom Date: 2007-11-07 07:31:37 -0800 (Wed, 07 Nov 2007) Log Message: ----------- Further speed improvements. For collections, now has faster ignoring of elements that aren't provided, such as offsets. Paths now do not even store the "default" codes array -- which is MOVETO followed by N LINETOs. These are generated automatically by the iterators if no codes array is provided. (Should also result in significant memory savings for graphs with many points.) Modified Paths: -------------- branches/transforms/lib/matplotlib/axes.py branches/transforms/lib/matplotlib/backend_bases.py branches/transforms/lib/matplotlib/collections.py branches/transforms/lib/matplotlib/lines.py branches/transforms/lib/matplotlib/patches.py branches/transforms/lib/matplotlib/path.py branches/transforms/src/_backend_agg.cpp branches/transforms/src/_path.cpp branches/transforms/src/agg_py_path_iterator.h Modified: branches/transforms/lib/matplotlib/axes.py =================================================================== --- branches/transforms/lib/matplotlib/axes.py 2007-11-07 15:30:25 UTC (rev 4141) +++ branches/transforms/lib/matplotlib/axes.py 2007-11-07 15:31:37 UTC (rev 4142) @@ -4697,7 +4697,6 @@ kwargs.setdefault('edgecolors', edgecolors) kwargs.setdefault('antialiaseds', (0,)) kwargs.setdefault('linewidths', (0.25,)) - kwargs['closed'] = False collection = mcoll.PolyCollection(verts, **kwargs) Modified: branches/transforms/lib/matplotlib/backend_bases.py =================================================================== --- branches/transforms/lib/matplotlib/backend_bases.py 2007-11-07 15:30:25 UTC (rev 4141) +++ branches/transforms/lib/matplotlib/backend_bases.py 2007-11-07 15:31:37 UTC (rev 4142) @@ -99,13 +99,12 @@ if Npaths == 0: return + transform = transforms.IdentityTransform() for i in xrange(N): path = paths[i % Npaths] - transform = all_transforms[i % Ntransforms] - if transform is None: - transform = transforms.IdentityTransform() - transform += master_transform - yield path, transform + if Ntransforms: + transform = all_transforms[i % Ntransforms] + yield path, transform + master_transform def _iter_collection(self, path_ids, cliprect, clippath, clippath_trans, offsets, offsetTrans, facecolors, edgecolors, @@ -146,8 +145,8 @@ if (Nfacecolors == 0 and Nedgecolors == 0) or Npaths == 0: return - - toffsets = offsetTrans.transform(offsets) + if Noffsets: + toffsets = offsetTrans.transform(offsets) gc = self.new_gc() @@ -159,9 +158,11 @@ if Nfacecolors == 0: rgbFace = None + xo, yo = 0, 0 for i in xrange(N): path_id = path_ids[i % Npaths] - xo, yo = toffsets[i % Noffsets] + if Noffsets: + xo, yo = toffsets[i % Noffsets] if Nfacecolors: rgbFace = facecolors[i % Nfacecolors] if Nedgecolors: Modified: branches/transforms/lib/matplotlib/collections.py =================================================================== --- branches/transforms/lib/matplotlib/collections.py 2007-11-07 15:30:25 UTC (rev 4141) +++ branches/transforms/lib/matplotlib/collections.py 2007-11-07 15:31:37 UTC (rev 4142) @@ -51,9 +51,9 @@ draw time a call to scalar mappable will be made to set the face colors. """ - _offsets = npy.zeros((1, 2)) + _offsets = npy.array([], npy.float_) _transOffset = transforms.IdentityTransform() - _transforms = [None] + _transforms = [] zorder = 1 def __init__(self, @@ -93,7 +93,7 @@ self._antialiaseds = self._get_value(antialiaseds) self._uniform_offsets = None - self._offsets = npy.zeros((1, 2)) + self._offsets = npy.array([], npy.float_) if offsets is not None: offsets = npy.asarray(offsets, npy.float_) if len(offsets.shape) == 1: @@ -135,10 +135,11 @@ if not transOffset.is_affine: offsets = transOffset.transform_non_affine(offsets) transOffset = transOffset.get_affine() - + offsets = npy.asarray(offsets, npy.float_) + result = mpath.get_path_collection_extents( transform.frozen(), paths, self.get_transforms(), - npy.asarray(offsets, npy.float_), transOffset.frozen()) + offsets, transOffset.frozen()) result = result.inverse_transformed(transData) return result @@ -158,12 +159,12 @@ xs = self.convert_xunits(xs) ys = self.convert_yunits(ys) paths.append(mpath.Path(zip(xs, ys), path.codes)) - if self._offsets is not None: + if len(self._offsets): xs = self.convert_xunits(self._offsets[:0]) ys = self.convert_yunits(self._offsets[:1]) offsets = zip(xs, ys) if len(offsets) == 0: - offsets = npy.zeros((1, 2)) + offsets = npy.array([], npy.float_) else: offsets = npy.asarray(offsets, npy.float_) @@ -384,27 +385,33 @@ self._coordinates = coordinates self._showedges = showedges - # MGDTODO: Is it possible to Numpify this? - coordinates = coordinates.reshape((meshHeight + 1, meshWidth + 1, 2)) - c = coordinates - paths = [] + c = coordinates.reshape((meshHeight + 1, meshWidth + 1, 2)) # We could let the Path constructor generate the codes for us, # but this is faster, since we know they'll always be the same - codes = npy.array([Path.MOVETO, Path.LINETO, Path.LINETO, Path.LINETO, Path.CLOSEPOLY]) - for m in xrange(meshHeight): - for n in xrange(meshWidth): - paths.append(Path( - [c[m , n], - c[m , n+1], - c[m+1, n+1], - c[m+1, n], - c[m , n]], - codes)) - self._paths = paths + codes = npy.array( + [Path.MOVETO, Path.LINETO, Path.LINETO, Path.LINETO, Path.CLOSEPOLY], + Path.code_type) + + points = npy.concatenate(( + c[0:-1, 0:-1], + c[0:-1, 1: ], + c[1: , 1: ], + c[1: , 0:-1], + c[0:-1, 0:-1] + ), axis=2) + points = points.reshape((meshWidth * meshHeight, 5, 2)) + self._paths = [Path(x, codes) for x in points] + + self._bbox = transforms.Bbox.unit() + self._bbox.update_from_data_xy(c.reshape( + ((meshWidth + 1) * (meshHeight + 1), 2))) def get_paths(self, dataTrans=None): return self._paths + def get_datalim(self, transData): + return self._bbox + def draw(self, renderer): self.update_scalarmappable() @@ -425,14 +432,13 @@ %(Collection)s """ - self.closed = kwargs.pop("closed", True) Collection.__init__(self,**kwargs) self.set_verts(verts) __init__.__doc__ = cbook.dedent(__init__.__doc__) % artist.kwdocd def set_verts(self, verts): '''This allows one to delay initialization of the vertices.''' - self._paths = [mpath.Path(v, closed=self.closed) for v in verts] + self._paths = [mpath.Path(v) for v in verts] def get_paths(self): return self._paths Modified: branches/transforms/lib/matplotlib/lines.py =================================================================== --- branches/transforms/lib/matplotlib/lines.py 2007-11-07 15:30:25 UTC (rev 4141) +++ branches/transforms/lib/matplotlib/lines.py 2007-11-07 15:31:37 UTC (rev 4142) @@ -928,7 +928,7 @@ path, path_trans) - _caret_path = Path([[-1.0, 1.5], [0.0, 0.0], [1.0, 1.5]], closed=False) + _caret_path = Path([[-1.0, 1.5], [0.0, 0.0], [1.0, 1.5]]) def _draw_caretdown(self, renderer, gc, path, path_trans): offset = 0.5*renderer.points_to_pixels(self._markersize) transform = Affine2D().scale(offset) Modified: branches/transforms/lib/matplotlib/patches.py =================================================================== --- branches/transforms/lib/matplotlib/patches.py 2007-11-07 15:30:25 UTC (rev 4141) +++ branches/transforms/lib/matplotlib/patches.py 2007-11-07 15:31:37 UTC (rev 4142) @@ -530,7 +530,7 @@ See Patch documentation for additional kwargs """ Patch.__init__(self, **kwargs) - self._path = Path(xy, closed=True) + self._path = Path(xy) __init__.__doc__ = cbook.dedent(__init__.__doc__) % artist.kwdocd def get_path(self): @@ -539,7 +539,7 @@ def _get_xy(self): return self._path.vertices def _set_xy(self, vertices): - self._path = Path(vertices, closed=True) + self._path = Path(vertices) xy = property(_get_xy, _set_xy) class Wedge(Patch): Modified: branches/transforms/lib/matplotlib/path.py =================================================================== --- branches/transforms/lib/matplotlib/path.py 2007-11-07 15:30:25 UTC (rev 4141) +++ branches/transforms/lib/matplotlib/path.py 2007-11-07 15:31:37 UTC (rev 4142) @@ -53,6 +53,12 @@ CLOSEPOLY : 1 vertex (ignored) Draw a line segment to the start point of the current polyline. + + Users of Path objects should not access the vertices and codes + arrays directly. Instead, they should use iter_segments to get + the vertex/code pairs. This is important since many Paths do not + store a codes array at all, but have a default one provided for + them by iter_segments. """ # Path codes @@ -67,10 +73,7 @@ code_type = npy.uint8 - _open_codes_cache = WeakValueDictionary() - _closed_codes_cache = WeakValueDictionary() - - def __init__(self, vertices, codes=None, closed=False): + def __init__(self, vertices, codes=None): """ Create a new path with the given vertices and codes. @@ -87,43 +90,17 @@ dimension. If codes is None, vertices will be treated as a series of line - segments. Additionally, if closed is also True, the polyline - will closed. If vertices contains masked values, the - resulting path will be compressed, with MOVETO codes inserted - in the correct places to jump over the masked regions. + segments. If vertices contains masked values, the resulting + path will be compressed, with MOVETO codes inserted in the + correct places to jump over the masked regions. """ if ma.isMaskedArray(vertices): mask = ma.getmask(vertices) else: vertices = npy.asarray(vertices, npy.float_) mask = ma.nomask - - if codes is None: - if closed: - # MGDTODO: Remove me once efficiency concerns are - # taken care of. - warnings.warn(""" -EFFICIENCY CONCERN: Having the Path constructor create a closed -polyline automatically is not always the most efficient way to do -things, since it causes a memory copy of the vertices array. If the -caller can easily close the polygon itself it should do so. -""") - codes = self._closed_codes_cache.get(len(vertices)) - if codes is None: - codes = self.LINETO * npy.ones( - vertices.shape[0] + 1, self.code_type) - codes[0] = self.MOVETO - codes[-1] = self.CLOSEPOLY - self._closed_codes_cache[len(vertices)] = codes - vertices = npy.concatenate((vertices, [vertices[0]])) - else: - codes = self._open_codes_cache.get(len(vertices)) - if codes is None: - codes = self.LINETO * npy.ones( - vertices.shape[0], self.code_type) - codes[0] = self.MOVETO - self._open_codes_cache[len(vertices)] = codes - else: + + if codes is not None: codes = npy.asarray(codes, self.code_type) assert codes.ndim == 1 assert len(codes) == len(vertices) @@ -136,6 +113,10 @@ if mask is not ma.nomask: mask1d = ma.mask_or(mask[:, 0], mask[:, 1]) vertices = ma.compress(npy.invert(mask1d), vertices, 0) + if codes is None: + codes = self.LINETO * npy.ones( + vertices.shape[0], self.code_type) + codes[0] = self.MOVETO codes = npy.where(npy.concatenate((mask1d[-1:], mask1d[:-1])), self.MOVETO, codes) codes = ma.masked_array(codes, mask=mask1d).compressed() @@ -144,23 +125,15 @@ assert vertices.ndim == 2 assert vertices.shape[1] == 2 - self._codes = codes - self._vertices = vertices + self.codes = codes + self.vertices = vertices def __repr__(self): return "Path(%s, %s)" % (self.vertices, self.codes) def __len__(self): - return len(self._vertices) + return len(self.vertices) - def _get_codes(self): - return self._codes - codes = property(_get_codes) - - def _get_vertices(self): - return self._vertices - vertices = property(_get_vertices) - def iter_segments(self): """ Iterates over all of the endpoints in the path. Unlike @@ -171,18 +144,28 @@ NUM_VERTICES = self.NUM_VERTICES vertices = self.vertices codes = self.codes + + if not len(vertices): + return - while i < len(vertices): - code = codes[i] - if code == self.CLOSEPOLY: - yield [], code - i += 1 - elif code == self.STOP: - return - else: - num_vertices = NUM_VERTICES[code] - yield vertices[i:i+num_vertices].flatten(), code - i += num_vertices + if codes is None: + code = self.MOVETO + yield vertices[0], self.MOVETO + i = 1 + for v in vertices[1:]: + yield v, self.LINETO + else: + while i < len(vertices): + code = codes[i] + if code == self.CLOSEPOLY: + yield [], code + i += 1 + elif code == self.STOP: + return + else: + num_vertices = NUM_VERTICES[code] + yield vertices[i:i+num_vertices].flatten(), code + i += num_vertices def transformed(self, transform): """ @@ -242,7 +225,7 @@ return cls._unit_rectangle unit_rectangle = classmethod(unit_rectangle) - _unit_regular_polygons = {} + _unit_regular_polygons = WeakValueDictionary() #@classmethod def unit_regular_polygon(cls, numVertices): """ @@ -262,7 +245,7 @@ return path unit_regular_polygon = classmethod(unit_regular_polygon) - _unit_regular_stars = {} + _unit_regular_stars = WeakValueDictionary() #@classmethod def unit_regular_star(cls, numVertices, innerCircle=0.5): """ @@ -413,6 +396,7 @@ return Path(vertices, codes) arc = classmethod(arc) + #@classmethod def wedge(cls, theta1, theta2): """ Returns a wedge of the unit circle from angle theta1 to angle Modified: branches/transforms/src/_backend_agg.cpp =================================================================== --- branches/transforms/src/_backend_agg.cpp 2007-11-07 15:30:25 UTC (rev 4141) +++ branches/transforms/src/_backend_agg.cpp 2007-11-07 15:31:37 UTC (rev 4142) @@ -940,9 +940,12 @@ PyArrayObject* edgecolors = NULL; try { - offsets = (PyArrayObject*)PyArray_FromObject(offsets_obj.ptr(), PyArray_DOUBLE, 2, 2); - if (!offsets || offsets->dimensions[1] != 2) + offsets = (PyArrayObject*)PyArray_FromObject(offsets_obj.ptr(), PyArray_DOUBLE, 0, 2); + if (!offsets || + (offsets->nd == 2 && offsets->dimensions[1] != 2) || + (offsets->nd == 1 && offsets->dimensions[0])) { throw Py::ValueError("Offsets array must be Nx2"); + } PyArrayObject* facecolors = (PyArrayObject*)PyArray_FromObject (facecolors_obj.ptr(), PyArray_DOUBLE, 1, 2); @@ -1004,14 +1007,22 @@ gc.linewidth = 0.0; facepair_t face; face.first = Nfacecolors != 0; - + agg::trans_affine trans; + for (i = 0; i < N; ++i) { PathIterator path(paths[i % Npaths]); - double xo = *(double*)PyArray_GETPTR2(offsets, i % Noffsets, 0); - double yo = *(double*)PyArray_GETPTR2(offsets, i % Noffsets, 1); - offset_trans.transform(&xo, &yo); - agg::trans_affine_translation transOffset(xo, yo); - agg::trans_affine& trans = transforms[i % Ntransforms]; + if (Ntransforms) { + trans = transforms[i % Ntransforms]; + } else { + trans = master_transform; + } + + if (Noffsets) { + double xo = *(double*)PyArray_GETPTR2(offsets, i % Noffsets, 0); + double yo = *(double*)PyArray_GETPTR2(offsets, i % Noffsets, 1); + offset_trans.transform(&xo, &yo); + trans *= agg::trans_affine_translation(xo, yo); + } if (Nfacecolors) { size_t fi = i % Nfacecolors; @@ -1034,7 +1045,7 @@ gc.isaa = bool(Py::Int(antialiaseds[i % Naa])); - _draw_path(path, trans * transOffset, has_clippath, face, gc); + _draw_path(path, trans, has_clippath, face, gc); } } catch (...) { Py_XDECREF(offsets); Modified: branches/transforms/src/_path.cpp =================================================================== --- branches/transforms/src/_path.cpp 2007-11-07 15:30:25 UTC (rev 4141) +++ branches/transforms/src/_path.cpp 2007-11-07 15:31:37 UTC (rev 4142) @@ -261,9 +261,12 @@ double x0, y0, x1, y1; try { - offsets = (PyArrayObject*)PyArray_FromObject(offsets_obj.ptr(), PyArray_DOUBLE, 2, 2); - if (!offsets || offsets->dimensions[1] != 2) + offsets = (PyArrayObject*)PyArray_FromObject(offsets_obj.ptr(), PyArray_DOUBLE, 0, 2); + if (!offsets || + (offsets->nd == 2 && offsets->dimensions[1] != 2) || + (offsets->nd == 1 && offsets->dimensions[0])) { throw Py::ValueError("Offsets array must be Nx2"); + } size_t Npaths = paths.length(); size_t Noffsets = offsets->dimensions[0]; @@ -287,16 +290,23 @@ y0 = std::numeric_limits<double>::infinity(); x1 = -std::numeric_limits<double>::infinity(); y1 = -std::numeric_limits<double>::infinity(); + agg::trans_affine trans; + for (i = 0; i < N; ++i) { PathIterator path(paths[i % Npaths]); - - double xo = *(double*)PyArray_GETPTR2(offsets, i % Noffsets, 0); - double yo = *(double*)PyArray_GETPTR2(offsets, i % Noffsets, 1); - offset_trans.transform(&xo, &yo); - agg::trans_affine_translation transOffset(xo, yo); - agg::trans_affine trans = transforms[i % Ntransforms]; - trans *= transOffset; + if (Ntransforms) { + trans = transforms[i % Ntransforms]; + } else { + trans = master_transform; + } + if (Noffsets) { + double xo = *(double*)PyArray_GETPTR2(offsets, i % Noffsets, 0); + double yo = *(double*)PyArray_GETPTR2(offsets, i % Noffsets, 1); + offset_trans.transform(&xo, &yo); + trans *= agg::trans_affine_translation(xo, yo); + } + ::get_path_extents(path, trans, &x0, &y0, &x1, &y1); } } catch (...) { Modified: branches/transforms/src/agg_py_path_iterator.h =================================================================== --- branches/transforms/src/agg_py_path_iterator.h 2007-11-07 15:30:25 UTC (rev 4141) +++ branches/transforms/src/agg_py_path_iterator.h 2007-11-07 15:31:37 UTC (rev 4142) @@ -23,15 +23,14 @@ if (!vertices || vertices->nd != 2 || vertices->dimensions[1] != 2) throw Py::ValueError("Invalid vertices array."); - codes = (PyArrayObject*)PyArray_FromObject - (codes_obj.ptr(), PyArray_UINT8, 1, 1); - if (!codes) - throw Py::ValueError("Invalid codes array."); - - if (codes->dimensions[0] != vertices->dimensions[0]) - throw Py::ValueError("vertices and codes arrays are not the same length."); + if (codes_obj.ptr() != Py_None) { + codes = (PyArrayObject*)PyArray_FromObject + (codes_obj.ptr(), PyArray_UINT8, 1, 1); + if (!codes) + throw Py::ValueError("Invalid codes array."); + } - m_total_vertices = codes->dimensions[0]; + m_total_vertices = vertices->dimensions[0]; } ~PathIterator() { @@ -46,7 +45,11 @@ throw Py::RuntimeError("Requested vertex past end"); *x = *(double*)PyArray_GETPTR2(vertices, idx, 0); *y = *(double*)PyArray_GETPTR2(vertices, idx, 1); - return code_map[(int)*(char *)PyArray_GETPTR1(codes, idx)]; + if (codes) { + return code_map[(int)*(char *)PyArray_GETPTR1(codes, idx)]; + } else { + return idx == 0 ? agg::path_cmd_move_to : agg::path_cmd_line_to; + } } inline unsigned vertex(double* x, double* y) { This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |