def from_euler(angles, name=None): r"""Converts Euler angles to an axis-angle representation. Note: The conversion is performed by first converting to a quaternion representation, and then by converting the quaternion to an axis-angle. Note: In the following, A1 to An are optional batch dimensions. Args: angles: A tensor of shape `[A1, ..., An, 3]`, where the last dimension represents the three Euler angles. `[A1, ..., An, 0]` is the angle about `x` in radians `[A1, ..., An, 1]` is the angle about `y` in radians and `[A1, ..., An, 2]` is the angle about `z` in radians. name: A name for this op that defaults to "axis_angle_from_euler". Returns: A tuple of two tensors, respectively of shape `[A1, ..., An, 3]` and `[A1, ..., An, 1]`, where the first tensor represents the axis, and the second represents the angle. The resulting axis is a normalized vector. """ with tf.compat.v1.name_scope(name, "axis_angle_from_euler", [angles]): quaternion = quaternion_lib.from_euler(angles) return from_quaternion(quaternion)
def test_energy_preset(self): """Checks that energy returns the expected value.""" vertices_rest_pose = np.array(((1.0, 0.0, 0.0), (-1.0, 0.0, 0.0))) vertices_deformed_pose = 2.0 * vertices_rest_pose quaternions = quaternion.from_euler( np.zeros(shape=vertices_deformed_pose.shape)) edges = ((0, 1), ) all_weights_1_energy = as_conformal_as_possible.energy( vertices_rest_pose, vertices_deformed_pose, quaternions, edges) all_weights_1_gt = 4.0 vertex_weights = np.array((2.0, 1.0)) vertex_weights_energy = as_conformal_as_possible.energy( vertices_rest_pose=vertices_rest_pose, vertices_deformed_pose=vertices_deformed_pose, quaternions=quaternions, edges=edges, vertex_weight=vertex_weights) vertex_weights_gt = 10.0 edge_weights = np.array((2.0, )) edge_weights_energy = as_conformal_as_possible.energy( vertices_rest_pose=vertices_rest_pose, vertices_deformed_pose=vertices_deformed_pose, quaternions=quaternions, edges=edges, edge_weight=edge_weights) edge_weights_gt = 16.0 with self.subTest(name="all_weights_1"): self.assertAllClose(all_weights_1_energy, all_weights_1_gt) with self.subTest(name="vertex_weights"): self.assertAllClose(vertex_weights_energy, vertex_weights_gt) with self.subTest(name="edge_weights"): self.assertAllClose(edge_weights_energy, edge_weights_gt)
def test_energy_identity(self): """Checks that energy evaluated between the rest pose and itself is zero.""" number_vertices = np.random.randint(3, 10) batch_size = np.random.randint(3) batch_shape = np.random.randint(1, 10, size=(batch_size)).tolist() vertices_rest_pose = np.random.uniform(size=(number_vertices, 3)) vertices_deformed_pose = tf.broadcast_to(vertices_rest_pose, shape=batch_shape + [number_vertices, 3]) quaternions = quaternion.from_euler( np.zeros(shape=batch_shape + [number_vertices, 3])) num_edges = int(round(number_vertices / 2)) edges = np.zeros(shape=(num_edges, 2), dtype=np.int32) edges[..., 0] = np.linspace(0, number_vertices / 2 - 1, num_edges, dtype=np.int32) edges[..., 1] = np.linspace(number_vertices / 2, number_vertices - 1, num_edges, dtype=np.int32) energy = as_conformal_as_possible.energy( vertices_rest_pose=vertices_rest_pose, vertices_deformed_pose=vertices_deformed_pose, quaternions=quaternions, edges=edges, conformal_energy=False) self.assertAllClose(energy, tf.zeros_like(energy))
def generate_preset_test_quaternions(): """Generates pre-set test quaternions.""" angles = generate_preset_test_euler_angles() preset_quaternion = quaternion.from_euler(angles) if tf.executing_eagerly(): return np.array(preset_quaternion) with tf.compat.v1.Session() as sess: return np.array(sess.run([preset_quaternion]))
def test_from_euler_jacobian_random(self): """Test the Jacobian of the from_euler function.""" x_init = test_helpers.generate_random_test_euler_angles() x = tf.convert_to_tensor(value=x_init) y = quaternion.from_euler(x) self.assert_jacobian_is_correct(x, x_init, y)
def test_from_quaternion_normalized_preset(self): """Tests that from_quaternion returns normalized axis-angles.""" euler_angles = test_helpers.generate_preset_test_euler_angles() quat = quaternion.from_euler(euler_angles) axis, angle = axis_angle.from_quaternion(quat) self.assertAllEqual( axis_angle.is_normalized(axis, angle), np.ones(angle.shape, dtype=bool))
def test_from_quaternion_preset(self): """Checks that Euler angles can be retrieved from quaternions.""" preset_euler_angles = test_helpers.generate_preset_test_euler_angles() preset_matrix = rotation_matrix_3d.from_euler(preset_euler_angles) preset_quaternion = quaternion.from_euler(preset_euler_angles) predicted_matrix = rotation_matrix_3d.from_quaternion(preset_quaternion) self.assertAllClose(preset_matrix, predicted_matrix, atol=2e-3)
def test_from_euler_normalized_random(self): """Tests that quaternions.from_euler returns normalized quaterions.""" random_euler_angles = test_helpers.generate_random_test_euler_angles() tensor_shape = random_euler_angles.shape[:-1] random_quaternion = quaternion.from_euler(random_euler_angles) self.assertAllEqual(quaternion.is_normalized(random_quaternion), np.ones(shape=tensor_shape + (1, ), dtype=bool))
def test_from_quaternion_normalized_preset(self): """Tests that quaternions can be converted to rotation matrices.""" euler_angles = test_helpers.generate_preset_test_euler_angles() quat = quaternion.from_euler(euler_angles) matrix_quat = rotation_matrix_3d.from_quaternion(quat) self.assertAllEqual(rotation_matrix_3d.is_valid(matrix_quat), np.ones(euler_angles.shape[0:-1] + (1, )))
def test_from_quaternion_random(self): """Tests that axis_angle.from_quaternion produces the expected result.""" random_euler_angles = test_helpers.generate_random_test_euler_angles() random_quaternions = quaternion.from_euler(random_euler_angles) random_axis_angle = axis_angle.from_euler(random_euler_angles) self.assertAllClose(random_axis_angle, axis_angle.from_quaternion(random_quaternions), rtol=1e-3)
def test_from_quaternion_preset(self): """Tests that axis_angle.from_quaternion produces the expected result.""" preset_euler_angles = test_helpers.generate_preset_test_euler_angles() preset_quaternions = quaternion.from_euler(preset_euler_angles) preset_axis_angle = axis_angle.from_euler(preset_euler_angles) self.assertAllClose(preset_axis_angle, axis_angle.from_quaternion(preset_quaternions), rtol=1e-3)
def test_from_quaternion_random(self): """Tests conversion to matrix.""" random_euler_angles = test_helpers.generate_random_test_euler_angles() random_quaternions = quaternion.from_euler(random_euler_angles) random_rotation_matrices = rotation_matrix_3d.from_euler( random_euler_angles) self.assertAllClose(random_rotation_matrices, rotation_matrix_3d.from_quaternion(random_quaternions))
def test_from_axis_angle_random(self): """Tests converting an axis-angle to a quaternion.""" random_euler_angles = test_helpers.generate_random_test_euler_angles() axis, angle = axis_angle.from_euler(random_euler_angles) grountruth = rotation_matrix_3d.from_quaternion( quaternion.from_euler(random_euler_angles)) prediction = rotation_matrix_3d.from_quaternion( quaternion.from_axis_angle(axis, angle)) self.assertAllClose(grountruth, prediction, rtol=1e-3)
def test_from_quaternion_gimbal(self, gimbal_configuration): """Checks that from_quaternion works when Ry = pi/2 or -pi/2.""" random_euler_angles = test_helpers.generate_random_test_euler_angles() random_euler_angles[..., 1] = gimbal_configuration random_quaternion = quaternion.from_euler(random_euler_angles) random_matrix = rotation_matrix_3d.from_euler(random_euler_angles) reconstructed_random_matrices = rotation_matrix_3d.from_quaternion( random_quaternion) self.assertAllClose(reconstructed_random_matrices, random_matrix, atol=2e-3)
def test_from_rotation_matrix_random(self): """Tests that from_rotation_matrix produces the expected quaternions.""" random_euler_angles = test_helpers.generate_random_test_euler_angles() random_rotation_matrix_3d = rotation_matrix_3d.from_euler( random_euler_angles) groundtruth = rotation_matrix_3d.from_quaternion( quaternion.from_euler(random_euler_angles)) prediction = rotation_matrix_3d.from_quaternion( quaternion.from_rotation_matrix(random_rotation_matrix_3d)) self.assertAllClose(groundtruth, prediction)
def test_from_euler_random(self): """Tests that quaternions can be constructed from Euler angles.""" random_euler_angles = test_helpers.generate_random_test_euler_angles() tensor_shape = random_euler_angles.shape[:-1] random_matrix = rotation_matrix_3d.from_euler(random_euler_angles) random_quaternion = quaternion.from_euler(random_euler_angles) random_point = np.random.normal(size=tensor_shape + (3,)) rotated_with_matrix = rotation_matrix_3d.rotate(random_point, random_matrix) rotated_with_quaternion = quaternion.rotate(random_point, random_quaternion) self.assertAllClose(rotated_with_matrix, rotated_with_quaternion)
def test_from_euler_with_small_angles_approximation_random(self): # Only generate small angles. For a test tolerance of 1e-3, 0.33 was found # empirically to be the range where the small angle approximation works. random_euler_angles = test_helpers.generate_random_test_euler_angles( min_angle=-0.33, max_angle=0.33) exact_quaternion = quaternion.from_euler(random_euler_angles) approximate_quaternion = ( quaternion.from_euler_with_small_angles_approximation( random_euler_angles)) self.assertAllClose(exact_quaternion, approximate_quaternion, atol=1e-3)
def rotate(vertices, features, num_samples, rot_range, angle_fixed): ''' :param vertices: [num_vertices, 3] :param features: [FEAT_CAP, 4] :param num_samples: :return: ''' vertices = vertices.astype(np.float32) # [FEAT_CAP] mask = features[:, 0].astype(np.bool) # [FEAT_CAP, 3] features = features[:, 1:].astype(np.float32) if angle_fixed: vertices = vertices[np.newaxis, :, :] features = features[np.newaxis, :, :] return vertices, features, mask random_angles_x = np.random.uniform(-rot_range[0], rot_range[0], (num_samples)).astype(np.float32) random_angles_y = np.random.uniform(-rot_range[1], rot_range[1], (num_samples)).astype(np.float32) random_angles_z = np.random.uniform(-rot_range[2], rot_range[2], (num_samples)).astype(np.float32) # random_angles.shape: (num_samples, 3) random_angles = np.stack( [random_angles_x, random_angles_y, random_angles_z], axis=1) ## debug regular_angles = np.concatenate([ np.linspace(-np.pi, np.pi, num_samples)[:, np.newaxis], np.zeros((num_samples, 2)) ], axis=-1).astype(np.float32) # random_quaternion.shape: (num_samples, 4) random_quaternion = quaternion.from_euler(random_angles) # vertices.shape : (num_samples, num_vertices, 3) vertices = quaternion.rotate(vertices[tf.newaxis, :, :], random_quaternion[:, tf.newaxis, :]) # features.shape : (num_samples, FEAT_CAP, 3) features = quaternion.rotate(features[tf.newaxis, :, :], random_quaternion[:, tf.newaxis, :]) return np.array(vertices), np.array(features), mask
def generate_preset_test_dual_quaternions(): """Generates pre-set test quaternions.""" angles = generate_preset_test_euler_angles() preset_quaternion_real = quaternion.from_euler(angles) translations = generate_preset_test_translations() translations = np.concatenate( (translations / 2.0, np.zeros((np.ma.size(translations, 0), 1))), axis=1) preset_quaternion_translation = tf.convert_to_tensor(value=translations) preset_quaternion_dual = quaternion.multiply(preset_quaternion_translation, preset_quaternion_real) preset_dual_quaternion = tf.concat( (preset_quaternion_real, preset_quaternion_dual), axis=-1) return preset_dual_quaternion
def rotate(vertices, num_samples): vertices = vertices.astype(np.float32) # random_angles.shape: (num_samples, 3) random_angles = np.random.uniform(-np.pi, np.pi, (num_samples, 3)).astype(np.float32) ## debug regular_angles = np.concatenate([ np.linspace(-np.pi, np.pi, num_samples)[:, np.newaxis], np.zeros((num_samples, 2)) ], axis=-1).astype(np.float32) ## # random_quaternion.shape: (num_samples, 4) random_quaternion = quaternion.from_euler(random_angles) # data.shape : (num_samples, num_vertices, 3) data = quaternion.rotate(vertices[tf.newaxis, :, :], random_quaternion[:, tf.newaxis, :]) return np.array(data), np.array(random_quaternion)
def generate_random_test_dual_quaternions(): """Generates random test dual quaternions.""" angles = generate_random_test_euler_angles() random_quaternion_real = quaternion.from_euler(angles) min_translation = -3.0 max_translation = 3.0 translations = np.random.uniform(min_translation, max_translation, angles.shape) translations_quaternion_shape = np.asarray(translations.shape) translations_quaternion_shape[-1] = 1 translations = np.concatenate( (translations / 2.0, np.zeros(translations_quaternion_shape)), axis=-1) random_quaternion_translation = tf.convert_to_tensor(value=translations) random_quaternion_dual = quaternion.multiply(random_quaternion_translation, random_quaternion_real) random_dual_quaternion = tf.concat( (random_quaternion_real, random_quaternion_dual), axis=-1) return random_dual_quaternion
# reduce ray sample count since it needs to be a square number sqrt_ray_sample_count = math.floor(math.sqrt(ray_sample_count)) ray_sample_count = sqrt_ray_sample_count * sqrt_ray_sample_count # actually want the center-to-edge distance, i.e. the 'square radius'. source_size /= 2 # ============================================================================= # Make the source. angle_type = "quaternion" if angle_type == "vector": source_angle = np.array((0.0, -source_y_offset, target_z_distance), dtype=np.float64) else: rot1 = quaternion.from_euler((0.0, -PI / 2, 0.0)) rot2 = quaternion.from_euler( tf.cast((math.atan2(source_y_offset, target_z_distance), 0.0, 0.0), dtype=tf.float64)) source_angle = quaternion.multiply(rot2, rot1) source_center = np.array( (source_x_offset, source_y_offset, -target_z_distance), dtype=np.float64) angular_distribution = distributions.SquareRankLambertianSphere( ray_sample_count, source_angular_cutoff) base_point_distribution = distributions.RandomUniformSquare( source_size, sqrt_ray_sample_count) source = sources.AngularSource(3, source_center, source_angle, angular_distribution,
def generate_preset_test_quaternions(): """Generates pre-set test quaternions.""" angles = generate_preset_test_euler_angles() preset_quaternion = quaternion.from_euler(angles) return preset_quaternion