Module openpack_torch.models.keypoint.graph

Expand source code
"""
- Ref.1: https://colab.research.google.com/github/machine-perception-robotics-group/MPRGDeepLearningLectureNotebook/blob/master/15_gcn/03_action_recognition_ST_GCN.ipynb#scrollTo=Vk-AMCVb5jqM
- Ref.2: https://github.com/open-mmlab/mmskeleton/blob/master/mmskeleton/ops/st_gcn/graph.py
"""
from typing import Tuple

import numpy as np

NUM_NODES_NTU_RGBD = 25
NTU_RGBD_SKELETON_LAYOUT = ((1, 2), (2, 21), (3, 21), (4, 3), (5, 21),
                            (6, 5), (7, 6), (8, 7), (9, 21), (10, 9),
                            (11, 10), (12, 11), (13, 1), (14, 13), (15, 14),
                            (16, 15), (17, 1), (18, 17), (19, 18), (20, 19),
                            (22, 23), (23, 8), (24, 25), (25, 12))

NUM_NODES_MSCOCO = 17
MSCOCO_SKELETON_LAYOUT = ((15, 13), (13, 11), (16, 14), (14, 12), (11, 12),
                          (5, 11), (6, 12), (5, 6), (5, 7), (6, 8), (7, 9),
                          (8, 10), (1, 2), (0, 1), (0, 2), (1, 3), (2, 4),
                          (3, 5), (4, 6))


class Graph():
    """
    Attributes:
        A (np.ndarray): adjacency matrix with shape = (NUM_HOP+1, NUM_NODE, NUM_NODE)
        skeleton (Tuple[Tuple[int,int], ...]): list of edges.
        hop_size (int): make connection with noded that can be reached in ``hop_size`` hop.
    Todo:
        - Update graph construction method with mmskeleton's implementation.
    """
    A: np.ndarray = None

    def __init__(self, hop_size: int = 2, num_nodes: int = None,
                 skeleton: Tuple[Tuple[int, int], ...] = None):
        self.skeleton = skeleton
        self.num_node = num_nodes
        self.get_edge()

        self.hop_size = hop_size
        self.hop_dist = self.get_hop_distance(
            self.num_node, self.edge, hop_size=hop_size)

        # TODO: Check original paper for the adjacency matrix definition.
        self.get_adjacency()

    def __str__(self):
        return str(self.A)

    def get_edge(self) -> None:
        self_link = [(i, i) for i in range(self.num_node)]  # ループ
        neighbor_link = [(i - 1, j - 1) for (i, j) in self.skeleton]
        self.edge = self_link + neighbor_link

    def get_adjacency(self):
        valid_hop = range(0, self.hop_size + 1, 1)
        adjacency = np.zeros((self.num_node, self.num_node))
        for hop in valid_hop:
            adjacency[self.hop_dist == hop] = 1
        normalize_adjacency = self.normalize_digraph(adjacency)
        A = np.zeros((len(valid_hop), self.num_node, self.num_node))
        for i, hop in enumerate(valid_hop):
            A[i][self.hop_dist == hop] = normalize_adjacency[self.hop_dist == hop]
        self.A = A

    def get_hop_distance(self, num_node, edge, hop_size):
        A = np.zeros((num_node, num_node))
        for i, j in edge:
            A[j, i] = 1
            A[i, j] = 1
        hop_dis = np.zeros((num_node, num_node)) + np.inf
        transfer_mat = [
            np.linalg.matrix_power(
                A,
                d) for d in range(
                hop_size +
                1)]
        arrive_mat = (np.stack(transfer_mat) > 0)
        for d in range(hop_size, -1, -1):
            hop_dis[arrive_mat[d]] = d
        return hop_dis

    def normalize_digraph(self, 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)
        DAD = np.dot(A, Dn)
        return DAD


def get_adjacency_matrix(
        layout: str = "MSCOCO",
        hop_size: int = 2) -> np.ndarray:
    """Returns adjacency matrix.

    Args:
        layout (str, optional): skeleton layout. {MSCOCO, NTU-RGBD}. Defaults to "MSCOCO".
        hop_size (int, optional): maximum distance of connection. Defaults to 2.

    Raises:
        ValueError: _description_

    Returns:
        np.ndarray: adjacency matrix
    """
    if layout.upper() == "NTU-RGBD":
        graph = Graph(hop_size, NUM_NODES_NTU_RGBD, NTU_RGBD_SKELETON_LAYOUT)
    elif layout.upper() == "MSCOCO":
        graph = Graph(hop_size, NUM_NODES_MSCOCO, MSCOCO_SKELETON_LAYOUT)
    else:
        raise ValueError(f"unknown layout, [layout = {layout}]")
    return graph.A

Functions

def get_adjacency_matrix(layout: str = 'MSCOCO', hop_size: int = 2) ‑> numpy.ndarray

Returns adjacency matrix.

Args

layout : str, optional
skeleton layout. {MSCOCO, NTU-RGBD}. Defaults to "MSCOCO".
hop_size : int, optional
maximum distance of connection. Defaults to 2.

Raises

ValueError
description

Returns

