Esempio n. 1
0
def project(point_3d: type_alias.TensorLike,
            name: str = "orthographic_project") -> tf.Tensor:
  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' = x, & y' = y.
  \end{matrix}
  $$

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

  Args:
    point_3d: A tensor of shape `[A1, ..., An, 3]`, where the last dimension
      represents a 3d point to project.
    name: A name for this op that defaults to "orthographic_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` is not supported.
  """
  with tf.name_scope(name):
    point_3d = tf.convert_to_tensor(value=point_3d)

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

    point_xy, _ = tf.split(point_3d, (2, 1), axis=-1)
    return point_xy
Esempio n. 2
0
def loss(matte, weights, name=None):
  """Computes the matting loss function based on the matting Laplacian weights.

  Computes the matting loss function based on the `weights` generated by the
  `laplacian_weights` function which implements the approach proposed by Levin
  et al. in "A Closed Form Solution to Natural Image Matting".

  Args:
    matte: A tensor of shape `[B, H, W, 1]`.
    weights: A tensor containing the Laplacian weights computed by the
      `laplacian_weights` function.
    name: A name for this op. Defaults to "matting_loss".

  Returns:
    A tensor containing a scalar value defining the matting loss.

  Raises:
    ValueError: If the last dimension of `matte` is not 1. If `matte` is not
    of rank 4. If the last two dimensions of `weights` are not of the
    same size. If `weights` is not of rank 5. If `B` is different
    between `matte` and `weights`.
  """
  with tf.compat.v1.name_scope(name, "matting_loss", [matte, weights]):
    matte = tf.convert_to_tensor(value=matte)
    weights = tf.convert_to_tensor(value=weights)

    pixels = tf.compat.dimension_value(weights.shape[-1])
    shape.check_static(matte, has_rank=4, has_dim_equals=(-1, 1))
    shape.check_static(weights, has_rank=5, has_dim_equals=(-2, pixels))
    shape.compare_batch_dimensions(
        tensors=(matte, weights), last_axes=0, broadcast_compatible=False)

    size = np.sqrt(pixels)
    patches = tf.expand_dims(_image_patches(matte, size), axis=-2)
    losses = _quadratic_form(weights, patches)
    return tf.reduce_mean(input_tensor=losses)
Esempio n. 3
0
def match_intermediate_batch_dimensions(tensor1, tensor2):
    """Match the batch dimensions.

  Args:
    tensor1: A tensor of shape `[A1, M]`.
    tensor2: A tensor of shape `[A1, ..., An, N]`.
  Returns:
    A tensor of shape `[A1, ..., An, M]`.
  """
    shape.check_static(tensor=tensor1, tensor_name="tensor1", has_rank=2)
    shape.check_static(tensor=tensor2,
                       tensor_name="tensor2",
                       has_rank_greater_than=1)
    shape.compare_dimensions(tensors=(tensor1, tensor2),
                             tensor_names=("tensor1", "tensor2"),
                             axes=0)

    shape1 = tf.shape(tensor1)
    shape2 = tf.shape(tensor2)
    shape_diff = len(shape2) - len(shape1)
    new_shape = tf.concat([[shape1[0]], [1] * shape_diff, [shape1[-1]]],
                          axis=-1)
    target_shape = tf.concat([shape2[:-1], [shape1[-1]]], axis=-1)
    return tf.broadcast_to(tf.reshape(tensor1, new_shape), target_shape)
Esempio n. 4
0
def inverse(axis, angle, name=None):
  """Computes the axis-angle that is the inverse of the input axis-angle.

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

  Args:
    axis: A tensor of shape `[A1, ..., An, 3]`, where the last dimension
      represents a normalized axis.
    angle: A tensor of shape `[A1, ..., An, 1]` where the last dimension
      represents an angle.
    name: A name for this op that defaults to "axis_angle_inverse".

  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.

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

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

    axis = asserts.assert_normalized(axis)
    return axis, -angle
Esempio n. 5
0
def from_axis_angle(axis, angle, name=None):
  """Converts an axis-angle representation to a quaternion.

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

  Args:
    axis: A tensor of shape `[A1, ..., An, 3]`, where the last dimension
      represents a normalized axis.
    angle: A tensor of shape `[A1, ..., An, 1]`, where the last dimension
      represents an angle.
    name: A name for this op that defaults to "quaternion_from_axis_angle".

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

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

    shape.check_static(tensor=axis, tensor_name="axis", has_dim_equals=(-1, 3))
    shape.check_static(
        tensor=angle, tensor_name="angle", has_dim_equals=(-1, 1))
    shape.compare_batch_dimensions(
        tensors=(axis, angle), last_axes=-2, broadcast_compatible=True)
    axis = asserts.assert_normalized(axis)

    half_angle = 0.5 * angle
    w = tf.cos(half_angle)
    xyz = tf.sin(half_angle) * axis
    return tf.concat((xyz, w), axis=-1)
Esempio n. 6
0
def upsample(image, num_levels, name=None):
  """Generates the different levels of the pyramid (upsampling).

  Args:
    image: A tensor of shape `[B, H, W, C]`, where `B` is the batch size, `H`
      the height of the image, `W` the width of the image, and `C` the number of
      channels of the image.
    num_levels: The number of levels to generate.
    name: A name for this op that defaults to "pyramid_upsample".

  Returns:
    A list containing `num_levels` tensors of shape `[B, H_i, W_i, C]`, where
    `H_i` and `W_i` are the height and width of the upsampled image for the
    level i.

  Raises:
    ValueError: If the shape of `image` is not supported.
  """
  with tf.compat.v1.name_scope(name, "pyramid_upsample", [image]):
    image = tf.convert_to_tensor(value=image)

    shape.check_static(tensor=image, tensor_name="image", has_rank=4)

    return _build_pyramid(image, _upsample, num_levels)
Esempio n. 7
0
def multiply(quaternion1, quaternion2, name=None):
  """Multiplies two quaternions.

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

  Args:
    quaternion1:  A tensor of shape `[A1, ..., An, 4]`, where the last dimension
      represents a quaternion.
    quaternion2:  A tensor of shape `[A1, ..., An, 4]`, where the last dimension
      represents a quaternion.
    name: A name for this op that defaults to "quaternion_multiply".

  Returns:
    A tensor of shape `[A1, ..., An, 4]` representing quaternions.

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

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

    x1, y1, z1, w1 = tf.unstack(quaternion1, axis=-1)
    x2, y2, z2, w2 = tf.unstack(quaternion2, axis=-1)
    x = x1 * w2 + y1 * z2 - z1 * y2 + w1 * x2
    y = -x1 * z2 + y1 * w2 + z1 * x2 + w1 * y2
    z = x1 * y2 - y1 * x2 + z1 * w2 + w1 * z2
    w = -x1 * x2 - y1 * y2 - z1 * z2 + w1 * w2
    return tf.stack((x, y, z, w), axis=-1)
