code: def feature_steered_convolution(
      data: type_alias.TensorLike,
      neighbors: tf.sparse.SparseTensor,
      sizes: type_alias.TensorLike,
      var_u: type_alias.TensorLike,
      var_v: type_alias.TensorLike,
      var_c: type_alias.TensorLike,
      var_w: type_alias.TensorLike,
      var_b: type_alias.TensorLike,
      name="graph_convolution_feature_steered_convolution",
  ) -> tf.Tensor:
      #  pyformat: disable
      """Implements the Feature Steered graph convolution.
  
      FeaStNet: Feature-Steered Graph Convolutions for 3D Shape Analysis
      Nitika Verma, Edmond Boyer, Jakob Verbeek
      CVPR 2018
      https://arxiv.org/abs/1706.05206
  
      The shorthands used below are
        `V`: The number of vertices.
        `C`: The number of channels in the input data.
        `D`: The number of channels in the output after convolution.
        `W`: The number of weight matrices used in the convolution.
        The input variables (`var_u`, `var_v`, `var_c`, `var_w`, `var_b`) correspond
        to the variables with the same names in the paper cited above.
  
      Note:
        In the following, A1 to An are optional batch dimensions.
  
      Args:
        data: A `float` tensor with shape `[A1, ..., An, V, C]`.
        neighbors: A `SparseTensor` with the same type as `data` and with shape
          `[A1, ..., An, V, V]` representing vertex neighborhoods. The neighborhood
          of a vertex defines the support region for convolution. For a mesh, a
          common choice for the neighborhood of vertex i would be the vertices in
          the K-ring of i (including i itself). Each vertex must have at least one
          neighbor. For a faithful implementation of the FeaStNet convolution,
          neighbors should be a row-normalized weight matrix corresponding to the
          graph adjacency matrix with self-edges: `neighbors[A1, ..., An, i, j] > 0`
          if vertex j is a neighbor of i, and `neighbors[A1, ..., An, i, i] > 0` for
          all i, and `sum(neighbors, axis=-1)[A1, ..., An, i] == 1.0 for all i`.
          These requirements are relaxed in this implementation.
        sizes: An `int` tensor of shape `[A1, ..., An]` indicating the true input
          sizes in case of padding (`sizes=None` indicates no padding).Note that
          `sizes[A1, ..., An] <= V`. If `data` and `neighbors` are 2-D, `sizes` will
          be ignored. An example usage of `sizes`: consider an input consisting of
          three graphs G0, G1, and G2 with V0, V1, and V2 vertices respectively. The
          padded input would have the following shapes: `data.shape = [3, V, C]` and
          `neighbors.shape = [3, V, V]`, where `V = max([V0, V1, V2])`. The true
          sizes of each graph will be specified by `sizes=[V0, V1, V2]`,
          `data[i, :Vi, :]` and `neighbors[i, :Vi, :Vi]` will be the vertex and
          neighborhood data of graph Gi. The `SparseTensor` `neighbors` should have
          no nonzero entries in the padded regions.
        var_u: A 2-D tensor with shape `[C, W]`.
        var_v: A 2-D tensor with shape `[C, W]`.
        var_c: A 1-D tensor with shape `[W]`.
        var_w: A 3-D tensor with shape `[W, C, D]`.
        var_b: A 1-D tensor with shape `[D]`.
        name: A name for this op. Defaults to
          `graph_convolution_feature_steered_convolution`.
  
      Returns:
        Tensor with shape `[A1, ..., An, V, D]`.
  
      Raises:
        TypeError: if the input types are invalid.
        ValueError: if the input dimensions are invalid.
      """
      #  pyformat: enable
      with tf.name_scope(name):
          data = tf.convert_to_tensor(value=data)
          neighbors = tf.compat.v1.convert_to_tensor_or_sparse_tensor(
              value=neighbors
          )
          if sizes is not None:
              sizes = tf.convert_to_tensor(value=sizes)
          var_u = tf.convert_to_tensor(value=var_u)
          var_v = tf.convert_to_tensor(value=var_v)
          var_c = tf.convert_to_tensor(value=var_c)
          var_w = tf.convert_to_tensor(value=var_w)
          var_b = tf.convert_to_tensor(value=var_b)
  
          data_ndims = data.shape.ndims
          utils.check_valid_graph_convolution_input(
              data, neighbors, sizes
          )
          shape.compare_dimensions(
              tensors=(data, var_u, var_v, var_w),
              tensor_names=("data", "var_u", "var_v", "var_w"),
              axes=(-1, 0, 0, 1),
          )
          shape.compare_dimensions(
              tensors=(var_u, var_v, var_c, var_w),
              tensor_names=("var_u", "var_v", "var_c", "var_w"),
              axes=(1, 1, 0, 0),
          )
          shape.compare_dimensions(
              tensors=(var_w, var_b),
              tensor_names=("var_w", "var_b"),
              axes=-1,
          )
  
          # Flatten the batch dimensions and remove any vertex padding.
          if data_ndims > 2:
              if sizes is not None:
                  sizes_square = tf.stack((sizes, sizes), axis=-1)
              else:
                  sizes_square = None
              x_flat, unflatten = utils.flatten_batch_to_2d(data, sizes)
              adjacency = utils.convert_to_block_diag_2d(
                  neighbors, sizes_square
              )
          else:
              x_flat = data
              adjacency = neighbors
          x_u = tf.matmul(x_flat, var_u)
          x_v = tf.matmul(x_flat, var_v)
          adjacency_ind_0 = adjacency.indices[:, 0]
          adjacency_ind_1 = adjacency.indices[:, 1]
          x_u_rep = tf.gather(x_u, adjacency_ind_0)
          x_v_sep = tf.gather(x_v, adjacency_ind_1)
          weights_q = tf.exp(
              x_u_rep + x_v_sep + tf.reshape(var_c, (1, -1))
          )
          weights_q_sum = tf.reduce_sum(
              input_tensor=weights_q, axis=-1, keepdims=True
          )
          weights_q = weights_q / weights_q_sum
          y_i_m = []
          x_sep = tf.gather(x_flat, adjacency_ind_1)
          q_m_list = tf.unstack(weights_q, axis=-1)
          w_m_list = tf.unstack(var_w, axis=0)
  
          x_flat_shape = tf.shape(input=x_flat)
          for q_m, w_m in zip(q_m_list, w_m_list):
              # Compute `y_i_m = sum_{j in neighborhood(i)} q_m(x_i, x_j) * w_m * x_j`.
              q_m = tf.expand_dims(q_m, axis=-1)
              p_sum = tf.math.unsorted_segment_sum(
                  data=(q_m * x_sep)
                  * tf.expand_dims(adjacency.values, -1),
                  segment_ids=adjacency_ind_0,
                  num_segments=x_flat_shape[0],
              )
              y_i_m.append(tf.matmul(p_sum, w_m))
          y_out = tf.add_n(inputs=y_i_m) + tf.reshape(var_b, [1, -1])
          if data_ndims > 2:
              y_out = unflatten(y_out)
          return y_out
  
file_path: graphics/tensorflow_graphics/geometry/convolution/graph_convolution.py
imports: from __future__ import absolute_import
  from __future__ import division
  from __future__ import print_function
  from typing import Any, Callable, Dict
  from six.moves import zip
  import tensorflow
  from tensorflow_graphics.geometry.convolution import utils
  from tensorflow_graphics.util import export_api
  from tensorflow_graphics.util import shape
  from tensorflow_graphics.util import type_alias
label_desc: Use image node_id 1 for calling a global function with example usage: feature_steered_convolution(data, neighbors, sizes, var_u, var_v, var_c, var_w, var_b, name) and returns: y_out