2323import jax .numpy as jnp
2424import numpy as np
2525import pytest
26+ from scipy .spatial .transform import RigidTransform as TF
27+ from scipy .spatial .transform import Rotation as R
2628
2729import splax
2830from splax ._project import _project_call
@@ -58,57 +60,14 @@ def _kw(H: int, W: int) -> _KW:
5860 }
5961
6062
61- def _euler_T (rx : float , ry : float , rz : float , t : tuple [float , float , float ]) -> np .ndarray :
62- """4x4 rigid transform from XYZ Euler angles in radians plus a translation."""
63-
64- def rot (axis : int , a : float ) -> np .ndarray :
65- ca , sa = np .cos (a ), np .sin (a )
66- m = np .eye (3 , dtype = np .float64 )
67- i , j = [(1 , 2 ), (0 , 2 ), (0 , 1 )][axis ]
68- m [i , i ] = ca
69- m [j , j ] = ca
70- m [i , j ] = - sa if axis != 1 else sa
71- m [j , i ] = sa if axis != 1 else - sa
72- return m
73-
74- R = rot (2 , rz ) @ rot (1 , ry ) @ rot (0 , rx )
75- T = np .eye (4 , dtype = np .float32 )
76- T [:3 , :3 ] = R .astype (np .float32 )
77- T [:3 , 3 ] = t
78- return T
79-
80-
81- def _rotmat_to_quat_wxyz (R : np .ndarray ) -> np .ndarray :
82- w = np .sqrt (max (0.0 , 1.0 + R [0 , 0 ] + R [1 , 1 ] + R [2 , 2 ])) / 2.0
83- x = (R [2 , 1 ] - R [1 , 2 ]) / (4.0 * w )
84- y = (R [0 , 2 ] - R [2 , 0 ]) / (4.0 * w )
85- z = (R [1 , 0 ] - R [0 , 1 ]) / (4.0 * w )
86- return np .array ([w , x , y , z ], np .float32 )
87-
88-
89- def _qmul_wxyz (a : jax .Array , b : jax .Array ) -> jax .Array :
90- w1 , x1 , y1 , z1 = a [..., 0 ], a [..., 1 ], a [..., 2 ], a [..., 3 ]
91- w2 , x2 , y2 , z2 = b [..., 0 ], b [..., 1 ], b [..., 2 ], b [..., 3 ]
92- return jnp .stack (
93- [
94- w1 * w2 - x1 * x2 - y1 * y2 - z1 * z2 ,
95- w1 * x2 + x1 * w2 + y1 * z2 - z1 * y2 ,
96- w1 * y2 - x1 * z2 + y1 * w2 + z1 * x2 ,
97- w1 * z2 + x1 * y2 - y1 * x2 + z1 * w2 ,
98- ],
99- axis = - 1 ,
100- )
101-
102-
10363def _manual_move (
10464 means : jax .Array , quats : jax .Array , T : np .ndarray , start : int , stop : int
10565) -> tuple [jax .Array , jax .Array ]:
10666 """Reference transform of a slice, applied to the splat arrays in JAX."""
107- R = jnp .asarray (T [:3 , :3 ])
108- t = jnp .asarray (T [:3 , 3 ])
109- q_obj = jnp .asarray (_rotmat_to_quat_wxyz (T [:3 , :3 ]))
110- m2 = means .at [start :stop ].set (means [start :stop ] @ R .T + t )
111- q2 = quats .at [start :stop ].set (_qmul_wxyz (q_obj [None ], quats [start :stop ]))
67+ transform = TF .from_matrix (jnp .asarray (T ))
68+ rotated = transform .rotation * R .from_quat (quats [start :stop ], scalar_first = True )
69+ m2 = means .at [start :stop ].set (transform .apply (means [start :stop ]))
70+ q2 = quats .at [start :stop ].set (rotated .as_quat (scalar_first = True ))
11271 return m2 , q2
11372
11473
@@ -143,7 +102,8 @@ def test_projection_matches_manual_transform() -> None:
143102 n = 4000
144103 means , scales , quats , _colors , opac = _scene (n , seed = 2 )
145104 kw = _kw (128 , 128 )
146- T = _euler_T (0.26 , - 0.17 , 0.52 , (0.3 , - 0.2 , 0.1 ))
105+ rot = R .from_euler ("xyz" , [0.26 , - 0.17 , 0.52 ])
106+ T = TF .from_components ((0.3 , - 0.2 , 0.1 ), rot ).as_matrix ().astype (np .float32 )
147107 args = (n , kw ["img_shape" ], kw ["f" ], kw ["c" ], 1.0 , 0.01 )
148108 tf_ids = jnp .full ((n ,), - 1 , jnp .int32 ).at [:1000 ].set (0 )
149109 a = _project_call (
@@ -167,7 +127,8 @@ def test_render_matches_manual_transform() -> None:
167127 n = 4000
168128 means , scales , quats , colors , opac = _scene (n , seed = 3 )
169129 kw = _kw (128 , 128 )
170- T = _euler_T (0.26 , - 0.17 , 0.52 , (0.3 , - 0.2 , 0.1 ))
130+ rot = R .from_euler ("xyz" , [0.26 , - 0.17 , 0.52 ])
131+ T = TF .from_components ((0.3 , - 0.2 , 0.1 ), rot ).as_matrix ().astype (np .float32 )
171132 moved = np .asarray (
172133 splax .inference .render (
173134 means ,
@@ -195,7 +156,9 @@ def test_vmap_over_transforms_matches_sequential() -> None:
195156 n , B = 4000 , 3
196157 means , scales , quats , colors , opac = _scene (n , seed = 4 )
197158 kw = _kw (96 , 96 )
198- Ts = np .stack ([_euler_T (0.0 , 0.0 , 0.3 * i , (0.05 * i , - 0.03 * i , 0.0 )) for i in range (B )])
159+ angles = np .array ([[0.0 , 0.0 , 0.3 * i ] for i in range (B )])
160+ trans = np .array ([[0.05 * i , - 0.03 * i , 0.0 ] for i in range (B )])
161+ Ts = TF .from_components (trans , R .from_euler ("xyz" , angles )).as_matrix ().astype (np .float32 )
199162 tfs = jnp .asarray (Ts )[:, None ] # (B, 1, 4, 4)
200163
201164 def render_tf (tf : jax .Array ) -> jax .Array :
@@ -222,8 +185,10 @@ def test_two_objects_move_independently() -> None:
222185 n = 4000
223186 means , scales , quats , colors , opac = _scene (n , seed = 5 )
224187 kw = _kw (128 , 128 )
225- Ta = _euler_T (0.0 , 0.0 , 0.4 , (0.2 , 0.0 , 0.0 ))
226- Tb = _euler_T (0.3 , 0.0 , 0.0 , (- 0.1 , 0.15 , 0.0 ))
188+ rot_a = R .from_euler ("xyz" , [0.0 , 0.0 , 0.4 ])
189+ Ta = TF .from_components ((0.2 , 0.0 , 0.0 ), rot_a ).as_matrix ().astype (np .float32 )
190+ rot_b = R .from_euler ("xyz" , [0.3 , 0.0 , 0.0 ])
191+ Tb = TF .from_components ((- 0.1 , 0.15 , 0.0 ), rot_b ).as_matrix ().astype (np .float32 )
227192 slices = ((0 , 800 ), (2000 , 2600 ))
228193 both = np .asarray (
229194 splax .inference .render (
0 commit comments