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)
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
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)
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)
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)
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))
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
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)
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)
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
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)