class_docstr: A customizable graph neural network (GNN) block.
  
  :param in_keys: One key identifying the input tensors.
  :param out_keys: One key identifying the output tensors.
  :param in_shapes: List of input shapes.
  :param edges: List of graph edges required for message passing (aggregation).
  :param aggregate: The aggregation function to use (max, mean, sum).
  :param hidden_units: List containing the number of hidden units for hidden layers.
  :param non_lin: The non-linearity to apply after each layer.
  :param with_layer_norm: If True layer normalization is applied.
  :param node2node_aggr: If True node to node message passing is applied.
  :param edge2node_aggr: If True edge to node message passing is applied.
  :param node2edge_aggr: If True node to edge message passing is applied.
  :param edge2edge_aggr: If True edge to edge message passing is applied.
  :param with_node_embedding: If True the node embedding is computed.
  :param with_edge_embedding: If True the edge embedding is computed.
code: def __repr__(self):
      mp = ""
      if self.node2node_aggr:
          mp += " n2n"
      if self.edge2edge_aggr:
          mp += " e2e"
      if self.node2edge_aggr:
          mp += " n2e"
      if self.edge2node_aggr:
          mp += " e2n"
  
      txt = f"{GNNBlock.__name__}"
      txt += f"\n\tIn Shapes: {self.in_shapes}"
      txt += f"\n\thidden_units: {self.hidden_units}, aggr: {self.aggregate}"
      txt += f"\n\tmessage passing:{mp}"
      txt += f"\n\tOut Shapes: {self.out_shapes()}"
      return txt
  
imports: from typing import Any, List, Tuple, Union, Sequence, Dict
  import numpy
  import torch
  from torch import nn
  from torch_scatter import scatter
  from maze.core.annotations import override
  from maze.core.utils.factory import Factory
  from maze.perception.blocks.shape_normalization import ShapeNormalizationBlock
label_desc: Use image node_id 6 for calling the GNNBlock obj's underlying member method code with example usage: obj.__repr__() and returns: txt