H-Liu1997 commited on
Commit
04afe87
·
verified ·
1 Parent(s): 4426a08

Upload visualization/MEI138/geometry.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. visualization/MEI138/geometry.py +303 -0
visualization/MEI138/geometry.py ADDED
@@ -0,0 +1,303 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Rotation conversion utilities for MEI representation.
2
+
3
+ All functions support arbitrary batch dimensions (..., ).
4
+ Coordinate convention: Y-up, right-handed (X-right, Y-up, Z-forward).
5
+
6
+ Core rotation functions ported from HY-Motion (hymotion/utils/geometry.py),
7
+ torch -> numpy.
8
+ """
9
+
10
+ import numpy as np
11
+
12
+
13
+ # ============================================================
14
+ # Helpers (from HY-Motion)
15
+ # ============================================================
16
+
17
+ def _sqrt_positive_part(x: np.ndarray) -> np.ndarray:
18
+ """Returns np.sqrt(np.maximum(0, x))."""
19
+ ret = np.zeros_like(x)
20
+ positive_mask = x > 0
21
+ ret[positive_mask] = np.sqrt(x[positive_mask])
22
+ return ret
23
+
24
+
25
+ def standardize_quaternion(quaternions: np.ndarray) -> np.ndarray:
26
+ """
27
+ Convert a unit quaternion to a standard form: one in which the real
28
+ part is non negative.
29
+
30
+ Args:
31
+ quaternions: Quaternions with real part first,
32
+ as array of shape (..., 4).
33
+
34
+ Returns:
35
+ Standardized quaternions as array of shape (..., 4).
36
+ """
37
+ return np.where(quaternions[..., 0:1] < 0, -quaternions, quaternions)
38
+
39
+
40
+ # ============================================================
41
+ # Axis-angle <-> Quaternion <-> Rotation matrix
42
+ # ============================================================
43
+
44
+ def axis_angle_to_quaternion(axis_angle: np.ndarray) -> np.ndarray:
45
+ """Convert rotations given as axis/angle to quaternions.
46
+
47
+ Args:
48
+ axis_angle: Rotations given as a vector in axis angle form,
49
+ as an array of shape (..., 3), where the magnitude is
50
+ the angle turned anticlockwise in radians around the
51
+ vector's direction.
52
+
53
+ Returns:
54
+ quaternions with real part first, as array of shape (..., 4).
55
+ """
56
+ angles = np.linalg.norm(axis_angle, axis=-1, keepdims=True)
57
+ half_angles = angles * 0.5
58
+ # sin(angle/2) / angle, exact; limit -> 0.5 as angle -> 0
59
+ nonzero = angles != 0
60
+ safe_angles = np.where(nonzero, angles, np.ones_like(angles))
61
+ sin_half_angles_over_angles = np.where(
62
+ nonzero, np.sin(half_angles) / safe_angles, 0.5
63
+ )
64
+ quaternions = np.concatenate(
65
+ [np.cos(half_angles), axis_angle * sin_half_angles_over_angles], axis=-1
66
+ )
67
+ return quaternions
68
+
69
+
70
+ def quaternion_to_matrix(quaternions: np.ndarray) -> np.ndarray:
71
+ """Convert rotations given as quaternions to rotation matrices.
72
+
73
+ Args:
74
+ quaternions: quaternions with real part first,
75
+ as array of shape (..., 4).
76
+
77
+ Returns:
78
+ Rotation matrices as array of shape (..., 3, 3).
79
+ """
80
+ r, i, j, k = (
81
+ quaternions[..., 0],
82
+ quaternions[..., 1],
83
+ quaternions[..., 2],
84
+ quaternions[..., 3],
85
+ )
86
+ two_s = 2.0 / (quaternions * quaternions).sum(-1)
87
+
88
+ o = np.stack(
89
+ (
90
+ 1 - two_s * (j * j + k * k),
91
+ two_s * (i * j - k * r),
92
+ two_s * (i * k + j * r),
93
+ two_s * (i * j + k * r),
94
+ 1 - two_s * (i * i + k * k),
95
+ two_s * (j * k - i * r),
96
+ two_s * (i * k - j * r),
97
+ two_s * (j * k + i * r),
98
+ 1 - two_s * (i * i + j * j),
99
+ ),
100
+ axis=-1,
101
+ )
102
+ return o.reshape(quaternions.shape[:-1] + (3, 3))
103
+
104
+
105
+ def axis_angle_to_matrix(axis_angle: np.ndarray) -> np.ndarray:
106
+ """Convert rotations given as axis/angle to rotation matrices.
107
+
108
+ Args:
109
+ axis_angle: Rotations given as a vector in axis angle form,
110
+ as an array of shape (..., 3), where the magnitude is
111
+ the angle turned anticlockwise in radians around the
112
+ vector's direction.
113
+
114
+ Returns:
115
+ Rotation matrices as array of shape (..., 3, 3).
116
+ """
117
+ return quaternion_to_matrix(axis_angle_to_quaternion(axis_angle))
118
+
119
+
120
+ def matrix_to_quaternion(matrix: np.ndarray) -> np.ndarray:
121
+ """Convert rotations given as rotation matrices to quaternions.
122
+
123
+ Args:
124
+ matrix: Rotation matrices as array of shape (..., 3, 3).
125
+
126
+ Returns:
127
+ quaternions with real part first, as array of shape (..., 4).
128
+ """
129
+ if matrix.shape[-1] != 3 or matrix.shape[-2] != 3:
130
+ raise ValueError(f"Invalid rotation matrix shape {matrix.shape}.")
131
+
132
+ batch_dim = matrix.shape[:-2]
133
+ m00, m01, m02, m10, m11, m12, m20, m21, m22 = np.split(
134
+ matrix.reshape(batch_dim + (9,)), 9, axis=-1
135
+ )
136
+ m00 = m00[..., 0]
137
+ m01 = m01[..., 0]
138
+ m02 = m02[..., 0]
139
+ m10 = m10[..., 0]
140
+ m11 = m11[..., 0]
141
+ m12 = m12[..., 0]
142
+ m20 = m20[..., 0]
143
+ m21 = m21[..., 0]
144
+ m22 = m22[..., 0]
145
+
146
+ q_abs = _sqrt_positive_part(
147
+ np.stack(
148
+ [
149
+ 1.0 + m00 + m11 + m22,
150
+ 1.0 + m00 - m11 - m22,
151
+ 1.0 - m00 + m11 - m22,
152
+ 1.0 - m00 - m11 + m22,
153
+ ],
154
+ axis=-1,
155
+ )
156
+ )
157
+
158
+ # we produce the desired quaternion multiplied by each of r, i, j, k
159
+ quat_by_rijk = np.stack(
160
+ [
161
+ np.stack(
162
+ [q_abs[..., 0] ** 2, m21 - m12, m02 - m20, m10 - m01], axis=-1
163
+ ),
164
+ np.stack(
165
+ [m21 - m12, q_abs[..., 1] ** 2, m10 + m01, m02 + m20], axis=-1
166
+ ),
167
+ np.stack(
168
+ [m02 - m20, m10 + m01, q_abs[..., 2] ** 2, m12 + m21], axis=-1
169
+ ),
170
+ np.stack(
171
+ [m10 - m01, m20 + m02, m21 + m12, q_abs[..., 3] ** 2], axis=-1
172
+ ),
173
+ ],
174
+ axis=-2,
175
+ )
176
+
177
+ # We floor here at 0.1 but the exact level is not important; if q_abs is small,
178
+ # the candidate won't be picked.
179
+ flr = 0.1
180
+ quat_candidates = quat_by_rijk / (2.0 * np.maximum(q_abs[..., None], flr))
181
+
182
+ # if not for numerical problems, quat_candidates[i] should be same (up to a sign),
183
+ # forall i; we pick the best-conditioned one (with the largest denominator)
184
+ best = q_abs.argmax(axis=-1) # (*batch_dim,)
185
+ # Advanced indexing to select the best candidate per element
186
+ flat_candidates = quat_candidates.reshape(-1, 4, 4)
187
+ flat_best = best.reshape(-1)
188
+ out = flat_candidates[np.arange(flat_candidates.shape[0]), flat_best, :]
189
+ out = out.reshape(batch_dim + (4,))
190
+ return standardize_quaternion(out)
191
+
192
+
193
+ def quaternion_to_axis_angle(quaternions: np.ndarray) -> np.ndarray:
194
+ """Convert rotations given as quaternions to axis/angle.
195
+
196
+ Args:
197
+ quaternions: quaternions with real part first,
198
+ as array of shape (..., 4).
199
+
200
+ Returns:
201
+ Rotations given as a vector in axis angle form, as an array
202
+ of shape (..., 3), where the magnitude is the angle
203
+ turned anticlockwise in radians around the vector's
204
+ direction.
205
+ """
206
+ norms = np.linalg.norm(quaternions[..., 1:], axis=-1, keepdims=True)
207
+ half_angles = np.arctan2(norms, quaternions[..., :1])
208
+ angles = 2 * half_angles
209
+ # sin(half_angle) / angle, exact; limit -> 0.5 as angle -> 0
210
+ nonzero = angles != 0
211
+ safe_angles = np.where(nonzero, angles, np.ones_like(angles))
212
+ sin_half_angles_over_angles = np.where(
213
+ nonzero, np.sin(half_angles) / safe_angles, 0.5
214
+ )
215
+ return quaternions[..., 1:] / sin_half_angles_over_angles
216
+
217
+
218
+ def matrix_to_axis_angle(matrix: np.ndarray) -> np.ndarray:
219
+ """Convert rotations given as rotation matrices to axis/angle.
220
+
221
+ Args:
222
+ matrix: Rotation matrices as array of shape (..., 3, 3).
223
+
224
+ Returns:
225
+ Rotations given as a vector in axis angle form, as an array
226
+ of shape (..., 3), where the magnitude is the angle
227
+ turned anticlockwise in radians around the vector's
228
+ direction.
229
+ """
230
+ return quaternion_to_axis_angle(matrix_to_quaternion(matrix))
231
+
232
+
233
+ # ============================================================
234
+ # 6D continuous rotation representation (Zhou et al., CVPR 2019)
235
+ # ============================================================
236
+
237
+ def rotation_6d_to_matrix(rot6d: np.ndarray) -> np.ndarray:
238
+ """Convert 6D rotation representation to 3x3 rotation matrix.
239
+
240
+ Based on Zhou et al., "On the Continuity of Rotation Representations
241
+ in Neural Networks", CVPR 2019.
242
+
243
+ Args:
244
+ rot6d: array of shape (*, 6) of 6d rotation representations.
245
+
246
+ Returns:
247
+ rotation matrices of size (*, 3, 3).
248
+ """
249
+ x = rot6d.reshape(*rot6d.shape[:-1], 3, 2)
250
+ a1 = x[..., 0]
251
+ a2 = x[..., 1]
252
+ b1 = a1 / np.maximum(np.linalg.norm(a1, axis=-1, keepdims=True), 1e-12)
253
+ b2 = a2 - np.sum(b1 * a2, axis=-1, keepdims=True) * b1
254
+ b2 = b2 / np.maximum(np.linalg.norm(b2, axis=-1, keepdims=True), 1e-12)
255
+ b3 = np.cross(b1, b2, axis=-1)
256
+ return np.stack((b1, b2, b3), axis=-1)
257
+
258
+
259
+ def matrix_to_rotation_6d(matrix: np.ndarray) -> np.ndarray:
260
+ """Convert 3x3 rotation matrix to 6D rotation representation.
261
+
262
+ Args:
263
+ matrix: rotation matrices of shape (*, 3, 3).
264
+
265
+ Returns:
266
+ 6D rotation representation of shape (*, 6).
267
+ """
268
+ v1 = matrix[..., 0:1]
269
+ v2 = matrix[..., 1:2]
270
+ rot6d = np.concatenate([v1, v2], axis=-1).reshape(*matrix.shape[:-2], 6)
271
+ return rot6d
272
+
273
+
274
+ # ============================================================
275
+ # Yaw (Y-axis) rotation helpers (MEI-specific)
276
+ # ============================================================
277
+
278
+ def yaw_rotation_matrix(angle: np.ndarray) -> np.ndarray:
279
+ """Create rotation matrices for yaw (Y-axis rotation).
280
+
281
+ R_y(theta) maps local Z-forward to the heading direction in world XZ plane.
282
+
283
+ Args:
284
+ angle: (...) yaw angles in radians.
285
+
286
+ Returns:
287
+ R: (..., 3, 3) rotation matrices.
288
+ """
289
+ c = np.cos(angle)
290
+ s = np.sin(angle)
291
+ z = np.zeros_like(angle)
292
+ o = np.ones_like(angle)
293
+
294
+ return np.stack([
295
+ c, z, s,
296
+ z, o, z,
297
+ -s, z, c,
298
+ ], axis=-1).reshape(*angle.shape, 3, 3)
299
+
300
+
301
+ def wrap_angle(angle: np.ndarray) -> np.ndarray:
302
+ """Wrap angle to [-pi, pi]."""
303
+ return (angle + np.pi) % (2 * np.pi) - np.pi