Ejemplo n.º 1
0
def eval_accuracy(X, Y, Y_hat, X_train, Y_train, X_test, Y_test, t_treshold, r_threshold):
  # Have to swap axes due to how tensorflow quaternions work
  Yq = tf.transpose(Y[:4], [1, 0])
  Yq_hat = tf.transpose(Y_hat[:4], [1, 0])
  # Compute difference quaternion then swap axes back
  diff = tfq.multiply(tfq.normalize(Yq), tfq.inverse(tfq.normalize(Yq_hat)))
  diff = tf.transpose(diff, [1, 0])
  # Compute angle difference in degrees
  diffW = tf.clip_by_value(diff[3], clip_value_min=-1.0, clip_value_max=1.0)
  angle_diff = tf_rad2deg(math.pi - tf.math.abs(2 * tf.math.acos(diffW) - math.pi))

  correct_rot = tf.cast(tf.less(angle_diff, [r_threshold]), "float")
  correct_trans = tf.cast(tf.less(tf.norm(Y[4:] - Y_hat[4:], axis = 0), [t_treshold]), "float")

  t_accuracy = tf.reduce_mean(tf.cast(correct_trans, "float"))
  r_accuracy = tf.reduce_mean(tf.cast(correct_rot, "float"))
  accuracy = tf.reduce_mean(tf.cast(correct_trans * correct_rot, "float"))

  print('\n--------------')
  print("Evaluating accuracy with rotation threshold of " + str(r_threshold) + " and translation treshold of " + str(t_treshold))
  print('')
  print ("Train Accuracy:", accuracy.eval({X: X_train, Y: Y_train}))
  print ("Test Accuracy:", accuracy.eval({X: X_test, Y: Y_test}))
  print('')

  print ("Train Accuracy on just translation:", t_accuracy.eval({X: X_train, Y: Y_train}))
  print ("Test Accuracy on just translation:", t_accuracy.eval({X: X_test, Y: Y_test}))
  print('')

  print ("Train Accuracy on just rotation:", r_accuracy.eval({X: X_train, Y: Y_train}))
  print ("Test Accuracy on just rotation:", r_accuracy.eval({X: X_test, Y: Y_test}))
  print('\n--------------')
Ejemplo n.º 2
0
  def test_normalize_jacobian_preset(self):
    """Test the Jacobian of the normalize function."""
    x_init = test_helpers.generate_preset_test_quaternions()
    x = tf.convert_to_tensor(value=x_init)

    y = quaternion.normalize(x)

    self.assert_jacobian_is_correct(x, x_init, y)
Ejemplo n.º 3
0
  def test_normalize_jacobian_random(self):
    """Test the Jacobian of the normalize function."""
    tensor_dimensions = np.random.randint(low=1, high=3)
    tensor_shape = np.random.randint(1, 10, size=(tensor_dimensions)).tolist()
    x_init = test_helpers.generate_random_test_quaternions(tensor_shape)
    x = tf.convert_to_tensor(value=x_init)

    y = quaternion.normalize(x)

    self.assert_jacobian_is_correct(x, x_init, y)
Ejemplo n.º 4
0
def d_q(q1, q2):
    """Distance between 2 quaternions
    
    The quaternion distance takes values between [0, pi]
    
    Parameters
    ----------
    q1: tf.tensor/np.ndarray
        1st quaternion
    q2: tf.tensor/np.ndarray
        2nd quaternion
    
    Returns
    -------
    : distnace between these 2 quaternions
    
    """
    q1 = tf.cast(tf.convert_to_tensor(value=q1), dtype=tf.float64)
    q2 = tf.cast(tf.convert_to_tensor(value=q2), dtype=tf.float64)

    shape.check_static(tensor=q1,
                       tensor_name="quaternion1",
                       has_dim_equals=(-1, 4))
    shape.check_static(tensor=q2,
                       tensor_name="quaternion2",
                       has_dim_equals=(-1, 4))

    q1 = quaternion.normalize(q1)
    q2 = quaternion.normalize(q2)

    dot_product = vector.dot(q1, q2, keepdims=False)

    # Ensure dot product is in range [-1. 1].
    eps_dot_prod = 1.8 * asserts.select_eps_for_addition(dot_product.dtype)
    dot_product = safe_ops.safe_shrink(dot_product,
                                       -1,
                                       1,
                                       open_bounds=False,
                                       eps=eps_dot_prod)

    return 2.0 * tf.acos(tf.abs(dot_product))
Ejemplo n.º 5
0
  def test_normalize_random(self):
    """Tests that normalize works as intended."""
    random_quaternion = test_helpers.generate_random_test_quaternions()
    tensor_shape = random_quaternion.shape[:-1]

    unnormalized_random_quaternion = random_quaternion * 1.01
    quat = np.concatenate((random_quaternion, unnormalized_random_quaternion),
                          axis=0)
    mask = np.concatenate(
        (np.ones(shape=tensor_shape + (1,),
                 dtype=bool), np.zeros(shape=tensor_shape + (1,), dtype=bool)),
        axis=0)
    is_normalized_before = quaternion.is_normalized(quat)
    normalized = quaternion.normalize(quat)
    is_normalized_after = quaternion.is_normalized(normalized)

    self.assertAllEqual(mask, is_normalized_before)
    self.assertAllEqual(is_normalized_after,
                        np.ones(shape=is_normalized_after.shape, dtype=bool))