Esempio n. 8
0
def from_euler_with_small_angles_approximation(angles, name=None):
  r"""Convert an Euler angle representation to a rotation matrix.

  The resulting matrix is $$\mathbf{R} = \mathbf{R}_z\mathbf{R}_y\mathbf{R}_x$$.
  Under the small angle assumption, $$\sin(x)$$ and $$\cos(x)$$ can be
  approximated by their second order Taylor expansions, where
  $$\sin(x) \approx x$$ and $$\cos(x) \approx 1 - \frac{x^2}{2}$$.
  In the current implementation, the smallness of the angles is not verified.

  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 small 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 "rotation_matrix_3d_from_euler".

  Returns:
    A tensor of shape `[A1, ..., An, 3, 3]`, where the last two dimensions
    represent a 3d rotation matrix.

  Raises:
    ValueError: If the shape of `angles` is not supported.
  """
  with tf.compat.v1.name_scope(
      name, "rotation_matrix_3d_from_euler_with_small_angles", [angles]):
    angles = tf.convert_to_tensor(value=angles)

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

    sin_angles = angles
    cos_angles = 1.0 - 0.5 * tf.square(angles)
    return _build_matrix_from_sines_and_cosines(sin_angles, cos_angles)
Esempio n. 9
0
def spherical_to_cartesian_coordinates(point_spherical, name=None):
  """Function to transform Cartesian coordinates to spherical coordinates.

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

  Args:
    point_spherical: A tensor of shape `[A1, ..., An, 3]`. The last dimension
      contains r, theta, and phi that respectively correspond to the radius,
      polar angle and azimuthal angle; r must be non-negative.
    name: A name for this op. Defaults to 'spherical_to_cartesian_coordinates'.

  Raises:
    tf.errors.InvalidArgumentError: If r, theta or phi contains out of range
    data.

  Returns:
    A tensor of shape `[A1, ..., An, 3]`, where the last dimension contains the
    cartesian coordinates in x,y,z order.
  """
  with tf.compat.v1.name_scope(name, "spherical_to_cartesian_coordinates",
                               [point_spherical]):
    point_spherical = tf.convert_to_tensor(value=point_spherical)

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

    r, theta, phi = tf.unstack(point_spherical, axis=-1)
    r = asserts.assert_all_above(r, 0)
    tmp = r * tf.sin(theta)
    x = tmp * tf.cos(phi)
    y = tmp * tf.sin(phi)
    z = r * tf.cos(theta)
    return tf.stack((x, y, z), axis=-1)
Esempio n. 10
0
def is_normalized(axis, angle, atol=1e-3, name=None):
    """Determines if the axis-angle is normalized or not.

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

  Args:
    axis: A tensor of shape `[A1, ..., An, 3]`, where the last dimension
      represents a normalized axis.
    angle: A tensor of shape `[A1, ..., An, 1]` where the last dimension
      represents an angle.
    atol: The absolute tolerance parameter.
    name: A name for this op that defaults to "axis_angle_is_normalized".

  Returns:
    A tensor of shape `[A1, ..., An, 1]`, where False indicates that the axis is
    not normalized.
  """
    with tf.compat.v1.name_scope(name, "axis_angle_is_normalized",
                                 [axis, angle]):
        axis = tf.convert_to_tensor(value=axis)
        angle = tf.convert_to_tensor(value=angle)

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

        norms = tf.norm(tensor=axis, axis=-1, keepdims=True)
        return tf.abs(norms - 1.) < atol
Esempio n. 11
0
def cross(vector1, vector2, axis=-1, name=None):
  """Computes the cross product between two tensors along an axis.

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

  Args:
    vector1: A tensor of shape `[A1, ..., Ai = 3, ..., An]`, where the dimension
      i = axis represents a 3d vector.
    vector2: A tensor of shape `[A1, ..., Ai = 3, ..., An]`, where the dimension
      i = axis represents a 3d vector.
    axis: The dimension along which to compute the cross product.
    name: A name for this op which defaults to "vector_cross".

  Returns:
    A tensor of shape `[A1, ..., Ai = 3, ..., An]`, where the dimension i = axis
    represents the result of the cross product.
  """
  with tf.compat.v1.name_scope(name, "vector_cross", [vector1, vector2]):
    vector1 = tf.convert_to_tensor(value=vector1)
    vector2 = tf.convert_to_tensor(value=vector2)

    shape.check_static(
        tensor=vector1, tensor_name="vector1", has_dim_equals=(axis, 3))
    shape.check_static(
        tensor=vector2, tensor_name="vector2", has_dim_equals=(axis, 3))
    shape.compare_batch_dimensions(
        tensors=(vector1, vector2), last_axes=-1, broadcast_compatible=True)

    vector1_x, vector1_y, vector1_z = tf.unstack(vector1, axis=axis)
    vector2_x, vector2_y, vector2_z = tf.unstack(vector2, axis=axis)
    n_x = vector1_y * vector2_z - vector1_z * vector2_y
    n_y = vector1_z * vector2_x - vector1_x * vector2_z
    n_z = vector1_x * vector2_y - vector1_y * vector2_x
    return tf.stack((n_x, n_y, n_z), axis=axis)
Esempio n. 12
0
def check_valid_graph_unpooling_input(data, pool_map, sizes):
  """Checks that the inputs are valid for graph unpooling.

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

  Args:
    data: A `float` tensor with shape `[A1, ..., A3, V1, C]`.
    pool_map: A `SparseTensor` with the same type as `data` and with shape
      `[A1, ..., A3, V1, V2]`.
    sizes: An `int` tensor of shape `[A1, ..., A3, 2]`. Can be `None`.

  Raises:
    TypeError: if the input types are invalid.
    ValueError: if the input dimensions are invalid.
  """
  if not data.dtype.is_floating:
    raise TypeError("'data' must have a float type.")
  if pool_map.dtype != data.dtype:
    raise TypeError("'pool_map' and 'data' must have the same type.")
  if sizes is not None and not sizes.dtype.is_integer:
    raise TypeError("'sizes' must have an integer type.")

  data_ndims = data.shape.ndims
  shape.check_static(tensor=data, tensor_name="data", has_rank_greater_than=1)
  shape.check_static(tensor=data, tensor_name="data", has_rank_less_than=6)
  shape.check_static(
      tensor=pool_map, tensor_name="pool_map", has_rank=data_ndims)
  shape.compare_dimensions(
      tensors=(data, pool_map),
      tensor_names=("data", "pool_map"),
      axes=(-2, -2))
  if sizes is None:
    shape.compare_batch_dimensions(
        tensors=(data, pool_map),
        tensor_names=("data", "pool_map"),
        last_axes=-3,
        broadcast_compatible=False)
  else:
    shape.check_static(
        tensor=sizes, tensor_name="sizes", has_rank=data_ndims - 1)
    shape.compare_batch_dimensions(
        tensors=(data, pool_map, sizes),
        tensor_names=("data", "pool_map", "sizes"),
        last_axes=(-3, -3, -2),
        broadcast_compatible=False)
