@@ -2216,182 +2216,208 @@ def inverted(self):
2216
2216
2217
2217
2218
2218
class _BlendedMixin :
2219
- """Common methods for `BlendedGenericTransform` and `BlendedAffine2D `."""
2219
+ """Common methods for `BlendedGenericTransform` and `BlendedAffine `."""
2220
2220
2221
2221
def __eq__ (self ,other ):
2222
- if isinstance (other , (BlendedAffine2D ,BlendedGenericTransform )):
2223
- return (self ._x == other ._x )and (self ._y == other ._y )
2224
- elif self ._x == self ._y :
2225
- return self ._x == other
2222
+
2223
+ num_transforms = len (self ._transforms )
2224
+
2225
+ if (isinstance (other , (BlendedGenericTransform ,BlendedAffine ))
2226
+ and num_transforms == len (other ._transforms )):
2227
+ return all (self ._transforms [i ]== other ._transforms [i ]
2228
+ for i in range (num_transforms ))
2226
2229
else :
2227
2230
return NotImplemented
2228
2231
2229
2232
def contains_branch_seperately (self ,transform ):
2230
- return (self ._x .contains_branch (transform ),
2231
- self ._y .contains_branch (transform ))
2233
+ return tuple (branch .contains_branch (transform )for branch in self ._transforms )
2232
2234
2233
- __str__ = _make_str_method ("_x" ,"_y" )
2235
+ def __str__ (self ):
2236
+ indent = functools .partial (textwrap .indent ,prefix = " " * 4 )
2237
+ return (
2238
+ type (self ).__name__ + "("
2239
+ + "," .join ([* (indent ("\n " + transform .__str__ ())
2240
+ for transform in self ._transforms )])
2241
+ + ")" )
2234
2242
2235
2243
2236
2244
class BlendedGenericTransform (_BlendedMixin ,Transform ):
2237
2245
"""
2238
- A "blended" transform uses one transform for the *x*-direction, and
2239
- another transform for the *y*-direction.
2246
+ A "blended" transform uses one transform for each direction
2240
2247
2241
- This "generic" version can handle any given childtransform in the
2242
- *x*- and *y*-directions .
2248
+ This "generic" version can handle anynumber of given childtransforms, each
2249
+ handling a different axis .
2243
2250
"""
2244
- input_dims = 2
2245
- output_dims = 2
2246
2251
is_separable = True
2247
2252
pass_through = True
2248
2253
2249
- def __init__ (self ,x_transform , y_transform ,** kwargs ):
2254
+ def __init__ (self ,* args ,** kwargs ):
2250
2255
"""
2251
- Create a new "blended" transform using *x_transform* to transform the
2252
- *x*-axis and *y_transform* to transform the *y*-axis.
2256
+ Create a new "blended" transform, with the first argument providing
2257
+ a transform for the *x*-axis, the second argument providing a transform
2258
+ for the *y*-axis, etc.
2253
2259
2254
2260
You will generally not call this constructor directly but use the
2255
2261
`blended_transform_factory` function instead, which can determine
2256
2262
automatically which kind of blended transform to create.
2257
2263
"""
2264
+ self .input_dims = self .output_dims = len (args )
2265
+
2266
+ for i in range (self .input_dims ):
2267
+ transform = args [i ]
2268
+ if transform .input_dims > 1 and transform .input_dims <= i :
2269
+ raise TypeError ("Invalid transform provided to"
2270
+ "`BlendedGenericTransform`" )
2271
+
2258
2272
Transform .__init__ (self ,** kwargs )
2259
- self ._x = x_transform
2260
- self ._y = y_transform
2261
- self .set_children (x_transform ,y_transform )
2273
+ self .set_children (* args )
2274
+ self ._transforms = args
2262
2275
self ._affine = None
2263
2276
2264
2277
@property
2265
2278
def depth (self ):
2266
- return max (self . _x . depth , self ._y . depth )
2279
+ return max (transform . depth for transform in self ._transforms )
2267
2280
2268
2281
def contains_branch (self ,other ):
2269
2282
# A blended transform cannot possibly contain a branch from two
2270
2283
# different transforms.
2271
2284
return False
2272
2285
2273
- is_affine = property (lambda self :self ._x .is_affine and self ._y .is_affine )
2274
- has_inverse = property (
2275
- lambda self :self ._x .has_inverse and self ._y .has_inverse )
2286
+ is_affine = property (lambda self :all (transform .is_affine
2287
+ for transform in self ._transforms ))
2288
+ has_inverse = property (lambda self :all (transform .has_inverse
2289
+ for transform in self ._transforms ))
2276
2290
2277
2291
def frozen (self ):
2278
2292
# docstring inherited
2279
- return blended_transform_factory (self ._x .frozen (),self ._y .frozen ())
2293
+ return blended_transform_factory (* (transform .frozen ()
2294
+ for transform in self ._transforms ))
2280
2295
2281
2296
@_api .rename_parameter ("3.8" ,"points" ,"values" )
2282
2297
def transform_non_affine (self ,values ):
2283
2298
# docstring inherited
2284
- if self ._x . is_affine and self . _y . is_affine :
2299
+ if self .is_affine :
2285
2300
return values
2286
- x = self ._x
2287
- y = self ._y
2288
2301
2289
- if x == y and x .input_dims == 2 :
2290
- return x .transform_non_affine (values )
2302
+ if all (transform == self ._transforms [0 ]
2303
+ for transform in self ._transforms )and self .input_dims >= 2 :
2304
+ return self ._transforms [0 ].transform_non_affine (values )
2291
2305
2292
- if x .input_dims == 2 :
2293
- x_points = x .transform_non_affine (values )[:,0 :1 ]
2294
- else :
2295
- x_points = x .transform_non_affine (values [:,0 ])
2296
- x_points = x_points .reshape ((len (x_points ),1 ))
2306
+ all_points = []
2307
+ masked = False
2297
2308
2298
- if y .input_dims == 2 :
2299
- y_points = y .transform_non_affine (values )[:,1 :]
2300
- else :
2301
- y_points = y .transform_non_affine (values [:,1 ])
2302
- y_points = y_points .reshape ((len (y_points ),1 ))
2309
+ for dim in range (self .input_dims ):
2310
+ transform = self ._transforms [dim ]
2311
+ if transform .input_dims == 1 :
2312
+ points = transform .transform_non_affine (values [:,dim ])
2313
+ points = points .reshape ((len (points ),1 ))
2314
+ else :
2315
+ points = transform .transform_non_affine (values )[:,dim :dim + 1 ]
2316
+
2317
+ masked = masked or isinstance (points ,np .ma .MaskedArray )
2318
+ all_points .append (points )
2303
2319
2304
- if (isinstance (x_points ,np .ma .MaskedArray )or
2305
- isinstance (y_points ,np .ma .MaskedArray )):
2306
- return np .ma .concatenate ((x_points ,y_points ),1 )
2320
+ if masked :
2321
+ return np .ma .concatenate (tuple (all_points ),1 )
2307
2322
else :
2308
- return np .concatenate (( x_points , y_points ),1 )
2323
+ return np .concatenate (tuple ( all_points ),1 )
2309
2324
2310
2325
def inverted (self ):
2311
2326
# docstring inherited
2312
- return BlendedGenericTransform (self ._x .inverted (),self ._y .inverted ())
2327
+ return BlendedGenericTransform (* (transform .inverted ()
2328
+ for transform in self ._transforms ))
2313
2329
2314
2330
def get_affine (self ):
2315
2331
# docstring inherited
2316
2332
if self ._invalid or self ._affine is None :
2317
- if self . _x == self ._y :
2318
- self ._affine = self ._x .get_affine ()
2333
+ if all ( transform == self ._transforms [ 0 ] for transform in self . _transforms ) :
2334
+ self ._affine = self ._transforms [ 0 ] .get_affine ()
2319
2335
else :
2320
- x_mtx = self ._x .get_affine ().get_matrix ()
2321
- y_mtx = self ._y .get_affine ().get_matrix ()
2322
- # We already know the transforms are separable, so we can skip
2323
- # setting b and c to zero.
2324
- mtx = np .array ([x_mtx [0 ],y_mtx [1 ], [0.0 ,0.0 ,1.0 ]])
2325
- self ._affine = Affine2D (mtx )
2336
+ mtx = np .identity (self .input_dims + 1 )
2337
+ for i in range (self .input_dims ):
2338
+ transform = self ._transforms [i ]
2339
+ if transform .output_dims > 1 :
2340
+ mtx [i ]= transform .get_affine ().get_matrix ()[i ]
2341
+
2342
+ self ._affine = _affine_factory (mtx ,dims = self .input_dims )
2326
2343
self ._invalid = 0
2327
2344
return self ._affine
2328
2345
2329
2346
2330
- class BlendedAffine2D (_BlendedMixin ,Affine2DBase ):
2347
+ class BlendedAffine (_BlendedMixin ,AffineImmutable ):
2331
2348
"""
2332
2349
A "blended" transform uses one transform for the *x*-direction, and
2333
2350
another transform for the *y*-direction.
2334
2351
2335
2352
This version is an optimization for the case where both child
2336
- transforms are of type `Affine2DBase `.
2353
+ transforms are of type `AffineImmutable `.
2337
2354
"""
2338
2355
2339
2356
is_separable = True
2340
2357
2341
- def __init__ (self ,x_transform , y_transform ,** kwargs ):
2358
+ def __init__ (self ,* args ,** kwargs ):
2342
2359
"""
2343
- Create a new "blended" transform using *x_transform* to transform the
2344
- *x*-axis and *y_transform* to transform the *y*-axis.
2360
+ Create a new "blended" transform, with the first argument providing
2361
+ a transform for the *x*-axis, the second argument providing a transform
2362
+ for the *y*-axis, etc.
2345
2363
2346
- Both *x_transform* and *y_transform* must be 2D affine transforms.
2364
+ All provided transforms must be affine transforms.
2347
2365
2348
2366
You will generally not call this constructor directly but use the
2349
2367
`blended_transform_factory` function instead, which can determine
2350
2368
automatically which kind of blended transform to create.
2351
2369
"""
2352
- is_affine = x_transform .is_affine and y_transform .is_affine
2353
- is_separable = x_transform .is_separable and y_transform .is_separable
2354
- is_correct = is_affine and is_separable
2355
- if not is_correct :
2356
- raise ValueError ("Both *x_transform* and *y_transform* must be 2D "
2357
- "affine transforms" )
2358
-
2359
2370
Transform .__init__ (self ,** kwargs )
2360
- self ._x = x_transform
2361
- self ._y = y_transform
2362
- self .set_children (x_transform ,y_transform )
2371
+ AffineImmutable .__init__ (self ,** kwargs )
2372
+
2373
+ if not all (transform .is_affine and transform .is_separable
2374
+ for transform in args ):
2375
+ raise ValueError ("Given transforms must be affine" )
2376
+
2377
+ for i in range (self .input_dims ):
2378
+ transform = args [i ]
2379
+ if transform .input_dims > 1 and transform .input_dims <= i :
2380
+ raise TypeError ("Invalid transform provided to"
2381
+ "`BlendedGenericTransform`" )
2382
+
2383
+ self ._transforms = args
2384
+ self .set_children (* args )
2363
2385
2364
- Affine2DBase .__init__ (self )
2365
2386
self ._mtx = None
2366
2387
2367
2388
def get_matrix (self ):
2368
2389
# docstring inherited
2369
2390
if self ._invalid :
2370
- if self . _x == self ._y :
2371
- self ._mtx = self ._x .get_matrix ()
2391
+ if all ( transform == self ._transforms [ 0 ] for transform in self . _transforms ) :
2392
+ self ._mtx = self ._transforms [ 0 ] .get_matrix ()
2372
2393
else :
2373
- x_mtx = self ._x .get_matrix ()
2374
- y_mtx = self ._y .get_matrix ()
2375
2394
# We already know the transforms are separable, so we can skip
2376
- # setting b and c to zero.
2377
- self ._mtx = np .array ([x_mtx [0 ],y_mtx [1 ], [0.0 ,0.0 ,1.0 ]])
2395
+ # setting non-diagonal values to zero.
2396
+ self ._mtx = np .array (
2397
+ [self ._transforms [i ].get_affine ().get_matrix ()[i ]
2398
+ for i in range (self .input_dims )]+
2399
+ [[0.0 ]* self .input_dims + [1.0 ]])
2378
2400
self ._inverted = None
2379
2401
self ._invalid = 0
2380
2402
return self ._mtx
2381
2403
2382
2404
2383
- def blended_transform_factory (x_transform ,y_transform ):
2405
+ @_api .deprecated ("3.9" ,alternative = "BlendedAffine" )
2406
+ class BlendedAffine2D (BlendedAffine ):
2407
+ pass
2408
+
2409
+
2410
+ def blended_transform_factory (* args ):
2384
2411
"""
2385
2412
Create a new "blended" transform using *x_transform* to transform
2386
2413
the *x*-axis and *y_transform* to transform the *y*-axis.
2387
2414
2388
2415
A faster version of the blended transform is returned for the case
2389
2416
where both child transforms are affine.
2390
2417
"""
2391
- if (isinstance (x_transform ,Affine2DBase )and
2392
- isinstance (y_transform ,Affine2DBase )):
2393
- return BlendedAffine2D (x_transform ,y_transform )
2394
- return BlendedGenericTransform (x_transform ,y_transform )
2418
+ if all (isinstance (transform ,AffineImmutable )for transform in args ):
2419
+ return BlendedAffine (* args )
2420
+ return BlendedGenericTransform (* args )
2395
2421
2396
2422
2397
2423
class CompositeGenericTransform (Transform ):