NTU 60 and NTU 120

1. Introduction

Homepage: http://rose1.ntu.edu.sg/datasets/actionrecognition.asp

PDF NTU60:

PDF NTU120:

2. Data download

File Note
http://rose1.ntu.edu.sg/Datasets/actionRecognition/download/nturgbd_skeletons_s001_to_s017.zip Required for both NTU60 and NTU120
http://rose1.ntu.edu.sg/datasets/actionRecognition/download/nturgbd_skeletons_s018_to_s032.zip Only for NTU120

3. Data preprocessing

Unzip the zip file, and run the following code, next to the ./nturgb+d_skeletons folder.

As XYZ pose sequences

import glob
import tqdm
import numpy
import matplotlib.pyplot as plt
import torch
import pathlib
import numpy as np
import os
import pickle
import joblib


# ---------------------------------------------------------
# 0. Adapted from st-gcn repo
# ---------------------------------------------------------
def read_skeleton(file):
    with open(file, 'r') as f:
        skeleton_sequence = {}
        skeleton_sequence['numFrame'] = int(f.readline())
        skeleton_sequence['frameInfo'] = []
        for t in range(skeleton_sequence['numFrame']):
            frame_info = {}
            frame_info['numBody'] = int(f.readline())
            frame_info['bodyInfo'] = []
            for m in range(frame_info['numBody']):
                body_info = {}
                body_info_key = [
                    'bodyID', 'clipedEdges', 'handLeftConfidence',
                    'handLeftState', 'handRightConfidence', 'handRightState',
                    'isResticted', 'leanX', 'leanY', 'trackingState'
                ]
                body_info = {
                    k: float(v)
                    for k, v in zip(body_info_key, f.readline().split())
                }
                body_info['numJoint'] = int(f.readline())
                body_info['jointInfo'] = []
                for v in range(body_info['numJoint']):
                    joint_info_key = [
                        'x', 'y', 'z', 'depthX', 'depthY', 'colorX', 'colorY',
                        'orientationW', 'orientationX', 'orientationY',
                        'orientationZ', 'trackingState'
                    ]
                    joint_info = {
                        k: float(v)
                        for k, v in zip(joint_info_key, f.readline().split())
                    }
                    body_info['jointInfo'].append(joint_info)
                frame_info['bodyInfo'].append(body_info)
            skeleton_sequence['frameInfo'].append(frame_info)
    return skeleton_sequence


def read_xyz(file, max_body=2, num_joint=25):
    seq_info = read_skeleton(file)
    data = np.zeros((3, seq_info['numFrame'], num_joint, max_body))
    for n, f in enumerate(seq_info['frameInfo']):
        for m, b in enumerate(f['bodyInfo']):
            for j, v in enumerate(b['jointInfo']):
                if m < max_body and j < num_joint:
                    data[:, n, j, m] = [v['x'], v['y'], v['z']]
                else:
                    pass
    data = np.moveaxis(data, [0, 1, 2, 3], [3, 0, 2, 1])
    # duration, body, joint, component
    return data


# ---------------------------------------------------------
# 1. Txt files to individual original numpy files
# ---------------------------------------------------------
def generate_np_files():
    assert pathlib.Path('./nturgb+d_skeletons/').exists()
    assert pathlib.Path('./ignored_sk.txt').exists()

    all_sk = glob.glob('nturgb+d_skeletons/*.skeleton')
    ignored_sk = ['nturgb+d_skeletons/' + r.rstrip('\n') + '.skeleton' for r in open('ignored_sk.txt', 'r').readlines()]
    all_sk = [ski for ski in all_sk if ski not in ignored_sk]

    pathlib.Path('./nturgb+d_skeletons_np_raw').mkdir(parents=True, exist_ok=True)

    # read first shape
    s0 = read_xyz(all_sk[0]).shape

    # save as numpy arrays for quicker access
    for idx, skf in enumerate(tqdm.tqdm(all_sk)):
        data = read_xyz(skf)
        # except duration, all the shapes dims should match
        assert data.shape[1:] == s0[1:]
        np.save(skf.replace('nturgb+d_skeletons/', 'nturgb+d_skeletons_np_raw/').replace('.skeleton', ''), data)


