Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 12e7d01

Browse files
committed
Generalize Affine2DBase to AffineImmutable
1 parent 199c31f commit 12e7d01

File tree

8 files changed

+115
-65
lines changed

8 files changed

+115
-65
lines changed

lib/matplotlib/backend_bases.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -439,10 +439,10 @@ def draw_image(self, gc, x, y, im, transform=None):
439439
im : (N, M, 4) array of `numpy.uint8`
440440
An array of RGBA pixels.
441441
442-
transform : `~matplotlib.transforms.Affine2DBase`
442+
transform : `~matplotlib.transforms.AffineImmutable`
443443
If and only if the concrete backend is written such that
444444
`option_scale_image` returns ``True``, an affine transformation
445-
(i.e., an `.Affine2DBase`) *may* be passed to `draw_image`. The
445+
(i.e., an `.AffineImmutable`) *may* be passed to `draw_image`. The
446446
translation vector of the transformation is given in physical units
447447
(i.e., dots or pixels). Note that the transformation does not
448448
override *x* and *y*, and has to be applied *before* translating

lib/matplotlib/backend_bases.pyi

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ class RendererBase:
9191
x: float,
9292
y: float,
9393
im: ArrayLike,
94-
transform: transforms.Affine2DBase | None = ...,
94+
transform: transforms.AffineImmutable | None = ...,
9595
) -> None: ...
9696
def option_image_nocomposite(self) -> bool: ...
9797
def option_scale_image(self) -> bool: ...

lib/matplotlib/backends/backend_svg.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
from matplotlib.dates import UTC
2323
from matplotlib.path import Path
2424
from matplotlib import _path
25-
from matplotlib.transforms import Affine2D, Affine2DBase
25+
from matplotlib.transforms import Affine2D, AffineImmutable
2626

2727

2828
_log = logging.getLogger(__name__)
@@ -255,7 +255,7 @@ def _generate_transform(transform_list):
255255
or type == 'translate' and value == (0, 0)
256256
or type == 'rotate' and value == (0,)):
257257
continue
258-
if type == 'matrix' and isinstance(value, Affine2DBase):
258+
if type == 'matrix' and isinstance(value, AffineImmutable):
259259
value = value.to_values()
260260
parts.append('{}({})'.format(
261261
type, ' '.join(_short_float_fmt(x) for x in value)))

lib/matplotlib/path.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1062,10 +1062,10 @@ def get_path_collection_extents(
10621062
master_transform : `~matplotlib.transforms.Transform`
10631063
Global transformation applied to all paths.
10641064
paths : list of `Path`
1065-
transforms : list of `~matplotlib.transforms.Affine2DBase`
1065+
transforms : list of `~matplotlib.transforms.AffineImmutable`
10661066
If non-empty, this overrides *master_transform*.
10671067
offsets : (N, 2) array-like
1068-
offset_transform : `~matplotlib.transforms.Affine2DBase`
1068+
offset_transform : `~matplotlib.transforms.AffineImmutable`
10691069
Transform applied to the offsets before offsetting the path.
10701070
10711071
Notes

lib/matplotlib/projections/polar.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ def inverted(self):
155155
)
156156

157157

158-
class PolarAffine(mtransforms.Affine2DBase):
158+
class PolarAffine(mtransforms.AffineImmutable):
159159
r"""
160160
The affine part of the polar projection.
161161
@@ -181,7 +181,7 @@ def __init__(self, scale_transform, limits):
181181
View limits of the data. The only part of its bounds that is used
182182
is the y limits (for the radius limits).
183183
"""
184-
super().__init__()
184+
super().__init__(dims=2)
185185
self._scale_transform = scale_transform
186186
self._limits = limits
187187
self.set_children(scale_transform, limits)

lib/matplotlib/projections/polar.pyi

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ class PolarTransform(mtransforms.Transform):
2323
) -> None: ...
2424
def inverted(self) -> InvertedPolarTransform: ...
2525

26-
class PolarAffine(mtransforms.Affine2DBase):
26+
class PolarAffine(mtransforms.AffineImmutable):
2727
def __init__(
2828
self, scale_transform: mtransforms.Transform, limits: mtransforms.BboxBase
2929
) -> None: ...

lib/matplotlib/transforms.py

