Ejemplo n.º 1
0
def inverse(dual_quaternion: type_alias.TensorLike,
            name: str = "dual_quaternion_inverse") -> tf.Tensor:
    """Computes the inverse of a dual quaternion.

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

  Args:
    dual_quaternion:  A TensorLike of shape `[A1, ..., An, 8]`, where the last
      dimension represents a dual quaternion.
    name: A name for this op that defaults to "dual_quaternion_inverse".

  Returns:
    A tensor of shape `[A1, ..., An, 8]`, where the last dimension represents
    a dual quaternion.

  Raises:
    ValueError: If the shape of `dual quaternion` is not supported.
  """
    with tf.name_scope(name):
        dual_quaternion = tf.convert_to_tensor(value=dual_quaternion)

        shape.check_static(tensor=dual_quaternion,
                           tensor_name="dual_quaternion",
                           has_dim_equals=(-1, 8))

        quaternion_real, quaternion_dual = tf.split(dual_quaternion, (4, 4),
                                                    axis=-1)

        quaternion_real_norm_squared = tf.norm(tensor=quaternion_real,
                                               axis=-1,
                                               keepdims=True)**2
        quaternion_real_conj = quaternion.conjugate(quaternion_real)

        quaternion_output_real = safe_ops.safe_signed_div(
            quaternion_real_conj, quaternion_real_norm_squared)

        normalized_dual = safe_ops.safe_signed_div(
            quaternion.conjugate(quaternion_dual),
            quaternion_real_norm_squared)
        normalized_dot_product = safe_ops.safe_signed_div(
            vector.dot(quaternion_real, quaternion_dual, keepdims=True),
            quaternion_real_norm_squared**2)
        quaternion_output_dual = (
            normalized_dual -
            2 * quaternion_real_conj * normalized_dot_product)

        return tf.concat((quaternion_output_real, quaternion_output_dual),
                         axis=-1)
Ejemplo n.º 2
0
def project(point_3d, focal, principal_point, name=None):
  r"""Projects a 3d point onto the 2d camera plane.

  Projects a 3d point \\((x, y, z)\\) to a 2d point \\((x', y')\\) onto the
  image plane with

  $$
  \begin{matrix}
  x' = \frac{f_x}{z}x + c_x, & y' = \frac{f_y}{z}y + c_y,
  \end{matrix}
  $$

  where \\((f_x, f_y)\\) is the focal length and \\((c_x, c_y)\\) the principal
  point.

  Note:
    In the following, A1 to An are optional batch dimensions that must be
    broadcast compatible.

  Args:
    point_3d: A tensor of shape `[A1, ..., An, 3]`, where the last dimension
      represents a 3d point to project.
    focal: A tensor of shape `[A1, ..., An, 2]`, where the last dimension
      represents a camera focal length.
    principal_point: A tensor of shape `[A1, ..., An, 2]`, where the last
      dimension represents a camera principal point.
    name: A name for this op that defaults to "perspective_project".

  Returns:
    A tensor of shape `[A1, ..., An, 2]`, where the last dimension represents
    a 2d point.

  Raises:
    ValueError: If the shape of `point_3d`, `focal`, or `principal_point` is not
    supported.
  """
  with tf.compat.v1.name_scope(name, "perspective_project",
                               [point_3d, focal, principal_point]):
    point_3d = tf.convert_to_tensor(value=point_3d)
    focal = tf.convert_to_tensor(value=focal)
    principal_point = tf.convert_to_tensor(value=principal_point)

    shape.check_static(
        tensor=point_3d, tensor_name="point_3d", has_dim_equals=(-1, 3))
    shape.check_static(
        tensor=focal, tensor_name="focal", has_dim_equals=(-1, 2))
    shape.check_static(
        tensor=principal_point,
        tensor_name="principal_point",
        has_dim_equals=(-1, 2))
    shape.compare_batch_dimensions(
        tensors=(point_3d, focal, principal_point),
        tensor_names=("point_3d", "focal", "principal_point"),
        last_axes=-2,
        broadcast_compatible=True)

    point_2d, depth = tf.split(point_3d, (2, 1), axis=-1)
    point_2d *= safe_ops.safe_signed_div(focal, depth)
    point_2d += principal_point
  return point_2d