Esempio n. 13
0
def compute_radiance(
        rgba_values: type_alias.TensorLike,
        distances: type_alias.TensorLike,
        name: str = "ray_radiance") -> Tuple[tf.Tensor, tf.Tensor, tf.Tensor]:
    """Renders the rgba values for points along a ray, as described in ["NeRF Representing Scenes as Neural Radiance Fields for View Synthesis"](https://github.com/bmild/nerf).

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

  Args:
    rgba_values: A tensor of shape `[A1, ..., An, N, 4]`, where N are the
      samples on the ray.
    distances: A tensor of shape `[A1, ..., An, N]` containing the distances
      between the samples, where N are the samples on the ray.
    name: A name for this op. Defaults to "ray_radiance".

  Returns:
    A tensor of shape `[A1, ..., An, 3]` for the estimated rgb values,
    a tensor of shape `[A1, ..., An, 1]` for the estimated density values,
    and a tensor of shape `[A1, ..., An, N]` for the sample weights.
  """

    with tf.name_scope(name):
        rgba_values = tf.convert_to_tensor(value=rgba_values)
        distances = tf.convert_to_tensor(value=distances)
        distances = tf.expand_dims(distances, -1)

        shape.check_static(tensor=rgba_values,
                           tensor_name="rgba_values",
                           has_dim_equals=(-1, 4))
        shape.check_static(tensor=rgba_values,
                           tensor_name="rgba_values",
                           has_rank_greater_than=1)
        shape.check_static(tensor=distances,
                           tensor_name="distances",
                           has_rank_greater_than=1)
        shape.compare_batch_dimensions(tensors=(rgba_values, distances),
                                       tensor_names=("ray_values", "dists"),
                                       last_axes=-3,
                                       broadcast_compatible=True)
        shape.compare_dimensions(tensors=(rgba_values, distances),
                                 tensor_names=("ray_values", "dists"),
                                 axes=-2)

        rgb, density = tf.split(rgba_values, [3, 1], axis=-1)
        alpha = 1. - tf.exp(-density * distances)
        alpha = tf.squeeze(alpha, -1)
        ray_sample_weights = alpha * tf.math.cumprod(
            1. - alpha + 1e-10, -1, exclusive=True)
        ray_rgb = tf.reduce_sum(
            input_tensor=tf.expand_dims(ray_sample_weights, -1) * rgb, axis=-2)
        ray_alpha = tf.expand_dims(tf.reduce_sum(
            input_tensor=ray_sample_weights, axis=-1),
                                   axis=-1)
        return ray_rgb, ray_alpha, ray_sample_weights
Esempio n. 14
0
def rasterizer_bounding_box(triangle_vertices_2d,
                            image_width,
                            image_height,
                            name=None):
    """Compute 2D bounding boxes for 2D triangles inside an image.

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

  Args:
    triangle_vertices_2d: A tensor of shape `[A1, ..., An, 3, 2], where the last
      two dimensions represent the x, y coordinates for each vertex in a
      triangle.
    image_width: A scalar tensor or an `int`.
    image_height: A scalar tensor or an `int`.
    name: A name for this op that defaults to "rasterizer_bounding_box".

  Returns:
    bottom_right_corner: A tensor of shape `[A1, ..., An, 2], where the last
      dimension represents the x_max, y_max of the 2D bounding boxes.
    top_left_corner: A tensor of shape `[A1, ..., An, 2], where the last
      dimension represents the x_min, y_min of the 2D bounding boxes.
  """
    with tf.compat.v1.name_scope(
            name, "rasterizer_bounding_box",
        [triangle_vertices_2d, image_width, image_height]):

        triangle_vertices_2d = tf.convert_to_tensor(value=triangle_vertices_2d)
        image_width = tf.convert_to_tensor(value=image_width,
                                           dtype=triangle_vertices_2d.dtype)
        image_height = tf.convert_to_tensor(value=image_height,
                                            dtype=triangle_vertices_2d.dtype)

        shape.check_static(tensor=triangle_vertices_2d,
                           tensor_name="triangle_vertices_2d",
                           has_dim_equals=((-1, 2), (-2, 3)))
        shape.check_static(tensor=image_width,
                           tensor_name="image_width",
                           has_rank=0)
        shape.check_static(tensor=image_height,
                           tensor_name="image_height",
                           has_rank=0)

        # clipping by the image size
        x, y = tf.unstack(triangle_vertices_2d, axis=-1)
        x = tf.clip_by_value(x, -image_width / 2, image_width / 2 - 1)
        y = tf.clip_by_value(y, -image_height / 2, image_height / 2 - 1)
        xy = tf.stack((x, y), axis=-1)

        # calculate the bottom right and top left corners of the 2d bounding box
        bottom_right_corner = tf.reduce_max(input_tensor=xy, axis=-2)
        bottom_right_corner = tf.math.ceil(bottom_right_corner)
        top_left_corner = tf.reduce_min(input_tensor=xy, axis=-2)
        top_left_corner = tf.math.floor(top_left_corner)
        return bottom_right_corner, top_left_corner
Esempio n. 15
0
def get_bounding_box(triangle_vertices, image_width, image_height, name=None):
    """Computes 2D bounding boxes for 2D triangles inside an image.

  This function rounds the estimated bounding box corners and therefore has zero
  derivative everywhere with respect to the vertices. Note that this does not
  prevent the rasterizer from being differentiable.

  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 represent the `x` and `y` coordinates of each vertex in a
      triangle.
    image_width: A scalar tensor or a `float`.
    image_height: A scalar tensor or a `float`.
    name: A name for this op that defaults to "rasterizer_get_bounding_box".

  Returns:
    bottom_right_corner: A tensor of shape `[A1, ..., An, 2], where the last
      dimension represents the `x_max` and `y_max` of the 2D bounding boxes.
    top_left_corner: A tensor of shape `[A1, ..., An, 2], where the last
      dimension represents the `x_min` and `y_min` of the 2D bounding boxes.
  """
    with tf.compat.v1.name_scope(
            name, "rasterizer_get_bounding_box",
        [triangle_vertices, image_width, image_height]):

        triangle_vertices = tf.convert_to_tensor(value=triangle_vertices)
        image_width = tf.convert_to_tensor(value=image_width,
                                           dtype=triangle_vertices.dtype)
        image_height = tf.convert_to_tensor(value=image_height,
                                            dtype=triangle_vertices.dtype)

        shape.check_static(tensor=triangle_vertices,
                           tensor_name="triangle_vertices",
                           has_dim_equals=((-1, 2), (-2, 3)))
        shape.check_static(tensor=image_width,
                           tensor_name="image_width",
                           has_rank=0)
        shape.check_static(tensor=image_height,
                           tensor_name="image_height",
                           has_rank=0)

        max_clip_values = tf.stack((image_width / 2, image_height / 2),
                                   axis=-1)
        min_clip_values = -max_clip_values
        clipped_vertices = tf.clip_by_value(triangle_vertices, min_clip_values,
                                            max_clip_values)
        bottom_right_corner = tf.reduce_max(input_tensor=clipped_vertices,
                                            axis=-2)
        bottom_right_corner = tf.math.ceil(bottom_right_corner)
        top_left_corner = tf.reduce_min(input_tensor=clipped_vertices, axis=-2)
        top_left_corner = tf.math.floor(top_left_corner)
        return bottom_right_corner, top_left_corner
