Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitcaaedc2

Browse files
committed
Implement Affine3D
1 parent427bbe5 commitcaaedc2

File tree

2 files changed

+324
-4
lines changed

2 files changed

+324
-4
lines changed

‎lib/matplotlib/transforms.py

Lines changed: 301 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1289,7 +1289,8 @@ class Transform(TransformNode):
12891289
actually perform a transformation.
12901290
12911291
All non-affine transformations should be subclasses of this class.
1292-
New affine transformations should be subclasses of `Affine2D`.
1292+
New affine transformations should be subclasses of `Affine2D` or
1293+
`Affine3D`.
12931294
12941295
Subclasses of this class should override the following members (at
12951296
minimum):
@@ -1510,7 +1511,7 @@ def transform(self, values):
15101511
returnres[0,0]
15111512
ifndim==1:
15121513
returnres.reshape(-1)
1513-
elifndim==2:
1514+
elifndim==2orndim==3:
15141515
returnres
15151516
raiseValueError(
15161517
"Input values must have shape (N, {dims}) or ({dims},)"
@@ -1839,8 +1840,8 @@ class AffineImmutable(AffineBase):
18391840
b d f
18401841
0 0 1
18411842
1842-
This class provides the read-only interface. For a mutable 2D
1843-
affine transformation, use `Affine2D`.
1843+
This class provides the read-only interface. For a mutable
1844+
affine transformation, use `Affine2D` or `Affine3D`.
18441845
18451846
Subclasses of this class will generally only need to override a
18461847
constructor and `~.Transform.get_matrix` that generates a custom matrix
@@ -1942,6 +1943,8 @@ class Affine2DBase(AffineImmutable):
19421943
def_affine_factory(mtx,dims,*args,**kwargs):
19431944
ifdims==2:
19441945
returnAffine2D(mtx,*args,**kwargs)
1946+
elifdims==3:
1947+
returnAffine3D(mtx,*args,**kwargs)
19451948
else:
19461949
returnNotImplemented
19471950

@@ -2169,6 +2172,298 @@ def skew_deg(self, xShear, yShear):
21692172
returnself.skew(math.radians(xShear),math.radians(yShear))
21702173

21712174

2175+
classAffine3D(AffineImmutable):
2176+
"""
2177+
A mutable 3D affine transformation.
2178+
"""
2179+
2180+
def__init__(self,matrix=None,**kwargs):
2181+
"""
2182+
Initialize an Affine transform from a 4x4 numpy float array::
2183+
2184+
a d g j
2185+
b e h k
2186+
c f i l
2187+
0 0 0 1
2188+
2189+
If *matrix* is None, initialize with the identity transform.
2190+
"""
2191+
super().__init__(dims=3,**kwargs)
2192+
ifmatrixisNone:
2193+
matrix=np.identity(4)
2194+
self._mtx=matrix.copy()
2195+
self._invalid=0
2196+
2197+
_base_str=_make_str_method("_mtx")
2198+
2199+
def__str__(self):
2200+
return (self._base_str()
2201+
if (self._mtx!=np.diag(np.diag(self._mtx))).any()
2202+
elsef"Affine3D().scale("
2203+
f"{self._mtx[0,0]}, "
2204+
f"{self._mtx[1,1]}, "
2205+
f"{self._mtx[2,2]})"
2206+
ifself._mtx[0,0]!=self._mtx[1,1]or
2207+
self._mtx[0,0]!=self._mtx[2,2]
2208+
elsef"Affine3D().scale({self._mtx[0,0]})")
2209+
2210+
@staticmethod
2211+
deffrom_values(a,b,c,d,e,f,g,h,i,j,k,l):
2212+
"""
2213+
Create a new Affine2D instance from the given values::
2214+
2215+
a d g j
2216+
b e h k
2217+
c f i l
2218+
0 0 0 1
2219+
2220+
.
2221+
"""
2222+
returnAffine3D(np.array([
2223+
a,d,g,j,
2224+
b,e,h,k,
2225+
c,f,i,l,
2226+
0.0,0.0,0.0,1.0
2227+
],float).reshape((4,4)))
2228+
2229+
defget_matrix(self):
2230+
"""
2231+
Get the underlying transformation matrix as a 4x4 array::
2232+
2233+
a d g j
2234+
b e h k
2235+
c f i l
2236+
0 0 0 1
2237+
2238+
.
2239+
"""
2240+
ifself._invalid:
2241+
self._inverted=None
2242+
self._invalid=0
2243+
returnself._mtx
2244+
2245+
defset_matrix(self,mtx):
2246+
"""
2247+
Set the underlying transformation matrix from a 4x4 array::
2248+
2249+
a d g j
2250+
b e h k
2251+
c f i l
2252+
0 0 0 1
2253+
2254+
.
2255+
"""
2256+
self._mtx=mtx
2257+
self.invalidate()
2258+
2259+
defset(self,other):
2260+
"""
2261+
Set this transformation from the frozen copy of another
2262+
`AffineImmutable` object with input and output dimension of 3.
2263+
"""
2264+
_api.check_isinstance(AffineImmutable,other=other)
2265+
if (other.input_dims!=3):
2266+
raiseTypeError("Mismatch between dimensions of AffineImmutable"
2267+
"and Affine3D")
2268+
self._mtx=other.get_matrix()
2269+
self.invalidate()
2270+
2271+
defclear(self):
2272+
"""
2273+
Reset the underlying matrix to the identity transform.
2274+
"""
2275+
self._mtx=np.identity(4)
2276+
self.invalidate()
2277+
returnself
2278+
2279+
defrotate(self,theta,dim=0):
2280+
"""
2281+
Add a rotation (in radians) to this transform in place, along
2282+
the dimension denoted by *dim*.
2283+
2284+
Returns *self*, so this method can easily be chained with more
2285+
calls to :meth:`rotate`, :meth:`rotate_deg`, :meth:`translate`
2286+
and :meth:`scale`.
2287+
"""
2288+
ifdim==0:
2289+
returnself.rotate_around_vector([1,0,0],theta)
2290+
elifdim==1:
2291+
returnself.rotate_around_vector([0,1,0],theta)
2292+
elifdim==2:
2293+
returnself.rotate_around_vector([0,0,1],theta)
2294+
2295+
self.invalidate()
2296+
returnself
2297+
2298+
defrotate_deg(self,degrees,dim=0):
2299+
"""
2300+
Add a rotation (in degrees) to this transform in place, along
2301+
the dimension denoted by *dim*.
2302+
2303+
Returns *self*, so this method can easily be chained with more
2304+
calls to :meth:`rotate`, :meth:`rotate_deg`, :meth:`translate`
2305+
and :meth:`scale`.
2306+
"""
2307+
returnself.rotate(math.radians(degrees),dim)
2308+
2309+
defrotate_around(self,x,y,z,theta,dim=0):
2310+
"""
2311+
Add a rotation (in radians) around the point (x, y, z) in place,
2312+
along the dimension denoted by *dim*.
2313+
2314+
Returns *self*, so this method can easily be chained with more
2315+
calls to :meth:`rotate`, :meth:`rotate_deg`, :meth:`translate`
2316+
and :meth:`scale`.
2317+
"""
2318+
returnself.translate(-x,-y,-z).rotate(theta,dim).translate(x,y,z)
2319+
2320+
defrotate_deg_around(self,x,y,z,degrees,dim=0):
2321+
"""
2322+
Add a rotation (in degrees) around the point (x, y, z) in place,
2323+
along the dimension denoted by *dim*.
2324+
2325+
Returns *self*, so this method can easily be chained with more
2326+
calls to :meth:`rotate`, :meth:`rotate_deg`, :meth:`translate`
2327+
and :meth:`scale`.
2328+
"""
2329+
# Cast to float to avoid wraparound issues with uint8's
2330+
x,y=float(x),float(y)
2331+
returnself.translate(-x,-y,-z).rotate_deg(degrees,dim).translate(x,y,z)
2332+
2333+
defrotate_around_vector(self,vector,theta):
2334+
"""
2335+
Add a rotation (in radians) around the vector (vx, vy, vz) in place.
2336+
2337+
Returns *self*, so this method can easily be chained with more
2338+
calls to :meth:`rotate`, :meth:`rotate_deg`, :meth:`translate`
2339+
and :meth:`scale`.
2340+
"""
2341+
vx,vy,vz=vector/np.linalg.norm(vector)
2342+
s=np.sin(theta)
2343+
c=np.cos(theta)
2344+
t=2*np.sin(theta/2)**2# more numerically stable than t = 1-c
2345+
rot= [[t*vx*vx+c,t*vx*vy-vz*s,t*vx*vz+vy*s,0],
2346+
[t*vy*vx+vz*s,t*vy*vy+c,t*vy*vz-vx*s,0],
2347+
[t*vz*vx-vy*s,t*vz*vy+vx*s,t*vz*vz+c,0],
2348+
[0,0,0,1]]
2349+
np.matmul(rot,self._mtx,out=self._mtx)
2350+
returnself
2351+
2352+
defrotate_around_vector_deg(self,vector,degrees):
2353+
"""
2354+
Add a rotation (in radians) around the vector (vx, vy, vz) in place.
2355+
2356+
Returns *self*, so this method can easily be chained with more
2357+
calls to :meth:`rotate`, :meth:`rotate_deg`, :meth:`translate`
2358+
and :meth:`scale`.
2359+
"""
2360+
returnself.roate_around_vector(vector,math.radians(degrees))
2361+
2362+
deftranslate(self,tx,ty,tz):
2363+
"""
2364+
Add a translation in place.
2365+
2366+
Returns *self*, so this method can easily be chained with more
2367+
calls to :meth:`rotate`, :meth:`rotate_deg`, :meth:`translate`
2368+
and :meth:`scale`.
2369+
"""
2370+
self._mtx[0,3]+=tx
2371+
self._mtx[1,3]+=ty
2372+
self._mtx[2,3]+=tz
2373+
self.invalidate()
2374+
returnself
2375+
2376+
defscale(self,sx,sy=None,sz=None):
2377+
"""
2378+
Add a scale in place.
2379+
2380+
If a scale is not provided in the *y* or *z* directions, *sx*
2381+
will be applied for that direction.
2382+
2383+
Returns *self*, so this method can easily be chained with more
2384+
calls to :meth:`rotate`, :meth:`rotate_deg`, :meth:`translate`
2385+
and :meth:`scale`.
2386+
"""
2387+
ifsyisNone:
2388+
sy=sx
2389+
2390+
ifszisNone:
2391+
sz=sx
2392+
# explicit element-wise scaling is fastest
2393+
self._mtx[0,0]*=sx
2394+
self._mtx[0,1]*=sx
2395+
self._mtx[0,2]*=sx
2396+
self._mtx[0,3]*=sx
2397+
self._mtx[1,0]*=sy
2398+
self._mtx[1,1]*=sy
2399+
self._mtx[1,2]*=sy
2400+
self._mtx[1,3]*=sy
2401+
self._mtx[2,0]*=sz
2402+
self._mtx[2,1]*=sz
2403+
self._mtx[2,2]*=sz
2404+
self._mtx[2,3]*=sz
2405+
2406+
self.invalidate()
2407+
returnself
2408+
2409+
defskew(self,xyShear,xzShear,yxShear,yzShear,zxShear,zyShear):
2410+
"""
2411+
Add a skew in place along for each plane in the 3rd dimension.
2412+
2413+
For example *zxShear* is the shear angle along the *zx* plane,
2414+
in radians.
2415+
2416+
Returns *self*, so this method can easily be chained with more
2417+
calls to :meth:`rotate`, :meth:`rotate_deg`, :meth:`translate`
2418+
and :meth:`scale`.
2419+
"""
2420+
rxy=math.tan(xyShear)
2421+
rxz=math.tan(xzShear)
2422+
ryx=math.tan(yxShear)
2423+
ryz=math.tan(yzShear)
2424+
rzx=math.tan(zxShear)
2425+
rzy=math.tan(zyShear)
2426+
mtx=self._mtx
2427+
# Operating and assigning one scalar at a time is much faster.
2428+
(xx,xy,xz,x0), (yx,yy,yz,y0), (zx,zy,zz,z0),_=mtx.tolist()
2429+
# mtx = [[1 rx 0], [ry 1 0], [0 0 1]] * mtx
2430+
2431+
mtx[0,0]+= (rxy*yx)+ (rxz*zx)
2432+
mtx[0,1]+= (rxy*yy)+ (rxz*zy)
2433+
mtx[0,2]+= (rxy*yz)+ (rxz*zz)
2434+
mtx[0,3]+= (rxy*y0)+ (rxz*z0)
2435+
mtx[1,0]= (ryx*xx)+yx+ (ryz*zx)
2436+
mtx[1,1]= (ryx*xy)+yy+ (ryz*zy)
2437+
mtx[1,2]= (ryx*xz)+yz+ (ryz*zz)
2438+
mtx[1,3]= (ryx*x0)+y0+ (ryz*z0)
2439+
mtx[2,0]= (rzx*xx)+ (rzy*yx)+zx
2440+
mtx[2,1]= (rzx*xy)+ (rzy*yy)+zy
2441+
mtx[2,2]= (rzx*xz)+ (rzy*yz)+zz
2442+
mtx[2,3]= (rzx*x0)+ (rzy*y0)+z0
2443+
2444+
self.invalidate()
2445+
returnself
2446+
2447+
defskew_deg(self,xyShear,xzShear,yxShear,yzShear,zxShear,zyShear):
2448+
"""
2449+
Add a skew in place along for each plane in the 3rd dimension.
2450+
2451+
For example *zxShear* is the shear angle along the *zx* plane,
2452+
in radians.
2453+
2454+
Returns *self*, so this method can easily be chained with more
2455+
calls to :meth:`rotate`, :meth:`rotate_deg`, :meth:`translate`
2456+
and :meth:`scale`.
2457+
"""
2458+
returnself.skew(
2459+
math.radians(xyShear),
2460+
math.radians(xzShear),
2461+
math.radians(yxShear),
2462+
math.radians(yzShear),
2463+
math.radians(zxShear),
2464+
math.radians(zyShear))
2465+
2466+
21722467
classIdentityTransform(AffineImmutable):
21732468
"""
21742469
A special class that does one thing, the identity transform, in a
@@ -2615,6 +2910,8 @@ def composite_transform_factory(a, b):
26152910
returna
26162911
elifisinstance(a,Affine2D)andisinstance(b,Affine2D):
26172912
returnCompositeAffine(a,b)
2913+
elifisinstance(a,Affine3D)andisinstance(b,Affine3D):
2914+
returnCompositeAffine(a,b)
26182915
returnCompositeGenericTransform(a,b)
26192916

26202917

‎lib/matplotlib/transforms.pyi

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,29 @@ class Affine2D(AffineImmutable):
251251
defskew(self,xShear:float,yShear:float)->Affine2D: ...
252252
defskew_deg(self,xShear:float,yShear:float)->Affine2D: ...
253253

254+
classAffine3D(AffineImmutable):
255+
def__init__(self,matrix:ArrayLike|None= ...,**kwargs)->None: ...
256+
@staticmethod
257+
deffrom_values(
258+
a:float,b:float,c:float,d:float,e:float,f:float,g:float,
259+
h:float,i:float,j:float,k:float,l:float
260+
)->Affine3D: ...
261+
defset_matrix(self,mtx:ArrayLike)->None: ...
262+
defclear(self)->Affine3D: ...
263+
defrotate(self,theta:float,dim:int= ...)->Affine3D: ...
264+
defrotate_deg(self,degrees:float,dim:int= ...)->Affine3D: ...
265+
defrotate_around(self,x:float,y:float,z:float,theta:float,dim:int= ...
266+
)->Affine3D: ...
267+
defrotate_deg_around(self,x:float,y:float,z:float,degrees:float,dim:int= ...
268+
)->Affine3D: ...
269+
deftranslate(self,tx:float,ty:float,tz:float)->Affine3D: ...
270+
defscale(self,sx:float,sy:float|None= ...,sz:float|None= ...
271+
)->Affine3D: ...
272+
defskew(self,xyShear:float,xzShear:float,yxShear:float,yzShear:float,
273+
zxShear:float,zyShear:float)->Affine3D: ...
274+
defskew_deg(self,xyShear:float,xzShear:float,yxShear:float,yzShear:float,
275+
zxShear:float,zyShear:float)->Affine3D: ...
276+
254277
classIdentityTransform(AffineImmutable): ...
255278

256279
class_BlendedMixin:

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp