Yzy00518 commited on
Commit
38cab0f
·
1 Parent(s): 1ce0df3

Upload src/utils/transforms.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. src/utils/transforms.py +395 -0
src/utils/transforms.py ADDED
@@ -0,0 +1,395 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import functools
2
+ import torch
3
+ import torch.nn.functional as F
4
+
5
+ ########################Implementations of the functions in the PyTorch3D########################
6
+ def quaternion_to_matrix(quaternions):
7
+ r, i, j, k = torch.unbind(quaternions, -1)
8
+ two_s = 2.0 / (quaternions * quaternions).sum(-1)
9
+
10
+ o = torch.stack(
11
+ (
12
+ 1 - two_s * (j * j + k * k),
13
+ two_s * (i * j - k * r),
14
+ two_s * (i * k + j * r),
15
+ two_s * (i * j + k * r),
16
+ 1 - two_s * (i * i + k * k),
17
+ two_s * (j * k - i * r),
18
+ two_s * (i * k - j * r),
19
+ two_s * (j * k + i * r),
20
+ 1 - two_s * (i * i + j * j),
21
+ ),
22
+ -1,
23
+ )
24
+ return o.reshape(quaternions.shape[:-1] + (3, 3))
25
+
26
+
27
+ def _copysign(a, b):
28
+ signs_differ = (a < 0) != (b < 0)
29
+ return torch.where(signs_differ, -a, a)
30
+
31
+
32
+ def _sqrt_positive_part(x: torch.Tensor) -> torch.Tensor:
33
+ ret = torch.zeros_like(x)
34
+ positive_mask = x > 0
35
+ ret[positive_mask] = torch.sqrt(x[positive_mask])
36
+ return ret
37
+
38
+
39
+ def matrix_to_quaternion(matrix: torch.Tensor) -> torch.Tensor:
40
+ if matrix.size(-1) != 3 or matrix.size(-2) != 3:
41
+ raise ValueError(f"Invalid rotation matrix shape f{matrix.shape}.")
42
+
43
+ batch_dim = matrix.shape[:-2]
44
+ m00, m01, m02, m10, m11, m12, m20, m21, m22 = torch.unbind(
45
+ matrix.reshape(*batch_dim, 9), dim=-1
46
+ )
47
+
48
+ q_abs = _sqrt_positive_part(
49
+ torch.stack(
50
+ [
51
+ 1.0 + m00 + m11 + m22,
52
+ 1.0 + m00 - m11 - m22,
53
+ 1.0 - m00 + m11 - m22,
54
+ 1.0 - m00 - m11 + m22,
55
+ ],
56
+ dim=-1,
57
+ )
58
+ )
59
+
60
+ quat_by_rijk = torch.stack(
61
+ [
62
+ torch.stack([q_abs[..., 0] ** 2, m21 - m12, m02 - m20, m10 - m01], dim=-1),
63
+ torch.stack([m21 - m12, q_abs[..., 1] ** 2, m10 + m01, m02 + m20], dim=-1),
64
+ torch.stack([m02 - m20, m10 + m01, q_abs[..., 2] ** 2, m12 + m21], dim=-1),
65
+ torch.stack([m10 - m01, m20 + m02, m21 + m12, q_abs[..., 3] ** 2], dim=-1),
66
+ ],
67
+ dim=-2,
68
+ )
69
+
70
+ quat_candidates = quat_by_rijk / (2.0 * q_abs[..., None].max(q_abs.new_tensor(0.1)))
71
+
72
+ return quat_candidates[
73
+ F.one_hot(q_abs.argmax(dim=-1), num_classes=4) > 0.5, :
74
+ ].reshape(*batch_dim, 4)
75
+
76
+
77
+ def _axis_angle_rotation(axis: str, angle):
78
+ cos = torch.cos(angle)
79
+ sin = torch.sin(angle)
80
+ one = torch.ones_like(angle)
81
+ zero = torch.zeros_like(angle)
82
+
83
+ if axis == "X":
84
+ R_flat = (one, zero, zero, zero, cos, -sin, zero, sin, cos)
85
+ if axis == "Y":
86
+ R_flat = (cos, zero, sin, zero, one, zero, -sin, zero, cos)
87
+ if axis == "Z":
88
+ R_flat = (cos, -sin, zero, sin, cos, zero, zero, zero, one)
89
+
90
+ return torch.stack(R_flat, -1).reshape(angle.shape + (3, 3))
91
+
92
+
93
+ def euler_angles_to_matrix(euler_angles, convention: str):
94
+ if euler_angles.dim() == 0 or euler_angles.shape[-1] != 3:
95
+ raise ValueError("Invalid input euler angles.")
96
+ if len(convention) != 3:
97
+ raise ValueError("Convention must have 3 letters.")
98
+ if convention[1] in (convention[0], convention[2]):
99
+ raise ValueError(f"Invalid convention {convention}.")
100
+ for letter in convention:
101
+ if letter not in ("X", "Y", "Z"):
102
+ raise ValueError(f"Invalid letter {letter} in convention string.")
103
+ matrices = map(_axis_angle_rotation, convention, torch.unbind(euler_angles, -1))
104
+ return functools.reduce(torch.matmul, matrices)
105
+
106
+
107
+ def _angle_from_tan(
108
+ axis: str, other_axis: str, data, horizontal: bool, tait_bryan: bool
109
+ ):
110
+ i1, i2 = {"X": (2, 1), "Y": (0, 2), "Z": (1, 0)}[axis]
111
+ if horizontal:
112
+ i2, i1 = i1, i2
113
+ even = (axis + other_axis) in ["XY", "YZ", "ZX"]
114
+ if horizontal == even:
115
+ return torch.atan2(data[..., i1], data[..., i2])
116
+ if tait_bryan:
117
+ return torch.atan2(-data[..., i2], data[..., i1])
118
+ return torch.atan2(data[..., i2], -data[..., i1])
119
+
120
+
121
+ def _index_from_letter(letter: str):
122
+ if letter == "X":
123
+ return 0
124
+ if letter == "Y":
125
+ return 1
126
+ if letter == "Z":
127
+ return 2
128
+
129
+
130
+ def matrix_to_euler_angles(matrix, convention: str):
131
+ if len(convention) != 3:
132
+ raise ValueError("Convention must have 3 letters.")
133
+ if convention[1] in (convention[0], convention[2]):
134
+ raise ValueError(f"Invalid convention {convention}.")
135
+ for letter in convention:
136
+ if letter not in ("X", "Y", "Z"):
137
+ raise ValueError(f"Invalid letter {letter} in convention string.")
138
+ if matrix.size(-1) != 3 or matrix.size(-2) != 3:
139
+ raise ValueError(f"Invalid rotation matrix shape f{matrix.shape}.")
140
+ i0 = _index_from_letter(convention[0])
141
+ i2 = _index_from_letter(convention[2])
142
+ tait_bryan = i0 != i2
143
+ if tait_bryan:
144
+ central_angle = torch.asin(
145
+ matrix[..., i0, i2] * (-1.0 if i0 - i2 in [-1, 2] else 1.0)
146
+ )
147
+ else:
148
+ central_angle = torch.acos(matrix[..., i0, i0])
149
+
150
+ o = (
151
+ _angle_from_tan(
152
+ convention[0], convention[1], matrix[..., i2], False, tait_bryan
153
+ ),
154
+ central_angle,
155
+ _angle_from_tan(
156
+ convention[2], convention[1], matrix[..., i0, :], True, tait_bryan
157
+ ),
158
+ )
159
+ return torch.stack(o, -1)
160
+
161
+
162
+ def standardize_quaternion(quaternions):
163
+ return torch.where(quaternions[..., 0:1] < 0, -quaternions, quaternions)
164
+
165
+
166
+ def quaternion_raw_multiply(a, b):
167
+ aw, ax, ay, az = torch.unbind(a, -1)
168
+ bw, bx, by, bz = torch.unbind(b, -1)
169
+ ow = aw * bw - ax * bx - ay * by - az * bz
170
+ ox = aw * bx + ax * bw + ay * bz - az * by
171
+ oy = aw * by - ax * bz + ay * bw + az * bx
172
+ oz = aw * bz + ax * by - ay * bx + az * bw
173
+ return torch.stack((ow, ox, oy, oz), -1)
174
+
175
+
176
+ def quaternion_multiply(a, b):
177
+ ab = quaternion_raw_multiply(a, b)
178
+ return standardize_quaternion(ab)
179
+
180
+
181
+ def quaternion_invert(quaternion):
182
+ return quaternion * quaternion.new_tensor([1, -1, -1, -1])
183
+
184
+
185
+ def quaternion_apply(quaternion, point):
186
+ if point.size(-1) != 3:
187
+ raise ValueError(f"Points are not in 3D, f{point.shape}.")
188
+ real_parts = point.new_zeros(point.shape[:-1] + (1,))
189
+ point_as_quaternion = torch.cat((real_parts, point), -1)
190
+ out = quaternion_raw_multiply(
191
+ quaternion_raw_multiply(quaternion, point_as_quaternion),
192
+ quaternion_invert(quaternion),
193
+ )
194
+ return out[..., 1:]
195
+
196
+
197
+ def axis_angle_to_matrix(axis_angle):
198
+ return quaternion_to_matrix(axis_angle_to_quaternion(axis_angle))
199
+
200
+
201
+ def matrix_to_axis_angle(matrix):
202
+ return quaternion_to_axis_angle(matrix_to_quaternion(matrix))
203
+
204
+
205
+ def axis_angle_to_quaternion(axis_angle):
206
+ angles = torch.norm(axis_angle, p=2, dim=-1, keepdim=True)
207
+ half_angles = 0.5 * angles
208
+ eps = 1e-6
209
+ small_angles = angles.abs() < eps
210
+ sin_half_angles_over_angles = torch.empty_like(angles)
211
+ sin_half_angles_over_angles[~small_angles] = (
212
+ torch.sin(half_angles[~small_angles]) / angles[~small_angles]
213
+ )
214
+ # for x small, sin(x/2) is about x/2 - (x/2)^3/6
215
+ # so sin(x/2)/x is about 1/2 - (x*x)/48
216
+ sin_half_angles_over_angles[small_angles] = (
217
+ 0.5 - (angles[small_angles] * angles[small_angles]) / 48
218
+ )
219
+ quaternions = torch.cat(
220
+ [torch.cos(half_angles), axis_angle * sin_half_angles_over_angles], dim=-1
221
+ )
222
+ return quaternions
223
+
224
+
225
+ def quaternion_to_axis_angle(quaternions):
226
+ norms = torch.norm(quaternions[..., 1:], p=2, dim=-1, keepdim=True)
227
+ half_angles = torch.atan2(norms, quaternions[..., :1])
228
+ angles = 2 * half_angles
229
+ eps = 1e-6
230
+ small_angles = angles.abs() < eps
231
+ sin_half_angles_over_angles = torch.empty_like(angles)
232
+ sin_half_angles_over_angles[~small_angles] = (
233
+ torch.sin(half_angles[~small_angles]) / angles[~small_angles]
234
+ )
235
+ # for x small, sin(x/2) is about x/2 - (x/2)^3/6
236
+ # so sin(x/2)/x is about 1/2 - (x*x)/48
237
+ sin_half_angles_over_angles[small_angles] = (
238
+ 0.5 - (angles[small_angles] * angles[small_angles]) / 48
239
+ )
240
+ return quaternions[..., 1:] / sin_half_angles_over_angles
241
+
242
+
243
+ def rotation_6d_to_matrix(d6: torch.Tensor) -> torch.Tensor:
244
+ a1, a2 = d6[..., :3], d6[..., 3:]
245
+ b1 = F.normalize(a1, dim=-1)
246
+ b2 = a2 - (b1 * a2).sum(-1, keepdim=True) * b1
247
+ b2 = F.normalize(b2, dim=-1)
248
+ b3 = torch.cross(b1, b2, dim=-1)
249
+ return torch.stack((b1, b2, b3), dim=-2)
250
+
251
+
252
+ def matrix_to_rotation_6d(matrix: torch.Tensor) -> torch.Tensor:
253
+ return matrix[..., :2, :].clone().reshape(*matrix.size()[:-2], 6)
254
+
255
+
256
+ import numpy as np
257
+ def rotation_6d_to_matrix_np(d6: np.ndarray) -> np.ndarray:
258
+ a1, a2 = d6[..., :3], d6[..., 3:]
259
+ b1 = a1 / np.linalg.norm(a1, axis=-1, keepdims=True)
260
+ b2 = a2 - np.sum(b1 * a2, axis=-1, keepdims=True) * b1
261
+ b2 = b2 / np.linalg.norm(b2, axis=-1, keepdims=True)
262
+ b3 = np.cross(b1, b2, axis=-1)
263
+ return np.stack((b1, b2, b3), axis=-2)
264
+
265
+ def matrix_to_rotation_6d_np(matrix: np.ndarray) -> np.ndarray:
266
+ return matrix[..., :2, :].reshape(*matrix.shape[:-2], 6)
267
+
268
+ ########################Implementations of the functions in the PyTorch3D########################
269
+
270
+
271
+
272
+ from einops import rearrange
273
+ def transform_points(x, mat):
274
+ shape = x.shape
275
+ x = rearrange(x, 'b t (j c) -> b (t j) c', c=3) # B x N x 3
276
+ x = torch.einsum('bpc,bck->bpk', mat[:, :3, :3], x.permute(0, 2, 1)) # B x 3 x N N x B x 3
277
+ x = x.permute(2, 0, 1) + mat[:, :3, 3]
278
+ x = x.permute(1, 0, 2)
279
+ x = x.reshape(shape)
280
+ return x
281
+
282
+
283
+ def transform_points_numpy(x, mat):
284
+ shape = x.shape
285
+ x = x.reshape(shape[0], -1, 3) # b x (t*j) x c
286
+ x = np.einsum('bpc,bck->bpk', mat[:, :3, :3], np.transpose(x, (0, 2, 1)))
287
+ x = np.transpose(x, (2, 0, 1)) + mat[:, :3, 3]
288
+ x = np.transpose(x, (1, 0, 2))
289
+ x = x.reshape(shape)
290
+ return x
291
+
292
+
293
+ def zup_to_yup(coord):
294
+ if len(coord.shape) > 1:
295
+ coord = coord[..., [0, 2, 1]]
296
+ coord[..., 2] *= -1
297
+ else:
298
+ coord = coord[[0, 2, 1]]
299
+ coord[2] *= -1
300
+ return coord
301
+
302
+
303
+ def rigid_transform_3D(A, B, scale=False):
304
+ assert len(A) == len(B)
305
+ N = A.shape[0] # total points
306
+ centroid_A = np.mean(A, axis=0)
307
+ centroid_B = np.mean(B, axis=0)
308
+
309
+ # center the points
310
+ AA = A - np.tile(centroid_A, (N, 1))
311
+ BB = B - np.tile(centroid_B, (N, 1))
312
+ if scale:
313
+ H = np.transpose(BB) * AA / N
314
+ else:
315
+ H = np.transpose(BB) * AA
316
+
317
+ U, S, Vt = np.linalg.svd(H)
318
+ R = Vt.T * U.T
319
+ # special reflection case
320
+ if np.linalg.det(R) < 0:
321
+ Vt[2, :] *= -1
322
+ R = Vt.T * U.T
323
+
324
+ if scale:
325
+ varA = np.var(A, axis=0).sum()
326
+ c = 1 / (1 / varA * np.sum(S)) # scale factor
327
+ t = -R * (centroid_B.T * c) + centroid_A.T
328
+ else:
329
+ c = 1
330
+ t = -R * centroid_B.T + centroid_A.T
331
+
332
+ return c, R, t
333
+
334
+
335
+
336
+
337
+ ##################joints blending######################
338
+ @torch.jit.script
339
+ def slerp(q0: torch.Tensor, q1: torch.Tensor, t: torch.Tensor) -> torch.Tensor:
340
+ """
341
+ Spherical linear interpolation between two quaternions.
342
+
343
+ Args:
344
+ q0: (..., 4) tensor of quaternions
345
+ q1: (..., 4) tensor of quaternions
346
+ t: (..., 1) tensor of interpolation coefficients
347
+
348
+ Returns:
349
+ (..., 4) tensor of quaternions
350
+ """
351
+ cos_half_theta = torch.sum(q0 * q1, dim=-1)
352
+
353
+ neg_mask = cos_half_theta < 0
354
+ q1 = q1.clone()
355
+ q1[neg_mask] = -q1[neg_mask]
356
+ cos_half_theta = torch.abs(cos_half_theta)
357
+ cos_half_theta = torch.unsqueeze(cos_half_theta, dim=-1)
358
+
359
+ half_theta = torch.acos(cos_half_theta)
360
+ sin_half_theta = torch.sqrt(1.0 - cos_half_theta * cos_half_theta)
361
+
362
+ ratioA = torch.sin((1 - t) * half_theta) / sin_half_theta
363
+ ratioB = torch.sin(t * half_theta) / sin_half_theta
364
+
365
+ new_q = ratioA * q0 + ratioB * q1
366
+
367
+ new_q = torch.where(torch.abs(sin_half_theta) < 0.001, 0.5 * q0 + 0.5 * q1, new_q)
368
+ new_q = torch.where(torch.abs(cos_half_theta) >= 1, q0, new_q)
369
+
370
+ return new_q
371
+
372
+
373
+ def blend_joint_rot_batch(body_pose_1, body_pose_2, t):
374
+ """
375
+ Blend two batches of joint rotations using spherical linear interpolation.
376
+
377
+ Args:
378
+ body_pose_1: (batch_size, sequence_length, num_joints, 3) tensor of axis-angle rotations
379
+ body_pose_2: (batch_size, sequence_length, num_joints, 3) tensor of axis-angle rotations
380
+ t: (batch_size, 1, num_joints, 1) tensor of interpolation coefficients
381
+
382
+ Returns:
383
+ (batch_size, sequence_length, num_joints, 3) tensor of axis-angle rotations
384
+ """
385
+ shape = body_pose_1.shape
386
+ if len(shape) == 3:
387
+ body_pose_1 = body_pose_1.reshape(shape[0], shape[1], -1, 3)
388
+ body_pose_2 = body_pose_2.reshape(shape[0], shape[1], -1, 3)
389
+ ret = quaternion_to_axis_angle(
390
+ slerp(axis_angle_to_quaternion(body_pose_1), axis_angle_to_quaternion(body_pose_2), t)
391
+ )
392
+ if len(shape) == 3:
393
+ ret = ret.reshape(shape)
394
+
395
+ return ret