Ejemplo n.º 3
0
def cartesian_to_spherical_coordinates(point_cartesian, name=None):
    """Function to transform Cartesian coordinates to spherical coordinates.

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

  Args:
    point_cartesian: A tensor of shape `[A1, ..., An, 3]`. In the last
      dimension, the data follows the x,y,z order.
    name: A name for this op. Defaults to 'cartesian_to_spherical_coordinates'.

  Returns:
    A tensor of shape `[A1, ..., An, 3]`. The last dimensions contains
    (r,theta,phi), where r is the sphere radius, theta the polar angle and phi
    the azimuthal angle.
  """
    with tf.compat.v1.name_scope(name, "cartesian_to_spherical_coordinates",
                                 [point_cartesian]):
        point_cartesian = tf.convert_to_tensor(value=point_cartesian)

        shape.check_static(tensor=point_cartesian,
                           tensor_name="point_cartesian",
                           has_dim_equals=(-1, 3))

        x, y, z = tf.unstack(point_cartesian, axis=-1)
        radius = tf.norm(tensor=point_cartesian, axis=-1)
        theta = tf.acos(safe_ops.safe_signed_div(z, radius))
        phi = tf.atan2(y, x)
        return tf.stack((radius, theta, phi), axis=-1)
Ejemplo n.º 4
0
def _brdf_normalization_factor(shininess: type_alias.TensorLike) -> tf.Tensor:
  """Returns the normalization factor needed to ensure energy conservation."""
  numerator = (shininess + 2.0) * (shininess + 4.0)
  denominator = 8.0 * math.pi * (
      tf.pow(tf.constant(2.0, dtype=shininess.dtype), -shininess / 2.0) +
      shininess)
  return safe_ops.safe_signed_div(numerator, denominator)
Ejemplo n.º 5
0
def evaluate(ground_truth: type_alias.TensorLike,
             prediction: type_alias.TensorLike,
             precision_function: Callable[...,
                                          Any] = precision_module.evaluate,
             recall_function: Callable[..., Any] = recall_module.evaluate,
             name: str = "fscore_evaluate") -> tf.Tensor:
    """Computes the fscore metric for the given ground truth and predicted labels.

  The fscore is calculated as 2 * (precision * recall) / (precision + recall)
  where the precision and recall are evaluated by the given function parameters.
  The precision and recall functions default to their definition for boolean
  labels (see https://en.wikipedia.org/wiki/Precision_and_recall for more
  details).

  Note:
    In the following, A1 to An are optional batch dimensions, which must be
    broadcast compatible.

  Args:
    ground_truth: A tensor of shape `[A1, ..., An, N]`, where the last axis
      represents the ground truth values.
    prediction: A tensor of shape `[A1, ..., An, N]`, where the last axis
      represents the predicted values.
    precision_function: The function to use for evaluating the precision.
      Defaults to the precision evaluation for binary ground-truth and
      predictions.
    recall_function: The function to use for evaluating the recall. Defaults to
      the recall evaluation for binary ground-truth and prediction.
    name: A name for this op. Defaults to "fscore_evaluate".

  Returns:
    A tensor of shape `[A1, ..., An]` that stores the fscore metric for the
    given ground truth labels and predictions.

  Raises:
    ValueError: if the shape of `ground_truth`, `prediction` is
    not supported.
  """
    with tf.name_scope(name):
        ground_truth = tf.convert_to_tensor(value=ground_truth)
        prediction = tf.convert_to_tensor(value=prediction)

        shape.compare_batch_dimensions(tensors=(ground_truth, prediction),
                                       tensor_names=("ground_truth",
                                                     "prediction"),
                                       last_axes=-1,
                                       broadcast_compatible=True)

        recall = recall_function(ground_truth, prediction)
        precision = precision_function(ground_truth, prediction)

        return safe_ops.safe_signed_div(2 * precision * recall,
                                        precision + recall)