Lines changed: 89 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
"""
22
Matplotlib includes a framework for arbitrary geometric
3-
transformations that is used determine the final position of all
3+
transformations that is used to determine the final position of all
44
elements drawn on the canvas.
55
66
Transforms are composed into trees of `TransformNode` objects
7-
whose actual value depends on their children. When the contents of
8-
children change, their parents are automatically invalidated. The
7+
whose actual value depends on their children. When the contents of
8+
children change, their parents are automatically invalidated. The
99
next time an invalidated transform is accessed, it is recomputed to
10-
reflect those changes. This invalidation/caching approach prevents
10+
reflect those changes. This invalidation/caching approach prevents
1111
unnecessary recomputations of transforms, and contributes to better
1212
interactive performance.
1313
@@ -1372,7 +1372,7 @@ def _iter_break_from_left_to_right(self):
13721372
This is equivalent to flattening the stack then yielding
13731373
``flat_stack[:i], flat_stack[i:]`` where i=0..(n-1).
13741374
"""
1375-
yield IdentityTransform(), self
1375+
yield IdentityTransform(dims=self.input_dims), self
13761376

13771377
@property
13781378
def depth(self):
@@ -1578,7 +1578,7 @@ def transform_bbox(self, bbox):
15781578

15791579
def get_affine(self):
15801580
"""Get the affine part of this transform."""
1581-
return IdentityTransform()
1581+
return IdentityTransform(dims=self.input_dims)
15821582

15831583
def get_matrix(self):
15841584
"""Get the matrix for the affine part of this transform."""
@@ -1821,11 +1821,13 @@ def get_affine(self):
18211821
return self
18221822

18231823

1824-
class Affine2DBase(AffineBase):
1824+
class AffineImmutable(AffineBase):
18251825
"""
1826-
The base class of all 2D affine transformations.
1826+
The base class of all affine transformations.
18271827
1828-
2D affine transformations are performed using a 3x3 numpy array::
1828+
Affine transformations for the n-th degree are performed using a
1829+
numpy array with shape (n+1, n+1). For example, 2D affine
1830+
transformations are performed using a 3x3 numpy array::
18291831
18301832
a c e
18311833
b d f
@@ -1835,34 +1837,65 @@ class Affine2DBase(AffineBase):
18351837
affine transformation, use `Affine2D`.
18361838
18371839
Subclasses of this class will generally only need to override a
1838-
constructor and `~.Transform.get_matrix` that generates a custom 3x3 matrix.
1840+
constructor and `~.Transform.get_matrix` that generates a custom matrix
1841+
with the appropriate shape.
18391842
"""
1840-
input_dims = 2
1841-
output_dims = 2
1843+
def __init__(self, *args, dims=2, **kwargs):
1844+
self.input_dims = dims
1845+
self.output_dims = dims
1846+
super().__init__(*args, **kwargs)
18421847

18431848
def frozen(self):
18441849
# docstring inherited
1845-
return Affine2D(self.get_matrix().copy())
1850+
return _affine_factory(self.get_matrix().copy(), self.input_dims)
18461851

18471852
@property
18481853
def is_separable(self):
18491854
mtx = self.get_matrix()
1850-
return mtx[0, 1] == mtx[1, 0] == 0.0
1855+
separable = True
1856+
for i in range(self.input_dims):
1857+
for j in range(i+1, self.input_dims):
1858+
separable = separable and mtx[i, j] == 0.0
1859+
separable = separable and mtx[j, i] == 0.0
1860+
return separable
18511861

18521862
def to_values(self):
18531863
"""
1854-
Return the values of the matrix as an ``(a, b, c, d, e, f)`` tuple.
1864+
Return the values of the matrix as a tuple.
18551865
"""
18561866
mtx = self.get_matrix()
1857-
return tuple(mtx[:2].swapaxes(0, 1).flat)
1867+
return tuple(mtx[:self.input_dims].swapaxes(0, 1).flat)
1868+
1869+
def _affine_transform(self, vertices):
1870+
"""
1871+
Default implementation of affine_transform if a C implementation isn't
1872+
available
1873+
"""
1874+
mtx = self.get_matrix()
1875+
values = np.asanyarray(vertices)
1876+
# single point
1877+
if (values.shape == (self.input_dims,)):
1878+
return mtx.dot(vertices + [1]).tolist()[:self.input_dims]
1879+
# multiple points
1880+
if (values.shape[1] == self.input_dims):
1881+
homogeneous = np.hstack((values, np.ones((values.shape[0], 1))))
1882+
return np.dot(mtx, homogeneous.T).T[:, :-1]
1883+
1884+
raise TypeError("Dimensions of input must match the input dimensions of "
1885+
"the transform")
18581886