# ---------------------------------------------------------
# 2. Resize individual numpy files to the same length and save as one dataset
# ---------------------------------------------------------
def generate_resized_dataset_file(final_length=100):
    npy = sorted(glob.glob('nturgb+d_skeletons_np_raw/*'))

    pathlib.Path('./nturgb+d_skeletons_np_{}'.format(final_length)).mkdir(parents=True, exist_ok=True)

    all_tensors = []
    all_scpra = []
    all_meta = []

    for n in tqdm.tqdm(npy):
        d = numpy.load(n)
        L0 = d.shape[0]
        nn = n.split('/')[-1].split('.npy')[0]
        S, C, P, R, A, filename = int(nn[1:4]), int(nn[5:8]), int(nn[9:12]), int(nn[13:16]), int(nn[17:20]), nn
        dr = torch.nn.functional.interpolate(input=torch.from_numpy(d).reshape(-1, 2 * 25 * 3).transpose(0, 1).unsqueeze(0),
                                             size=final_length).squeeze(0).transpose(1, 0).reshape(-1, 2, 25, 3).numpy()
        numpy.save(n.replace('nturgb+d_skeletons_np_raw', 'nturgb+d_skeletons_np_{}'.format(final_length)), dr)
        all_tensors += [dr]
        all_scpra += [numpy.array([S, C, P, R, A])]
        all_meta += [(filename, 'original_len_{}'.format(L0), 'resized_len_{}'.format(final_length))]

    all_scpra = numpy.array(all_scpra)
    all_tensors = numpy.array(all_tensors)

    data = {
        'x': all_tensors,
        'scpra': all_scpra,
        'meta': all_meta
    }
    return data


# ---------------------------------------------------------
# 3. I/O
# ---------------------------------------------------------
def write_data(data, filepath):
    """Save the dataset to a file. Note: data is a dict with keys 'x', ..."""
    with open(filepath, 'wb') as output_file:
        joblib.dump(data, output_file)


def load_ntu_data(filepath='./nturgbd__len100__data.pckl'):
    """
    Returns gesture sequences (X) and their associated labels (SCPRA) and meta information (meta).
    X.shape = (56578, 100, 2, 25, 3) = (batch, duration, body, joint, joint_component)
    SCPRA.shape = (56578, 5) = (batch, labels_s_c_p_r_and_a)
    len(Y) = 56578, with each line in Y being: (original_filename, original_len, resized_len=100)
    """
    file = open(filepath, 'rb')
    data = joblib.load(file)
    file.close()
    return data['x'], data['scpra'], data['meta']


# ---------------------------------------------------------
# 4. main
# ---------------------------------------------------------
if __name__ == '__main__':
    final_length = 100
    filename = 'nturgbd__len{}__data.pckl'.format(final_length)

    # 1. Generate individual numpy files
    #generate_np_files()

    # 2. Resize sequences and generate one dataset file
    #data = generate_resized_dataset_file(final_length=final_length)
    #write_data(data, filename)

    # 3. Check everything went as we wanted
    x, scpra, meta = load_ntu_data(filename)
    y = scpra[:, -1]
    print('NTU (xyz version) dataset shape:', x.shape, scpra.shape)
    print('Y labels values are between {} (included) and {} (included).'.format(int(y.min()), int(y.max())))

    sanity_check = True
    idx_sanity_check = 2222

    sanity_check_filename = meta[idx_sanity_check][0]
    x_res = x[idx_sanity_check]
    x_nonres = read_xyz('./nturgb+d_skeletons/' + sanity_check_filename + '.skeleton')
    print('Shape of same sequence before and after resize:', x_nonres.shape, '--->', x_res.shape)

    if sanity_check:
        # shapes: duration, body, joint, joint_component
        x_res_viz_alternated = x_res[:, 0, :, :].reshape(x_res.shape[0], -1)
        x_nonres_viz_alternated = x_nonres[:, 0, :, :].reshape(x_nonres.shape[0], -1)
        x_res_viz_block = numpy.hstack([x_res_viz_alternated[:, ::3], x_res_viz_alternated[:, 1::3], x_res_viz_alternated[:, 2::3]])
        x_nonres_viz_block = numpy.hstack([x_nonres_viz_alternated[:, ::3], x_nonres_viz_alternated[:, 1::3], x_nonres_viz_alternated[:, 2::3]])
        fig, (ax1, ax2) = plt.subplots(1, 2)
        fig.suptitle('Same sequence resized or not (viz: alternated)')
        ax1.imshow(x_nonres_viz_alternated)
        ax1.set_title('Original')
        ax2.imshow(x_res_viz_alternated)
        ax2.set_title('Resized')
        plt.show()
        fig, (ax1, ax2) = plt.subplots(1, 2)
        fig.suptitle('Same sequence resized or not (viz: block)')
        ax1.imshow(x_nonres_viz_block)
        ax1.set_title('Original')
        ax2.imshow(x_res_viz_block)
        ax2.set_title('Resized')
        plt.show()

As WXYZ orientation sequences

# TODO

4. Data loading

As XYZ pose sequences

import joblib

def load_ntu_data(filepath='./nturgbd__len100__data.pckl'):
    """
    Returns gesture sequences (X) and their associated labels (SCPRA) and meta information (meta).
    X.shape = (56578, 100, 2, 25, 3) = (batch, duration, body, joint, joint_component)
    SCPRA.shape = (56578, 5) = (batch, labels_s_c_p_r_and_a)
    len(Y) = 56578, with each line in Y being: (original_filename, original_len, resized_len=100)
    """
    file = open(filepath, 'rb')
    data = joblib.load(file)
    file.close()
    return data['x'], data['scpra'], data['meta']