Ejemplo n.º 6
0
def _normalize_pdf(pdf: TensorLike, name="normalize_pdf") -> tf.Tensor:
    """Normalizes a probability density function.

  Args:
    pdf: A tensor of shape `[A1, ..., An, M]` containing the probability
      distribution in M bins.
    name:  A name for this op that defaults to "_normalize_pdf".

  Returns:
    A tensor of shape `[A1, ..., An, M]`.
  """
    with tf.name_scope(name):
        pdf = tf.convert_to_tensor(value=pdf)
        pdf += 1e-5
        return safe_ops.safe_signed_div(pdf,
                                        tf.reduce_sum(pdf, -1, keepdims=True))
Ejemplo n.º 7
0
def evaluate(ground_truth,
             prediction,
             classes=None,
             reduce_average=True,
             prediction_to_category_function=_cast_to_int,
             name=None):
  """Computes the precision metric for the given ground truth and predictions.

  Note:
    In the following, A1 to An are optional batch dimensions, which must be
    broadcast compatible.

  Args:
    ground_truth: A tensor of shape `[A1, ..., An, N]`, where the last axis
      represents the ground truth labels. Will be cast to int32.
    prediction: A tensor of shape `[A1, ..., An, N]`, where the last axis
      represents the predictions (which can be continuous).
    classes: An integer or a list/tuple of integers representing the classes for
      which the precision will be evaluated. In case 'classes' is 'None', the
      number of classes will be inferred from the given labels and the precision
      will be calculated for each of the classes. Defaults to 'None'.
    reduce_average: Whether to calculate the average of the precision for each
      class and return a single precision value. Defaults to true.
    prediction_to_category_function: A function to associate a `prediction` to a
      category. Defaults to rounding down the value of the prediction to the
      nearest integer value.
    name: A name for this op. Defaults to "precision_evaluate".

  Returns:
    A tensor of shape `[A1, ..., An, C]`, where the last axis represents the
    precision calculated for each of the requested classes.

  Raises:
    ValueError: if the shape of `ground_truth`, `prediction` is not supported.
  """
  with tf.compat.v1.name_scope(name, "precision_evaluate",
                               [ground_truth, prediction]):
    ground_truth = tf.cast(
        x=tf.convert_to_tensor(value=ground_truth), dtype=tf.int32)
    prediction = tf.convert_to_tensor(value=prediction)

    shape.compare_batch_dimensions(
        tensors=(ground_truth, prediction),
        tensor_names=("ground_truth", "prediction"),
        last_axes=-1,
        broadcast_compatible=True)

    prediction = prediction_to_category_function(prediction)
    if classes is None:
      num_classes = tf.math.maximum(
          tf.math.reduce_max(input_tensor=ground_truth),
          tf.math.reduce_max(input_tensor=prediction)) + 1
      classes = tf.range(num_classes)
    else:
      classes = tf.convert_to_tensor(value=classes)
      # Make sure classes is a tensor of rank 1.
      classes = tf.reshape(classes, [1]) if tf.rank(classes) == 0 else classes

    # Create a confusion matrix for each of the classes (with dimensions
    # [A1, ..., An, C, N]).
    classes = tf.expand_dims(classes, -1)
    ground_truth_per_class = tf.equal(tf.expand_dims(ground_truth, -2), classes)
    prediction_per_class = tf.equal(tf.expand_dims(prediction, -2), classes)

    # Calculate the precision for each of the classes.
    true_positives = tf.math.reduce_sum(
        input_tensor=tf.cast(
            x=tf.math.logical_and(ground_truth_per_class, prediction_per_class),
            dtype=tf.float32),
        axis=-1)
    total_predicted_positives = tf.math.reduce_sum(
        input_tensor=tf.cast(x=prediction_per_class, dtype=tf.float32), axis=-1)

    precision_per_class = safe_ops.safe_signed_div(true_positives,
                                                   total_predicted_positives)
    if reduce_average:
      return tf.math.reduce_mean(input_tensor=precision_per_class, axis=-1)
    else:
      return precision_per_class