Esempio n. 16
0
def rotate_zonal_harmonics(
        zonal_coeffs: TensorLike,
        theta: TensorLike,
        phi: TensorLike,
        name: str = "spherical_harmonics_rotate_zonal_harmonics"
) -> TensorLike:
    """Rotates zonal harmonics.

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

  Args:
    zonal_coeffs: A tensor of shape `[C]` storing zonal harmonics coefficients.
    theta: A tensor of shape `[A1, ..., An, 1]` storing polar angles.
    phi: A tensor of shape `[A1, ..., An, 1]` storing azimuthal angles.
    name: A name for this op. Defaults to
      "spherical_harmonics_rotate_zonal_harmonics".

  Returns:
    A tensor of shape `[A1, ..., An, C*C]` storing coefficients of the rotated
    harmonics.

  Raises:
    ValueError: If the shape of `zonal_coeffs`, `theta` or `phi` is not
      supported.
  """
    with tf.name_scope(name):
        zonal_coeffs = tf.convert_to_tensor(value=zonal_coeffs)
        theta = tf.convert_to_tensor(value=theta)
        phi = tf.convert_to_tensor(value=phi)

        shape.check_static(tensor=zonal_coeffs,
                           tensor_name="zonal_coeffs",
                           has_rank=1)
        shape.check_static(tensor=phi,
                           tensor_name="phi",
                           has_dim_equals=(-1, 1))
        shape.check_static(tensor=theta,
                           tensor_name="theta",
                           has_dim_equals=(-1, 1))
        shape.compare_batch_dimensions(tensors=(theta, phi),
                                       last_axes=-2,
                                       tensor_names=("theta", "phi"),
                                       broadcast_compatible=False)

        tiled_zonal_coeffs = tile_zonal_coefficients(zonal_coeffs)
        max_band = zonal_coeffs.shape.as_list()[-1]
        l, m = generate_l_m_permutations(max_band - 1)
        broadcast_shape = theta.shape.as_list()[:-1] + l.shape.as_list()
        l_broadcasted = tf.broadcast_to(l, broadcast_shape)
        m_broadcasted = tf.broadcast_to(m, broadcast_shape)
        n_star = tf.sqrt(4.0 * np.pi /
                         (2.0 * tf.cast(l, dtype=theta.dtype) + 1.0))
        return n_star * tiled_zonal_coeffs * evaluate_spherical_harmonics(
            l_broadcasted, m_broadcasted, theta, phi)
Esempio n. 17
0
def rotate(point, axis, angle, name=None):
    r"""Rotates a 3d point using an axis-angle by applying the Rodrigues' formula.

  Rotates a vector $$\mathbf{v} \in {\mathbb{R}^3}$$ into a vector
  $$\mathbf{v}' \in {\mathbb{R}^3}$$ using the Rodrigues' rotation formula:

  $$\mathbf{v}'=\mathbf{v}\cos(\theta)+(\mathbf{a}\times\mathbf{v})\sin(\theta)
  +\mathbf{a}(\mathbf{a}\cdot\mathbf{v})(1-\cos(\theta)).$$

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

  Args:
    point: A tensor of shape `[A1, ..., An, 3]`, where the last dimension
      represents a 3d point to rotate.
    axis: A tensor of shape `[A1, ..., An, 3]`, where the last dimension
      represents a normalized axis.
    angle: A tensor of shape `[A1, ..., An, 1]`, where the last dimension
      represents an angle.
    name: A name for this op that defaults to "axis_angle_rotate".

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

  Raises:
    ValueError: If `point`, `axis`, or `angle` are of different shape or if
    their respective shape is not supported.
  """
    with tf.compat.v1.name_scope(name, "axis_angle_rotate",
                                 [point, axis, angle]):
        point = tf.convert_to_tensor(value=point)
        axis = tf.convert_to_tensor(value=axis)
        angle = tf.convert_to_tensor(value=angle)

        shape.check_static(tensor=point,
                           tensor_name="point",
                           has_dim_equals=(-1, 3))
        shape.check_static(tensor=axis,
                           tensor_name="axis",
                           has_dim_equals=(-1, 3))
        shape.check_static(tensor=angle,
                           tensor_name="angle",
                           has_dim_equals=(-1, 1))
        shape.compare_batch_dimensions(tensors=(point, axis, angle),
                                       tensor_names=("point", "axis", "angle"),
                                       last_axes=-2,
                                       broadcast_compatible=True)
        axis = asserts.assert_normalized(axis)

        cos_angle = tf.cos(angle)
        axis_dot_point = vector.dot(axis, point)
        return point * cos_angle + vector.cross(axis, point) * tf.sin(
            angle) + axis * axis_dot_point * (1.0 - cos_angle)
Esempio n. 18
0
def check_valid_graph_convolution_input(data: type_alias.TensorLike,
                                        neighbors: tf.sparse.SparseTensor,
                                        sizes: type_alias.TensorLike):
    """Checks that the inputs are valid for graph convolution ops.

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

  Args:
    data: A `float` tensor with shape `[A1, ..., An, V1, V2]`.
    neighbors: A SparseTensor with the same type as `data` and with shape `[A1,
      ..., An, V1, V1]`.
    sizes: An `int` tensor of shape `[A1, ..., An]`. Optional, can be `None`.

  Raises:
    TypeError: if the input types are invalid.
    ValueError: if the input dimensions are invalid.
  """
    if not data.dtype.is_floating:
        raise TypeError("'data' must have a float type.")
    if neighbors.dtype != data.dtype:
        raise TypeError("'neighbors' and 'data' must have the same type.")
    if sizes is not None and not sizes.dtype.is_integer:
        raise TypeError("'sizes' must have an integer type.")
    if not isinstance(neighbors, tf.sparse.SparseTensor):
        raise ValueError("'neighbors' must be a SparseTensor.")

    data_ndims = data.shape.ndims
    shape.check_static(tensor=data,
                       tensor_name="data",
                       has_rank_greater_than=1)
    shape.check_static(tensor=neighbors,
                       tensor_name="neighbors",
                       has_rank=data_ndims)
    if not _is_dynamic_shape(tensors=(data, neighbors)):
        shape.compare_dimensions(tensors=(data, neighbors, neighbors),
                                 tensor_names=("data", "neighbors",
                                               "neighbors"),
                                 axes=(-2, -2, -1))
    if sizes is None:
        shape.compare_batch_dimensions(tensors=(data, neighbors),
                                       tensor_names=("data", "neighbors"),
                                       last_axes=-3,
                                       broadcast_compatible=False)
    else:
        shape.check_static(tensor=sizes,
                           tensor_name="sizes",
                           has_rank=data_ndims - 2)
        shape.compare_batch_dimensions(tensors=(data, neighbors, sizes),
                                       tensor_names=("data", "neighbors",
                                                     "sizes"),
                                       last_axes=(-3, -3, -1),
                                       broadcast_compatible=False)
Esempio n. 19
0
def partition_sums_2d(data, group_ids, row_weights=None, name=None):
    """Sum over subsets of rows in a 2-D tensor.

  Args:
    data: 2-D tensor with shape `[D1, D2]`.
    group_ids: 1-D `int` tensor with shape `[D1]`.
    row_weights: 1-D tensor with shape `[D1]`. Can be `None`.
    name: A name for this op. Defaults to 'utils_partition_sums_2d'.

  Returns:
    A 2-D tensor with shape `[max(group_ids) + 1, D2]` where
      `output[i, :] = sum(data[j, :] * weight[j] * 1(group_ids[j] == i)), 1(.)`
      is the indicator function.

  Raises:
    ValueError: if the inputs have invalid dimensions or types.
  """
    with tf.compat.v1.name_scope(name, "utils_partition_sums_2d",
                                 [data, group_ids, row_weights]):
        data = tf.convert_to_tensor(value=data)
        group_ids = tf.convert_to_tensor(value=group_ids)
        if not group_ids.dtype.is_integer:
            raise TypeError("'group_ids' must be an integer tensor.")
        elif group_ids.dtype != tf.int64:
            group_ids = tf.cast(group_ids, dtype=tf.int64)
        if row_weights is None:
            row_weights = tf.ones_like(group_ids, dtype=data.dtype)
        else:
            row_weights = tf.convert_to_tensor(value=row_weights)

        if row_weights.dtype != data.dtype:
            raise TypeError(
                "'data' and 'row_weights' must have the same type.")
        shape.check_static(tensor=data, tensor_name="data", has_rank=2)
        shape.check_static(tensor=group_ids,
                           tensor_name="group_ids",
                           has_rank=1)
        shape.check_static(tensor=row_weights,
                           tensor_name="row_weights",
                           has_rank=1)
        shape.compare_dimensions(tensors=(data, group_ids, row_weights),
                                 tensor_names=("data", "group_ids",
                                               "row_weights"),
                                 axes=0)

        num_rows = tf.size(input=group_ids, out_type=tf.int64)
        sparse_indices = tf.stack((group_ids, tf.range(num_rows)), axis=1)
        out_shape = (tf.reduce_max(input_tensor=group_ids) + 1, num_rows)
        sparse = tf.SparseTensor(sparse_indices,
                                 row_weights,
                                 dense_shape=out_shape)
        return tf.sparse.sparse_dense_matmul(sparse, data)
