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