Ejemplo n.º 8
0
def unproject(point_2d, depth, focal, principal_point, name=None):
  r"""Unprojects a 2d point in 3d.

  Unprojects a 2d point \\((x', y')\\) to a 3d point \\((x, y, z)\\) knowing the
  depth \\(z\\) with

  $$
  \begin{matrix}
  x = \frac{z (x' - c_x)}{f_x}, & y = \frac{z(y' - c_y)}{f_y}, & z = z,
  \end{matrix}
  $$

  where \\((f_x, f_y)\\) is the focal length and \\((c_x, c_y)\\) the principal
  point.

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

  Args:
    point_2d: A tensor of shape `[A1, ..., An, 2]`, where the last dimension
      represents a 2d point to unproject.
    depth: A tensor of shape `[A1, ..., An, 1]`, where the last dimension
      represents the depth of a 2d point.
    focal: A tensor of shape `[A1, ..., An, 2]`, where the last dimension
      represents a camera focal length.
    principal_point: A tensor of shape `[A1, ..., An, 2]`, where the last
      dimension represents a camera principal point.
    name: A name for this op that defaults to "perspective_unproject".

  Returns:
    A tensor of shape `[A1, ..., An, 3]`, where the last dimension represents
    a 3d point.

  Raises:
    ValueError: If the shape of `point_2d`, `depth`, `focal`, or
    `principal_point` is not supported.
  """
  with tf.compat.v1.name_scope(name, "perspective_unproject",
                               [point_2d, depth, focal, principal_point]):
    point_2d = tf.convert_to_tensor(value=point_2d)
    depth = tf.convert_to_tensor(value=depth)
    focal = tf.convert_to_tensor(value=focal)
    principal_point = tf.convert_to_tensor(value=principal_point)

    shape.check_static(
        tensor=point_2d, tensor_name="point_2d", has_dim_equals=(-1, 2))
    shape.check_static(
        tensor=depth, tensor_name="depth", has_dim_equals=(-1, 1))
    shape.check_static(
        tensor=focal, tensor_name="focal", has_dim_equals=(-1, 2))
    shape.check_static(
        tensor=principal_point,
        tensor_name="principal_point",
        has_dim_equals=(-1, 2))
    shape.compare_batch_dimensions(
        tensors=(point_2d, depth, focal, principal_point),
        tensor_names=("point_2d", "depth", "focal", "principal_point"),
        last_axes=-2,
        broadcast_compatible=False)

    point_2d -= principal_point
    point_2d *= safe_ops.safe_signed_div(depth, focal)
    return tf.concat((point_2d, depth), axis=-1)