Esempio n. 20
0
def normal(v0: type_alias.TensorLike,
           v1: type_alias.TensorLike,
           v2: type_alias.TensorLike,
           clockwise: bool = False,
           normalize: bool = True,
           name: str = "triangle_normal") -> tf.Tensor:
    """Computes face normals (triangles).

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

  Args:
    v0: A tensor of shape `[A1, ..., An, 3]`, where the last dimension
      represents the first vertex of a triangle.
    v1: A tensor of shape `[A1, ..., An, 3]`, where the last dimension
      represents the second vertex of a triangle.
    v2: A tensor of shape `[A1, ..., An, 3]`, where the last dimension
      represents the third vertex of a triangle.
    clockwise: Winding order to determine front-facing triangles.
    normalize: A `bool` indicating whether output normals should be normalized
      by the function.
    name: A name for this op. Defaults to "triangle_normal".

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

  Raises:
    ValueError: If the shape of `v0`, `v1`, or `v2` is not supported.
  """
    with tf.name_scope(name):
        v0 = tf.convert_to_tensor(value=v0)
        v1 = tf.convert_to_tensor(value=v1)
        v2 = tf.convert_to_tensor(value=v2)

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

        normal_vector = vector.cross(v1 - v0, v2 - v0, axis=-1)
        normal_vector = asserts.assert_nonzero_norm(normal_vector)
        if not clockwise:
            normal_vector *= -1.0
        if normalize:
            return tf.nn.l2_normalize(normal_vector, axis=-1)
        return normal_vector
Esempio n. 21
0
def compute_density(density_values, distances, name=None):
    """Renders the density values (alpha) for points along a ray, as described in ["NeRF Representing Scenes as Neural Radiance Fields for View Synthesis"](https://github.com/bmild/nerf).

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

  Args:
    density_values: A tensor of shape `[A1, ..., An, N, 1]`,
      where N are the samples on the ray.
    distances: A tensor of shape `[A1, ..., An, N]` containing the distances
      between the samples, where N are the samples on the ray.
    name: A name for this op. Defaults to "ray_radiance".

  Returns:
    A tensor of shape `[A1, ..., An, 1]` for the estimated density values,
    and a tensor of shape `[A1, ..., An, N]` for the sample weights.
  """

    with tf.compat.v1.name_scope(name, "ray_density",
                                 [density_values, distances]):
        density_values = tf.convert_to_tensor(value=density_values)
        distances = tf.convert_to_tensor(value=distances)
        distances = tf.expand_dims(distances, -1)

        shape.check_static(tensor=density_values,
                           tensor_name="density_values",
                           has_dim_equals=(-1, 1))
        shape.check_static(tensor=density_values,
                           tensor_name="density_values",
                           has_rank_greater_than=1)
        shape.check_static(tensor=distances,
                           tensor_name="distances",
                           has_rank_greater_than=1)
        shape.compare_batch_dimensions(tensors=(density_values, distances),
                                       tensor_names=("density_values",
                                                     "dists"),
                                       last_axes=-3,
                                       broadcast_compatible=True)
        shape.compare_dimensions(tensors=(density_values, distances),
                                 tensor_names=("density_values", "dists"),
                                 axes=-2)

        alpha = 1. - tf.exp(-density_values * distances)
        alpha = tf.squeeze(alpha, -1)
        ray_sample_weights = alpha * tf.math.cumprod(
            1. - alpha + 1e-10, -1, exclusive=True)
        ray_alpha = tf.expand_dims(tf.reduce_sum(ray_sample_weights, -1),
                                   axis=-1)
        return ray_alpha, ray_sample_weights
Esempio n. 22
0
def area(v0: type_alias.TensorLike,
         v1: type_alias.TensorLike,
         v2: type_alias.TensorLike,
         name: str = "triangle_area") -> tf.Tensor:
    """Computes triangle areas.

    Note: Computed triangle area = 0.5 * | e1 x e2 | where e1 and e2 are edges
      of triangle. A degenerate triangle will return 0 area, whereas the normal
      for a degenerate triangle is not defined.


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

  Args:
    v0: A tensor of shape `[A1, ..., An, 3]`, where the last dimension
      represents the first vertex of a triangle.
    v1: A tensor of shape `[A1, ..., An, 3]`, where the last dimension
      represents the second vertex of a triangle.
    v2: A tensor of shape `[A1, ..., An, 3]`, where the last dimension
      represents the third vertex of a triangle.
    name: A name for this op. Defaults to "triangle_area".

  Returns:
    A tensor of shape `[A1, ..., An, 1]`, where the last dimension represents
      a normalized vector.
  """
    with tf.name_scope(name):
        v0 = tf.convert_to_tensor(value=v0)
        v1 = tf.convert_to_tensor(value=v1)
        v2 = tf.convert_to_tensor(value=v2)

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

        normals = vector.cross(v1 - v0, v2 - v0, axis=-1)
        return 0.5 * tf.linalg.norm(tensor=normals, axis=-1, keepdims=True)
Esempio n. 23
0
def reconstruct(image, coeff_mul, coeff_add, name=None):
    """Reconstruct the matte from the image using the linear coefficients.

  Reconstruct the matte from the image using the linear coefficients (a, b)
  returned by the linear_coefficients function.

  Args:
    image: A tensor of shape `[B, H, W, C]` .
    coeff_mul: A tensor of shape `[B, H, W, C]` representing the multiplicative
      part of the linear coefficients.
    coeff_add: A tensor of shape `[B, H, W, 1]` representing the additive part
      of the linear coefficients.
    name: A name for this op. Defaults to "matting_reconstruct".

  Returns:
    A tensor of shape `[B, H, W, 1]` containing the mattes.

  Raises:
    ValueError: If `image`, `coeff_mul`, or `coeff_add` are not of rank 4. If
    the last dimension of `coeff_add` is not 1. If the batch dimensions of
    `image`, `coeff_mul`, and `coeff_add` do not match.
  """
    with tf.compat.v1.name_scope(name, "matting_reconstruct",
                                 [image, coeff_mul, coeff_add]):
        image = tf.convert_to_tensor(value=image)
        coeff_mul = tf.convert_to_tensor(value=coeff_mul)
        coeff_add = tf.convert_to_tensor(value=coeff_add)

        shape.check_static(image, has_rank=4)
        shape.check_static(coeff_mul, has_rank=4)
        shape.check_static(coeff_add, has_rank=4, has_dim_equals=(-1, 1))
        shape.compare_batch_dimensions(tensors=(image, coeff_mul),
                                       last_axes=-1,
                                       broadcast_compatible=False)
        shape.compare_batch_dimensions(tensors=(image, coeff_add),
                                       last_axes=-2,
                                       broadcast_compatible=False)

        return tfg_vector.dot(coeff_mul, image) + coeff_add
