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