Ejemplo n.º 9
0
def ray(point_2d, focal, principal_point, name=None):
  r"""Computes the 3d ray for a 2d point (the z component of the ray is 1).

  Computes the 3d ray \\((r_x, r_y, 1)\\) from the camera center to a 2d point
  \\((x', y')\\) on the image plane with

  $$
  \begin{matrix}
  r_x = \frac{(x' - c_x)}{f_x}, & r_y = \frac{(y' - c_y)}{f_y}, & z = 1,
  \end{matrix}
  $$

  where \\((f_x, f_y)\\) is the focal length and \\((c_x, c_y)\\) the principal
  point. The camera optical center is assumed to be at \\((0, 0, 0)\\).

  Note:
    In the following, A1 to An are optional batch dimensions that must be
    broadcast compatible.

  Args:
    point_2d: A tensor of shape `[A1, ..., An, 2]`, where the last dimension
      represents a 2d point.
    focal: A tensor of shape `[A1, ..., An, 2]`, where the last dimension
      represents a camera focal length.
    principal_point: A tensor of shape `[A1, ..., An, 2]`, where the last
      dimension represents a camera principal point.
    name: A name for this op that defaults to "perspective_ray".

  Returns:
    A tensor of shape `[A1, ..., An, 3]`, where the last dimension represents
    a 3d ray.

  Raises:
    ValueError: If the shape of `point_2d`, `focal`, or `principal_point` is not
    supported.
  """
  with tf.compat.v1.name_scope(name, "perspective_ray",
                               [point_2d, focal, principal_point]):
    point_2d = tf.convert_to_tensor(value=point_2d)
    focal = tf.convert_to_tensor(value=focal)
    principal_point = tf.convert_to_tensor(value=principal_point)

    shape.check_static(
        tensor=point_2d, tensor_name="point_2d", has_dim_equals=(-1, 2))
    shape.check_static(
        tensor=focal, tensor_name="focal", has_dim_equals=(-1, 2))
    shape.check_static(
        tensor=principal_point,
        tensor_name="principal_point",
        has_dim_equals=(-1, 2))
    shape.compare_batch_dimensions(
        tensors=(point_2d, focal, principal_point),
        tensor_names=("point_2d", "focal", "principal_point"),
        last_axes=-2,
        broadcast_compatible=True)

    point_2d -= principal_point
    point_2d = safe_ops.safe_signed_div(point_2d, focal)
    padding = [[0, 0] for _ in point_2d.shape]
    padding[-1][-1] = 1
    return tf.pad(
        tensor=point_2d, paddings=padding, mode="CONSTANT", constant_values=1.0)
Ejemplo n.º 10
0
def get_barycentric_coordinates(triangle_vertices, pixels, name=None):
    """Computes the barycentric coordinates of pixels for 2D triangles.

  Barycentric coordinates of a point `p` are represented as coefficients
  $(w_1, w_2, w_3)$ corresponding to the masses placed at the vertices of a
  reference triangle if `p` is the center of mass. Barycentric coordinates are
  normalized so that $w_1 + w_2 + w_3 = 1$. These coordinates play an essential
  role in computing the pixel attributes (e.g. depth, color, normals, and
  texture coordinates) of a point lying on the surface of a triangle. The point
  `p` is inside the triangle if all of its barycentric coordinates are positive.

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

  Args:
    triangle_vertices: A tensor of shape `[A1, ..., An, 3, 2]`, where the last
      two dimensions represents the `x` and `y` coordinates for each vertex of a
      2D triangle.
    pixels: A tensor of shape `[A1, ..., An, N, 2]`, where `N` represents the
      number of pixels, and the last dimension represents the `x` and `y`
      coordinates of each pixel.
    name: A name for this op that defaults to
      "rasterizer_get_barycentric_coordinates".

  Returns:
    barycentric_coordinates: A float tensor of shape `[A1, ..., An, N, 3]`,
      representing the barycentric coordinates.
    valid: A boolean tensor of shape `[A1, ..., An, N], which is `True` where
      pixels are inside the triangle, and `False` otherwise.
  """
    with tf.compat.v1.name_scope(name,
                                 "rasterizer_get_barycentric_coordinates",
                                 [triangle_vertices, pixels]):
        triangle_vertices = tf.convert_to_tensor(value=triangle_vertices)
        pixels = tf.convert_to_tensor(value=pixels)

        shape.check_static(tensor=triangle_vertices,
                           tensor_name="triangle_vertices",
                           has_dim_equals=((-1, 2), (-2, 3)))
        shape.check_static(tensor=pixels,
                           tensor_name="pixels",
                           has_dim_equals=(-1, 2))
        shape.compare_batch_dimensions(tensors=(triangle_vertices, pixels),
                                       last_axes=(-3, -3),
                                       broadcast_compatible=True)

        vertex_1, vertex_2, vertex_3 = tf.unstack(tf.expand_dims(
            triangle_vertices, axis=-3),
                                                  axis=-2)
        vertex_x1, vertex_y1 = tf.unstack(vertex_1, axis=-1)
        vertex_x2, vertex_y2 = tf.unstack(vertex_2, axis=-1)
        vertex_x3, vertex_y3 = tf.unstack(vertex_3, axis=-1)
        pixels_x, pixels_y = tf.unstack(pixels, axis=-1)

        x1_minus_x3 = vertex_x1 - vertex_x3
        x3_minus_x2 = vertex_x3 - vertex_x2
        y3_minus_y1 = vertex_y3 - vertex_y1
        y2_minus_y3 = vertex_y2 - vertex_y3
        x_minus_x3 = pixels_x - vertex_x3
        y_minus_y3 = pixels_y - vertex_y3

        determinant = y2_minus_y3 * x1_minus_x3 - x3_minus_x2 * y3_minus_y1
        coordinate_1 = y2_minus_y3 * x_minus_x3 + x3_minus_x2 * y_minus_y3
        coordinate_1 = safe_ops.safe_signed_div(coordinate_1, determinant)
        coordinate_2 = y3_minus_y1 * x_minus_x3 + x1_minus_x3 * y_minus_y3
        coordinate_2 = safe_ops.safe_signed_div(coordinate_2, determinant)
        coordinate_3 = 1.0 - (coordinate_1 + coordinate_2)

        barycentric_coordinates = tf.stack(
            (coordinate_1, coordinate_2, coordinate_3), axis=-1)
        valid = tf.logical_and(
            tf.logical_and(coordinate_1 >= 0.0, coordinate_2 >= 0.0),
            coordinate_3 >= 0.0)
        return barycentric_coordinates, valid