Esempio n. 24
0
def rasterize(vertices,
              triangles,
              attributes,
              model_to_eye_matrix,
              perspective_matrix,
              image_size,
              backend=rasterization_backend.RasterizationBackends.OPENGL,
              name=None):
  """Rasterizes the scene.

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

  Args:
    vertices: A tensor of shape `[A1, ..., An, V, 3]` containing batches of `V`
      vertices, each defined by a 3D point.
    triangles: A tensor of shape `[T, 3]` containing `T` triangles, each
      associated with 3 vertices from `vertices`.
    attributes: A dictionary of tensors, each of shape `[A1, ..., An, V, K_a]`
      containing batches of `V` vertices, each associated with K-dimensional
      attributes. K_a may vary by attribute.
    model_to_eye_matrix: A tensor of shape `[A1, ..., An, 4, 4]` containing
      batches of matrices used to transform vertices from model to eye
      coordinates.
    perspective_matrix: A tensor of shape `[A1, ..., An, 4, 4]` containing
      batches of matrices used to project vertices from eye to clip coordinates.
    image_size: A tuple (height, width) containing the dimensions in pixels of
      the rasterized image.
    backend: A rasterization_backend.RasterizationBackends enum containing the
      backend method to use for rasterization.
    name: A name for this op. Defaults to 'triangle_rasterizer_rasterize'.

  Returns:
    A dictionary. The key "mask" is of shape `[A1, ..., An, height, width, 1]`
    and stores a value of `0` of the pixel is assciated with the background,
    and `1` with the foreground. The key "barycentrics" is of shape
    `[A1, ..., An, height, width, 3]` and stores barycentric weights. Finally,
    the dictionary contains perspective correct interpolated attributes of shape
    `[A1, ..., An, height, width, K]` per entry in the `attributes` dictionary.
  """
  with tf.compat.v1.name_scope(name, "triangle_rasterizer_rasterize",
                               (vertices, triangles, attributes,
                                model_to_eye_matrix, perspective_matrix)):
    vertices = tf.convert_to_tensor(value=vertices)
    triangles = tf.convert_to_tensor(value=triangles)
    model_to_eye_matrix = tf.convert_to_tensor(value=model_to_eye_matrix)
    perspective_matrix = tf.convert_to_tensor(value=perspective_matrix)

    shape.check_static(
        tensor=vertices,
        tensor_name="vertices",
        has_rank_greater_than=1,
        has_dim_equals=((-1, 3)))
    shape.check_static(
        tensor=triangles,
        tensor_name="triangles",
        has_rank=2,
        has_dim_equals=((-1, 3)))
    shape.check_static(
        tensor=model_to_eye_matrix,
        tensor_name="model_to_eye_matrix",
        has_dim_equals=(((-2, 4), (-1, 4))))
    shape.check_static(
        tensor=perspective_matrix,
        tensor_name="perspective_matrix",
        has_dim_equals=(((-2, 4), (-1, 4))))

    image_size_float = (float(image_size[0]), float(image_size[1]))
    image_size_backend = (int(image_size[1]), int(image_size[0]))

    view_projection_matrix = tf.linalg.matmul(perspective_matrix,
                                              model_to_eye_matrix)
    rasterized = rasterization_backend.rasterize(vertices, triangles,
                                                 view_projection_matrix,
                                                 image_size_backend, backend)
    outputs = {
        "mask": rasterized.foreground_mask,
        "triangle_indices": rasterized.triangle_id
    }

    # Extract batch shape in order to make sure it is preserved after `gather`
    # operation.
    batch_shape = rasterized.triangle_id.shape[:-3]
    batch_shape = [_dim_value(dim) for dim in batch_shape]

    vertices_per_pixel = tf.gather(
        vertices, rasterized.vertex_ids, batch_dims=len(batch_shape))
    barycentrics = _perspective_correct_barycentrics(vertices_per_pixel,
                                                     model_to_eye_matrix,
                                                     perspective_matrix,
                                                     image_size_float)
    mask_float = tf.cast(rasterized.foreground_mask, vertices.dtype)
    outputs["barycentrics"] = mask_float * barycentrics

    for key, attribute in attributes.items():
      attribute = tf.convert_to_tensor(value=attribute)
      outputs[key] = mask_float * _perspective_correct_attributes(
          attribute, barycentrics, triangles, rasterized.triangle_id[..., 0],
          len(batch_shape))

    return outputs
Esempio n. 25
0
def rasterize(vertices,
              triangles,
              view_projection_matrices,
              image_size,
              name=None):
  """Rasterizes the scene.

    This rasterizer estimates which triangle is associated with each pixel using
    OpenGL.

  Note:
    In the following, A1 to An are optional batch dimensions which must be
    broadcast compatible for inputs `vertices` and `view_projection_matrices`.

  Args:
    vertices: A tensor of shape `[A1, ..., An, V, 3]` containing batches of `V`
      vertices, each defined by a 3D point.
    triangles: A tensor of shape `[T, 3]` containing `T` triangles, each
      associated with 3 vertices from `scene_vertices`
    view_projection_matrices: A tensor of shape `[A1, ..., An, 4, 4]` containing
      batches of view projection matrices
    image_size: An tuple of integers (height, width) containing the dimensions
      in pixels of the rasterized image.
    name: A name for this op. Defaults to 'rasterization_backend_rasterize'.

  Returns:
    A tuple of 3 elements. The first one of shape `[A1, ..., An, H, W, 1]`
    representing the triangle index associated with each pixel. If no triangle
    is associated to a pixel, the index is set to -1.
    The second element in the tuple is of shape `[A1, ..., An, H, W, 3]` and
    correspond to barycentric coordinates per pixel. The last element in the
    tuple is of shape `[A1, ..., An, H, W, 1]` and stores a value of `0` of the
    pixel is assciated with the background, and `1` with the foreground
  """
  with tf.compat.v1.name_scope(name, "rasterization_backend_rasterize",
                               (vertices, triangles, view_projection_matrices)):
    vertices = tf.convert_to_tensor(value=vertices)
    triangles = tf.convert_to_tensor(value=triangles)
    view_projection_matrices = tf.convert_to_tensor(
        value=view_projection_matrices)

    shape.check_static(
        tensor=vertices,
        tensor_name="vertices",
        has_rank_greater_than=1,
        has_dim_equals=((-1, 3)))
    shape.check_static(
        tensor=triangles,
        tensor_name="triangles",
        has_rank=2,
        has_dim_equals=((-1, 3)))
    shape.check_static(
        tensor=view_projection_matrices,
        tensor_name="view_projection_matrices",
        has_rank_greater_than=1,
        has_dim_equals=((-1, 4), (-2, 4)))
    shape.compare_batch_dimensions(
        tensors=(vertices, view_projection_matrices),
        tensor_names=("vertices", "view_projection_matrices"),
        last_axes=(-3, -3),
        broadcast_compatible=True)

    common_batch_shape = shape.get_broadcasted_shape(
        vertices.shape[:-2], view_projection_matrices.shape[:-2])
    common_batch_shape = [_dim_value(dim) for dim in common_batch_shape]
    vertices = tf.broadcast_to(vertices,
                               common_batch_shape + vertices.shape[-2:])
    view_projection_matrices = tf.broadcast_to(view_projection_matrices,
                                               common_batch_shape + [4, 4])

    geometry = tf.gather(vertices, triangles, axis=-2)

    rasterized = render_ops.rasterize(
        num_points=geometry.shape[-3],
        variable_names=("view_projection_matrix", "triangular_mesh"),
        variable_kinds=("mat", "buffer"),
        variable_values=(view_projection_matrices,
                         tf.reshape(geometry, shape=common_batch_shape + [-1])),
        output_resolution=image_size,
        vertex_shader=vertex_shader,
        geometry_shader=geometry_shader,
        fragment_shader=fragment_shader)

    triangle_index = tf.cast(rasterized[..., 0] - 1, tf.int32)
    barycentric_coordinates = rasterized[..., 1:3]
    barycentric_coordinates = tf.concat(
        (barycentric_coordinates, 1.0 - barycentric_coordinates[..., 0:1] -
         barycentric_coordinates[..., 1:2]),
        axis=-1)
    mask = tf.cast(triangle_index >= 0, tf.int32)

    return triangle_index, barycentric_coordinates, mask