18591887
@_api.rename_parameter("3.8", "points", "values")
18601888
def transform_affine(self, values):
18611889
mtx = self.get_matrix()
1890+
1891+
# Default to python implementation if C implementation isn't available
1892+
transform_fn = (affine_transform if self.input_dims <= 2
1893+
else self._affine_transform)
1894+
18621895
if isinstance(values, np.ma.MaskedArray):
1863-
tpoints = affine_transform(values.data, mtx)
1896+
tpoints = transform_fn(values.data, mtx)
18641897
return np.ma.MaskedArray(tpoints, mask=np.ma.getmask(values))
1865-
return affine_transform(values, mtx)
1898+
return transform_fn(values, mtx)
18661899

18671900
if DEBUG:
18681901
_transform_affine = transform_affine
@@ -1886,12 +1919,25 @@ def inverted(self):
18861919
shorthand_name = None
18871920
if self._shorthand_name:
18881921
shorthand_name = '(%s)-1' % self._shorthand_name
1889-
self._inverted = Affine2D(inv(mtx), shorthand_name=shorthand_name)
1922+
self._inverted = _affine_factory(inv(mtx), self.input_dims,
1923+
shorthand_name=shorthand_name)
18901924
self._invalid = 0
18911925
return self._inverted
18921926

18931927

1894-
class Affine2D(Affine2DBase):
1928+
@_api.deprecated("3.9", alternative="AffineImmutable")
1929+
class Affine2DBase(AffineImmutable):
1930+
pass
1931+
1932+
1933+
def _affine_factory(mtx, dims, *args, **kwargs):
1934+
if dims == 2:
1935+
return Affine2D(mtx, *args, **kwargs)
1936+
else:
1937+
return NotImplemented
1938+
1939+
1940+
class Affine2D(AffineImmutable):
18951941
"""
18961942
A mutable 2D affine transformation.
18971943
"""
@@ -1906,10 +1952,9 @@ def __init__(self, matrix=None, **kwargs):
19061952
19071953
If *matrix* is None, initialize with the identity transform.
19081954
"""
1909-
super().__init__(**kwargs)
1955+
super().__init__(dims=2, **kwargs)
19101956
if matrix is None:
1911-
# A bit faster than np.identity(3).
1912-
matrix = IdentityTransform._mtx
1957+
matrix = np.identity(3)
19131958
self._mtx = matrix.copy()
19141959
self._invalid = 0
19151960

@@ -1967,18 +2012,20 @@ def set_matrix(self, mtx):
19672012
def set(self, other):
19682013
"""
19692014
Set this transformation from the frozen copy of another
1970-
`Affine2DBase` object.
2015+
2D `AffineImmutable` object.
19712016
"""
1972-
_api.check_isinstance(Affine2DBase, other=other)
2017+
_api.check_isinstance(AffineImmutable, other=other)
2018+
if (other.input_dims != 2):
2019+
raise TypeError("Mismatch between dimensions of AffineImmutable "
2020+
"and Affine2D")
19732021
self._mtx = other.get_matrix()
19742022
self.invalidate()
19752023

19762024
def clear(self):
19772025
"""
19782026
Reset the underlying matrix to the identity transform.
19792027
"""
1980-
# A bit faster than np.identity(3).
1981-
self._mtx = IdentityTransform._mtx.copy()
2028+
self._mtx = np.identity(3)
19822029
self.invalidate()
19832030
return self
19842031

@@ -2113,12 +2160,14 @@ def skew_deg(self, xShear, yShear):
21132160
return self.skew(math.radians(xShear), math.radians(yShear))
21142161

21152162

2116-
class IdentityTransform(Affine2DBase):
2163+
class IdentityTransform(AffineImmutable):
21172164
"""
21182165
A special class that does one thing, the identity transform, in a
21192166
fast way.
21202167
"""
2121-
_mtx = np.identity(3)
2168+
def __init__(self, *args, **kwargs):
2169+
super().__init__(self, *args, **kwargs)
2170+
self._mtx = np.identity(self.input_dims + 1)
21222171

21232172
def frozen(self):
21242173
# docstring inherited
@@ -2532,7 +2581,7 @@ def composite_transform_factory(a, b):
25322581
return CompositeGenericTransform(a, b)
25332582

25342583

2535-
class BboxTransform(Affine2DBase):
2584+
class BboxTransform(AffineImmutable):
25362585
"""
25372586
`BboxTransform` linearly transforms points from one `Bbox` to another.
25382587
"""
@@ -2546,7 +2595,7 @@ def __init__(self, boxin, boxout, **kwargs):
25462595
"""
25472596
_api.check_isinstance(BboxBase, boxin=boxin, boxout=boxout)
25482597

