text: ### Reference from: https://github.com/yysijie/st-gcn/blob/master/net/utils/graph.py
  
  import os
  import numpy as np
  
  
  class Graph:
      """The Graph to model the skeletons extracted by the Alpha-Pose.
      Args:
          - strategy: (string) must be one of the follow candidates
              - uniform: Uniform Labeling,
              - distance: Distance Partitioning,
              - spatial: Spatial Configuration,
          For more information, please refer to the section 'Partition Strategies'
              in our paper (https://arxiv.org/abs/1801.07455).
          - layout: (string) must be one of the follow candidates
              - coco_cut: Is COCO format but cut 4 joints (L-R ears, L-R eyes) out.
          - max_hop: (int) the maximal distance between two connected nodes.
          - dilation: (int) controls the spacing between the kernel points.
      """
      def __init__(self,
                   layout='coco_cut',
                   strategy='uniform',
                   max_hop=1,
                   dilation=1):
          self.max_hop = max_hop
          self.dilation = dilation
  
          self.get_edge(layout)
          self.hop_dis = get_hop_distance(self.num_node, self.edge, max_hop)
          self.get_adjacency(strategy)
  
      def get_edge(self, layout):
          if layout == 'coco_cut':
              self.num_node = 14
              self_link = [(i, i) for i in range(self.num_node)]
              neighbor_link = [(6, 4), (4, 2), (2, 13), (13, 1), (5, 3), (3, 1), (12, 10),
                               (10, 8), (8, 2), (11, 9), (9, 7), (7, 1), (13, 0)]
              self.edge = self_link + neighbor_link
              self.center = 13
          else:
              raise ValueError('This layout is not supported!')
  
      def get_adjacency(self, strategy):
          valid_hop = range(0, self.max_hop + 1, self.dilation)
          adjacency = np.zeros((self.num_node, self.num_node))
          for hop in valid_hop:
              adjacency[self.hop_dis == hop] = 1
          normalize_adjacency = normalize_digraph(adjacency)
  
          if strategy == 'uniform':
              A = np.zeros((1, self.num_node, self.num_node))
              A[0] = normalize_adjacency
              self.A = A
          elif strategy == 'distance':
              A = np.zeros((len(valid_hop), self.num_node, self.num_node))
              for i, hop in enumerate(valid_hop):
                  A[i][self.hop_dis == hop] = normalize_adjacency[self.hop_dis ==
                                                                  hop]
              self.A = A
          elif strategy == 'spatial':
              A = []
              for hop in valid_hop:
                  a_root = np.zeros((self.num_node, self.num_node))
                  a_close = np.zeros((self.num_node, self.num_node))
                  a_further = np.zeros((self.num_node, self.num_node))
                  for i in range(self.num_node):
                      for j in range(self.num_node):
                          if self.hop_dis[j, i] == hop:
                              if self.hop_dis[j, self.center] == self.hop_dis[i, self.center]:
                                  a_root[j, i] = normalize_adjacency[j, i]
                              elif self.hop_dis[j, self.center] > self.hop_dis[i, self.center]:
                                  a_close[j, i] = normalize_adjacency[j, i]
                              else:
                                  a_further[j, i] = normalize_adjacency[j, i]
                  if hop == 0:
                      A.append(a_root)
                  else:
                      A.append(a_root + a_close)
                      A.append(a_further)
              A = np.stack(A)
              self.A = A
              #self.A = np.swapaxes(np.swapaxes(A, 0, 1), 1, 2)
          else:
              raise ValueError("This strategy is not supported!")
  
  
  def get_hop_distance(num_node, edge, max_hop=1):
      A = np.zeros((num_node, num_node))
      for i, j in edge:
          A[j, i] = 1
          A[i, j] = 1
  
      # compute hop steps
      hop_dis = np.zeros((num_node, num_node)) + np.inf
      transfer_mat = [np.linalg.matrix_power(A, d) for d in range(max_hop + 1)]
      arrive_mat = (np.stack(transfer_mat) > 0)
      for d in range(max_hop, -1, -1):
          hop_dis[arrive_mat[d]] = d
      return hop_dis
  
  
  def normalize_digraph(A):
      Dl = np.sum(A, 0)
      num_node = A.shape[0]
      Dn = np.zeros((num_node, num_node))
      for i in range(num_node):
          if Dl[i] > 0:
              Dn[i, i] = Dl[i]**(-1)
      AD = np.dot(A, Dn)
      return AD
  
  
  def normalize_undigraph(A):
      Dl = np.sum(A, 0)
      num_node = A.shape[0]
      Dn = np.zeros((num_node, num_node))
      for i in range(num_node):
          if Dl[i] > 0:
              Dn[i, i] = Dl[i]**(-0.5)
      DAD = np.dot(np.dot(Dn, A), Dn)
      return DAD
  