Esempio n. 26
0
def area_weighted_random_sample_triangle_mesh(vertex_attributes,
                                              faces,
                                              num_samples,
                                              vertex_positions=None,
                                              seed=None,
                                              stateless=False,
                                              name=None):
  """Performs a face area weighted random sampling of a tri mesh.

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

  Args:
    vertex_attributes: A `float` tensor of shape `[A1, ..., An, V, D]`, where V
      is the number of vertices, and D is dimensionality of a feature defined on
      each vertex. If `vertex_positions` is not provided, then first 3
      dimensions of `vertex_attributes` denote the vertex positions.
    faces: A `int` tensor of shape `[A1, ..., An, F, 3]`, where F is the number
      of faces.
    num_samples: An `int` scalar denoting number of samples to be drawn from
      each mesh.
    vertex_positions: An optional `float` tensor of shape `[A1, ..., An, V, 3]`,
      where V is the number of vertices. If None, then vertex_attributes[...,
        :3] is used as vertex positions.
    seed: Optional random seed.
    stateless: Optional flag to use stateless random sampler. If stateless=True,
      then seed must be provided as shape `[2]` int tensor. Stateless random
      sampling is useful for testing to generate same sequence across calls.
    name: Name for op. Defaults to "area_weighted_random_sample_triangle_mesh".

  Returns:
    sample_pts: A `float` tensor of shape `[A1, ..., An, num_samples, D]`,
      where D is dimensionality of each sampled point.
    sample_face_indices: A `int` tensor of shape `[A1, ..., An, num_samples]`.
  """
  with tf.compat.v1.name_scope(name,
                               "area_weighted_random_sample_triangle_mesh"):
    faces = tf.convert_to_tensor(value=faces)
    vertex_attributes = tf.convert_to_tensor(value=vertex_attributes)
    num_samples = tf.convert_to_tensor(value=num_samples)

    shape.check_static(
        tensor=vertex_attributes,
        tensor_name="vertex_attributes",
        has_rank_greater_than=1)
    shape.check_static(
        tensor=vertex_attributes,
        tensor_name="vertex_attributes",
        has_dim_greater_than=(-1, 2))

    if vertex_positions is not None:
      vertex_positions = tf.convert_to_tensor(value=vertex_positions)
    else:
      vertex_positions = vertex_attributes[..., :3]

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

    triangle_vertex_positions = normals.gather_faces(vertex_positions, faces)
    triangle_areas = triangle_area(triangle_vertex_positions[..., 0, :],
                                   triangle_vertex_positions[..., 1, :],
                                   triangle_vertex_positions[..., 2, :])
    return weighted_random_sample_triangle_mesh(
        vertex_attributes,
        faces,
        num_samples,
        face_weights=triangle_areas,
        seed=seed,
        stateless=stateless)
Esempio n. 27
0
def weighted_random_sample_triangle_mesh(vertex_attributes,
                                         faces,
                                         num_samples,
                                         face_weights,
                                         seed=None,
                                         stateless=False,
                                         name=None):
  """Performs a face probability weighted random sampling of a tri mesh.

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

  Args:
    vertex_attributes: A `float` tensor of shape `[A1, ..., An, V, D]`, where V
      is the number of vertices, and D is dimensionality of each vertex.
    faces: A `int` tensor of shape `[A1, ..., An, F, 3]`, where F is the number
      of faces.
    num_samples: A `int` 0-D tensor denoting number of samples to be drawn from
      each mesh.
    face_weights: A `float` tensor of shape ``[A1, ..., An, F]`, denoting
      unnormalized sampling probability of each face, where F is the number of
      faces.
    seed: Optional random seed.
    stateless: Optional flag to use stateless random sampler. If stateless=True,
      then seed must be provided as shape `[2]` int tensor. Stateless random
      sampling is useful for testing to generate same sequence across calls.
    name: Name for op. Defaults to "weighted_random_sample_triangle_mesh".

  Returns:
    sample_points: A `float` tensor of shape `[A1, ..., An, num_samples, D]`,
      where D is dimensionality of each sampled point.
    sample_face_indices: A `int` tensor of shape `[A1, ..., An, num_samples]`.
  """
  with tf.compat.v1.name_scope(name, "weighted_random_sample_triangle_mesh"):
    faces = tf.convert_to_tensor(value=faces)
    vertex_attributes = tf.convert_to_tensor(value=vertex_attributes)
    face_weights = tf.convert_to_tensor(value=face_weights)
    num_samples = tf.convert_to_tensor(value=num_samples)

    shape.check_static(
        tensor=vertex_attributes,
        tensor_name="vertex_attributes",
        has_rank_greater_than=1)
    shape.check_static(
        tensor=faces, tensor_name="faces", has_rank_greater_than=1)
    shape.check_static(
        tensor=face_weights,
        tensor_name="face_weights",
        has_rank_greater_than=0)
    shape.compare_batch_dimensions(
        tensors=(faces, face_weights),
        last_axes=(-2, -1),
        tensor_names=("faces", "face_weights"),
        broadcast_compatible=False)
    shape.compare_batch_dimensions(
        tensors=(vertex_attributes, faces, face_weights),
        last_axes=(-3, -3, -2),
        tensor_names=("vertex_attributes", "faces", "face_weights"),
        broadcast_compatible=False)

    asserts.assert_all_above(face_weights, 0)

    batch_dims = faces.shape.ndims - 2
    batch_shape = faces.shape.as_list()[:-2]
    sample_shape = tf.concat(
        (batch_shape, tf.convert_to_tensor(
            value=(num_samples,), dtype=tf.int32)),
        axis=0)

    sample_face_indices = generate_random_face_indices(
        num_samples, face_weights, seed=seed, stateless=stateless)
    sample_vertex_indices = tf.gather(
        faces, sample_face_indices, batch_dims=batch_dims)
    sample_vertices = tf.gather(
        vertex_attributes, sample_vertex_indices, batch_dims=batch_dims)
    barycentric = generate_random_barycentric_coordinates(
        sample_shape,
        dtype=vertex_attributes.dtype,
        seed=seed,
        stateless=stateless)
    barycentric = tf.expand_dims(barycentric, axis=-1)
    sample_points = tf.math.multiply(sample_vertices, barycentric)
    sample_points = tf.reduce_sum(input_tensor=sample_points, axis=-2)
    return sample_points, sample_face_indices
