def sample_quadric_surface(quadric, center, samples): """Samples the algebraic distance to the input quadric at sparse locations. Args: quadric: Tensor with shape [..., 4, 4]. Contains the matrix of the quadric surface. center: Tensor with shape [..., 3]. Contains the [x,y,z] coordinates of the center of the coordinate frame of the quadric surface in NIC space with a top-left origin. samples: Tensor with shape [..., N, 3], where N is the number of samples to evaluate. These are the sample locations in the same space in which the quadric surface center is defined. Supports broadcasting the batching dimensions. Returns: distances: Tensor with shape [..., N, 1]. Contains the algebraic distance to the surface at each sample. """ with tf.name_scope('sample_quadric_surface'): batching_dimensions = quadric.get_shape().as_list()[:-2] batching_rank = len(batching_dimensions) tf_util.assert_shape(quadric, batching_dimensions + [4, 4], 'sample_quadric_surface:quadric') tf_util.assert_shape(center, batching_dimensions + [-1], 'sample_quadric_surface:center') tf_util.assert_shape(samples, batching_rank * [-1] + [-1, 3], 'sample_quadric_surface:samples') # We want to transform the coordinates so that they are in the coordinate # frame of the conic section matrix, so we subtract the center of the # conic. samples = samples - tf.expand_dims(center, axis=batching_rank) sample_count = samples.get_shape().as_list()[-2] homogeneous_sample_ones = tf.ones(samples.get_shape().as_list()[:-1] + [1], dtype=tf.float32) homogeneous_sample_coords = tf.concat( [samples, homogeneous_sample_ones], axis=-1) # When we transform the coordinates per-image, we broadcast on both sides- # the batching dimensions broadcast up the coordinate grid, and the # coordinate center broadcasts up along the height and width. # Per-pixel, the algebraic distance is v^T * M * v, where M is the matrix # of the conic section, and v is the homogeneous column vector [x y z 1]^T. half_distance = tf.matmul(quadric, homogeneous_sample_coords, transpose_b=True) rank = batching_rank + 2 half_distance = tf.transpose(half_distance, perm=list(range(rank - 2)) + [rank - 1, rank - 2]) algebraic_distance = tf.reduce_sum(tf.multiply( homogeneous_sample_coords, half_distance), axis=-1) return tf.reshape(algebraic_distance, batching_dimensions + [sample_count, 1])
def compute_shape_element_influences(quadrics, centers, radii, samples): """Computes the per-shape-element values at given sample locations. Args: quadrics: quadric parameters with shape [batch_size, quadric_count, 4, 4]. centers: rbf centers with shape [batch_size, quadric_count, 3]. radii: rbf radii with shape [batch_size, quadric_count, radius_length]. radius_length can be 1, 3, or 6 depending on whether it is isotropic, anisotropic, or a general symmetric covariance matrix, respectively. samples: a grid of samples with shape [batch_size, quadric_count, sample_count, 3] or shape [batch_size, sample_count, 3]. Returns: Two tensors (the quadric values and the RBF values, respectively), each with shape [batch_size, quadric_count, sample_count, 1] """ print( '[HERE: In ldif.representation.quadrics.compute_shape_element_influences] quadrics shape =', quadrics.shape) print( '[HERE: In ldif.representation.quadrics.compute_shape_element_influences] centers shape =', centers.shape) print( '[HERE: In ldif.representation.quadrics.compute_shape_element_influences] radii shape =', radii.shape) print( '[HERE: In ldif.representation.quadrics.compute_shape_element_influences] samples shape =', samples.shape) with tf.name_scope('compute_shape_element_influences'): # Select the number of samples along the ray. The larger this is, the # more memory that will be consumed and the slower the algorithm. But it # reduces warping artifacts and the likelihood of missing a thin surface. batch_size, quadric_count = quadrics.get_shape().as_list()[0:2] tf_util.assert_shape(quadrics, [batch_size, quadric_count, 4, 4], 'quadrics') tf_util.assert_shape(centers, [batch_size, quadric_count, 3], 'centers') # We separate the isometric, axis-aligned, and general RBF functions. # The primary reason for this is that the type of basis function # affects the shape of many tensors, and it is easier to make # everything correct when the shape is known. Once the shape function is # set we can clean it up and choose one basis function. radii_shape = radii.get_shape().as_list() if len(radii_shape) != 3: raise tf.errors.InvalidArgumentError( 'radii must have shape [batch_size, quadric_count, radii_values].' ) elif radii_shape[2] == 1: rbf_sampler = sample_isotropic_bf radius_shape = [1] elif radii_shape[2] == 3: rbf_sampler = sample_axis_aligned_bf radius_shape = [3] elif radii_shape[2] == 6: rbf_sampler = sample_cov_bf radius_shape = [6] else: raise tf.errors.InvalidArgumentError( 'radii must have either 1, 3, or 6 elements.') tf_util.assert_shape(radii, [batch_size, quadric_count] + radius_shape, 'radii') # Ensure the samples have the right shape and tile in an axis for the # quadric dimension if it wasn't provided. sample_shape = samples.get_shape().as_list() sample_rank = len(sample_shape) if (sample_rank not in [3, 4] or sample_shape[-1] != 3 or sample_shape[0] != batch_size): raise tf.errors.InvalidArgumentError( 'Input tensor samples must have shape [batch_size, quadric_count,' ' sample_count, 3] or shape [batch_size, sample_count, 3]. The input' ' shape was %s' % repr(sample_shape)) missing_quadric_dim = len(sample_shape) == 3 if missing_quadric_dim: samples = tf_util.tile_new_axis(samples, axis=1, length=quadric_count) sample_count = sample_shape[-2] # Sample the quadric surfaces and the RBFs in world space, and composite # them. sampled_quadrics = sample_quadric_surface(quadrics, centers, samples) tf_util.assert_shape(sampled_quadrics, [batch_size, quadric_count, sample_count, 1], 'sampled_quadrics') tf_util.assert_shape(centers, [batch_size, quadric_count, 3], 'centers') tf_util.assert_shape(samples, [batch_size, quadric_count, sample_count, 3], 'samples') print( '[HERE: In ldif.representation.quadrics.compute_shape_element_incluences] About to run rbf_sampler' ) print( '[HERE: In ldif.representation.quadrics.compute_shape_element_incluences] centers shape =', centers.shape) print( '[HERE: In ldif.representation.quadrics.compute_shape_element_incluences] radii shape =', radii.shape) print( '[HERE: In ldif.representation.quadrics.compute_shape_element_incluences] samples shape =', samples.shape) sampled_rbfs = rbf_sampler(centers, radii, samples) sampled_rbfs = tf.reshape(sampled_rbfs, [batch_size, quadric_count, sample_count, 1]) return sampled_quadrics, sampled_rbfs
def interpolate_from_grid_coordinates(samples, grid): """Performs trilinear interpolation to estimate the value of a grid function. This function makes several assumptions to do the lookup: 1) The grid is LHW and has evenly spaced samples in the range (0, 1), which is really the screen space range [0.5, {L, H, W}-0.5]. Args: samples: Tensor with shape [batch_size, sample_count, 3]. grid: Tensor with shape [batch_size, length, height, width, 1]. Returns: sample: Tensor with shape [batch_size, sample_count, 1] and type float32. mask: Tensor with shape [batch_size, sample_count, 1] and type float32 """ batch_size, length, height, width = grid.get_shape().as_list()[:4] # These asserts aren't required by the algorithm, but they are currently # true for the pipeline: assert length == height assert length == width sample_count = samples.get_shape().as_list()[1] tf_util.assert_shape(samples, [batch_size, sample_count, 3], 'interpolate_from_grid:samples') tf_util.assert_shape(grid, [batch_size, length, height, width, 1], 'interpolate_from_grid:grid') offset_samples = samples # Used to subtract 0.5 lower_coords = tf.cast(tf.math.floor(offset_samples), dtype=tf.int32) upper_coords = lower_coords + 1 alphas = tf.floormod(offset_samples, 1.0) maximum_value = grid.get_shape().as_list()[1:4] size_per_channel = tf.tile( tf.reshape(tf.constant(maximum_value, dtype=tf.int32), [1, 1, 3]), [batch_size, sample_count, 1]) # We only need to check that the floor is at least zero and the ceil is # no greater than the max index, because floor round negative numbers to # be more negative: is_valid = tf.logical_and(lower_coords >= 0, upper_coords < size_per_channel) # Validity mask has shape [batch_size, sample_count] and is 1.0 where all of # x,y,z are within the [0,1] range of the grid. validity_mask = tf.reduce_min( tf.cast(is_valid, dtype=tf.float32), axis=2, keep_dims=True) lookup_coords = [[[], []], [[], []]] corners = [[[], []], [[], []]] flattened_grid = tf.reshape(grid, [batch_size, length * height * width]) for xi, x_coord in enumerate([lower_coords[:, :, 0], upper_coords[:, :, 0]]): x_coord = tf.clip_by_value(x_coord, 0, width - 1) for yi, y_coord in enumerate([lower_coords[:, :, 1], upper_coords[:, :, 1]]): y_coord = tf.clip_by_value(y_coord, 0, height - 1) for zi, z_coord in enumerate( [lower_coords[:, :, 2], upper_coords[:, :, 2]]): z_coord = tf.clip_by_value(z_coord, 0, length - 1) flat_lookup = z_coord * height * width + y_coord * width + x_coord lookup_coords[xi][yi].append(flat_lookup) lookup_result = tf.batch_gather(flattened_grid, flat_lookup) tf_util.assert_shape(lookup_result, [batch_size, sample_count], 'interpolate_from_grid:lookup_result x/8') print_op = tf.print('corner xyz=%i, %i, %i' % (xi, yi, zi), lookup_result, '\n', 'flat_lookup:', flat_lookup, '\n\n') with tf.control_dependencies([print_op]): lookup_result = 1.0 * lookup_result corners[xi][yi].append(lookup_result) alpha_x, alpha_y, alpha_z = tf.unstack(alphas, axis=2) one_minus_alpha_x = 1.0 - alpha_x one_minus_alpha_y = 1.0 - alpha_y # First interpolate a face along x: f00 = corners[0][0][0] * one_minus_alpha_x + corners[1][0][0] * alpha_x f01 = corners[0][0][1] * one_minus_alpha_x + corners[1][0][1] * alpha_x f10 = corners[0][1][0] * one_minus_alpha_x + corners[1][1][0] * alpha_x f11 = corners[0][1][1] * one_minus_alpha_x + corners[1][1][1] * alpha_x # Next interpolate a long along y: l0 = f00 * one_minus_alpha_y + f10 * alpha_y l1 = f01 * one_minus_alpha_y + f11 * alpha_y # Finally interpolate a point along z: p = l0 * (1.0 - alpha_z) + l1 * alpha_z tf_util.assert_shape(p, [batch_size, sample_count], 'interpolate_from_grid:p') p = tf.reshape(p, [batch_size, sample_count, 1]) validity_mask = tf.reshape(validity_mask, [batch_size, sample_count, 1]) return p, validity_mask