Ejemplo n.º 11
0
def interpolate(points,
                weights,
                indices,
                normalize=True,
                allow_negative_weights=False,
                name=None):
    """Weighted interpolation for M-D point sets.

  Given an M-D point set, this function can be used to generate a new point set
  that is formed by interpolating a subset of points in the set.

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

  Args:
    points: A tensor with shape `[B1, ..., Bk, M] and rank R > 1, where M is the
      dimensionality of the points.
    weights: A tensor with shape `[A1, ..., An, P]`, where P is the number of
      points to interpolate for each output point.
    indices: A tensor of dtype tf.int32 and shape `[A1, ..., An, P, R-1]`, which
      contains the point indices to be used for each output point. The R-1
      dimensional axis gives the slice index of a single point in `points`. The
      first n+1 dimensions of weights and indices must match, or be broadcast
      compatible.
    normalize: A `bool` describing whether or not to normalize the weights on
      the last axis.
    allow_negative_weights: A `bool` describing whether or not negative weights
      are allowed.
    name: A name for this op. Defaults to "weighted_interpolate".

  Returns:
    A tensor of shape `[A1, ..., An, M]` storing the interpolated M-D
    points. The first n dimensions will be the same as weights and indices.
  """
    with tf.compat.v1.name_scope(name, "weighted_interpolate",
                                 [points, weights, indices]):
        points = tf.convert_to_tensor(value=points)
        weights = tf.convert_to_tensor(value=weights)
        indices = tf.convert_to_tensor(value=indices)

        shape.check_static(tensor=points,
                           tensor_name="points",
                           has_rank_greater_than=1)
        shape.check_static(tensor=indices,
                           tensor_name="indices",
                           has_rank_greater_than=1,
                           has_dim_equals=(-1, points.shape.ndims - 1))
        shape.compare_dimensions(tensors=(weights, indices),
                                 axes=(-1, -2),
                                 tensor_names=("weights", "indices"))
        shape.compare_batch_dimensions(tensors=(weights, indices),
                                       last_axes=(-2, -3),
                                       tensor_names=("weights", "indices"),
                                       broadcast_compatible=True)
        if not allow_negative_weights:
            weights = asserts.assert_all_above(weights, 0.0, open_bound=False)

        if normalize:
            sums = tf.reduce_sum(input_tensor=weights, axis=-1, keepdims=True)
            sums = asserts.assert_nonzero_norm(sums)
            weights = safe_ops.safe_signed_div(weights, sums)
        point_lists = tf.gather_nd(points, indices)
        return vector.dot(point_lists,
                          tf.expand_dims(weights, axis=-1),
                          axis=-2,
                          keepdims=False)