Ejemplo n.º 6
0
def transform_from_quat_and_trans(quaternion, trans_vector):
    """
    Method that creates an augmented transform which includes the batch size
    in the output shape
    :param quaternion: (batch_size, 4) quaternion vectors
    :param trans_vector: (batch_size, 3, 1) translation vectors
    :return: transform_augm (batch_size, 4, 4) augmented transform matrix
    """
    #We use [w, x, y, z] quaternion notation but TF Geometry lib expects [x, y, z, w]
    #quaternion = tf.concat([quaternion2[:, 1:], tf.expand_dims(quaternion2[:, 0], axis=1)], axis=-1)
    quaternion = tfg_quaternion.normalize(quaternion)
    #predicted_rot_mat = tfg_rot_mat.from_quaternion(quat_normalized)
    predicted_rot_mat = rot_matrix_from_quat_wxyz(quaternion)
    paddings = tf.constant([[0, 0], [0, 1], [0, 0]])
    predicted_rot_mat_augm = tf.pad(predicted_rot_mat,
                                    paddings,
                                    constant_values=0)
    decalib_qt_trans_augm = tf.pad(trans_vector, paddings, constant_values=1)
    transform_augm = tf.concat([predicted_rot_mat_augm, decalib_qt_trans_augm],
                               axis=-1)

    return transform_augm