np.ndarray
adjacency matrix
Expand source code
def get_adjacency_matrix(
        layout: str = "MSCOCO",
        hop_size: int = 2) -> np.ndarray:
    """Returns adjacency matrix.

    Args:
        layout (str, optional): skeleton layout. {MSCOCO, NTU-RGBD}. Defaults to "MSCOCO".
        hop_size (int, optional): maximum distance of connection. Defaults to 2.

    Raises:
        ValueError: _description_

    Returns:
        np.ndarray: adjacency matrix
    """
    if layout.upper() == "NTU-RGBD":
        graph = Graph(hop_size, NUM_NODES_NTU_RGBD, NTU_RGBD_SKELETON_LAYOUT)
    elif layout.upper() == "MSCOCO":
        graph = Graph(hop_size, NUM_NODES_MSCOCO, MSCOCO_SKELETON_LAYOUT)
    else:
        raise ValueError(f"unknown layout, [layout = {layout}]")
    return graph.A

Classes

class Graph (hop_size: int = 2, num_nodes: int = None, skeleton: Tuple[Tuple[int, int], ...] = None)

Attributes

A : np.ndarray
adjacency matrix with shape = (NUM_HOP+1, NUM_NODE, NUM_NODE)
skeleton : Tuple[Tuple[int,int], …]
list of edges.
hop_size : int
make connection with noded that can be reached in hop_size hop.

Todo

  • Update graph construction method with mmskeleton's implementation.
Expand source code
class Graph():
    """
    Attributes:
        A (np.ndarray): adjacency matrix with shape = (NUM_HOP+1, NUM_NODE, NUM_NODE)
        skeleton (Tuple[Tuple[int,int], ...]): list of edges.
        hop_size (int): make connection with noded that can be reached in ``hop_size`` hop.
    Todo:
        - Update graph construction method with mmskeleton's implementation.
    """
    A: np.ndarray = None

    def __init__(self, hop_size: int = 2, num_nodes: int = None,
                 skeleton: Tuple[Tuple[int, int], ...] = None):
        self.skeleton = skeleton
        self.num_node = num_nodes
        self.get_edge()

        self.hop_size = hop_size
        self.hop_dist = self.get_hop_distance(
            self.num_node, self.edge, hop_size=hop_size)

        # TODO: Check original paper for the adjacency matrix definition.
        self.get_adjacency()

    def __str__(self):
        return str(self.A)

    def get_edge(self) -> None:
        self_link = [(i, i) for i in range(self.num_node)]  # ループ
        neighbor_link = [(i - 1, j - 1) for (i, j) in self.skeleton]
        self.edge = self_link + neighbor_link

    def get_adjacency(self):
        valid_hop = range(0, self.hop_size + 1, 1)
        adjacency = np.zeros((self.num_node, self.num_node))
        for hop in valid_hop:
            adjacency[self.hop_dist == hop] = 1
        normalize_adjacency = self.normalize_digraph(adjacency)
        A = np.zeros((len(valid_hop), self.num_node, self.num_node))
        for i, hop in enumerate(valid_hop):
            A[i][self.hop_dist == hop] = normalize_adjacency[self.hop_dist == hop]
        self.A = A

    def get_hop_distance(self, num_node, edge, hop_size):
        A = np.zeros((num_node, num_node))
        for i, j in edge:
            A[j, i] = 1
            A[i, j] = 1
        hop_dis = np.zeros((num_node, num_node)) + np.inf
        transfer_mat = [
            np.linalg.matrix_power(
                A,
                d) for d in range(
                hop_size +
                1)]
        arrive_mat = (np.stack(transfer_mat) > 0)
        for d in range(hop_size, -1, -1):
            hop_dis[arrive_mat[d]] = d
        return hop_dis

    def normalize_digraph(self, 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)
        DAD = np.dot(A, Dn)
        return DAD

Class variables

var A : numpy.ndarray

Methods

def get_adjacency(self)
Expand source code
def get_adjacency(self):
    valid_hop = range(0, self.hop_size + 1, 1)
    adjacency = np.zeros((self.num_node, self.num_node))
    for hop in valid_hop:
        adjacency[self.hop_dist == hop] = 1
    normalize_adjacency = self.normalize_digraph(adjacency)
    A = np.zeros((len(valid_hop), self.num_node, self.num_node))
    for i, hop in enumerate(valid_hop):
        A[i][self.hop_dist == hop] = normalize_adjacency[self.hop_dist == hop]
    self.A = A
def get_edge(self) ‑> None
Expand source code
def get_edge(self) -> None:
    self_link = [(i, i) for i in range(self.num_node)]  # ループ
    neighbor_link = [(i - 1, j - 1) for (i, j) in self.skeleton]
    self.edge = self_link + neighbor_link
def get_hop_distance(self, num_node, edge, hop_size)
Expand source code
def get_hop_distance(self, num_node, edge, hop_size):
    A = np.zeros((num_node, num_node))
    for i, j in edge:
        A[j, i] = 1
        A[i, j] = 1
    hop_dis = np.zeros((num_node, num_node)) + np.inf
    transfer_mat = [
        np.linalg.matrix_power(
            A,
            d) for d in range(
            hop_size +
            1)]
    arrive_mat = (np.stack(transfer_mat) > 0)
    for d in range(hop_size, -1, -1):
        hop_dis[arrive_mat[d]] = d
    return hop_dis
def normalize_digraph(self, A)
Expand source code
def normalize_digraph(self, 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)
    DAD = np.dot(A, Dn)
    return DAD