filename = './nturgbd__len100__data.pckl'
x, scpra, meta = load_ntu_data(filename)
y = scpra[:, -1]

As WXYZ orientation sequences

# TODO

5. Classes Names

Action type Classes in NTU60 Classes in NTU60
Daily Actions 40 82
Medical Conditions 9 12
Mutual Actions: Two Person Interactions 11 26
Total 60 120
import itertools

# NTU 120
ntu_120_daily_actions = {
    1: 'Drink water',
    2: 'Eat meal',
    3: 'Brush teeth',
    4: 'Brush hair',
    5: 'Drop',
    6: 'Pick up',
    7: 'Throw',
    8: 'Sit down',
    9: 'Stand up',
    10: 'Clapping',
    11: 'Reading',
    12: 'Writing',
    13: 'Tear up paper',
    14: 'Put on jacket',
    15: 'Take off jacket',
    16: 'Put on a shoe',
    17: 'Take off a shoe',
    18: 'Put on glasses',
    19: 'Take off glasses',
    20: 'Put on a hat/cap',
    21: 'Take off a hat/cap',
    22: 'Cheer up',
    23: 'Hand waving',
    24: 'Kicking something',
    25: 'Reach into pocket',
    26: 'Hopping',
    27: 'Jump up',
    28: 'Phone call',
    29: 'Play with phone/tablet',
    30: 'Type on a keyboard',
    31: 'Point to something',
    32: 'Taking a selfie',
    33: 'Check time (from watch',
    34: 'Rub two hands',
    35: 'Nod head/bow',
    36: 'Shake head',
    37: 'Wipe face',
    38: 'Salute',
    39: 'Put palms together',
    40: 'Cross hands in front',
    61: 'Put on headphone',
    62: 'Take off headphone',
    63: 'Shoot at basket',
    64: 'Bounce ball',
    65: 'Tennis bat swing',
    66: 'Juggle table tennis ball',
    67: 'Hush',
    68: 'Flick hair',
    69: 'Thumb up',
    70: 'Thumb down',
    71: 'Make OK sign',
    72: 'Make victory sign',
    73: 'Staple book',
    74: 'Counting money',
    75: 'Cutting nails',
    76: 'Cutting paper',
    77: 'Snap fingers',
    78: 'Open bottle',
    79: 'Sniff/smell',
    80: 'Squat down',
    81: 'Toss a coin',
    82: 'Fold paper',
    83: 'Ball up paper',
    84: 'Play magic cube',
    85: 'Apply cream on face',
    86: 'Apply cream on hand',
    87: 'Put on bag',
    88: 'Take off bag',
    89: 'Put object into bag',
    90: 'Take object out of bag',
    91: 'Open a box',
    92: 'Move heavy objects',
    93: 'Shake fist',
    94: 'Throw up cap/hat',
    95: 'Capitulate',
    96: 'Cross arms',
    97: 'Arm circles',
    98: 'Arm swings',
    99: 'Run on the spot',
    100: 'Butt kicks',
    101: 'Cross toe touch',
    102: 'Side kick'
}
ntu_120_medical_actions = {
    41: 'Sneeze/cough',
    42: 'Staggering',
    43: 'Falling down',
    44: 'Headache',
    45: 'Chest pain',
    46: 'Back pain',
    47: 'Neck pain',
    48: 'Nausea/vomiting',
    49: 'Fan self',
    103: 'Yawn',
    104: 'Stretch oneself',
    105: 'Blow nose'
}
ntu_120_two_person_actions = {
    50: 'Punch/slap',
    51: 'Kicking',
    52: 'Pushing',
    53: 'Pat on back',
    54: 'Point finger',
    55: 'Hugging',
    56: 'Giving object',
    57: 'Touch pocket',
    58: 'Shaking hands',
    59: 'Walking towards',
    60: 'Walking apart',
    106: 'Hit with object',
    107: 'Wield knife',
    108: 'Knock over',
    109: 'Grab stuff',
    110: 'Shoot with gun',
    111: 'Step on foot',
    112: 'High-five',
    113: 'Cheers and drink',
    114: 'Carry object',
    115: 'Take a photo',
    116: 'Follow',
    117: 'Whisper',
    118: 'Exchange things',
    119: 'Support somebody',
    120: 'Rock-paper-scissors',
}
ntu_120_actions = dict(itertools.chain.from_iterable(d.items() for d in (ntu_120_daily_actions, ntu_120_medical_actions, ntu_120_two_person_actions)))

# NTU 60
ntu_60_daily_actions = {k:ntu_120_daily_actions[k] for k in ntu_120_daily_actions if k<=60}
ntu_60_medical_actions = {k:ntu_120_medical_actions[k] for k in ntu_120_medical_actions if k<=60}
ntu_60_two_person_actions = {k:ntu_120_two_person_actions[k] for k in ntu_120_two_person_actions if k<=60}
ntu_60_actions = {k:ntu_120_actions[k] for k in ntu_120_actions if k<=60}

6. Full example

# TODO