Ejemplo n.º 7
0
def energy(vertices_rest_pose,
           vertices_deformed_pose,
           quaternions,
           edges,
           vertex_weight=None,
           edge_weight=None,
           conformal_energy=True,
           aggregate_loss=True,
           name=None):
    """Estimates an As Conformal As Possible (ACAP) fitting energy.

  For a given mesh in rest pose, this function evaluates a variant of the ACAP
  [1] fitting energy for a batch of deformed meshes. The vertex weights and edge
  weights are defined on the rest pose.

  The method implemented here is similar to [2], but with an added free variable
    capturing a scale factor per vertex.

  [1]: Yusuke Yoshiyasu, Wan-Chun Ma, Eiichi Yoshida, and Fumio Kanehiro.
  "As-Conformal-As-Possible Surface Registration." Computer Graphics Forum. Vol.
  33. No. 5. 2014.</br>
  [2]: Olga Sorkine, and Marc Alexa.
  "As-rigid-as-possible surface modeling". Symposium on Geometry Processing.
  Vol. 4. 2007.

  Note:
    In the description of the arguments, V corresponds to
      the number of vertices in the mesh, and E to the number of edges in this
      mesh.

  Note:
    In the following, A1 to An are optional batch dimensions.

  Args:
    vertices_rest_pose: A tensor of shape `[V, 3]` containing the position of
      all the vertices of the mesh in rest pose.
    vertices_deformed_pose: A tensor of shape `[A1, ..., An, V, 3]` containing
      the position of all the vertices of the mesh in deformed pose.
    quaternions: A tensor of shape `[A1, ..., An, V, 4]` defining a rigid
      transformation to apply to each vertex of the rest pose. See Section 2
      from [1] for further details.
    edges: A tensor of shape `[E, 2]` defining indices of vertices that are
      connected by an edge.
    vertex_weight: An optional tensor of shape `[V]` defining the weight
      associated with each vertex. Defaults to a tensor of ones.
    edge_weight: A tensor of shape `[E]` defining the weight of edges. Common
      choices for these weights include uniform weighting, and cotangent
      weights. Defaults to a tensor of ones.
    conformal_energy: A `bool` indicating whether each vertex is associated with
      a scale factor or not. If this parameter is True, scaling information must
      be encoded in the norm of `quaternions`. If this parameter is False, this
      function implements the energy described in [2].
    aggregate_loss: A `bool` defining whether the returned loss should be an
      aggregate measure. When True, the mean squared error is returned. When
      False, returns two losses for every edge of the mesh.
    name: A name for this op. Defaults to "as_conformal_as_possible_energy".

  Returns:
    When aggregate_loss is `True`, returns a tensor of shape `[A1, ..., An]`
    containing the ACAP energies. When aggregate_loss is `False`, returns a
    tensor of shape `[A1, ..., An, 2*E]` containing each term of the summation
    described in the equation 7 of [2].

  Raises:
    ValueError: if the shape of `vertices_rest_pose`, `vertices_deformed_pose`,
    `quaternions`, `edges`, `vertex_weight`, or `edge_weight` is not supported.
  """
    with tf.compat.v1.name_scope(name, "as_conformal_as_possible_energy", [
            vertices_rest_pose, vertices_deformed_pose, quaternions, edges,
            conformal_energy, vertex_weight, edge_weight
    ]):
        vertices_rest_pose = tf.convert_to_tensor(value=vertices_rest_pose)
        vertices_deformed_pose = tf.convert_to_tensor(
            value=vertices_deformed_pose)
        quaternions = tf.convert_to_tensor(value=quaternions)
        edges = tf.convert_to_tensor(value=edges)
        if vertex_weight is not None:
            vertex_weight = tf.convert_to_tensor(value=vertex_weight)
        if edge_weight is not None:
            edge_weight = tf.convert_to_tensor(value=edge_weight)

        shape.check_static(tensor=vertices_rest_pose,
                           tensor_name="vertices_rest_pose",
                           has_rank=2,
                           has_dim_equals=(-1, 3))
        shape.check_static(tensor=vertices_deformed_pose,
                           tensor_name="vertices_deformed_pose",
                           has_rank_greater_than=1,
                           has_dim_equals=(-1, 3))
        shape.check_static(tensor=quaternions,
                           tensor_name="quaternions",
                           has_rank_greater_than=1,
                           has_dim_equals=(-1, 4))
        shape.compare_batch_dimensions(tensors=(vertices_deformed_pose,
                                                quaternions),
                                       last_axes=(-3, -3),
                                       broadcast_compatible=False)
        shape.check_static(tensor=edges,
                           tensor_name="edges",
                           has_rank=2,
                           has_dim_equals=(-1, 2))
        tensors_with_vertices = [
            vertices_rest_pose, vertices_deformed_pose, quaternions
        ]
        names_with_vertices = [
            "vertices_rest_pose", "vertices_deformed_pose", "quaternions"
        ]
        axes_with_vertices = [-2, -2, -2]
        if vertex_weight is not None:
            shape.check_static(tensor=vertex_weight,
                               tensor_name="vertex_weight",
                               has_rank=1)
            tensors_with_vertices.append(vertex_weight)
            names_with_vertices.append("vertex_weight")
            axes_with_vertices.append(0)
        shape.compare_dimensions(tensors=tensors_with_vertices,
                                 axes=axes_with_vertices,
                                 tensor_names=names_with_vertices)
        if edge_weight is not None:
            shape.check_static(tensor=edge_weight,
                               tensor_name="edge_weight",
                               has_rank=1)
            shape.compare_dimensions(tensors=(edges, edge_weight),
                                     axes=(0, 0),
                                     tensor_names=("edges", "edge_weight"))

        if not conformal_energy:
            quaternions = quaternion.normalize(quaternions)
        # Extracts the indices of vertices.
        indices_i, indices_j = tf.unstack(edges, axis=-1)
        # Extracts the vertices we need per term.
        vertices_i_rest = tf.gather(vertices_rest_pose, indices_i, axis=-2)
        vertices_j_rest = tf.gather(vertices_rest_pose, indices_j, axis=-2)
        vertices_i_deformed = tf.gather(vertices_deformed_pose,
                                        indices_i,
                                        axis=-2)
        vertices_j_deformed = tf.gather(vertices_deformed_pose,
                                        indices_j,
                                        axis=-2)
        # Extracts the weights we need per term.
        weights_shape = vertices_i_rest.shape.as_list()[-2]
        if vertex_weight is not None:
            weight_i = tf.gather(vertex_weight, indices_i)
            weight_j = tf.gather(vertex_weight, indices_j)
        else:
            weight_i = weight_j = tf.ones(weights_shape,
                                          dtype=vertices_rest_pose.dtype)
        weight_i = tf.expand_dims(weight_i, axis=-1)
        weight_j = tf.expand_dims(weight_j, axis=-1)
        if edge_weight is not None:
            weight_ij = edge_weight
        else:
            weight_ij = tf.ones(weights_shape, dtype=vertices_rest_pose.dtype)
        weight_ij = tf.expand_dims(weight_ij, axis=-1)
        # Extracts the rotation we need per term.
        quaternion_i = tf.gather(quaternions, indices_i, axis=-2)
        quaternion_j = tf.gather(quaternions, indices_j, axis=-2)
        # Computes the energy.
        deformed_ij = vertices_i_deformed - vertices_j_deformed
        rotated_rest_ij = quaternion.rotate(
            (vertices_i_rest - vertices_j_rest), quaternion_i)
        energy_ij = weight_i * weight_ij * (deformed_ij - rotated_rest_ij)
        deformed_ji = vertices_j_deformed - vertices_i_deformed
        rotated_rest_ji = quaternion.rotate(
            (vertices_j_rest - vertices_i_rest), quaternion_j)
        energy_ji = weight_j * weight_ij * (deformed_ji - rotated_rest_ji)
        energy_ij_squared = vector.dot(energy_ij, energy_ij, keepdims=False)
        energy_ji_squared = vector.dot(energy_ji, energy_ji, keepdims=False)
        if aggregate_loss:
            average_energy_ij = tf.reduce_mean(input_tensor=energy_ij_squared,
                                               axis=-1)
            average_energy_ji = tf.reduce_mean(input_tensor=energy_ji_squared,
                                               axis=-1)
            return (average_energy_ij + average_energy_ji) / 2.0
        return tf.concat((energy_ij_squared, energy_ji_squared), axis=-1)