| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 12f2624 commit 907235d
3 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -2002,6 +2002,107 @@ def _array_perimeter(arr): | |||
| 2002 | 2002 | )) | |
| 2003 | 2003 | ||
| 2004 | 2004 | ||
| 2005 | + def _unfold(arr, axis, size, step): | ||
| 2006 | + """ | ||
| 2007 | + Append an extra dimension containing sliding windows along *axis*. | ||
| 2008 | + | ||
| 2009 | + All windows are of size *size* and begin with every *step* elements. | ||
| 2010 | + | ||
| 2011 | + Parameters | ||
| 2012 | + ---------- | ||
| 2013 | + arr : ndarray, shape (N_1, ..., N_k) | ||
| 2014 | + The input array | ||
| 2015 | + axis : int | ||
| 2016 | + Axis along which the windows are extracted | ||
| 2017 | + size : int | ||
| 2018 | + Size of the windows | ||
| 2019 | + step : int | ||
| 2020 | + Stride between first elements of subsequent windows. | ||
| 2021 | + | ||
| 2022 | + Returns | ||
| 2023 | + ------- | ||
| 2024 | + windows : ndarray, shape (N_1, ..., 1 + (N_axis-size)/step, ..., N_k, size) | ||
| 2025 | + | ||
| 2026 | + Examples | ||
| 2027 | + -------- | ||
| 2028 | + >>> i, j = np.ogrid[:3,:7] | ||
| 2029 | + >>> a = i*10 + j | ||
| 2030 | + >>> a | ||
| 2031 | + array([[ 0, 1, 2, 3, 4, 5, 6], | ||
| 2032 | + [10, 11, 12, 13, 14, 15, 16], | ||
| 2033 | + [20, 21, 22, 23, 24, 25, 26]]) | ||
| 2034 | + >>> _unfold(a, axis=1, size=3, step=2) | ||
| 2035 | + array([[[ 0, 1, 2], | ||
| 2036 | + [ 2, 3, 4], | ||
| 2037 | + [ 4, 5, 6]], | ||
| 2038 | + | ||
| 2039 | + [[10, 11, 12], | ||
| 2040 | + [12, 13, 14], | ||
| 2041 | + [14, 15, 16]], | ||
| 2042 | + | ||
| 2043 | + [[20, 21, 22], | ||
| 2044 | + [22, 23, 24], | ||
| 2045 | + [24, 25, 26]]]) | ||
| 2046 | + """ | ||
| 2047 | + new_shape = [*arr.shape, size] | ||
| 2048 | + new_strides = [*arr.strides, arr.strides[axis]] | ||
| 2049 | + new_shape[axis] = (new_shape[axis] - size) // step + 1 | ||
| 2050 | + new_strides[axis] = new_strides[axis] * step | ||
| 2051 | + return np.lib.stride_tricks.as_strided(arr, | ||
| 2052 | + shape=new_shape, | ||
| 2053 | + strides=new_strides, | ||
| 2054 | + writeable=False) | ||
| 2055 | + | ||
| 2056 | + | ||
| 2057 | + def _array_patch_perimeters(x, rstride, cstride): | ||
| 2058 | + """ | ||
| 2059 | + Extract perimeters of patches from *arr*. | ||
| 2060 | + | ||
| 2061 | + Extracted patches are of size (*rstride* + 1) x (*cstride* + 1) and | ||
| 2062 | + share perimeters with their neighbors. The ordering of the vertices matches | ||
| 2063 | + that returned by ``_array_perimeter``. | ||
| 2064 | + | ||
| 2065 | + Parameters | ||
| 2066 | + ---------- | ||
| 2067 | + x : ndarray, shape (N, M) | ||
| 2068 | + Input array | ||
| 2069 | + rstride : int | ||
| 2070 | + Vertical (row) stride between corresponding elements of each patch | ||
| 2071 | + cstride : int | ||
| 2072 | + Horizontal (column) stride between corresponding elements of each patch | ||
| 2073 | + | ||
| 2074 | + Returns | ||
| 2075 | + ------- | ||
| 2076 | + patches : ndarray, shape (N/rstride * M/cstride, 2 * (rstride + cstride)) | ||
| 2077 | + """ | ||
| 2078 | + assert rstride > 0 and cstride > 0 | ||
| 2079 | + assert (x.shape[0] - 1) % rstride == 0 | ||
| 2080 | + assert (x.shape[1] - 1) % cstride == 0 | ||
| 2081 | + # We build up each perimeter from four half-open intervals. Here is an | ||
| 2082 | + # illustrated explanation for rstride == cstride == 3 | ||
| 2083 | + # | ||
| 2084 | + # T T T R | ||
| 2085 | + # L R | ||
| 2086 | + # L R | ||
| 2087 | + # L B B B | ||
| 2088 | + # | ||
| 2089 | + # where T means that this element will be in the top array, R for right, | ||
| 2090 | + # B for bottom and L for left. Each of the arrays below has a shape of: | ||
| 2091 | + # | ||
| 2092 | + # (number of perimeters that can be extracted vertically, | ||
| 2093 | + # number of perimeters that can be extracted horizontally, | ||
| 2094 | + # cstride for top and bottom and rstride for left and right) | ||
| 2095 | + # | ||
| 2096 | + # Note that _unfold doesn't incur any memory copies, so the only costly | ||
| 2097 | + # operation here is the np.concatenate. | ||
| 2098 | + top = _unfold(x[:-1:rstride, :-1], 1, cstride, cstride) | ||
| 2099 | + bottom = _unfold(x[rstride::rstride, 1:], 1, cstride, cstride)[..., ::-1] | ||
| 2100 | + right = _unfold(x[:-1, cstride::cstride], 0, rstride, rstride) | ||
| 2101 | + left = _unfold(x[1:, :-1:cstride], 0, rstride, rstride)[..., ::-1] | ||
| 2102 | + return (np.concatenate((top, right, bottom, left), axis=2) | ||
| 2103 | + .reshape(-1, 2 * (rstride + cstride))) | ||
| 2104 | + | ||
| 2105 | + | ||
| 2005 | 2106 | @contextlib.contextmanager | |
| 2006 | 2107 | def _setattr_cm(obj, **kwargs): | |
| 2007 | 2108 | """Temporarily set some attributes; restore original state at context exit. | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -591,3 +591,31 @@ def test_warn_external(recwarn): | |||
| 591 | 591 | cbook._warn_external("oops") | |
| 592 | 592 | assert len(recwarn) == 1 | |
| 593 | 593 | assert recwarn[0].filename == __file__ | |
| 594 | + | ||
| 595 | + | ||
| 596 | + def test_array_patch_perimeters(): | ||
| 597 | + # This compares the old implementation as a reference for the | ||
| 598 | + # vectorized one. | ||
| 599 | + def check(x, rstride, cstride): | ||
| 600 | + rows, cols = x.shape | ||
| 601 | + row_inds = [*range(0, rows-1, rstride), rows-1] | ||
| 602 | + col_inds = [*range(0, cols-1, cstride), cols-1] | ||
| 603 | + polys = [] | ||
| 604 | + for rs, rs_next in zip(row_inds[:-1], row_inds[1:]): | ||
| 605 | + for cs, cs_next in zip(col_inds[:-1], col_inds[1:]): | ||
| 606 | + # +1 ensures we share edges between polygons | ||
| 607 | + ps = cbook._array_perimeter(x[rs:rs_next+1, cs:cs_next+1]).T | ||
| 608 | + polys.append(ps) | ||
| 609 | + polys = np.asarray(polys) | ||
| 610 | + assert np.array_equal(polys, | ||
| 611 | + cbook._array_patch_perimeters( | ||
| 612 | + x, rstride=rstride, cstride=cstride)) | ||
| 613 | + | ||
| 614 | + def divisors(n): | ||
| 615 | + return [i for i in range(1, n + 1) if n % i == 0] | ||
| 616 | + | ||
| 617 | + for rows, cols in [(5, 5), (7, 14), (13, 9)]: | ||
| 618 | + x = np.arange(rows * cols).reshape(rows, cols) | ||
| 619 | + for rstride, cstride in itertools.product(divisors(rows - 1), | ||
| 620 | + divisors(cols - 1)): | ||
| 621 | + check(x, rstride=rstride, cstride=cstride) | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1444,6 +1444,17 @@ def plot_surface(self, X, Y, Z, *args, norm=None, vmin=None, | |||
| 1444 | 1444 | the input data is larger, it will be downsampled (by slicing) to | |
| 1445 | 1445 | these numbers of points. | |
| 1446 | 1446 | ||
| 1447 | + .. note:: | ||
| 1448 | + | ||
| 1449 | + To maximize rendering speed consider setting *rstride* and *cstride* | ||
| 1450 | + to divisors of the number of rows minus 1 and columns minus 1 | ||
| 1451 | + respectively. For example, given 51 rows rstride can be any of the | ||
| 1452 | + divisors of 50. | ||
| 1453 | + | ||
| 1454 | + Similarly, a setting of *rstride* and *cstride* equal to 1 (or | ||
| 1455 | + *rcount* and *ccount* equal the number of rows and columns) can use | ||
| 1456 | + the optimized path. | ||
| 1457 | + | ||
| 1447 | 1458 | Parameters | |
| 1448 | 1459 | ---------- | |
| 1449 | 1460 | X, Y, Z : 2d arrays | |
@@ -1547,25 +1558,33 @@ def plot_surface(self, X, Y, Z, *args, norm=None, vmin=None, | |||
| 1547 | 1558 | "semantic or raise an error in matplotlib 3.3. " | |
| 1548 | 1559 | "Please use shade=False instead.") | |
| 1549 | 1560 | ||
| 1550 | - # evenly spaced, and including both endpoints | ||
| 1551 | - row_inds = list(range(0, rows-1, rstride)) + [rows-1] | ||
| 1552 | - col_inds = list(range(0, cols-1, cstride)) + [cols-1] | ||
| 1553 | - | ||
| 1554 | 1561 | colset = [] # the sampled facecolor | |
| 1555 | - polys = [] | ||
| 1556 | - for rs, rs_next in zip(row_inds[:-1], row_inds[1:]): | ||
| 1557 | - for cs, cs_next in zip(col_inds[:-1], col_inds[1:]): | ||
| 1558 | - ps = [ | ||
| 1559 | - # +1 ensures we share edges between polygons | ||
| 1560 | - cbook._array_perimeter(a[rs:rs_next+1, cs:cs_next+1]) | ||
| 1561 | - for a in (X, Y, Z) | ||
| 1562 | - ] | ||
| 1563 | - # ps = np.stack(ps, axis=-1) | ||
| 1564 | - ps = np.array(ps).T | ||
| 1565 | - polys.append(ps) | ||
| 1566 | - | ||
| 1567 | - if fcolors is not None: | ||
| 1568 | - colset.append(fcolors[rs][cs]) | ||
| 1562 | + if (rows - 1) % rstride == 0 and \ | ||
| 1563 | + (cols - 1) % cstride == 0 and \ | ||
| 1564 | + fcolors is None: | ||
| 1565 | + polys = np.stack( | ||
| 1566 | + [cbook._array_patch_perimeters(a, rstride, cstride) | ||
| 1567 | + for a in (X, Y, Z)], | ||
| 1568 | + axis=-1) | ||
| 1569 | + else: | ||
| 1570 | + # evenly spaced, and including both endpoints | ||
| 1571 | + row_inds = list(range(0, rows-1, rstride)) + [rows-1] | ||
| 1572 | + col_inds = list(range(0, cols-1, cstride)) + [cols-1] | ||
| 1573 | + | ||
| 1574 | + polys = [] | ||
| 1575 | + for rs, rs_next in zip(row_inds[:-1], row_inds[1:]): | ||
| 1576 | + for cs, cs_next in zip(col_inds[:-1], col_inds[1:]): | ||
| 1577 | + ps = [ | ||
| 1578 | + # +1 ensures we share edges between polygons | ||
| 1579 | + cbook._array_perimeter(a[rs:rs_next+1, cs:cs_next+1]) | ||
| 1580 | + for a in (X, Y, Z) | ||
| 1581 | + ] | ||
| 1582 | + # ps = np.stack(ps, axis=-1) | ||
| 1583 | + ps = np.array(ps).T | ||
| 1584 | + polys.append(ps) | ||
| 1585 | + | ||
| 1586 | + if fcolors is not None: | ||
| 1587 | + colset.append(fcolors[rs][cs]) | ||
| 1569 | 1588 | ||
| 1570 | 1589 | # note that the striding causes some polygons to have more coordinates | |
| 1571 | 1590 | # than others | |
@@ -1578,8 +1597,11 @@ def plot_surface(self, X, Y, Z, *args, norm=None, vmin=None, | |||
| 1578 | 1597 | polyc.set_facecolors(colset) | |
| 1579 | 1598 | polyc.set_edgecolors(colset) | |
| 1580 | 1599 | elif cmap: | |
| 1581 | - # doesn't vectorize because polys is jagged | ||
| 1582 | - avg_z = np.array([ps[:, 2].mean() for ps in polys]) | ||
| 1600 | + # can't always vectorize, because polys might be jagged | ||
| 1601 | + if isinstance(polys, np.ndarray): | ||
| 1602 | + avg_z = polys[..., 2].mean(axis=-1) | ||
| 1603 | + else: | ||
| 1604 | + avg_z = np.array([ps[:, 2].mean() for ps in polys]) | ||
| 1583 | 1605 | polyc.set_array(avg_z) | |
| 1584 | 1606 | if vmin is not None or vmax is not None: | |
| 1585 | 1607 | polyc.set_clim(vmin, vmax) | |
| Back | FazBrowse Home | New Git URL |
0 commit comments