2549-
super().__init__(**kwargs)
2598+
super().__init__(dims=2, **kwargs)
25502599
self._boxin = boxin
25512600
self._boxout = boxout
25522601
self.set_children(boxin, boxout)
@@ -2574,7 +2623,7 @@ def get_matrix(self):
25742623
return self._mtx
25752624

25762625

2577-
class BboxTransformTo(Affine2DBase):
2626+
class BboxTransformTo(AffineImmutable):
25782627
"""
25792628
`BboxTransformTo` is a transformation that linearly transforms points from
25802629
the unit bounding box to a given `Bbox`.
@@ -2589,7 +2638,7 @@ def __init__(self, boxout, **kwargs):
25892638
"""
25902639
_api.check_isinstance(BboxBase, boxout=boxout)
25912640

2592-
super().__init__(**kwargs)
2641+
super().__init__(dims=2, **kwargs)
25932642
self._boxout = boxout
25942643
self.set_children(boxout)
25952644
self._mtx = None
@@ -2633,7 +2682,7 @@ def get_matrix(self):
26332682
return self._mtx
26342683

26352684

2636-
class BboxTransformFrom(Affine2DBase):
2685+
class BboxTransformFrom(AffineImmutable):
26372686
"""
26382687
`BboxTransformFrom` linearly transforms points from a given `Bbox` to the
26392688
unit bounding box.
@@ -2643,7 +2692,7 @@ class BboxTransformFrom(Affine2DBase):
26432692
def __init__(self, boxin, **kwargs):
26442693
_api.check_isinstance(BboxBase, boxin=boxin)
26452694

2646-
super().__init__(**kwargs)
2695+
super().__init__(dims=2, **kwargs)
26472696
self._boxin = boxin
26482697
self.set_children(boxin)
26492698
self._mtx = None
@@ -2668,13 +2717,13 @@ def get_matrix(self):
26682717
return self._mtx
26692718

26702719

2671-
class ScaledTranslation(Affine2DBase):
2720+
class ScaledTranslation(AffineImmutable):
26722721
"""
26732722
A transformation that translates by *xt* and *yt*, after *xt* and *yt*
26742723
have been transformed by *scale_trans*.
26752724
"""
26762725
def __init__(self, xt, yt, scale_trans, **kwargs):
2677-
super().__init__(**kwargs)
2726+
super().__init__(dims=2, **kwargs)
26782727
self._t = (xt, yt)
26792728
self._scale_trans = scale_trans
26802729
self.set_children(scale_trans)
@@ -2686,15 +2735,14 @@ def __init__(self, xt, yt, scale_trans, **kwargs):
26862735
def get_matrix(self):
26872736
# docstring inherited
26882737
if self._invalid:
2689-
# A bit faster than np.identity(3).
2690-
self._mtx = IdentityTransform._mtx.copy()
2738+
self._mtx = np.identity(3)
26912739
self._mtx[:2, 2] = self._scale_trans.transform(self._t)
26922740
self._invalid = 0
26932741
self._inverted = None
26942742
return self._mtx
26952743

26962744

2697-
class AffineDeltaTransform(Affine2DBase):
2745+
class AffineDeltaTransform(AffineImmutable):
26982746
r"""
26992747
A transform wrapper for transforming displacements between pairs of points.
27002748
@@ -2712,7 +2760,7 @@ class AffineDeltaTransform(Affine2DBase):
27122760
"""
27132761

27142762
def __init__(self, transform, **kwargs):
2715-
super().__init__(**kwargs)
2763+
super().__init__(dims=2, **kwargs)
27162764
self._base_transform = transform
27172765

27182766
__str__ = _make_str_method("_base_transform")

0 commit comments

Comments
 (0)