Esempio n. 28
0
def sample(image: type_alias.TensorLike,
           warp: type_alias.TensorLike,
           resampling_type: ResamplingType = ResamplingType.BILINEAR,
           border_type: BorderType = BorderType.ZERO,
           pixel_type: PixelType = PixelType.HALF_INTEGER,
           name: Optional[str] = "sample") -> tf.Tensor:
  """Samples an image at user defined coordinates.

  Note:
    The warp maps target to source. In the following, A1 to An are optional
    batch dimensions.

  Args:
    image: A tensor of shape `[B, H_i, W_i, C]`, where `B` is the batch size,
      `H_i` the height of the image, `W_i` the width of the image, and `C` the
      number of channels of the image.
    warp: A tensor of shape `[B, A_1, ..., A_n, 2]` containing the x and y
      coordinates at which sampling will be performed. The last dimension must
      be 2, representing the (x, y) coordinate where x is the index for width
      and y is the index for height.
   resampling_type: Resampling mode. Supported values are
     `ResamplingType.NEAREST` and `ResamplingType.BILINEAR`.
    border_type: Border mode. Supported values are `BorderType.ZERO` and
      `BorderType.DUPLICATE`.
    pixel_type: Pixel mode. Supported values are `PixelType.INTEGER` and
      `PixelType.HALF_INTEGER`.
    name: A name for this op. Defaults to "sample".

  Returns:
    Tensor of sampled values from `image`. The output tensor shape
    is `[B, A_1, ..., A_n, C]`.

  Raises:
    ValueError: If `image` has rank != 4. If `warp` has rank < 2 or its last
    dimension is not 2. If `image` and `warp` batch dimension does not match.
  """
  with tf.name_scope(name):
    image = tf.convert_to_tensor(value=image, name="image")
    warp = tf.convert_to_tensor(value=warp, name="warp")

    shape.check_static(image, tensor_name="image", has_rank=4)
    shape.check_static(
        warp,
        tensor_name="warp",
        has_rank_greater_than=1,
        has_dim_equals=(-1, 2))
    shape.compare_batch_dimensions(
        tensors=(image, warp), last_axes=0, broadcast_compatible=False)

    if pixel_type == PixelType.HALF_INTEGER:
      warp -= 0.5

    if resampling_type == ResamplingType.NEAREST:
      warp = tf.math.round(warp)

    if border_type == BorderType.DUPLICATE:
      image_size = tf.cast(tf.shape(input=image)[1:3], dtype=warp.dtype)
      height, width = tf.unstack(image_size, axis=-1)
      warp_x, warp_y = tf.unstack(warp, axis=-1)
      warp_x = tf.clip_by_value(warp_x, 0.0, width - 1.0)
      warp_y = tf.clip_by_value(warp_y, 0.0, height - 1.0)
      warp = tf.stack((warp_x, warp_y), axis=-1)

    return tfa_image.resampler(image, warp)
Esempio n. 29
0
def perspective_transform(
    image: type_alias.TensorLike,
    transform_matrix: type_alias.TensorLike,
    output_shape: Optional[type_alias.TensorLike] = None,
    resampling_type: ResamplingType = ResamplingType.BILINEAR,
    border_type: BorderType = BorderType.ZERO,
    pixel_type: PixelType = PixelType.HALF_INTEGER,
    name: Optional[str] = "perspective_transform",
) -> tf.Tensor:
  """Applies a projective transformation to an image.

  The projective transformation is represented by a 3 x 3 matrix
  [[a0, a1, a2], [b0, b1, b2], [c0, c1, c2]], mapping a point `[x, y]` to a
  transformed point
  `[x', y'] = [(a0 x + a1 y + a2) / k, (b0 x + b1 y + b2) / k]`, where
  `k = c0 x + c1 y + c2`.

  Note:
      The transformation matrix maps target to source by transforming output
      points to input points.

  Args:
    image: A tensor of shape `[B, H_i, W_i, C]`, where `B` is the batch size,
      `H_i` the height of the image, `W_i` the width of the image, and `C` the
      number of channels of the image.
    transform_matrix: A tensor of shape `[B, 3, 3]` containing projective
      transform matrices. The transformation maps target to source by
      transforming output points to input points.
    output_shape: The heigh `H_o` and width `W_o` output dimensions after the
      transform. If None, output is the same size as input image.
    resampling_type: Resampling mode. Supported values are
      `ResamplingType.NEAREST` and `ResamplingType.BILINEAR`.
    border_type: Border mode. Supported values are `BorderType.ZERO` and
      `BorderType.DUPLICATE`.
    pixel_type: Pixel mode. Supported values are `PixelType.INTEGER` and
      `PixelType.HALF_INTEGER`.
    name: A name for this op. Defaults to "perspective_transform".

  Returns:
    A tensor of shape `[B, H_o, W_o, C]` containing transformed images.

  Raises:
    ValueError: If `image` has rank != 4. If `transform_matrix` has rank < 3 or
    its last two dimensions are not 3. If `image` and `transform_matrix` batch
    dimension does not match.
  """
  with tf.name_scope(name):
    image = tf.convert_to_tensor(value=image, name="image")
    transform_matrix = tf.convert_to_tensor(
        value=transform_matrix, name="transform_matrix")
    output_shape = tf.shape(
        input=image)[-3:-1] if output_shape is None else tf.convert_to_tensor(
            value=output_shape, name="output_shape")

    shape.check_static(image, tensor_name="image", has_rank=4)
    shape.check_static(
        transform_matrix,
        tensor_name="transform_matrix",
        has_rank=3,
        has_dim_equals=((-1, 3), (-2, 3)))
    shape.compare_batch_dimensions(
        tensors=(image, transform_matrix),
        last_axes=0,
        broadcast_compatible=False)

    dtype = image.dtype
    zero = tf.cast(0.0, dtype)
    height, width = tf.unstack(output_shape, axis=-1)
    warp = grid.generate(
        starts=(zero, zero),
        stops=(tf.cast(width, dtype) - 1.0, tf.cast(height, dtype) - 1.0),
        nums=(width, height))
    warp = tf.transpose(a=warp, perm=[1, 0, 2])

    if pixel_type == PixelType.HALF_INTEGER:
      warp += 0.5

    padding = [[0, 0] for _ in range(warp.shape.ndims)]
    padding[-1][-1] = 1
    warp = tf.pad(
        tensor=warp, paddings=padding, mode="CONSTANT", constant_values=1.0)

    warp = warp[..., tf.newaxis]
    transform_matrix = transform_matrix[:, tf.newaxis, tf.newaxis, ...]
    warp = tf.linalg.matmul(transform_matrix, warp)
    warp = warp[..., 0:2, 0] / warp[..., 2, :]

    return sample(image, warp, resampling_type, border_type, pixel_type)
Esempio n. 30
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)