Add files using upload-large-folder tool
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- TransRAC/.idea/.gitignore +8 -0
- TransRAC/.idea/SVIP_Counting.iml +8 -0
- TransRAC/.idea/deployment.xml +14 -0
- TransRAC/.idea/misc.xml +4 -0
- TransRAC/.idea/modules.xml +8 -0
- TransRAC/.idea/vcs.xml +6 -0
- TransRAC/dataset/RepCountA_Loader.py +104 -0
- TransRAC/dataset/RepCountA_raw_Loader.py +142 -0
- TransRAC/dataset/RepCountB_Loader.py +111 -0
- TransRAC/dataset/UCFRep_loader.py +113 -0
- TransRAC/dataset/__init__.py +0 -0
- TransRAC/dataset/label_norm.py +35 -0
- TransRAC/figures/readme.md +1 -0
- TransRAC/log/__init__.py +1 -0
- TransRAC/mmaction/__init__.py +15 -0
- TransRAC/mmaction/apis/__init__.py +8 -0
- TransRAC/mmaction/apis/inference.py +156 -0
- TransRAC/mmaction/apis/test.py +204 -0
- TransRAC/mmaction/apis/train.py +261 -0
- TransRAC/mmaction/core/__init__.py +6 -0
- TransRAC/mmaction/datasets/__init__.py +27 -0
- TransRAC/mmaction/datasets/activitynet_dataset.py +269 -0
- TransRAC/mmaction/datasets/audio_dataset.py +69 -0
- TransRAC/mmaction/datasets/audio_feature_dataset.py +70 -0
- TransRAC/mmaction/datasets/audio_visual_dataset.py +76 -0
- TransRAC/mmaction/datasets/ava_dataset.py +382 -0
- TransRAC/mmaction/datasets/base.py +287 -0
- TransRAC/mmaction/datasets/builder.py +132 -0
- TransRAC/mmaction/datasets/dataset_wrappers.py +30 -0
- TransRAC/mmaction/datasets/hvu_dataset.py +191 -0
- TransRAC/mmaction/datasets/image_dataset.py +45 -0
- TransRAC/mmaction/datasets/pose_dataset.py +98 -0
- TransRAC/mmaction/datasets/rawframe_dataset.py +183 -0
- TransRAC/mmaction/datasets/rawvideo_dataset.py +146 -0
- TransRAC/mmaction/datasets/ssn_dataset.py +881 -0
- TransRAC/mmaction/datasets/video_dataset.py +60 -0
- TransRAC/mmaction/localization/__init__.py +10 -0
- TransRAC/mmaction/localization/bsn_utils.py +267 -0
- TransRAC/mmaction/localization/proposal_utils.py +94 -0
- TransRAC/mmaction/localization/ssn_utils.py +168 -0
- TransRAC/mmaction/utils/decorators.py +32 -0
- TransRAC/mmaction/utils/logger.py +24 -0
- TransRAC/mmaction/utils/optimizer.py +33 -0
- TransRAC/mmaction/version.py +18 -0
- TransRAC/mmcv_custom/__init__.py +1 -0
- TransRAC/models/TransRAC.py +254 -0
- TransRAC/models/__init__.py +0 -0
- TransRAC/open_set/new_test.csv +0 -0
- TransRAC/open_set/new_train.csv +0 -0
- TransRAC/open_set/new_valid.csv +129 -0
TransRAC/.idea/.gitignore
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Default ignored files
|
| 2 |
+
/shelf/
|
| 3 |
+
/workspace.xml
|
| 4 |
+
# Datasource local storage ignored files
|
| 5 |
+
/dataSources/
|
| 6 |
+
/dataSources.local.xml
|
| 7 |
+
# Editor-based HTTP Client requests
|
| 8 |
+
/httpRequests/
|
TransRAC/.idea/SVIP_Counting.iml
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?xml version="1.0" encoding="UTF-8"?>
|
| 2 |
+
<module type="PYTHON_MODULE" version="4">
|
| 3 |
+
<component name="NewModuleRootManager">
|
| 4 |
+
<content url="file://$MODULE_DIR$" />
|
| 5 |
+
<orderEntry type="inheritedJdk" />
|
| 6 |
+
<orderEntry type="sourceFolder" forTests="false" />
|
| 7 |
+
</component>
|
| 8 |
+
</module>
|
TransRAC/.idea/deployment.xml
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?xml version="1.0" encoding="UTF-8"?>
|
| 2 |
+
<project version="4">
|
| 3 |
+
<component name="PublishConfigData" remoteFilesAllowedToDisappearOnAutoupload="false">
|
| 4 |
+
<serverData>
|
| 5 |
+
<paths name="root@10.15.89.41:23035 password">
|
| 6 |
+
<serverdata>
|
| 7 |
+
<mappings>
|
| 8 |
+
<mapping local="$PROJECT_DIR$" web="/" />
|
| 9 |
+
</mappings>
|
| 10 |
+
</serverdata>
|
| 11 |
+
</paths>
|
| 12 |
+
</serverData>
|
| 13 |
+
</component>
|
| 14 |
+
</project>
|
TransRAC/.idea/misc.xml
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?xml version="1.0" encoding="UTF-8"?>
|
| 2 |
+
<project version="4">
|
| 3 |
+
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.8" project-jdk-type="Python SDK" />
|
| 4 |
+
</project>
|
TransRAC/.idea/modules.xml
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?xml version="1.0" encoding="UTF-8"?>
|
| 2 |
+
<project version="4">
|
| 3 |
+
<component name="ProjectModuleManager">
|
| 4 |
+
<modules>
|
| 5 |
+
<module fileurl="file://$PROJECT_DIR$/.idea/SVIP_Counting.iml" filepath="$PROJECT_DIR$/.idea/SVIP_Counting.iml" />
|
| 6 |
+
</modules>
|
| 7 |
+
</component>
|
| 8 |
+
</project>
|
TransRAC/.idea/vcs.xml
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?xml version="1.0" encoding="UTF-8"?>
|
| 2 |
+
<project version="4">
|
| 3 |
+
<component name="VcsDirectoryMappings">
|
| 4 |
+
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
| 5 |
+
</component>
|
| 6 |
+
</project>
|
TransRAC/dataset/RepCountA_Loader.py
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
'''
|
| 2 |
+
Repcount data loader from fixed frames file(.npz) which will be uploaded soon.
|
| 3 |
+
if you don't pre-process the data file,for example,your raw file is .mp4,
|
| 4 |
+
you can use the *RepCountA_raw_Loader.py*(slowly).
|
| 5 |
+
or
|
| 6 |
+
you can use 'tools.video2npz.py' to transform .mp4 tp .npz
|
| 7 |
+
'''
|
| 8 |
+
import csv
|
| 9 |
+
import os
|
| 10 |
+
import os.path as osp
|
| 11 |
+
import numpy as np
|
| 12 |
+
import math
|
| 13 |
+
|
| 14 |
+
from torch.utils.data import Dataset, DataLoader
|
| 15 |
+
import torch
|
| 16 |
+
from .label_norm import normalize_label
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
class MyData(Dataset):
|
| 20 |
+
|
| 21 |
+
def __init__(self, root_path, video_path, label_path, num_frame):
|
| 22 |
+
"""
|
| 23 |
+
:param root_path: root path
|
| 24 |
+
:param video_path: video child path (folder)
|
| 25 |
+
:param label_path: label child path(.csv)
|
| 26 |
+
"""
|
| 27 |
+
self.root_path = root_path
|
| 28 |
+
self.video_path = os.path.join(self.root_path, video_path) # train or valid
|
| 29 |
+
self.label_path = os.path.join(self.root_path, label_path)
|
| 30 |
+
self.video_dir = os.listdir(self.video_path)
|
| 31 |
+
self.label_dict = get_labels_dict(self.label_path) # get all labels
|
| 32 |
+
self.num_frame = num_frame
|
| 33 |
+
|
| 34 |
+
def __getitem__(self, inx):
|
| 35 |
+
""" get data item
|
| 36 |
+
:param video_tensor, label
|
| 37 |
+
"""
|
| 38 |
+
video_file_name = self.video_dir[inx]
|
| 39 |
+
file_path = os.path.join(self.video_path, video_file_name)
|
| 40 |
+
video_tensor, video_frame_length = get_frames(file_path) # [64, 3, 224, 224]
|
| 41 |
+
video_tensor = video_tensor.transpose(0, 1) # [64, 3, 224, 224] -> [ 3, 64, 224, 224]
|
| 42 |
+
if video_file_name in self.label_dict.keys():
|
| 43 |
+
time_points = self.label_dict[video_file_name]
|
| 44 |
+
label = preprocess(video_frame_length, time_points, num_frames=self.num_frame)
|
| 45 |
+
label = torch.tensor(label)
|
| 46 |
+
return [video_tensor, label]
|
| 47 |
+
else:
|
| 48 |
+
print(video_file_name, 'not exist')
|
| 49 |
+
return
|
| 50 |
+
|
| 51 |
+
def __len__(self):
|
| 52 |
+
""":return the number of video """
|
| 53 |
+
return len(self.video_dir)
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
def get_frames(npz_path):
|
| 57 |
+
# get frames from .npz files
|
| 58 |
+
with np.load(npz_path, allow_pickle=True) as data:
|
| 59 |
+
frames = data['imgs'] # numpy.narray [64, 3, 224, 224]
|
| 60 |
+
frames_length = data['fps'].item() # the raw video(.mp4) total frames number
|
| 61 |
+
frames = torch.FloatTensor(frames)
|
| 62 |
+
frames -= 127.5
|
| 63 |
+
frames /= 127.5
|
| 64 |
+
return frames, frames_length
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
def get_labels_dict(path):
|
| 68 |
+
# read label.csv to RAM
|
| 69 |
+
labels_dict = {}
|
| 70 |
+
check_file_exist(path)
|
| 71 |
+
with open(path, encoding='utf-8') as f:
|
| 72 |
+
f_csv = csv.DictReader(f)
|
| 73 |
+
for row in f_csv:
|
| 74 |
+
cycle = [int(float(row[key])) for key in row.keys() if 'L' in key and row[key] != '']
|
| 75 |
+
if not row['count']:
|
| 76 |
+
print(row['name'] + 'error')
|
| 77 |
+
else:
|
| 78 |
+
labels_dict[row['name'].split('.')[0] + str('.npz')] = cycle
|
| 79 |
+
|
| 80 |
+
return labels_dict
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
def preprocess(video_frame_length, time_points, num_frames):
|
| 84 |
+
"""
|
| 85 |
+
process label(.csv) to density map label
|
| 86 |
+
Args:
|
| 87 |
+
video_frame_length: video total frame number, i.e 1024frames
|
| 88 |
+
time_points: label point example [1, 23, 23, 40,45,70,.....] or [0]
|
| 89 |
+
num_frames: 64
|
| 90 |
+
Returns: for example [0.1,0.8,0.1, .....]
|
| 91 |
+
"""
|
| 92 |
+
new_crop = []
|
| 93 |
+
for i in range(len(time_points)): # frame_length -> 64
|
| 94 |
+
item = min(math.ceil((float((time_points[i])) / float(video_frame_length)) * num_frames), num_frames - 1)
|
| 95 |
+
new_crop.append(item)
|
| 96 |
+
new_crop = np.sort(new_crop)
|
| 97 |
+
label = normalize_label(new_crop, num_frames)
|
| 98 |
+
|
| 99 |
+
return label
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
def check_file_exist(filename, msg_tmpl='file "{}" does not exist'):
|
| 103 |
+
if not osp.isfile(filename):
|
| 104 |
+
raise FileNotFoundError(msg_tmpl.format(filename))
|
TransRAC/dataset/RepCountA_raw_Loader.py
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
If you don't want to pre-process the RepCount dataset,
|
| 3 |
+
you can use this script to load data from raw video(.mp4).
|
| 4 |
+
The trainning speed is slower and the memory cost more.
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import os
|
| 8 |
+
import os.path as osp
|
| 9 |
+
import numpy as np
|
| 10 |
+
import math
|
| 11 |
+
import cv2
|
| 12 |
+
from torch.utils.data import Dataset
|
| 13 |
+
import torch
|
| 14 |
+
import csv
|
| 15 |
+
import kornia
|
| 16 |
+
from .label_norm import normalize_label
|
| 17 |
+
import torchvision.transforms as transforms
|
| 18 |
+
|
| 19 |
+
class MyData(Dataset):
|
| 20 |
+
|
| 21 |
+
def __init__(self, root_path, video_path, label_path, num_frame):
|
| 22 |
+
self.root_path = root_path
|
| 23 |
+
self.video_path = video_path
|
| 24 |
+
self.label_dir = os.path.join(root_path, label_path)
|
| 25 |
+
self.video_dir = os.listdir(os.path.join(self.root_path, self.video_path))
|
| 26 |
+
self.label_dict = get_labels_dict(self.label_dir) # get all labels
|
| 27 |
+
self.num_frame = num_frame
|
| 28 |
+
|
| 29 |
+
def __getitem__(self, inx):
|
| 30 |
+
video_file_name= self.video_dir[inx]
|
| 31 |
+
file_path = os.path.join(self.root_path, self.video_path, video_file_name)
|
| 32 |
+
video_rd = VideoRead(file_path, num_frames=self.num_frame)
|
| 33 |
+
video_tensor = video_rd.crop_frame()
|
| 34 |
+
video_frame_length = video_rd.frame_length
|
| 35 |
+
video_tensor = video_tensor.transpose(0, 1) # [64, 3, 224, 224] -> [ 3, 64, 224, 224]
|
| 36 |
+
if video_file_name in self.label_dict.keys():
|
| 37 |
+
time_points = self.label_dict[video_file_name]
|
| 38 |
+
label = preprocess(video_frame_length, time_points, num_frames=self.num_frame)
|
| 39 |
+
label = torch.tensor(label)
|
| 40 |
+
return [video_tensor, label]
|
| 41 |
+
else:
|
| 42 |
+
print(video_file_name, 'not exist')
|
| 43 |
+
return
|
| 44 |
+
|
| 45 |
+
def __len__(self):
|
| 46 |
+
"""返回数据集的大小"""
|
| 47 |
+
return len(self.video_dir)
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
class VideoRead:
|
| 51 |
+
def __init__(self, video_path, num_frames):
|
| 52 |
+
self.video_path = video_path
|
| 53 |
+
self.frame_length = 0
|
| 54 |
+
self.num_frames = num_frames
|
| 55 |
+
|
| 56 |
+
def get_frame(self):
|
| 57 |
+
cap = cv2.VideoCapture(self.video_path)
|
| 58 |
+
assert cap.isOpened()
|
| 59 |
+
frames = []
|
| 60 |
+
|
| 61 |
+
while cap.isOpened():
|
| 62 |
+
ret, frame = cap.read()
|
| 63 |
+
if not ret:
|
| 64 |
+
break
|
| 65 |
+
frames.append(frame)
|
| 66 |
+
cap.release()
|
| 67 |
+
self.frame_length = len(frames)
|
| 68 |
+
return frames
|
| 69 |
+
|
| 70 |
+
def crop_frame(self):
|
| 71 |
+
"""to crop frames to tensor
|
| 72 |
+
return: tensor [64, 3, 224, 224]
|
| 73 |
+
"""
|
| 74 |
+
frames = self.get_frame() # frames: the all frames of video
|
| 75 |
+
frames_tensor = []
|
| 76 |
+
if self.num_frames <= len(frames):
|
| 77 |
+
for i in range(self.num_frames):
|
| 78 |
+
# select 64 frames from total original frames, proportionally
|
| 79 |
+
frame = frames[i * len(frames) // self.num_frames]
|
| 80 |
+
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
|
| 81 |
+
frame = cv2.resize(frame, (224, 224)) # [3, 224, 224]
|
| 82 |
+
frame = transforms.ToTensor()(frame)
|
| 83 |
+
frames_tensor.append(frame)
|
| 84 |
+
|
| 85 |
+
else: # if raw frames number lower than 64, padding it.
|
| 86 |
+
for i in range(self.frame_length):
|
| 87 |
+
frame = frames[i]
|
| 88 |
+
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
|
| 89 |
+
frame = cv2.resize(frame, (224, 224)) # [ 3, 224, 224]
|
| 90 |
+
frame = transforms.ToTensor()(frame)
|
| 91 |
+
frames_tensor.append(frame)
|
| 92 |
+
for i in range(self.num_frames - self.frame_length):
|
| 93 |
+
frame = frames[self.frame_length - 1]
|
| 94 |
+
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
|
| 95 |
+
frame = cv2.resize(frame, (224, 224)) # [ 3, 224, 224]
|
| 96 |
+
frame = transforms.ToTensor()(frame)
|
| 97 |
+
frames_tensor.append(frame)
|
| 98 |
+
Frame_Tensor=torch.as_tensor(np.stack(frames_tensor))
|
| 99 |
+
|
| 100 |
+
return Frame_Tensor
|
| 101 |
+
|
| 102 |
+
|
| 103 |
+
def get_labels_dict(path):
|
| 104 |
+
labels_dict = {}
|
| 105 |
+
check_file_exist(path)
|
| 106 |
+
with open(path, encoding='utf-8') as f:
|
| 107 |
+
f_csv = csv.DictReader(f)
|
| 108 |
+
for row in f_csv:
|
| 109 |
+
cycle = [int(row[key]) for key in row.keys() if 'L' in key and row[key] != '']
|
| 110 |
+
labels_dict[row['name']] = cycle
|
| 111 |
+
|
| 112 |
+
return labels_dict
|
| 113 |
+
|
| 114 |
+
def preprocess(video_frame_length, time_points, num_frames):
|
| 115 |
+
"""
|
| 116 |
+
process label(.csv) to density map label
|
| 117 |
+
Args:
|
| 118 |
+
video_frame_length: video total frame number, i.e 1024frames
|
| 119 |
+
time_points: label point example [1, 23, 23, 40,45,70,.....] or [0]
|
| 120 |
+
num_frames: 64
|
| 121 |
+
Returns: for example list [0.1,0.8,0.1, .....]
|
| 122 |
+
"""
|
| 123 |
+
new_crop = []
|
| 124 |
+
for i in range(len(time_points)): # frame_length -> 64
|
| 125 |
+
item = min(math.ceil((float((time_points[i])) / float(video_frame_length)) * num_frames), num_frames - 1)
|
| 126 |
+
new_crop.append(item)
|
| 127 |
+
new_crop = np.sort(new_crop)
|
| 128 |
+
label = normalize_label(new_crop, num_frames)
|
| 129 |
+
|
| 130 |
+
return label
|
| 131 |
+
|
| 132 |
+
def check_file_exist(filename, msg_tmpl='file "{}" does not exist'):
|
| 133 |
+
if not osp.isfile(filename):
|
| 134 |
+
raise FileNotFoundError(msg_tmpl.format(filename))
|
| 135 |
+
|
| 136 |
+
# # example
|
| 137 |
+
# root_dir = r'/p300/data/dataset/'
|
| 138 |
+
# video_dir = 'train'
|
| 139 |
+
# label_dir = 'train.csv'
|
| 140 |
+
# train_dataset = MyData(root_dir, video_dir, label_dir,64)
|
| 141 |
+
# trainloader = DataLoader(train_dataset, batch_size=1, shuffle=True)
|
| 142 |
+
|
TransRAC/dataset/RepCountB_Loader.py
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
|
| 3 |
+
import numpy as np
|
| 4 |
+
import csv
|
| 5 |
+
import cv2
|
| 6 |
+
from torch.utils.data import Dataset, DataLoader
|
| 7 |
+
import kornia
|
| 8 |
+
import torch
|
| 9 |
+
import torchvision.transforms as transforms
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
class TestData(Dataset):
|
| 13 |
+
def __init__(self, root_path, video_path, label_path, num_frame):
|
| 14 |
+
self.root_path = root_path
|
| 15 |
+
self.video_path = video_path
|
| 16 |
+
self.label_dir = os.path.join(root_path, label_path)
|
| 17 |
+
self.video_dir = os.listdir(os.path.join(self.root_path, self.video_path))
|
| 18 |
+
self.label_dict = get_labels_dict(self.label_dir) # get all labels
|
| 19 |
+
self.num_frame = num_frame
|
| 20 |
+
|
| 21 |
+
def __getitem__(self, inx):
|
| 22 |
+
video_name= self.video_dir[inx]
|
| 23 |
+
file_path = os.path.join(self.root_path, self.video_path, video_name)
|
| 24 |
+
video_rd = VideoRead(file_path, num_frames=self.num_frame)
|
| 25 |
+
video_tensor = video_rd.crop_frame()
|
| 26 |
+
video_frame_length = video_rd.frame_length
|
| 27 |
+
video_tensor = video_tensor.transpose(0, 1) # [64, 3, 224, 224] -> [ 3, 64, 224, 224]
|
| 28 |
+
count = self.label_dict[video_name]
|
| 29 |
+
count = torch.tensor(count)
|
| 30 |
+
|
| 31 |
+
return video_tensor, count
|
| 32 |
+
def __len__(self):
|
| 33 |
+
"""返回数据集的大小"""
|
| 34 |
+
return len(self.video_dir)
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
class VideoRead:
|
| 38 |
+
def __init__(self, video_path, num_frames=64):
|
| 39 |
+
self.video_path = video_path
|
| 40 |
+
self.frame_length = 0
|
| 41 |
+
self.num_frames = num_frames
|
| 42 |
+
|
| 43 |
+
def get_frame(self):
|
| 44 |
+
cap = cv2.VideoCapture(self.video_path)
|
| 45 |
+
assert cap.isOpened()
|
| 46 |
+
frames = []
|
| 47 |
+
|
| 48 |
+
while cap.isOpened():
|
| 49 |
+
ret, frame = cap.read()
|
| 50 |
+
if not ret:
|
| 51 |
+
break
|
| 52 |
+
frames.append(frame)
|
| 53 |
+
cap.release()
|
| 54 |
+
self.frame_length = len(frames)
|
| 55 |
+
return frames
|
| 56 |
+
|
| 57 |
+
def crop_frame(self):
|
| 58 |
+
"""to crop frames to tensor [64, 3, 224, 224]"""
|
| 59 |
+
frames = self.get_frame() # frames: the all frames of video
|
| 60 |
+
frames_tensor = []
|
| 61 |
+
if self.num_frames <= len(frames):
|
| 62 |
+
for i in range(self.num_frames):
|
| 63 |
+
# select 64 frames from total original frames, proportionally
|
| 64 |
+
frame = frames[i * len(frames) // self.num_frames]
|
| 65 |
+
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
|
| 66 |
+
frame = cv2.resize(frame, (224, 224)) # [64, 3, 224, 224]
|
| 67 |
+
frame = transforms.ToTensor()(frame)
|
| 68 |
+
frames_tensor.append(frame)
|
| 69 |
+
|
| 70 |
+
else: # if raw frames number lower than 64, padding it.
|
| 71 |
+
for i in range(self.frame_length):
|
| 72 |
+
frame = frames[i]
|
| 73 |
+
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
|
| 74 |
+
frame = cv2.resize(frame, (224, 224)) # [64, 3, 224, 224]
|
| 75 |
+
frame = transforms.ToTensor()(frame)
|
| 76 |
+
frames_tensor.append(frame)
|
| 77 |
+
for i in range(self.num_frames - self.frame_length):
|
| 78 |
+
frame = frames[self.frame_length - 1]
|
| 79 |
+
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
|
| 80 |
+
frame = cv2.resize(frame, (224, 224)) # [64, 3, 224, 224]
|
| 81 |
+
frame = transforms.ToTensor()(frame)
|
| 82 |
+
frames_tensor.append(frame)
|
| 83 |
+
# frames_tensor = np.asarray_chkfinite(frames_tensor, dtype=np.uint8)
|
| 84 |
+
# frames_tensor = kornia.image_to_tensor(frames_tensor, keepdim=False).div(255.0)
|
| 85 |
+
Frame_Tensor=torch.as_tensor(np.stack(frames_tensor))
|
| 86 |
+
|
| 87 |
+
return Frame_Tensor
|
| 88 |
+
|
| 89 |
+
|
| 90 |
+
def get_labels_dict(path):
|
| 91 |
+
labels_dict = {}
|
| 92 |
+
check_file_exist(path)
|
| 93 |
+
with open(path, encoding='utf-8') as f:
|
| 94 |
+
f_csv = csv.DictReader(f)
|
| 95 |
+
for row in f_csv:
|
| 96 |
+
cycle = [int(row[key]) for key in row.keys() if 'L' in key and row[key] != '']
|
| 97 |
+
if not row['count']:
|
| 98 |
+
print(row['name']+'error')
|
| 99 |
+
else:
|
| 100 |
+
labels_dict[row['name']] = int(row['count'])
|
| 101 |
+
|
| 102 |
+
return labels_dict
|
| 103 |
+
|
| 104 |
+
|
| 105 |
+
def check_file_exist(filename, msg_tmpl='file "{}" does not exist'):
|
| 106 |
+
if not os.path.isfile(filename):
|
| 107 |
+
raise FileNotFoundError(msg_tmpl.format(filename))
|
| 108 |
+
|
| 109 |
+
|
| 110 |
+
|
| 111 |
+
|
TransRAC/dataset/UCFRep_loader.py
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
|
| 3 |
+
import numpy as np
|
| 4 |
+
import csv
|
| 5 |
+
import cv2
|
| 6 |
+
from torch.utils.data import Dataset, DataLoader
|
| 7 |
+
import kornia
|
| 8 |
+
import torch
|
| 9 |
+
|
| 10 |
+
class TestData(Dataset):
|
| 11 |
+
def __init__(self, root_path, video_path, label_path, num_frame):
|
| 12 |
+
"""
|
| 13 |
+
Args:
|
| 14 |
+
root_path:
|
| 15 |
+
video_path:
|
| 16 |
+
label_path:
|
| 17 |
+
num_frame:
|
| 18 |
+
"""
|
| 19 |
+
self.root_path = root_path
|
| 20 |
+
self.video_path = video_path
|
| 21 |
+
self.label_dir = os.path.join(root_path, label_path)
|
| 22 |
+
self.video_dir = os.listdir(os.path.join(self.root_path, self.video_path))
|
| 23 |
+
self.label_dict = get_labels_dict(self.label_dir) # get all labels
|
| 24 |
+
self.num_frame = num_frame
|
| 25 |
+
|
| 26 |
+
def __getitem__(self, inx):
|
| 27 |
+
"""获取数据集中的item """
|
| 28 |
+
|
| 29 |
+
video_name= self.video_dir[inx]
|
| 30 |
+
file_path = os.path.join(self.root_path, self.video_path, video_name)
|
| 31 |
+
video_rd = VideoRead(file_path, num_frames=self.num_frame)
|
| 32 |
+
video_tensor = video_rd.crop_frame()
|
| 33 |
+
video_frame_length = video_rd.frame_length
|
| 34 |
+
video_tensor = video_tensor.transpose(0, 1) # [64, 3, 224, 224] -> [ 3, 64, 224, 224]
|
| 35 |
+
count = self.label_dict[video_name]
|
| 36 |
+
count = torch.tensor(count)
|
| 37 |
+
|
| 38 |
+
return video_tensor, count
|
| 39 |
+
def __len__(self):
|
| 40 |
+
"""返回数据集的大小"""
|
| 41 |
+
return len(self.video_dir)
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
class VideoRead:
|
| 45 |
+
def __init__(self, video_path, num_frames):
|
| 46 |
+
self.video_path = video_path
|
| 47 |
+
self.frame_length = 0
|
| 48 |
+
self.num_frames = num_frames
|
| 49 |
+
|
| 50 |
+
def get_frame(self):
|
| 51 |
+
cap = cv2.VideoCapture(self.video_path)
|
| 52 |
+
assert cap.isOpened()
|
| 53 |
+
frames = []
|
| 54 |
+
|
| 55 |
+
while cap.isOpened():
|
| 56 |
+
ret, frame = cap.read()
|
| 57 |
+
if not ret:
|
| 58 |
+
break
|
| 59 |
+
frames.append(frame)
|
| 60 |
+
cap.release()
|
| 61 |
+
self.frame_length = len(frames)
|
| 62 |
+
return frames
|
| 63 |
+
|
| 64 |
+
def crop_frame(self):
|
| 65 |
+
"""to crop frames to 64 frames"""
|
| 66 |
+
frames = self.get_frame()
|
| 67 |
+
frames_tensor = []
|
| 68 |
+
if self.num_frames <= self.frame_length:
|
| 69 |
+
for i in range(1, self.num_frames + 1):
|
| 70 |
+
frame = frames[i * len(frames) // self.num_frames - 1] # Proportional extraction (64 frames)
|
| 71 |
+
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
|
| 72 |
+
frame = cv2.resize(frame, (224, 224)) # [64, 3, 224, 224]
|
| 73 |
+
# frame = transform(frame).unsqueeze(0)
|
| 74 |
+
frames_tensor.append(frame)
|
| 75 |
+
|
| 76 |
+
else: # if raw frames number lower than 64, padding it.
|
| 77 |
+
for i in range(self.frame_length):
|
| 78 |
+
frame = frames[i]
|
| 79 |
+
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
|
| 80 |
+
frame = cv2.resize(frame, (224, 224)) # [64, 3, 224, 224]
|
| 81 |
+
frames_tensor.append(frame)
|
| 82 |
+
for i in range(self.num_frames - self.frame_length):
|
| 83 |
+
frame = frames[self.frame_length - 1]
|
| 84 |
+
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
|
| 85 |
+
frame = cv2.resize(frame, (224, 224)) # [64, 3, 224, 224]
|
| 86 |
+
frames_tensor.append(frame)
|
| 87 |
+
frames_tensor = np.asarray_chkfinite(frames_tensor, dtype=np.uint8)
|
| 88 |
+
frames_tensor = kornia.image_to_tensor(frames_tensor, keepdim=False).div(255.0)
|
| 89 |
+
|
| 90 |
+
return frames_tensor
|
| 91 |
+
|
| 92 |
+
|
| 93 |
+
def get_labels_dict(path):
|
| 94 |
+
labels_dict = {}
|
| 95 |
+
check_file_exist(path)
|
| 96 |
+
with open(path, encoding='utf-8') as f:
|
| 97 |
+
f_csv = csv.DictReader(f)
|
| 98 |
+
for row in f_csv:
|
| 99 |
+
if not row['count']:
|
| 100 |
+
print(row['name']+'error')
|
| 101 |
+
else:
|
| 102 |
+
labels_dict[row['name']] = int(row['count'])
|
| 103 |
+
|
| 104 |
+
return labels_dict
|
| 105 |
+
|
| 106 |
+
|
| 107 |
+
def check_file_exist(filename, msg_tmpl='file "{}" does not exist'):
|
| 108 |
+
if not os.path.isfile(filename):
|
| 109 |
+
raise FileNotFoundError(msg_tmpl.format(filename))
|
| 110 |
+
|
| 111 |
+
|
| 112 |
+
|
| 113 |
+
|
TransRAC/dataset/__init__.py
ADDED
|
File without changes
|
TransRAC/dataset/label_norm.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
""" transform label to groundtruth(density map)"""
|
| 2 |
+
from scipy import integrate
|
| 3 |
+
import math
|
| 4 |
+
import numpy as np
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
def PDF(x, u, sig):
|
| 8 |
+
# f(x)
|
| 9 |
+
return np.exp(-(x - u) ** 2 / (2 * sig ** 2)) / (math.sqrt(2 * math.pi) * sig)
|
| 10 |
+
|
| 11 |
+
# integral f(x)
|
| 12 |
+
def get_integrate(x_1, x_2, avg, sig):
|
| 13 |
+
y, err = integrate.quad(PDF, x_1, x_2, args=(avg, sig))
|
| 14 |
+
return y
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
def normalize_label(y_frame, y_length):
|
| 18 |
+
# y_length: total frames
|
| 19 |
+
# return: normalize_label size:nparray(y_length,)
|
| 20 |
+
y_label = [0 for i in range(y_length)] # 坐标轴长度,即帧数
|
| 21 |
+
for i in range(0, len(y_frame), 2):
|
| 22 |
+
x_a = y_frame[i]
|
| 23 |
+
x_b = y_frame[i + 1]
|
| 24 |
+
avg = (x_b + x_a) / 2
|
| 25 |
+
sig = (x_b - x_a) / 6
|
| 26 |
+
num = x_b - x_a + 1 # 帧数量 update 1104
|
| 27 |
+
if num != 1:
|
| 28 |
+
for j in range(num):
|
| 29 |
+
x_1 = x_a - 0.5 + j
|
| 30 |
+
x_2 = x_a + 0.5 + j
|
| 31 |
+
y_ing = get_integrate(x_1, x_2, avg, sig)
|
| 32 |
+
y_label[x_a + j] = y_ing
|
| 33 |
+
else:
|
| 34 |
+
y_label[x_a] = 1
|
| 35 |
+
return y_label
|
TransRAC/figures/readme.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
|
TransRAC/log/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
## store log
|
TransRAC/mmaction/__init__.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import mmcv
|
| 2 |
+
from mmcv import digit_version
|
| 3 |
+
|
| 4 |
+
from .version import __version__
|
| 5 |
+
|
| 6 |
+
mmcv_minimum_version = '1.3.1'
|
| 7 |
+
mmcv_maximum_version = '1.4.0'
|
| 8 |
+
mmcv_version = digit_version(mmcv.__version__)
|
| 9 |
+
|
| 10 |
+
assert (digit_version(mmcv_minimum_version) <= mmcv_version
|
| 11 |
+
<= digit_version(mmcv_maximum_version)), \
|
| 12 |
+
f'MMCV=={mmcv.__version__} is used but incompatible. ' \
|
| 13 |
+
f'Please install mmcv>={mmcv_minimum_version}, <={mmcv_maximum_version}.'
|
| 14 |
+
|
| 15 |
+
__all__ = ['__version__']
|
TransRAC/mmaction/apis/__init__.py
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .inference import inference_recognizer, init_recognizer
|
| 2 |
+
from .test import multi_gpu_test, single_gpu_test
|
| 3 |
+
from .train import train_model
|
| 4 |
+
|
| 5 |
+
__all__ = [
|
| 6 |
+
'train_model', 'init_recognizer', 'inference_recognizer', 'multi_gpu_test',
|
| 7 |
+
'single_gpu_test'
|
| 8 |
+
]
|
TransRAC/mmaction/apis/inference.py
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import os.path as osp
|
| 3 |
+
import re
|
| 4 |
+
from operator import itemgetter
|
| 5 |
+
|
| 6 |
+
import mmcv
|
| 7 |
+
import torch
|
| 8 |
+
from mmcv.parallel import collate, scatter
|
| 9 |
+
from mmcv.runner import load_checkpoint
|
| 10 |
+
|
| 11 |
+
from mmaction.core import OutputHook
|
| 12 |
+
from mmaction.datasets.pipelines import Compose
|
| 13 |
+
from mmaction.models import build_recognizer
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
def init_recognizer(config,
|
| 17 |
+
checkpoint=None,
|
| 18 |
+
device='cuda:0',
|
| 19 |
+
use_frames=False):
|
| 20 |
+
"""Initialize a recognizer from config file.
|
| 21 |
+
|
| 22 |
+
Args:
|
| 23 |
+
config (str | :obj:`mmcv.Config`): Config file path or the config
|
| 24 |
+
object.
|
| 25 |
+
checkpoint (str | None, optional): Checkpoint path/url. If set to None,
|
| 26 |
+
the model will not load any weights. Default: None.
|
| 27 |
+
device (str | :obj:`torch.device`): The desired device of returned
|
| 28 |
+
tensor. Default: 'cuda:0'.
|
| 29 |
+
use_frames (bool): Whether to use rawframes as input. Default:False.
|
| 30 |
+
|
| 31 |
+
Returns:
|
| 32 |
+
nn.Module: The constructed recognizer.
|
| 33 |
+
"""
|
| 34 |
+
if isinstance(config, str):
|
| 35 |
+
config = mmcv.Config.fromfile(config)
|
| 36 |
+
elif not isinstance(config, mmcv.Config):
|
| 37 |
+
raise TypeError('config must be a filename or Config object, '
|
| 38 |
+
f'but got {type(config)}')
|
| 39 |
+
if ((use_frames and config.dataset_type != 'RawframeDataset')
|
| 40 |
+
or (not use_frames and config.dataset_type != 'VideoDataset')):
|
| 41 |
+
input_type = 'rawframes' if use_frames else 'video'
|
| 42 |
+
raise RuntimeError('input data type should be consist with the '
|
| 43 |
+
f'dataset type in config, but got input type '
|
| 44 |
+
f"'{input_type}' and dataset type "
|
| 45 |
+
f"'{config.dataset_type}'")
|
| 46 |
+
|
| 47 |
+
# pretrained model is unnecessary since we directly load checkpoint later
|
| 48 |
+
config.model.backbone.pretrained = None
|
| 49 |
+
model = build_recognizer(config.model, test_cfg=config.get('test_cfg'))
|
| 50 |
+
|
| 51 |
+
if checkpoint is not None:
|
| 52 |
+
load_checkpoint(model, checkpoint, map_location=device)
|
| 53 |
+
model.cfg = config
|
| 54 |
+
model.to(device)
|
| 55 |
+
model.eval()
|
| 56 |
+
return model
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
def inference_recognizer(model,
|
| 60 |
+
video_path,
|
| 61 |
+
label_path,
|
| 62 |
+
use_frames=False,
|
| 63 |
+
outputs=None,
|
| 64 |
+
as_tensor=True):
|
| 65 |
+
"""Inference a video with the detector.
|
| 66 |
+
|
| 67 |
+
Args:
|
| 68 |
+
model (nn.Module): The loaded recognizer.
|
| 69 |
+
video_path (str): The video file path/url or the rawframes directory
|
| 70 |
+
path. If ``use_frames`` is set to True, it should be rawframes
|
| 71 |
+
directory path. Otherwise, it should be video file path.
|
| 72 |
+
label_path (str): The label file path.
|
| 73 |
+
use_frames (bool): Whether to use rawframes as input. Default:False.
|
| 74 |
+
outputs (list(str) | tuple(str) | str | None) : Names of layers whose
|
| 75 |
+
outputs need to be returned, default: None.
|
| 76 |
+
as_tensor (bool): Same as that in ``OutputHook``. Default: True.
|
| 77 |
+
|
| 78 |
+
Returns:
|
| 79 |
+
dict[tuple(str, float)]: Top-5 recognition result dict.
|
| 80 |
+
dict[torch.tensor | np.ndarray]:
|
| 81 |
+
Output feature maps from layers specified in `outputs`.
|
| 82 |
+
"""
|
| 83 |
+
if not (osp.exists(video_path) or video_path.startswith('http')):
|
| 84 |
+
raise RuntimeError(f"'{video_path}' is missing")
|
| 85 |
+
|
| 86 |
+
if osp.isfile(video_path) and use_frames:
|
| 87 |
+
raise RuntimeError(
|
| 88 |
+
f"'{video_path}' is a video file, not a rawframe directory")
|
| 89 |
+
if osp.isdir(video_path) and not use_frames:
|
| 90 |
+
raise RuntimeError(
|
| 91 |
+
f"'{video_path}' is a rawframe directory, not a video file")
|
| 92 |
+
|
| 93 |
+
if isinstance(outputs, str):
|
| 94 |
+
outputs = (outputs, )
|
| 95 |
+
assert outputs is None or isinstance(outputs, (tuple, list))
|
| 96 |
+
|
| 97 |
+
cfg = model.cfg
|
| 98 |
+
device = next(model.parameters()).device # model device
|
| 99 |
+
# construct label map
|
| 100 |
+
with open(label_path, 'r') as f:
|
| 101 |
+
label = [line.strip() for line in f]
|
| 102 |
+
# build the data pipeline
|
| 103 |
+
test_pipeline = cfg.data.test.pipeline
|
| 104 |
+
test_pipeline = Compose(test_pipeline)
|
| 105 |
+
# prepare data
|
| 106 |
+
if use_frames:
|
| 107 |
+
filename_tmpl = cfg.data.test.get('filename_tmpl', 'img_{:05}.jpg')
|
| 108 |
+
modality = cfg.data.test.get('modality', 'RGB')
|
| 109 |
+
start_index = cfg.data.test.get('start_index', 1)
|
| 110 |
+
|
| 111 |
+
# count the number of frames that match the format of `filename_tmpl`
|
| 112 |
+
# RGB pattern example: img_{:05}.jpg -> ^img_\d+.jpg$
|
| 113 |
+
# Flow patteren example: {}_{:05d}.jpg -> ^x_\d+.jpg$
|
| 114 |
+
pattern = f'^{filename_tmpl}$'
|
| 115 |
+
if modality == 'Flow':
|
| 116 |
+
pattern = pattern.replace('{}', 'x')
|
| 117 |
+
pattern = pattern.replace(
|
| 118 |
+
pattern[pattern.find('{'):pattern.find('}') + 1], '\\d+')
|
| 119 |
+
total_frames = len(
|
| 120 |
+
list(
|
| 121 |
+
filter(lambda x: re.match(pattern, x) is not None,
|
| 122 |
+
os.listdir(video_path))))
|
| 123 |
+
|
| 124 |
+
data = dict(
|
| 125 |
+
frame_dir=video_path,
|
| 126 |
+
total_frames=total_frames,
|
| 127 |
+
label=-1,
|
| 128 |
+
start_index=start_index,
|
| 129 |
+
filename_tmpl=filename_tmpl,
|
| 130 |
+
modality=modality)
|
| 131 |
+
else:
|
| 132 |
+
start_index = cfg.data.test.get('start_index', 0)
|
| 133 |
+
data = dict(
|
| 134 |
+
filename=video_path,
|
| 135 |
+
label=-1,
|
| 136 |
+
start_index=start_index,
|
| 137 |
+
modality='RGB')
|
| 138 |
+
data = test_pipeline(data)
|
| 139 |
+
data = collate([data], samples_per_gpu=1)
|
| 140 |
+
if next(model.parameters()).is_cuda:
|
| 141 |
+
# scatter to specified GPU
|
| 142 |
+
data = scatter(data, [device])[0]
|
| 143 |
+
|
| 144 |
+
# forward the model
|
| 145 |
+
with OutputHook(model, outputs=outputs, as_tensor=as_tensor) as h:
|
| 146 |
+
with torch.no_grad():
|
| 147 |
+
scores = model(return_loss=False, **data)[0]
|
| 148 |
+
returned_features = h.layer_outputs if outputs else None
|
| 149 |
+
|
| 150 |
+
score_tuples = tuple(zip(label, scores))
|
| 151 |
+
score_sorted = sorted(score_tuples, key=itemgetter(1), reverse=True)
|
| 152 |
+
|
| 153 |
+
top5_label = score_sorted[:5]
|
| 154 |
+
if outputs:
|
| 155 |
+
return top5_label, returned_features
|
| 156 |
+
return top5_label
|
TransRAC/mmaction/apis/test.py
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os.path as osp
|
| 2 |
+
import pickle
|
| 3 |
+
import shutil
|
| 4 |
+
import tempfile
|
| 5 |
+
# TODO import test functions from mmcv and delete them from mmaction2
|
| 6 |
+
import warnings
|
| 7 |
+
|
| 8 |
+
import mmcv
|
| 9 |
+
import torch
|
| 10 |
+
import torch.distributed as dist
|
| 11 |
+
from mmcv.runner import get_dist_info
|
| 12 |
+
|
| 13 |
+
try:
|
| 14 |
+
from mmcv.engine import (single_gpu_test, multi_gpu_test,
|
| 15 |
+
collect_results_gpu, collect_results_cpu)
|
| 16 |
+
from_mmcv = True
|
| 17 |
+
except (ImportError, ModuleNotFoundError):
|
| 18 |
+
warnings.warn(
|
| 19 |
+
'DeprecationWarning: single_gpu_test, multi_gpu_test, '
|
| 20 |
+
'collect_results_cpu, collect_results_gpu from mmaction2 will be '
|
| 21 |
+
'deprecated. Please install mmcv through master branch.')
|
| 22 |
+
from_mmcv = False
|
| 23 |
+
|
| 24 |
+
if not from_mmcv:
|
| 25 |
+
|
| 26 |
+
def single_gpu_test(model, data_loader): # noqa: F811
|
| 27 |
+
"""Test model with a single gpu.
|
| 28 |
+
|
| 29 |
+
This method tests model with a single gpu and
|
| 30 |
+
displays test progress bar.
|
| 31 |
+
|
| 32 |
+
Args:
|
| 33 |
+
model (nn.Module): Model to be tested.
|
| 34 |
+
data_loader (nn.Dataloader): Pytorch data loader.
|
| 35 |
+
|
| 36 |
+
Returns:
|
| 37 |
+
list: The prediction results.
|
| 38 |
+
"""
|
| 39 |
+
model.eval()
|
| 40 |
+
results = []
|
| 41 |
+
dataset = data_loader.dataset
|
| 42 |
+
prog_bar = mmcv.ProgressBar(len(dataset))
|
| 43 |
+
for data in data_loader:
|
| 44 |
+
with torch.no_grad():
|
| 45 |
+
result = model(return_loss=False, **data)
|
| 46 |
+
results.extend(result)
|
| 47 |
+
|
| 48 |
+
# use the first key as main key to calculate the batch size
|
| 49 |
+
batch_size = len(next(iter(data.values())))
|
| 50 |
+
for _ in range(batch_size):
|
| 51 |
+
prog_bar.update()
|
| 52 |
+
return results
|
| 53 |
+
|
| 54 |
+
def multi_gpu_test( # noqa: F811
|
| 55 |
+
model, data_loader, tmpdir=None, gpu_collect=True):
|
| 56 |
+
"""Test model with multiple gpus.
|
| 57 |
+
|
| 58 |
+
This method tests model with multiple gpus and collects the results
|
| 59 |
+
under two different modes: gpu and cpu modes. By setting
|
| 60 |
+
'gpu_collect=True' it encodes results to gpu tensors and use gpu
|
| 61 |
+
communication for results collection. On cpu mode it saves the results
|
| 62 |
+
on different gpus to 'tmpdir' and collects them by the rank 0 worker.
|
| 63 |
+
|
| 64 |
+
Args:
|
| 65 |
+
model (nn.Module): Model to be tested.
|
| 66 |
+
data_loader (nn.Dataloader): Pytorch data loader.
|
| 67 |
+
tmpdir (str): Path of directory to save the temporary results from
|
| 68 |
+
different gpus under cpu mode. Default: None
|
| 69 |
+
gpu_collect (bool): Option to use either gpu or cpu to collect
|
| 70 |
+
results. Default: True
|
| 71 |
+
|
| 72 |
+
Returns:
|
| 73 |
+
list: The prediction results.
|
| 74 |
+
"""
|
| 75 |
+
model.eval()
|
| 76 |
+
results = []
|
| 77 |
+
dataset = data_loader.dataset
|
| 78 |
+
rank, world_size = get_dist_info()
|
| 79 |
+
if rank == 0:
|
| 80 |
+
prog_bar = mmcv.ProgressBar(len(dataset))
|
| 81 |
+
for data in data_loader:
|
| 82 |
+
with torch.no_grad():
|
| 83 |
+
result = model(return_loss=False, **data)
|
| 84 |
+
results.extend(result)
|
| 85 |
+
|
| 86 |
+
if rank == 0:
|
| 87 |
+
# use the first key as main key to calculate the batch size
|
| 88 |
+
batch_size = len(next(iter(data.values())))
|
| 89 |
+
for _ in range(batch_size * world_size):
|
| 90 |
+
prog_bar.update()
|
| 91 |
+
|
| 92 |
+
# collect results from all ranks
|
| 93 |
+
if gpu_collect:
|
| 94 |
+
results = collect_results_gpu(results, len(dataset))
|
| 95 |
+
else:
|
| 96 |
+
results = collect_results_cpu(results, len(dataset), tmpdir)
|
| 97 |
+
return results
|
| 98 |
+
|
| 99 |
+
def collect_results_cpu(result_part, size, tmpdir=None): # noqa: F811
|
| 100 |
+
"""Collect results in cpu mode.
|
| 101 |
+
|
| 102 |
+
It saves the results on different gpus to 'tmpdir' and collects
|
| 103 |
+
them by the rank 0 worker.
|
| 104 |
+
|
| 105 |
+
Args:
|
| 106 |
+
result_part (list): Results to be collected
|
| 107 |
+
size (int): Result size.
|
| 108 |
+
tmpdir (str): Path of directory to save the temporary results from
|
| 109 |
+
different gpus under cpu mode. Default: None
|
| 110 |
+
|
| 111 |
+
Returns:
|
| 112 |
+
list: Ordered results.
|
| 113 |
+
"""
|
| 114 |
+
rank, world_size = get_dist_info()
|
| 115 |
+
# create a tmp dir if it is not specified
|
| 116 |
+
if tmpdir is None:
|
| 117 |
+
MAX_LEN = 512
|
| 118 |
+
# 32 is whitespace
|
| 119 |
+
dir_tensor = torch.full((MAX_LEN, ),
|
| 120 |
+
32,
|
| 121 |
+
dtype=torch.uint8,
|
| 122 |
+
device='cuda')
|
| 123 |
+
if rank == 0:
|
| 124 |
+
mmcv.mkdir_or_exist('.dist_test')
|
| 125 |
+
tmpdir = tempfile.mkdtemp(dir='.dist_test')
|
| 126 |
+
tmpdir = torch.tensor(
|
| 127 |
+
bytearray(tmpdir.encode()),
|
| 128 |
+
dtype=torch.uint8,
|
| 129 |
+
device='cuda')
|
| 130 |
+
dir_tensor[:len(tmpdir)] = tmpdir
|
| 131 |
+
dist.broadcast(dir_tensor, 0)
|
| 132 |
+
tmpdir = dir_tensor.cpu().numpy().tobytes().decode().rstrip()
|
| 133 |
+
else:
|
| 134 |
+
mmcv.mkdir_or_exist(tmpdir)
|
| 135 |
+
# synchronizes all processes to make sure tmpdir exist
|
| 136 |
+
dist.barrier()
|
| 137 |
+
# dump the part result to the dir
|
| 138 |
+
mmcv.dump(result_part, osp.join(tmpdir, f'part_{rank}.pkl'))
|
| 139 |
+
# synchronizes all processes for loding pickle file
|
| 140 |
+
dist.barrier()
|
| 141 |
+
# collect all parts
|
| 142 |
+
if rank != 0:
|
| 143 |
+
return None
|
| 144 |
+
# load results of all parts from tmp dir
|
| 145 |
+
part_list = []
|
| 146 |
+
for i in range(world_size):
|
| 147 |
+
part_file = osp.join(tmpdir, f'part_{i}.pkl')
|
| 148 |
+
part_list.append(mmcv.load(part_file))
|
| 149 |
+
# sort the results
|
| 150 |
+
ordered_results = []
|
| 151 |
+
for res in zip(*part_list):
|
| 152 |
+
ordered_results.extend(list(res))
|
| 153 |
+
# the dataloader may pad some samples
|
| 154 |
+
ordered_results = ordered_results[:size]
|
| 155 |
+
# remove tmp dir
|
| 156 |
+
shutil.rmtree(tmpdir)
|
| 157 |
+
return ordered_results
|
| 158 |
+
|
| 159 |
+
def collect_results_gpu(result_part, size): # noqa: F811
|
| 160 |
+
"""Collect results in gpu mode.
|
| 161 |
+
|
| 162 |
+
It encodes results to gpu tensors and use gpu communication for results
|
| 163 |
+
collection.
|
| 164 |
+
|
| 165 |
+
Args:
|
| 166 |
+
result_part (list): Results to be collected
|
| 167 |
+
size (int): Result size.
|
| 168 |
+
|
| 169 |
+
Returns:
|
| 170 |
+
list: Ordered results.
|
| 171 |
+
"""
|
| 172 |
+
rank, world_size = get_dist_info()
|
| 173 |
+
# dump result part to tensor with pickle
|
| 174 |
+
part_tensor = torch.tensor(
|
| 175 |
+
bytearray(pickle.dumps(result_part)),
|
| 176 |
+
dtype=torch.uint8,
|
| 177 |
+
device='cuda')
|
| 178 |
+
# gather all result part tensor shape
|
| 179 |
+
shape_tensor = torch.tensor(part_tensor.shape, device='cuda')
|
| 180 |
+
shape_list = [shape_tensor.clone() for _ in range(world_size)]
|
| 181 |
+
dist.all_gather(shape_list, shape_tensor)
|
| 182 |
+
# padding result part tensor to max length
|
| 183 |
+
shape_max = torch.tensor(shape_list).max()
|
| 184 |
+
part_send = torch.zeros(shape_max, dtype=torch.uint8, device='cuda')
|
| 185 |
+
part_send[:shape_tensor[0]] = part_tensor
|
| 186 |
+
part_recv_list = [
|
| 187 |
+
part_tensor.new_zeros(shape_max) for _ in range(world_size)
|
| 188 |
+
]
|
| 189 |
+
# gather all result part
|
| 190 |
+
dist.all_gather(part_recv_list, part_send)
|
| 191 |
+
|
| 192 |
+
if rank == 0:
|
| 193 |
+
part_list = []
|
| 194 |
+
for recv, shape in zip(part_recv_list, shape_list):
|
| 195 |
+
part_list.append(
|
| 196 |
+
pickle.loads(recv[:shape[0]].cpu().numpy().tobytes()))
|
| 197 |
+
# sort the results
|
| 198 |
+
ordered_results = []
|
| 199 |
+
for res in zip(*part_list):
|
| 200 |
+
ordered_results.extend(list(res))
|
| 201 |
+
# the dataloader may pad some samples
|
| 202 |
+
ordered_results = ordered_results[:size]
|
| 203 |
+
return ordered_results
|
| 204 |
+
return None
|
TransRAC/mmaction/apis/train.py
ADDED
|
@@ -0,0 +1,261 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import copy as cp
|
| 2 |
+
import os.path as osp
|
| 3 |
+
|
| 4 |
+
import torch
|
| 5 |
+
from mmcv.parallel import MMDataParallel, MMDistributedDataParallel
|
| 6 |
+
from mmcv.runner import (DistSamplerSeedHook, EpochBasedRunner, OptimizerHook,
|
| 7 |
+
build_optimizer, get_dist_info)
|
| 8 |
+
from mmcv.runner.hooks import Fp16OptimizerHook
|
| 9 |
+
|
| 10 |
+
from ..core import (DistEvalHook, EvalHook, OmniSourceDistSamplerSeedHook,
|
| 11 |
+
OmniSourceRunner)
|
| 12 |
+
from ..datasets import build_dataloader, build_dataset
|
| 13 |
+
from ..utils import PreciseBNHook, get_root_logger
|
| 14 |
+
from .test import multi_gpu_test
|
| 15 |
+
from mmcv_custom.runner import EpochBasedRunnerAmp
|
| 16 |
+
import apex
|
| 17 |
+
import os.path as osp
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def train_model(model,
|
| 21 |
+
dataset,
|
| 22 |
+
cfg,
|
| 23 |
+
distributed=False,
|
| 24 |
+
validate=False,
|
| 25 |
+
test=dict(test_best=False, test_last=False),
|
| 26 |
+
timestamp=None,
|
| 27 |
+
meta=None):
|
| 28 |
+
"""Train model entry function.
|
| 29 |
+
|
| 30 |
+
Args:
|
| 31 |
+
model (nn.Module): The model to be trained.
|
| 32 |
+
dataset (:obj:`Dataset`): Train dataset.
|
| 33 |
+
cfg (dict): The config dict for training.
|
| 34 |
+
distributed (bool): Whether to use distributed training.
|
| 35 |
+
Default: False.
|
| 36 |
+
validate (bool): Whether to do evaluation. Default: False.
|
| 37 |
+
test (dict): The testing option, with two keys: test_last & test_best.
|
| 38 |
+
The value is True or False, indicating whether to test the
|
| 39 |
+
corresponding checkpoint.
|
| 40 |
+
Default: dict(test_best=False, test_last=False).
|
| 41 |
+
timestamp (str | None): Local time for runner. Default: None.
|
| 42 |
+
meta (dict | None): Meta dict to record some important information.
|
| 43 |
+
Default: None
|
| 44 |
+
"""
|
| 45 |
+
logger = get_root_logger(log_level=cfg.log_level)
|
| 46 |
+
|
| 47 |
+
# prepare data loaders
|
| 48 |
+
dataset = dataset if isinstance(dataset, (list, tuple)) else [dataset]
|
| 49 |
+
|
| 50 |
+
if 'optimizer_config' not in cfg:
|
| 51 |
+
cfg.optimizer_config={}
|
| 52 |
+
dataloader_setting = dict(
|
| 53 |
+
videos_per_gpu=cfg.data.get('videos_per_gpu', 1) // cfg.optimizer_config.get('update_interval', 1),
|
| 54 |
+
workers_per_gpu=cfg.data.get('workers_per_gpu', 1),
|
| 55 |
+
num_gpus=len(cfg.gpu_ids),
|
| 56 |
+
dist=distributed,
|
| 57 |
+
seed=cfg.seed)
|
| 58 |
+
dataloader_setting = dict(dataloader_setting,
|
| 59 |
+
**cfg.data.get('train_dataloader', {}))
|
| 60 |
+
|
| 61 |
+
if cfg.omnisource:
|
| 62 |
+
# The option can override videos_per_gpu
|
| 63 |
+
train_ratio = cfg.data.get('train_ratio', [1] * len(dataset))
|
| 64 |
+
omni_videos_per_gpu = cfg.data.get('omni_videos_per_gpu', None)
|
| 65 |
+
if omni_videos_per_gpu is None:
|
| 66 |
+
dataloader_settings = [dataloader_setting] * len(dataset)
|
| 67 |
+
else:
|
| 68 |
+
dataloader_settings = []
|
| 69 |
+
for videos_per_gpu in omni_videos_per_gpu:
|
| 70 |
+
this_setting = cp.deepcopy(dataloader_setting)
|
| 71 |
+
this_setting['videos_per_gpu'] = videos_per_gpu
|
| 72 |
+
dataloader_settings.append(this_setting)
|
| 73 |
+
data_loaders = [
|
| 74 |
+
build_dataloader(ds, **setting)
|
| 75 |
+
for ds, setting in zip(dataset, dataloader_settings)
|
| 76 |
+
]
|
| 77 |
+
|
| 78 |
+
else:
|
| 79 |
+
data_loaders = [
|
| 80 |
+
build_dataloader(ds, **dataloader_setting) for ds in dataset
|
| 81 |
+
]
|
| 82 |
+
|
| 83 |
+
# build runner
|
| 84 |
+
optimizer = build_optimizer(model, cfg.optimizer)
|
| 85 |
+
# use apex fp16 optimizer
|
| 86 |
+
# Noticed that this is just a temporary patch. We shoud not encourage this kind of code style
|
| 87 |
+
use_amp = False
|
| 88 |
+
if (
|
| 89 |
+
cfg.optimizer_config.get("type", None)
|
| 90 |
+
and cfg.optimizer_config["type"] == "DistOptimizerHook"
|
| 91 |
+
):
|
| 92 |
+
if cfg.optimizer_config.get("use_fp16", False):
|
| 93 |
+
model, optimizer = apex.amp.initialize(
|
| 94 |
+
model.cuda(), optimizer, opt_level="O1"
|
| 95 |
+
)
|
| 96 |
+
for m in model.modules():
|
| 97 |
+
if hasattr(m, "fp16_enabled"):
|
| 98 |
+
m.fp16_enabled = True
|
| 99 |
+
use_amp = True
|
| 100 |
+
|
| 101 |
+
# put model on gpus
|
| 102 |
+
if distributed:
|
| 103 |
+
find_unused_parameters = cfg.get('find_unused_parameters', False)
|
| 104 |
+
# Sets the `find_unused_parameters` parameter in
|
| 105 |
+
# torch.nn.parallel.DistributedDataParallel
|
| 106 |
+
model = MMDistributedDataParallel(
|
| 107 |
+
model.cuda(),
|
| 108 |
+
device_ids=[torch.cuda.current_device()],
|
| 109 |
+
broadcast_buffers=False,
|
| 110 |
+
find_unused_parameters=find_unused_parameters)
|
| 111 |
+
else:
|
| 112 |
+
model = MMDataParallel(
|
| 113 |
+
model.cuda(cfg.gpu_ids[0]), device_ids=cfg.gpu_ids)
|
| 114 |
+
|
| 115 |
+
if use_amp:
|
| 116 |
+
Runner = EpochBasedRunnerAmp
|
| 117 |
+
runner = Runner(
|
| 118 |
+
model,
|
| 119 |
+
optimizer=optimizer,
|
| 120 |
+
work_dir=cfg.work_dir,
|
| 121 |
+
logger=logger,
|
| 122 |
+
meta=meta,
|
| 123 |
+
amp=use_amp)
|
| 124 |
+
else:
|
| 125 |
+
Runner = OmniSourceRunner if cfg.omnisource else EpochBasedRunner
|
| 126 |
+
runner = Runner(
|
| 127 |
+
model,
|
| 128 |
+
optimizer=optimizer,
|
| 129 |
+
work_dir=cfg.work_dir,
|
| 130 |
+
logger=logger,
|
| 131 |
+
meta=meta)
|
| 132 |
+
# an ugly workaround to make .log and .log.json filenames the same
|
| 133 |
+
runner.timestamp = timestamp
|
| 134 |
+
|
| 135 |
+
# fp16 setting
|
| 136 |
+
fp16_cfg = cfg.get('fp16', None)
|
| 137 |
+
if fp16_cfg is not None:
|
| 138 |
+
optimizer_config = Fp16OptimizerHook(
|
| 139 |
+
**cfg.optimizer_config, **fp16_cfg, distributed=distributed)
|
| 140 |
+
elif distributed and 'type' not in cfg.optimizer_config:
|
| 141 |
+
optimizer_config = OptimizerHook(**cfg.optimizer_config)
|
| 142 |
+
else:
|
| 143 |
+
optimizer_config = cfg.optimizer_config
|
| 144 |
+
|
| 145 |
+
# register hooks
|
| 146 |
+
runner.register_training_hooks(cfg.lr_config, optimizer_config,
|
| 147 |
+
cfg.checkpoint_config, cfg.log_config,
|
| 148 |
+
cfg.get('momentum_config', None))
|
| 149 |
+
if distributed:
|
| 150 |
+
if cfg.omnisource:
|
| 151 |
+
runner.register_hook(OmniSourceDistSamplerSeedHook())
|
| 152 |
+
else:
|
| 153 |
+
runner.register_hook(DistSamplerSeedHook())
|
| 154 |
+
|
| 155 |
+
# precise bn setting
|
| 156 |
+
if cfg.get('precise_bn', False):
|
| 157 |
+
precise_bn_dataset = build_dataset(cfg.data.train)
|
| 158 |
+
dataloader_setting = dict(
|
| 159 |
+
videos_per_gpu=cfg.data.get('videos_per_gpu', 1),
|
| 160 |
+
workers_per_gpu=0, # save memory and time
|
| 161 |
+
num_gpus=len(cfg.gpu_ids),
|
| 162 |
+
dist=distributed,
|
| 163 |
+
seed=cfg.seed)
|
| 164 |
+
data_loader_precise_bn = build_dataloader(precise_bn_dataset,
|
| 165 |
+
**dataloader_setting)
|
| 166 |
+
precise_bn_hook = PreciseBNHook(data_loader_precise_bn,
|
| 167 |
+
**cfg.get('precise_bn'))
|
| 168 |
+
runner.register_hook(precise_bn_hook)
|
| 169 |
+
|
| 170 |
+
if validate:
|
| 171 |
+
eval_cfg = cfg.get('evaluation', {})
|
| 172 |
+
val_dataset = build_dataset(cfg.data.val, dict(test_mode=True))
|
| 173 |
+
dataloader_setting = dict(
|
| 174 |
+
videos_per_gpu=cfg.data.get('videos_per_gpu', 1),
|
| 175 |
+
workers_per_gpu=cfg.data.get('workers_per_gpu', 1),
|
| 176 |
+
# cfg.gpus will be ignored if distributed
|
| 177 |
+
num_gpus=len(cfg.gpu_ids),
|
| 178 |
+
dist=distributed,
|
| 179 |
+
shuffle=False)
|
| 180 |
+
dataloader_setting = dict(dataloader_setting,
|
| 181 |
+
**cfg.data.get('val_dataloader', {}))
|
| 182 |
+
val_dataloader = build_dataloader(val_dataset, **dataloader_setting)
|
| 183 |
+
eval_hook = DistEvalHook if distributed else EvalHook
|
| 184 |
+
runner.register_hook(eval_hook(val_dataloader, **eval_cfg))
|
| 185 |
+
|
| 186 |
+
if cfg.resume_from:
|
| 187 |
+
runner.resume(cfg.resume_from, resume_amp=use_amp)
|
| 188 |
+
elif cfg.get("auto_resume", False) and osp.exists(osp.join(runner.work_dir, 'latest.pth')):
|
| 189 |
+
runner.auto_resume()
|
| 190 |
+
elif cfg.load_from:
|
| 191 |
+
runner.load_checkpoint(cfg.load_from)
|
| 192 |
+
runner_kwargs = dict()
|
| 193 |
+
if cfg.omnisource:
|
| 194 |
+
runner_kwargs = dict(train_ratio=train_ratio)
|
| 195 |
+
runner.run(data_loaders, cfg.workflow, cfg.total_epochs, **runner_kwargs)
|
| 196 |
+
|
| 197 |
+
if test['test_last'] or test['test_best']:
|
| 198 |
+
best_ckpt_path = None
|
| 199 |
+
if test['test_best']:
|
| 200 |
+
if hasattr(eval_hook, 'best_ckpt_path'):
|
| 201 |
+
best_ckpt_path = eval_hook.best_ckpt_path
|
| 202 |
+
|
| 203 |
+
if best_ckpt_path is None or not osp.exists(best_ckpt_path):
|
| 204 |
+
test['test_best'] = False
|
| 205 |
+
if best_ckpt_path is None:
|
| 206 |
+
runner.logger.info('Warning: test_best set as True, but '
|
| 207 |
+
'is not applicable '
|
| 208 |
+
'(eval_hook.best_ckpt_path is None)')
|
| 209 |
+
else:
|
| 210 |
+
runner.logger.info('Warning: test_best set as True, but '
|
| 211 |
+
'is not applicable (best_ckpt '
|
| 212 |
+
f'{best_ckpt_path} not found)')
|
| 213 |
+
if not test['test_last']:
|
| 214 |
+
return
|
| 215 |
+
|
| 216 |
+
test_dataset = build_dataset(cfg.data.test, dict(test_mode=True))
|
| 217 |
+
gpu_collect = cfg.get('evaluation', {}).get('gpu_collect', False)
|
| 218 |
+
tmpdir = cfg.get('evaluation', {}).get('tmpdir',
|
| 219 |
+
osp.join(cfg.work_dir, 'tmp'))
|
| 220 |
+
dataloader_setting = dict(
|
| 221 |
+
videos_per_gpu=cfg.data.get('videos_per_gpu', 1),
|
| 222 |
+
workers_per_gpu=cfg.data.get('workers_per_gpu', 1),
|
| 223 |
+
num_gpus=len(cfg.gpu_ids),
|
| 224 |
+
dist=distributed,
|
| 225 |
+
shuffle=False)
|
| 226 |
+
dataloader_setting = dict(dataloader_setting,
|
| 227 |
+
**cfg.data.get('test_dataloader', {}))
|
| 228 |
+
|
| 229 |
+
test_dataloader = build_dataloader(test_dataset, **dataloader_setting)
|
| 230 |
+
|
| 231 |
+
names, ckpts = [], []
|
| 232 |
+
|
| 233 |
+
if test['test_last']:
|
| 234 |
+
names.append('last')
|
| 235 |
+
ckpts.append(None)
|
| 236 |
+
if test['test_best']:
|
| 237 |
+
names.append('best')
|
| 238 |
+
ckpts.append(best_ckpt_path)
|
| 239 |
+
|
| 240 |
+
for name, ckpt in zip(names, ckpts):
|
| 241 |
+
if ckpt is not None:
|
| 242 |
+
runner.load_checkpoint(ckpt)
|
| 243 |
+
|
| 244 |
+
outputs = multi_gpu_test(runner.model, test_dataloader, tmpdir,
|
| 245 |
+
gpu_collect)
|
| 246 |
+
rank, _ = get_dist_info()
|
| 247 |
+
if rank == 0:
|
| 248 |
+
out = osp.join(cfg.work_dir, f'{name}_pred.pkl')
|
| 249 |
+
test_dataset.dump_results(outputs, out)
|
| 250 |
+
|
| 251 |
+
eval_cfg = cfg.get('evaluation', {})
|
| 252 |
+
for key in [
|
| 253 |
+
'interval', 'tmpdir', 'start', 'gpu_collect',
|
| 254 |
+
'save_best', 'rule', 'by_epoch', 'broadcast_bn_buffers'
|
| 255 |
+
]:
|
| 256 |
+
eval_cfg.pop(key, None)
|
| 257 |
+
|
| 258 |
+
eval_res = test_dataset.evaluate(outputs, **eval_cfg)
|
| 259 |
+
runner.logger.info(f'Testing results of the {name} checkpoint')
|
| 260 |
+
for metric_name, val in eval_res.items():
|
| 261 |
+
runner.logger.info(f'{metric_name}: {val:.04f}')
|
TransRAC/mmaction/core/__init__.py
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .bbox import * # noqa: F401, F403
|
| 2 |
+
from .evaluation import * # noqa: F401, F403
|
| 3 |
+
from .hooks import * # noqa: F401, F403
|
| 4 |
+
from .optimizer import * # noqa: F401, F403
|
| 5 |
+
from .runner import * # noqa: F401, F403
|
| 6 |
+
from .scheduler import * # noqa: F401, F403
|
TransRAC/mmaction/datasets/__init__.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .activitynet_dataset import ActivityNetDataset
|
| 2 |
+
from .audio_dataset import AudioDataset
|
| 3 |
+
from .audio_feature_dataset import AudioFeatureDataset
|
| 4 |
+
from .audio_visual_dataset import AudioVisualDataset
|
| 5 |
+
from .ava_dataset import AVADataset
|
| 6 |
+
from .base import BaseDataset
|
| 7 |
+
from .blending_utils import (BaseMiniBatchBlending, CutmixBlending,
|
| 8 |
+
MixupBlending, LabelSmoothing)
|
| 9 |
+
from .builder import (BLENDINGS, DATASETS, PIPELINES, build_dataloader,
|
| 10 |
+
build_dataset)
|
| 11 |
+
from .dataset_wrappers import RepeatDataset
|
| 12 |
+
from .hvu_dataset import HVUDataset
|
| 13 |
+
from .image_dataset import ImageDataset
|
| 14 |
+
from .pose_dataset import PoseDataset
|
| 15 |
+
from .rawframe_dataset import RawframeDataset
|
| 16 |
+
from .rawvideo_dataset import RawVideoDataset
|
| 17 |
+
from .ssn_dataset import SSNDataset
|
| 18 |
+
from .video_dataset import VideoDataset
|
| 19 |
+
|
| 20 |
+
__all__ = [
|
| 21 |
+
'VideoDataset', 'build_dataloader', 'build_dataset', 'RepeatDataset',
|
| 22 |
+
'RawframeDataset', 'BaseDataset', 'ActivityNetDataset', 'SSNDataset',
|
| 23 |
+
'HVUDataset', 'AudioDataset', 'AudioFeatureDataset', 'ImageDataset',
|
| 24 |
+
'RawVideoDataset', 'AVADataset', 'AudioVisualDataset',
|
| 25 |
+
'BaseMiniBatchBlending', 'CutmixBlending', 'MixupBlending', 'LabelSmoothing', 'DATASETS',
|
| 26 |
+
'PIPELINES', 'BLENDINGS', 'PoseDataset'
|
| 27 |
+
]
|
TransRAC/mmaction/datasets/activitynet_dataset.py
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import copy
|
| 2 |
+
import os
|
| 3 |
+
import os.path as osp
|
| 4 |
+
import warnings
|
| 5 |
+
from collections import OrderedDict
|
| 6 |
+
|
| 7 |
+
import mmcv
|
| 8 |
+
import numpy as np
|
| 9 |
+
|
| 10 |
+
from ..core import average_recall_at_avg_proposals
|
| 11 |
+
from .base import BaseDataset
|
| 12 |
+
from .builder import DATASETS
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
@DATASETS.register_module()
|
| 16 |
+
class ActivityNetDataset(BaseDataset):
|
| 17 |
+
"""ActivityNet dataset for temporal action localization.
|
| 18 |
+
|
| 19 |
+
The dataset loads raw features and apply specified transforms to return a
|
| 20 |
+
dict containing the frame tensors and other information.
|
| 21 |
+
|
| 22 |
+
The ann_file is a json file with multiple objects, and each object has a
|
| 23 |
+
key of the name of a video, and value of total frames of the video, total
|
| 24 |
+
seconds of the video, annotations of a video, feature frames (frames
|
| 25 |
+
covered by features) of the video, fps and rfps. Example of a
|
| 26 |
+
annotation file:
|
| 27 |
+
|
| 28 |
+
.. code-block:: JSON
|
| 29 |
+
|
| 30 |
+
{
|
| 31 |
+
"v_--1DO2V4K74": {
|
| 32 |
+
"duration_second": 211.53,
|
| 33 |
+
"duration_frame": 6337,
|
| 34 |
+
"annotations": [
|
| 35 |
+
{
|
| 36 |
+
"segment": [
|
| 37 |
+
30.025882995319815,
|
| 38 |
+
205.2318595943838
|
| 39 |
+
],
|
| 40 |
+
"label": "Rock climbing"
|
| 41 |
+
}
|
| 42 |
+
],
|
| 43 |
+
"feature_frame": 6336,
|
| 44 |
+
"fps": 30.0,
|
| 45 |
+
"rfps": 29.9579255898
|
| 46 |
+
},
|
| 47 |
+
"v_--6bJUbfpnQ": {
|
| 48 |
+
"duration_second": 26.75,
|
| 49 |
+
"duration_frame": 647,
|
| 50 |
+
"annotations": [
|
| 51 |
+
{
|
| 52 |
+
"segment": [
|
| 53 |
+
2.578755070202808,
|
| 54 |
+
24.914101404056165
|
| 55 |
+
],
|
| 56 |
+
"label": "Drinking beer"
|
| 57 |
+
}
|
| 58 |
+
],
|
| 59 |
+
"feature_frame": 624,
|
| 60 |
+
"fps": 24.0,
|
| 61 |
+
"rfps": 24.1869158879
|
| 62 |
+
},
|
| 63 |
+
...
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
Args:
|
| 68 |
+
ann_file (str): Path to the annotation file.
|
| 69 |
+
pipeline (list[dict | callable]): A sequence of data transforms.
|
| 70 |
+
data_prefix (str | None): Path to a directory where videos are held.
|
| 71 |
+
Default: None.
|
| 72 |
+
test_mode (bool): Store True when building test or validation dataset.
|
| 73 |
+
Default: False.
|
| 74 |
+
"""
|
| 75 |
+
|
| 76 |
+
def __init__(self, ann_file, pipeline, data_prefix=None, test_mode=False):
|
| 77 |
+
super().__init__(ann_file, pipeline, data_prefix, test_mode)
|
| 78 |
+
|
| 79 |
+
def load_annotations(self):
|
| 80 |
+
"""Load the annotation according to ann_file into video_infos."""
|
| 81 |
+
video_infos = []
|
| 82 |
+
anno_database = mmcv.load(self.ann_file)
|
| 83 |
+
for video_name in anno_database:
|
| 84 |
+
video_info = anno_database[video_name]
|
| 85 |
+
video_info['video_name'] = video_name
|
| 86 |
+
video_infos.append(video_info)
|
| 87 |
+
return video_infos
|
| 88 |
+
|
| 89 |
+
def prepare_test_frames(self, idx):
|
| 90 |
+
"""Prepare the frames for testing given the index."""
|
| 91 |
+
results = copy.deepcopy(self.video_infos[idx])
|
| 92 |
+
results['data_prefix'] = self.data_prefix
|
| 93 |
+
return self.pipeline(results)
|
| 94 |
+
|
| 95 |
+
def prepare_train_frames(self, idx):
|
| 96 |
+
"""Prepare the frames for training given the index."""
|
| 97 |
+
results = copy.deepcopy(self.video_infos[idx])
|
| 98 |
+
results['data_prefix'] = self.data_prefix
|
| 99 |
+
return self.pipeline(results)
|
| 100 |
+
|
| 101 |
+
def __len__(self):
|
| 102 |
+
"""Get the size of the dataset."""
|
| 103 |
+
return len(self.video_infos)
|
| 104 |
+
|
| 105 |
+
def _import_ground_truth(self):
|
| 106 |
+
"""Read ground truth data from video_infos."""
|
| 107 |
+
ground_truth = {}
|
| 108 |
+
for video_info in self.video_infos:
|
| 109 |
+
video_id = video_info['video_name'][2:]
|
| 110 |
+
this_video_ground_truths = []
|
| 111 |
+
for ann in video_info['annotations']:
|
| 112 |
+
t_start, t_end = ann['segment']
|
| 113 |
+
label = ann['label']
|
| 114 |
+
this_video_ground_truths.append([t_start, t_end, label])
|
| 115 |
+
ground_truth[video_id] = np.array(this_video_ground_truths)
|
| 116 |
+
return ground_truth
|
| 117 |
+
|
| 118 |
+
@staticmethod
|
| 119 |
+
def proposals2json(results, show_progress=False):
|
| 120 |
+
"""Convert all proposals to a final dict(json) format.
|
| 121 |
+
|
| 122 |
+
Args:
|
| 123 |
+
results (list[dict]): All proposals.
|
| 124 |
+
show_progress (bool): Whether to show the progress bar.
|
| 125 |
+
Defaults: False.
|
| 126 |
+
|
| 127 |
+
Returns:
|
| 128 |
+
dict: The final result dict. E.g.
|
| 129 |
+
|
| 130 |
+
.. code-block:: Python
|
| 131 |
+
|
| 132 |
+
dict(video-1=[dict(segment=[1.1,2.0]. score=0.9),
|
| 133 |
+
dict(segment=[50.1, 129.3], score=0.6)])
|
| 134 |
+
"""
|
| 135 |
+
result_dict = {}
|
| 136 |
+
print('Convert proposals to json format')
|
| 137 |
+
if show_progress:
|
| 138 |
+
prog_bar = mmcv.ProgressBar(len(results))
|
| 139 |
+
for result in results:
|
| 140 |
+
video_name = result['video_name']
|
| 141 |
+
result_dict[video_name[2:]] = result['proposal_list']
|
| 142 |
+
if show_progress:
|
| 143 |
+
prog_bar.update()
|
| 144 |
+
return result_dict
|
| 145 |
+
|
| 146 |
+
@staticmethod
|
| 147 |
+
def _import_proposals(results):
|
| 148 |
+
"""Read predictions from results."""
|
| 149 |
+
proposals = {}
|
| 150 |
+
num_proposals = 0
|
| 151 |
+
for result in results:
|
| 152 |
+
video_id = result['video_name'][2:]
|
| 153 |
+
this_video_proposals = []
|
| 154 |
+
for proposal in result['proposal_list']:
|
| 155 |
+
t_start, t_end = proposal['segment']
|
| 156 |
+
score = proposal['score']
|
| 157 |
+
this_video_proposals.append([t_start, t_end, score])
|
| 158 |
+
num_proposals += 1
|
| 159 |
+
proposals[video_id] = np.array(this_video_proposals)
|
| 160 |
+
return proposals, num_proposals
|
| 161 |
+
|
| 162 |
+
def dump_results(self, results, out, output_format, version='VERSION 1.3'):
|
| 163 |
+
"""Dump data to json/csv files."""
|
| 164 |
+
if output_format == 'json':
|
| 165 |
+
result_dict = self.proposals2json(results)
|
| 166 |
+
output_dict = {
|
| 167 |
+
'version': version,
|
| 168 |
+
'results': result_dict,
|
| 169 |
+
'external_data': {}
|
| 170 |
+
}
|
| 171 |
+
mmcv.dump(output_dict, out)
|
| 172 |
+
elif output_format == 'csv':
|
| 173 |
+
# TODO: add csv handler to mmcv and use mmcv.dump
|
| 174 |
+
os.makedirs(out, exist_ok=True)
|
| 175 |
+
header = 'action,start,end,tmin,tmax'
|
| 176 |
+
for result in results:
|
| 177 |
+
video_name, outputs = result
|
| 178 |
+
output_path = osp.join(out, video_name + '.csv')
|
| 179 |
+
np.savetxt(
|
| 180 |
+
output_path,
|
| 181 |
+
outputs,
|
| 182 |
+
header=header,
|
| 183 |
+
delimiter=',',
|
| 184 |
+
comments='')
|
| 185 |
+
else:
|
| 186 |
+
raise ValueError(
|
| 187 |
+
f'The output format {output_format} is not supported.')
|
| 188 |
+
|
| 189 |
+
def evaluate(
|
| 190 |
+
self,
|
| 191 |
+
results,
|
| 192 |
+
metrics='AR@AN',
|
| 193 |
+
metric_options={
|
| 194 |
+
'AR@AN':
|
| 195 |
+
dict(
|
| 196 |
+
max_avg_proposals=100,
|
| 197 |
+
temporal_iou_thresholds=np.linspace(0.5, 0.95, 10))
|
| 198 |
+
},
|
| 199 |
+
logger=None,
|
| 200 |
+
**deprecated_kwargs):
|
| 201 |
+
"""Evaluation in feature dataset.
|
| 202 |
+
|
| 203 |
+
Args:
|
| 204 |
+
results (list[dict]): Output results.
|
| 205 |
+
metrics (str | sequence[str]): Metrics to be performed.
|
| 206 |
+
Defaults: 'AR@AN'.
|
| 207 |
+
metric_options (dict): Dict for metric options. Options are
|
| 208 |
+
``max_avg_proposals``, ``temporal_iou_thresholds`` for
|
| 209 |
+
``AR@AN``.
|
| 210 |
+
default: ``{'AR@AN': dict(max_avg_proposals=100,
|
| 211 |
+
temporal_iou_thresholds=np.linspace(0.5, 0.95, 10))}``.
|
| 212 |
+
logger (logging.Logger | None): Training logger. Defaults: None.
|
| 213 |
+
deprecated_kwargs (dict): Used for containing deprecated arguments.
|
| 214 |
+
See 'https://github.com/open-mmlab/mmaction2/pull/286'.
|
| 215 |
+
|
| 216 |
+
Returns:
|
| 217 |
+
dict: Evaluation results for evaluation metrics.
|
| 218 |
+
"""
|
| 219 |
+
# Protect ``metric_options`` since it uses mutable value as default
|
| 220 |
+
metric_options = copy.deepcopy(metric_options)
|
| 221 |
+
|
| 222 |
+
if deprecated_kwargs != {}:
|
| 223 |
+
warnings.warn(
|
| 224 |
+
'Option arguments for metrics has been changed to '
|
| 225 |
+
"`metric_options`, See 'https://github.com/open-mmlab/mmaction2/pull/286' " # noqa: E501
|
| 226 |
+
'for more details')
|
| 227 |
+
metric_options['AR@AN'] = dict(metric_options['AR@AN'],
|
| 228 |
+
**deprecated_kwargs)
|
| 229 |
+
|
| 230 |
+
if not isinstance(results, list):
|
| 231 |
+
raise TypeError(f'results must be a list, but got {type(results)}')
|
| 232 |
+
assert len(results) == len(self), (
|
| 233 |
+
f'The length of results is not equal to the dataset len: '
|
| 234 |
+
f'{len(results)} != {len(self)}')
|
| 235 |
+
|
| 236 |
+
metrics = metrics if isinstance(metrics, (list, tuple)) else [metrics]
|
| 237 |
+
allowed_metrics = ['AR@AN']
|
| 238 |
+
for metric in metrics:
|
| 239 |
+
if metric not in allowed_metrics:
|
| 240 |
+
raise KeyError(f'metric {metric} is not supported')
|
| 241 |
+
|
| 242 |
+
eval_results = OrderedDict()
|
| 243 |
+
ground_truth = self._import_ground_truth()
|
| 244 |
+
proposal, num_proposals = self._import_proposals(results)
|
| 245 |
+
|
| 246 |
+
for metric in metrics:
|
| 247 |
+
if metric == 'AR@AN':
|
| 248 |
+
temporal_iou_thresholds = metric_options.setdefault(
|
| 249 |
+
'AR@AN', {}).setdefault('temporal_iou_thresholds',
|
| 250 |
+
np.linspace(0.5, 0.95, 10))
|
| 251 |
+
max_avg_proposals = metric_options.setdefault(
|
| 252 |
+
'AR@AN', {}).setdefault('max_avg_proposals', 100)
|
| 253 |
+
if isinstance(temporal_iou_thresholds, list):
|
| 254 |
+
temporal_iou_thresholds = np.array(temporal_iou_thresholds)
|
| 255 |
+
|
| 256 |
+
recall, _, _, auc = (
|
| 257 |
+
average_recall_at_avg_proposals(
|
| 258 |
+
ground_truth,
|
| 259 |
+
proposal,
|
| 260 |
+
num_proposals,
|
| 261 |
+
max_avg_proposals=max_avg_proposals,
|
| 262 |
+
temporal_iou_thresholds=temporal_iou_thresholds))
|
| 263 |
+
eval_results['auc'] = auc
|
| 264 |
+
eval_results['AR@1'] = np.mean(recall[:, 0])
|
| 265 |
+
eval_results['AR@5'] = np.mean(recall[:, 4])
|
| 266 |
+
eval_results['AR@10'] = np.mean(recall[:, 9])
|
| 267 |
+
eval_results['AR@100'] = np.mean(recall[:, 99])
|
| 268 |
+
|
| 269 |
+
return eval_results
|
TransRAC/mmaction/datasets/audio_dataset.py
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os.path as osp
|
| 2 |
+
|
| 3 |
+
import torch
|
| 4 |
+
|
| 5 |
+
from .base import BaseDataset
|
| 6 |
+
from .builder import DATASETS
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
@DATASETS.register_module()
|
| 10 |
+
class AudioDataset(BaseDataset):
|
| 11 |
+
"""Audio dataset for video recognition. Extracts the audio feature on-the-
|
| 12 |
+
fly. Annotation file can be that of the rawframe dataset, or:
|
| 13 |
+
|
| 14 |
+
.. code-block:: txt
|
| 15 |
+
|
| 16 |
+
some/directory-1.wav 163 1
|
| 17 |
+
some/directory-2.wav 122 1
|
| 18 |
+
some/directory-3.wav 258 2
|
| 19 |
+
some/directory-4.wav 234 2
|
| 20 |
+
some/directory-5.wav 295 3
|
| 21 |
+
some/directory-6.wav 121 3
|
| 22 |
+
|
| 23 |
+
Args:
|
| 24 |
+
ann_file (str): Path to the annotation file.
|
| 25 |
+
pipeline (list[dict | callable]): A sequence of data transforms.
|
| 26 |
+
suffix (str): The suffix of the audio file. Default: '.wav'.
|
| 27 |
+
kwargs (dict): Other keyword args for `BaseDataset`.
|
| 28 |
+
"""
|
| 29 |
+
|
| 30 |
+
def __init__(self, ann_file, pipeline, suffix='.wav', **kwargs):
|
| 31 |
+
self.suffix = suffix
|
| 32 |
+
super().__init__(ann_file, pipeline, modality='Audio', **kwargs)
|
| 33 |
+
|
| 34 |
+
def load_annotations(self):
|
| 35 |
+
"""Load annotation file to get video information."""
|
| 36 |
+
if self.ann_file.endswith('.json'):
|
| 37 |
+
return self.load_json_annotations()
|
| 38 |
+
video_infos = []
|
| 39 |
+
with open(self.ann_file, 'r') as fin:
|
| 40 |
+
for line in fin:
|
| 41 |
+
line_split = line.strip().split()
|
| 42 |
+
video_info = {}
|
| 43 |
+
idx = 0
|
| 44 |
+
filename = line_split[idx]
|
| 45 |
+
if self.data_prefix is not None:
|
| 46 |
+
if not filename.endswith(self.suffix):
|
| 47 |
+
filename = osp.join(self.data_prefix,
|
| 48 |
+
filename + self.suffix)
|
| 49 |
+
else:
|
| 50 |
+
filename = osp.join(self.data_prefix, filename)
|
| 51 |
+
video_info['audio_path'] = filename
|
| 52 |
+
idx += 1
|
| 53 |
+
# idx for total_frames
|
| 54 |
+
video_info['total_frames'] = int(line_split[idx])
|
| 55 |
+
idx += 1
|
| 56 |
+
# idx for label[s]
|
| 57 |
+
label = [int(x) for x in line_split[idx:]]
|
| 58 |
+
assert label, f'missing label in line: {line}'
|
| 59 |
+
if self.multi_class:
|
| 60 |
+
assert self.num_classes is not None
|
| 61 |
+
onehot = torch.zeros(self.num_classes)
|
| 62 |
+
onehot[label] = 1.0
|
| 63 |
+
video_info['label'] = onehot
|
| 64 |
+
else:
|
| 65 |
+
assert len(label) == 1
|
| 66 |
+
video_info['label'] = label[0]
|
| 67 |
+
video_infos.append(video_info)
|
| 68 |
+
|
| 69 |
+
return video_infos
|
TransRAC/mmaction/datasets/audio_feature_dataset.py
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os.path as osp
|
| 2 |
+
|
| 3 |
+
import torch
|
| 4 |
+
|
| 5 |
+
from .base import BaseDataset
|
| 6 |
+
from .builder import DATASETS
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
@DATASETS.register_module()
|
| 10 |
+
class AudioFeatureDataset(BaseDataset):
|
| 11 |
+
"""Audio feature dataset for video recognition. Reads the features
|
| 12 |
+
extracted off-line. Annotation file can be that of the rawframe dataset,
|
| 13 |
+
or:
|
| 14 |
+
|
| 15 |
+
.. code-block:: txt
|
| 16 |
+
|
| 17 |
+
some/directory-1.npy 163 1
|
| 18 |
+
some/directory-2.npy 122 1
|
| 19 |
+
some/directory-3.npy 258 2
|
| 20 |
+
some/directory-4.npy 234 2
|
| 21 |
+
some/directory-5.npy 295 3
|
| 22 |
+
some/directory-6.npy 121 3
|
| 23 |
+
|
| 24 |
+
Args:
|
| 25 |
+
ann_file (str): Path to the annotation file.
|
| 26 |
+
pipeline (list[dict | callable]): A sequence of data transforms.
|
| 27 |
+
suffix (str): The suffix of the audio feature file. Default: '.npy'.
|
| 28 |
+
kwargs (dict): Other keyword args for `BaseDataset`.
|
| 29 |
+
"""
|
| 30 |
+
|
| 31 |
+
def __init__(self, ann_file, pipeline, suffix='.npy', **kwargs):
|
| 32 |
+
self.suffix = suffix
|
| 33 |
+
super().__init__(ann_file, pipeline, modality='Audio', **kwargs)
|
| 34 |
+
|
| 35 |
+
def load_annotations(self):
|
| 36 |
+
"""Load annotation file to get video information."""
|
| 37 |
+
if self.ann_file.endswith('.json'):
|
| 38 |
+
return self.load_json_annotations()
|
| 39 |
+
video_infos = []
|
| 40 |
+
with open(self.ann_file, 'r') as fin:
|
| 41 |
+
for line in fin:
|
| 42 |
+
line_split = line.strip().split()
|
| 43 |
+
video_info = {}
|
| 44 |
+
idx = 0
|
| 45 |
+
filename = line_split[idx]
|
| 46 |
+
if self.data_prefix is not None:
|
| 47 |
+
if not filename.endswith(self.suffix):
|
| 48 |
+
filename = osp.join(self.data_prefix,
|
| 49 |
+
filename) + self.suffix
|
| 50 |
+
else:
|
| 51 |
+
filename = osp.join(self.data_prefix, filename)
|
| 52 |
+
video_info['audio_path'] = filename
|
| 53 |
+
idx += 1
|
| 54 |
+
# idx for total_frames
|
| 55 |
+
video_info['total_frames'] = int(line_split[idx])
|
| 56 |
+
idx += 1
|
| 57 |
+
# idx for label[s]
|
| 58 |
+
label = [int(x) for x in line_split[idx:]]
|
| 59 |
+
assert label, f'missing label in line: {line}'
|
| 60 |
+
if self.multi_class:
|
| 61 |
+
assert self.num_classes is not None
|
| 62 |
+
onehot = torch.zeros(self.num_classes)
|
| 63 |
+
onehot[label] = 1.0
|
| 64 |
+
video_info['label'] = onehot
|
| 65 |
+
else:
|
| 66 |
+
assert len(label) == 1
|
| 67 |
+
video_info['label'] = label[0]
|
| 68 |
+
video_infos.append(video_info)
|
| 69 |
+
|
| 70 |
+
return video_infos
|
TransRAC/mmaction/datasets/audio_visual_dataset.py
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os.path as osp
|
| 2 |
+
|
| 3 |
+
from .builder import DATASETS
|
| 4 |
+
from .rawframe_dataset import RawframeDataset
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
@DATASETS.register_module()
|
| 8 |
+
class AudioVisualDataset(RawframeDataset):
|
| 9 |
+
"""Dataset that reads both audio and visual data, supporting both rawframes
|
| 10 |
+
and videos. The annotation file is same as that of the rawframe dataset,
|
| 11 |
+
such as:
|
| 12 |
+
|
| 13 |
+
.. code-block:: txt
|
| 14 |
+
|
| 15 |
+
some/directory-1 163 1
|
| 16 |
+
some/directory-2 122 1
|
| 17 |
+
some/directory-3 258 2
|
| 18 |
+
some/directory-4 234 2
|
| 19 |
+
some/directory-5 295 3
|
| 20 |
+
some/directory-6 121 3
|
| 21 |
+
|
| 22 |
+
Args:
|
| 23 |
+
ann_file (str): Path to the annotation file.
|
| 24 |
+
pipeline (list[dict | callable]): A sequence of data transforms.
|
| 25 |
+
audio_prefix (str): Directory of the audio files.
|
| 26 |
+
kwargs (dict): Other keyword args for `RawframeDataset`. `video_prefix`
|
| 27 |
+
is also allowed if pipeline is designed for videos.
|
| 28 |
+
"""
|
| 29 |
+
|
| 30 |
+
def __init__(self, ann_file, pipeline, audio_prefix, **kwargs):
|
| 31 |
+
self.audio_prefix = audio_prefix
|
| 32 |
+
self.video_prefix = kwargs.pop('video_prefix', None)
|
| 33 |
+
self.data_prefix = kwargs.get('data_prefix', None)
|
| 34 |
+
super().__init__(ann_file, pipeline, **kwargs)
|
| 35 |
+
|
| 36 |
+
def load_annotations(self):
|
| 37 |
+
video_infos = []
|
| 38 |
+
with open(self.ann_file, 'r') as fin:
|
| 39 |
+
for line in fin:
|
| 40 |
+
line_split = line.strip().split()
|
| 41 |
+
video_info = {}
|
| 42 |
+
idx = 0
|
| 43 |
+
# idx for frame_dir
|
| 44 |
+
frame_dir = line_split[idx]
|
| 45 |
+
if self.audio_prefix is not None:
|
| 46 |
+
audio_path = osp.join(self.audio_prefix,
|
| 47 |
+
frame_dir + '.npy')
|
| 48 |
+
video_info['audio_path'] = audio_path
|
| 49 |
+
if self.video_prefix:
|
| 50 |
+
video_path = osp.join(self.video_prefix,
|
| 51 |
+
frame_dir + '.mp4')
|
| 52 |
+
video_info['filename'] = video_path
|
| 53 |
+
if self.data_prefix is not None:
|
| 54 |
+
frame_dir = osp.join(self.data_prefix, frame_dir)
|
| 55 |
+
video_info['frame_dir'] = frame_dir
|
| 56 |
+
idx += 1
|
| 57 |
+
if self.with_offset:
|
| 58 |
+
# idx for offset and total_frames
|
| 59 |
+
video_info['offset'] = int(line_split[idx])
|
| 60 |
+
video_info['total_frames'] = int(line_split[idx + 1])
|
| 61 |
+
idx += 2
|
| 62 |
+
else:
|
| 63 |
+
# idx for total_frames
|
| 64 |
+
video_info['total_frames'] = int(line_split[idx])
|
| 65 |
+
idx += 1
|
| 66 |
+
# idx for label[s]
|
| 67 |
+
label = [int(x) for x in line_split[idx:]]
|
| 68 |
+
assert len(label) != 0, f'missing label in line: {line}'
|
| 69 |
+
if self.multi_class:
|
| 70 |
+
assert self.num_classes is not None
|
| 71 |
+
video_info['label'] = label
|
| 72 |
+
else:
|
| 73 |
+
assert len(label) == 1
|
| 74 |
+
video_info['label'] = label[0]
|
| 75 |
+
video_infos.append(video_info)
|
| 76 |
+
return video_infos
|
TransRAC/mmaction/datasets/ava_dataset.py
ADDED
|
@@ -0,0 +1,382 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import copy
|
| 2 |
+
import os
|
| 3 |
+
import os.path as osp
|
| 4 |
+
from collections import defaultdict
|
| 5 |
+
from datetime import datetime
|
| 6 |
+
|
| 7 |
+
import mmcv
|
| 8 |
+
import numpy as np
|
| 9 |
+
from mmcv.utils import print_log
|
| 10 |
+
|
| 11 |
+
from ..core.evaluation.ava_utils import ava_eval, read_labelmap, results2csv
|
| 12 |
+
from ..utils import get_root_logger
|
| 13 |
+
from .base import BaseDataset
|
| 14 |
+
from .builder import DATASETS
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
@DATASETS.register_module()
|
| 18 |
+
class AVADataset(BaseDataset):
|
| 19 |
+
"""AVA dataset for spatial temporal detection.
|
| 20 |
+
|
| 21 |
+
Based on official AVA annotation files, the dataset loads raw frames,
|
| 22 |
+
bounding boxes, proposals and applies specified transformations to return
|
| 23 |
+
a dict containing the frame tensors and other information.
|
| 24 |
+
|
| 25 |
+
This datasets can load information from the following files:
|
| 26 |
+
|
| 27 |
+
.. code-block:: txt
|
| 28 |
+
|
| 29 |
+
ann_file -> ava_{train, val}_{v2.1, v2.2}.csv
|
| 30 |
+
exclude_file -> ava_{train, val}_excluded_timestamps_{v2.1, v2.2}.csv
|
| 31 |
+
label_file -> ava_action_list_{v2.1, v2.2}.pbtxt /
|
| 32 |
+
ava_action_list_{v2.1, v2.2}_for_activitynet_2019.pbtxt
|
| 33 |
+
proposal_file -> ava_dense_proposals_{train, val}.FAIR.recall_93.9.pkl
|
| 34 |
+
|
| 35 |
+
Particularly, the proposal_file is a pickle file which contains
|
| 36 |
+
``img_key`` (in format of ``{video_id},{timestamp}``). Example of a pickle
|
| 37 |
+
file:
|
| 38 |
+
|
| 39 |
+
.. code-block:: JSON
|
| 40 |
+
|
| 41 |
+
{
|
| 42 |
+
...
|
| 43 |
+
'0f39OWEqJ24,0902':
|
| 44 |
+
array([[0.011 , 0.157 , 0.655 , 0.983 , 0.998163]]),
|
| 45 |
+
'0f39OWEqJ24,0912':
|
| 46 |
+
array([[0.054 , 0.088 , 0.91 , 0.998 , 0.068273],
|
| 47 |
+
[0.016 , 0.161 , 0.519 , 0.974 , 0.984025],
|
| 48 |
+
[0.493 , 0.283 , 0.981 , 0.984 , 0.983621]]),
|
| 49 |
+
...
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
Args:
|
| 53 |
+
ann_file (str): Path to the annotation file like
|
| 54 |
+
``ava_{train, val}_{v2.1, v2.2}.csv``.
|
| 55 |
+
exclude_file (str): Path to the excluded timestamp file like
|
| 56 |
+
``ava_{train, val}_excluded_timestamps_{v2.1, v2.2}.csv``.
|
| 57 |
+
pipeline (list[dict | callable]): A sequence of data transforms.
|
| 58 |
+
label_file (str): Path to the label file like
|
| 59 |
+
``ava_action_list_{v2.1, v2.2}.pbtxt`` or
|
| 60 |
+
``ava_action_list_{v2.1, v2.2}_for_activitynet_2019.pbtxt``.
|
| 61 |
+
Default: None.
|
| 62 |
+
filename_tmpl (str): Template for each filename.
|
| 63 |
+
Default: 'img_{:05}.jpg'.
|
| 64 |
+
proposal_file (str): Path to the proposal file like
|
| 65 |
+
``ava_dense_proposals_{train, val}.FAIR.recall_93.9.pkl``.
|
| 66 |
+
Default: None.
|
| 67 |
+
person_det_score_thr (float): The threshold of person detection scores,
|
| 68 |
+
bboxes with scores above the threshold will be used. Default: 0.9.
|
| 69 |
+
Note that 0 <= person_det_score_thr <= 1. If no proposal has
|
| 70 |
+
detection score larger than the threshold, the one with the largest
|
| 71 |
+
detection score will be used.
|
| 72 |
+
num_classes (int): The number of classes of the dataset. Default: 81.
|
| 73 |
+
(AVA has 80 action classes, another 1-dim is added for potential
|
| 74 |
+
usage)
|
| 75 |
+
custom_classes (list[int]): A subset of class ids from origin dataset.
|
| 76 |
+
Please note that 0 should NOT be selected, and ``num_classes``
|
| 77 |
+
should be equal to ``len(custom_classes) + 1``
|
| 78 |
+
data_prefix (str): Path to a directory where videos are held.
|
| 79 |
+
Default: None.
|
| 80 |
+
test_mode (bool): Store True when building test or validation dataset.
|
| 81 |
+
Default: False.
|
| 82 |
+
modality (str): Modality of data. Support 'RGB', 'Flow'.
|
| 83 |
+
Default: 'RGB'.
|
| 84 |
+
num_max_proposals (int): Max proposals number to store. Default: 1000.
|
| 85 |
+
timestamp_start (int): The start point of included timestamps. The
|
| 86 |
+
default value is referred from the official website. Default: 902.
|
| 87 |
+
timestamp_end (int): The end point of included timestamps. The
|
| 88 |
+
default value is referred from the official website. Default: 1798.
|
| 89 |
+
"""
|
| 90 |
+
|
| 91 |
+
_FPS = 30
|
| 92 |
+
|
| 93 |
+
def __init__(self,
|
| 94 |
+
ann_file,
|
| 95 |
+
exclude_file,
|
| 96 |
+
pipeline,
|
| 97 |
+
label_file=None,
|
| 98 |
+
filename_tmpl='img_{:05}.jpg',
|
| 99 |
+
proposal_file=None,
|
| 100 |
+
person_det_score_thr=0.9,
|
| 101 |
+
num_classes=81,
|
| 102 |
+
custom_classes=None,
|
| 103 |
+
data_prefix=None,
|
| 104 |
+
test_mode=False,
|
| 105 |
+
modality='RGB',
|
| 106 |
+
num_max_proposals=1000,
|
| 107 |
+
timestamp_start=900,
|
| 108 |
+
timestamp_end=1800):
|
| 109 |
+
# since it inherits from `BaseDataset`, some arguments
|
| 110 |
+
# should be assigned before performing `load_annotations()`
|
| 111 |
+
self.custom_classes = custom_classes
|
| 112 |
+
if custom_classes is not None:
|
| 113 |
+
assert num_classes == len(custom_classes) + 1
|
| 114 |
+
assert 0 not in custom_classes
|
| 115 |
+
_, class_whitelist = read_labelmap(open(label_file))
|
| 116 |
+
assert set(custom_classes).issubset(class_whitelist)
|
| 117 |
+
|
| 118 |
+
self.custom_classes = tuple([0] + custom_classes)
|
| 119 |
+
self.exclude_file = exclude_file
|
| 120 |
+
self.label_file = label_file
|
| 121 |
+
self.proposal_file = proposal_file
|
| 122 |
+
assert 0 <= person_det_score_thr <= 1, (
|
| 123 |
+
'The value of '
|
| 124 |
+
'person_det_score_thr should in [0, 1]. ')
|
| 125 |
+
self.person_det_score_thr = person_det_score_thr
|
| 126 |
+
self.num_classes = num_classes
|
| 127 |
+
self.filename_tmpl = filename_tmpl
|
| 128 |
+
self.num_max_proposals = num_max_proposals
|
| 129 |
+
self.timestamp_start = timestamp_start
|
| 130 |
+
self.timestamp_end = timestamp_end
|
| 131 |
+
self.logger = get_root_logger()
|
| 132 |
+
super().__init__(
|
| 133 |
+
ann_file,
|
| 134 |
+
pipeline,
|
| 135 |
+
data_prefix,
|
| 136 |
+
test_mode,
|
| 137 |
+
modality=modality,
|
| 138 |
+
num_classes=num_classes)
|
| 139 |
+
|
| 140 |
+
if self.proposal_file is not None:
|
| 141 |
+
self.proposals = mmcv.load(self.proposal_file)
|
| 142 |
+
else:
|
| 143 |
+
self.proposals = None
|
| 144 |
+
|
| 145 |
+
if not test_mode:
|
| 146 |
+
valid_indexes = self.filter_exclude_file()
|
| 147 |
+
self.logger.info(
|
| 148 |
+
f'{len(valid_indexes)} out of {len(self.video_infos)} '
|
| 149 |
+
f'frames are valid.')
|
| 150 |
+
self.video_infos = [self.video_infos[i] for i in valid_indexes]
|
| 151 |
+
|
| 152 |
+
def parse_img_record(self, img_records):
|
| 153 |
+
"""Merge image records of the same entity at the same time.
|
| 154 |
+
|
| 155 |
+
Args:
|
| 156 |
+
img_records (list[dict]): List of img_records (lines in AVA
|
| 157 |
+
annotations).
|
| 158 |
+
|
| 159 |
+
Returns:
|
| 160 |
+
tuple(list): A tuple consists of lists of bboxes, action labels and
|
| 161 |
+
entity_ids
|
| 162 |
+
"""
|
| 163 |
+
bboxes, labels, entity_ids = [], [], []
|
| 164 |
+
while len(img_records) > 0:
|
| 165 |
+
img_record = img_records[0]
|
| 166 |
+
num_img_records = len(img_records)
|
| 167 |
+
selected_records = list(
|
| 168 |
+
filter(
|
| 169 |
+
lambda x: np.array_equal(x['entity_box'], img_record[
|
| 170 |
+
'entity_box']), img_records))
|
| 171 |
+
num_selected_records = len(selected_records)
|
| 172 |
+
img_records = list(
|
| 173 |
+
filter(
|
| 174 |
+
lambda x: not np.array_equal(x['entity_box'], img_record[
|
| 175 |
+
'entity_box']), img_records))
|
| 176 |
+
assert len(img_records) + num_selected_records == num_img_records
|
| 177 |
+
|
| 178 |
+
bboxes.append(img_record['entity_box'])
|
| 179 |
+
valid_labels = np.array([
|
| 180 |
+
selected_record['label']
|
| 181 |
+
for selected_record in selected_records
|
| 182 |
+
])
|
| 183 |
+
|
| 184 |
+
# The format can be directly used by BCELossWithLogits
|
| 185 |
+
label = np.zeros(self.num_classes, dtype=np.float32)
|
| 186 |
+
label[valid_labels] = 1.
|
| 187 |
+
|
| 188 |
+
labels.append(label)
|
| 189 |
+
entity_ids.append(img_record['entity_id'])
|
| 190 |
+
|
| 191 |
+
bboxes = np.stack(bboxes)
|
| 192 |
+
labels = np.stack(labels)
|
| 193 |
+
entity_ids = np.stack(entity_ids)
|
| 194 |
+
return bboxes, labels, entity_ids
|
| 195 |
+
|
| 196 |
+
def filter_exclude_file(self):
|
| 197 |
+
"""Filter out records in the exclude_file."""
|
| 198 |
+
valid_indexes = []
|
| 199 |
+
if self.exclude_file is None:
|
| 200 |
+
valid_indexes = list(range(len(self.video_infos)))
|
| 201 |
+
else:
|
| 202 |
+
exclude_video_infos = [
|
| 203 |
+
x.strip().split(',') for x in open(self.exclude_file)
|
| 204 |
+
]
|
| 205 |
+
for i, video_info in enumerate(self.video_infos):
|
| 206 |
+
valid_indexes.append(i)
|
| 207 |
+
for video_id, timestamp in exclude_video_infos:
|
| 208 |
+
if (video_info['video_id'] == video_id
|
| 209 |
+
and video_info['timestamp'] == int(timestamp)):
|
| 210 |
+
valid_indexes.pop()
|
| 211 |
+
break
|
| 212 |
+
return valid_indexes
|
| 213 |
+
|
| 214 |
+
def load_annotations(self):
|
| 215 |
+
"""Load AVA annotations."""
|
| 216 |
+
video_infos = []
|
| 217 |
+
records_dict_by_img = defaultdict(list)
|
| 218 |
+
with open(self.ann_file, 'r') as fin:
|
| 219 |
+
for line in fin:
|
| 220 |
+
line_split = line.strip().split(',')
|
| 221 |
+
|
| 222 |
+
label = int(line_split[6])
|
| 223 |
+
if self.custom_classes is not None:
|
| 224 |
+
if label not in self.custom_classes:
|
| 225 |
+
continue
|
| 226 |
+
label = self.custom_classes.index(label)
|
| 227 |
+
|
| 228 |
+
video_id = line_split[0]
|
| 229 |
+
timestamp = int(line_split[1])
|
| 230 |
+
img_key = f'{video_id},{timestamp:04d}'
|
| 231 |
+
|
| 232 |
+
entity_box = np.array(list(map(float, line_split[2:6])))
|
| 233 |
+
entity_id = int(line_split[7])
|
| 234 |
+
shot_info = (0, (self.timestamp_end - self.timestamp_start) *
|
| 235 |
+
self._FPS)
|
| 236 |
+
|
| 237 |
+
video_info = dict(
|
| 238 |
+
video_id=video_id,
|
| 239 |
+
timestamp=timestamp,
|
| 240 |
+
entity_box=entity_box,
|
| 241 |
+
label=label,
|
| 242 |
+
entity_id=entity_id,
|
| 243 |
+
shot_info=shot_info)
|
| 244 |
+
records_dict_by_img[img_key].append(video_info)
|
| 245 |
+
|
| 246 |
+
for img_key in records_dict_by_img:
|
| 247 |
+
video_id, timestamp = img_key.split(',')
|
| 248 |
+
bboxes, labels, entity_ids = self.parse_img_record(
|
| 249 |
+
records_dict_by_img[img_key])
|
| 250 |
+
ann = dict(
|
| 251 |
+
gt_bboxes=bboxes, gt_labels=labels, entity_ids=entity_ids)
|
| 252 |
+
frame_dir = video_id
|
| 253 |
+
if self.data_prefix is not None:
|
| 254 |
+
frame_dir = osp.join(self.data_prefix, frame_dir)
|
| 255 |
+
video_info = dict(
|
| 256 |
+
frame_dir=frame_dir,
|
| 257 |
+
video_id=video_id,
|
| 258 |
+
timestamp=int(timestamp),
|
| 259 |
+
img_key=img_key,
|
| 260 |
+
shot_info=shot_info,
|
| 261 |
+
fps=self._FPS,
|
| 262 |
+
ann=ann)
|
| 263 |
+
video_infos.append(video_info)
|
| 264 |
+
|
| 265 |
+
return video_infos
|
| 266 |
+
|
| 267 |
+
def prepare_train_frames(self, idx):
|
| 268 |
+
"""Prepare the frames for training given the index."""
|
| 269 |
+
results = copy.deepcopy(self.video_infos[idx])
|
| 270 |
+
img_key = results['img_key']
|
| 271 |
+
|
| 272 |
+
results['filename_tmpl'] = self.filename_tmpl
|
| 273 |
+
results['modality'] = self.modality
|
| 274 |
+
results['start_index'] = self.start_index
|
| 275 |
+
results['timestamp_start'] = self.timestamp_start
|
| 276 |
+
results['timestamp_end'] = self.timestamp_end
|
| 277 |
+
|
| 278 |
+
if self.proposals is not None:
|
| 279 |
+
if img_key not in self.proposals:
|
| 280 |
+
results['proposals'] = np.array([[0, 0, 1, 1]])
|
| 281 |
+
results['scores'] = np.array([1])
|
| 282 |
+
else:
|
| 283 |
+
proposals = self.proposals[img_key]
|
| 284 |
+
assert proposals.shape[-1] in [4, 5]
|
| 285 |
+
if proposals.shape[-1] == 5:
|
| 286 |
+
thr = min(self.person_det_score_thr, max(proposals[:, 4]))
|
| 287 |
+
positive_inds = (proposals[:, 4] >= thr)
|
| 288 |
+
proposals = proposals[positive_inds]
|
| 289 |
+
proposals = proposals[:self.num_max_proposals]
|
| 290 |
+
results['proposals'] = proposals[:, :4]
|
| 291 |
+
results['scores'] = proposals[:, 4]
|
| 292 |
+
else:
|
| 293 |
+
proposals = proposals[:self.num_max_proposals]
|
| 294 |
+
results['proposals'] = proposals
|
| 295 |
+
|
| 296 |
+
ann = results.pop('ann')
|
| 297 |
+
results['gt_bboxes'] = ann['gt_bboxes']
|
| 298 |
+
results['gt_labels'] = ann['gt_labels']
|
| 299 |
+
results['entity_ids'] = ann['entity_ids']
|
| 300 |
+
|
| 301 |
+
return self.pipeline(results)
|
| 302 |
+
|
| 303 |
+
def prepare_test_frames(self, idx):
|
| 304 |
+
"""Prepare the frames for testing given the index."""
|
| 305 |
+
results = copy.deepcopy(self.video_infos[idx])
|
| 306 |
+
img_key = results['img_key']
|
| 307 |
+
|
| 308 |
+
results['filename_tmpl'] = self.filename_tmpl
|
| 309 |
+
results['modality'] = self.modality
|
| 310 |
+
results['start_index'] = self.start_index
|
| 311 |
+
results['timestamp_start'] = self.timestamp_start
|
| 312 |
+
results['timestamp_end'] = self.timestamp_end
|
| 313 |
+
|
| 314 |
+
if self.proposals is not None:
|
| 315 |
+
if img_key not in self.proposals:
|
| 316 |
+
results['proposals'] = np.array([[0, 0, 1, 1]])
|
| 317 |
+
results['scores'] = np.array([1])
|
| 318 |
+
else:
|
| 319 |
+
proposals = self.proposals[img_key]
|
| 320 |
+
assert proposals.shape[-1] in [4, 5]
|
| 321 |
+
if proposals.shape[-1] == 5:
|
| 322 |
+
thr = min(self.person_det_score_thr, max(proposals[:, 4]))
|
| 323 |
+
positive_inds = (proposals[:, 4] >= thr)
|
| 324 |
+
proposals = proposals[positive_inds]
|
| 325 |
+
proposals = proposals[:self.num_max_proposals]
|
| 326 |
+
results['proposals'] = proposals[:, :4]
|
| 327 |
+
results['scores'] = proposals[:, 4]
|
| 328 |
+
else:
|
| 329 |
+
proposals = proposals[:self.num_max_proposals]
|
| 330 |
+
results['proposals'] = proposals
|
| 331 |
+
|
| 332 |
+
ann = results.pop('ann')
|
| 333 |
+
# Follow the mmdet variable naming style.
|
| 334 |
+
results['gt_bboxes'] = ann['gt_bboxes']
|
| 335 |
+
results['gt_labels'] = ann['gt_labels']
|
| 336 |
+
results['entity_ids'] = ann['entity_ids']
|
| 337 |
+
|
| 338 |
+
return self.pipeline(results)
|
| 339 |
+
|
| 340 |
+
def dump_results(self, results, out):
|
| 341 |
+
"""Dump predictions into a csv file."""
|
| 342 |
+
assert out.endswith('csv')
|
| 343 |
+
results2csv(self, results, out, self.custom_classes)
|
| 344 |
+
|
| 345 |
+
def evaluate(self,
|
| 346 |
+
results,
|
| 347 |
+
metrics=('mAP', ),
|
| 348 |
+
metric_options=None,
|
| 349 |
+
logger=None):
|
| 350 |
+
"""Evaluate the prediction results and report mAP."""
|
| 351 |
+
assert len(metrics) == 1 and metrics[0] == 'mAP', (
|
| 352 |
+
'For evaluation on AVADataset, you need to use metrics "mAP" '
|
| 353 |
+
'See https://github.com/open-mmlab/mmaction2/pull/567 '
|
| 354 |
+
'for more info.')
|
| 355 |
+
time_now = datetime.now().strftime('%Y%m%d_%H%M%S')
|
| 356 |
+
temp_file = f'AVA_{time_now}_result.csv'
|
| 357 |
+
results2csv(self, results, temp_file, self.custom_classes)
|
| 358 |
+
|
| 359 |
+
ret = {}
|
| 360 |
+
for metric in metrics:
|
| 361 |
+
msg = f'Evaluating {metric} ...'
|
| 362 |
+
if logger is None:
|
| 363 |
+
msg = '\n' + msg
|
| 364 |
+
print_log(msg, logger=logger)
|
| 365 |
+
|
| 366 |
+
eval_result = ava_eval(
|
| 367 |
+
temp_file,
|
| 368 |
+
metric,
|
| 369 |
+
self.label_file,
|
| 370 |
+
self.ann_file,
|
| 371 |
+
self.exclude_file,
|
| 372 |
+
custom_classes=self.custom_classes)
|
| 373 |
+
log_msg = []
|
| 374 |
+
for k, v in eval_result.items():
|
| 375 |
+
log_msg.append(f'\n{k}\t{v: .4f}')
|
| 376 |
+
log_msg = ''.join(log_msg)
|
| 377 |
+
print_log(log_msg, logger=logger)
|
| 378 |
+
ret.update(eval_result)
|
| 379 |
+
|
| 380 |
+
os.remove(temp_file)
|
| 381 |
+
|
| 382 |
+
return ret
|
TransRAC/mmaction/datasets/base.py
ADDED
|
@@ -0,0 +1,287 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import copy
|
| 2 |
+
import os.path as osp
|
| 3 |
+
import warnings
|
| 4 |
+
from abc import ABCMeta, abstractmethod
|
| 5 |
+
from collections import OrderedDict, defaultdict
|
| 6 |
+
|
| 7 |
+
import mmcv
|
| 8 |
+
import numpy as np
|
| 9 |
+
import torch
|
| 10 |
+
from mmcv.utils import print_log
|
| 11 |
+
from torch.utils.data import Dataset
|
| 12 |
+
|
| 13 |
+
from ..core import (mean_average_precision, mean_class_accuracy,
|
| 14 |
+
mmit_mean_average_precision, top_k_accuracy)
|
| 15 |
+
from .pipelines import Compose
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
class BaseDataset(Dataset, metaclass=ABCMeta):
|
| 19 |
+
"""Base class for datasets.
|
| 20 |
+
|
| 21 |
+
All datasets to process video should subclass it.
|
| 22 |
+
All subclasses should overwrite:
|
| 23 |
+
|
| 24 |
+
- Methods:`load_annotations`, supporting to load information from an
|
| 25 |
+
annotation file.
|
| 26 |
+
- Methods:`prepare_train_frames`, providing train data.
|
| 27 |
+
- Methods:`prepare_test_frames`, providing test data.
|
| 28 |
+
|
| 29 |
+
Args:
|
| 30 |
+
ann_file (str): Path to the annotation file.
|
| 31 |
+
pipeline (list[dict | callable]): A sequence of data transforms.
|
| 32 |
+
data_prefix (str | None): Path to a directory where videos are held.
|
| 33 |
+
Default: None.
|
| 34 |
+
test_mode (bool): Store True when building test or validation dataset.
|
| 35 |
+
Default: False.
|
| 36 |
+
multi_class (bool): Determines whether the dataset is a multi-class
|
| 37 |
+
dataset. Default: False.
|
| 38 |
+
num_classes (int | None): Number of classes of the dataset, used in
|
| 39 |
+
multi-class datasets. Default: None.
|
| 40 |
+
start_index (int): Specify a start index for frames in consideration of
|
| 41 |
+
different filename format. However, when taking videos as input,
|
| 42 |
+
it should be set to 0, since frames loaded from videos count
|
| 43 |
+
from 0. Default: 1.
|
| 44 |
+
modality (str): Modality of data. Support 'RGB', 'Flow', 'Audio'.
|
| 45 |
+
Default: 'RGB'.
|
| 46 |
+
sample_by_class (bool): Sampling by class, should be set `True` when
|
| 47 |
+
performing inter-class data balancing. Only compatible with
|
| 48 |
+
`multi_class == False`. Only applies for training. Default: False.
|
| 49 |
+
power (float): We support sampling data with the probability
|
| 50 |
+
proportional to the power of its label frequency (freq ^ power)
|
| 51 |
+
when sampling data. `power == 1` indicates uniformly sampling all
|
| 52 |
+
data; `power == 0` indicates uniformly sampling all classes.
|
| 53 |
+
Default: 0.
|
| 54 |
+
dynamic_length (bool): If the dataset length is dynamic (used by
|
| 55 |
+
ClassSpecificDistributedSampler). Default: False.
|
| 56 |
+
"""
|
| 57 |
+
|
| 58 |
+
def __init__(self,
|
| 59 |
+
ann_file,
|
| 60 |
+
pipeline,
|
| 61 |
+
data_prefix=None,
|
| 62 |
+
test_mode=False,
|
| 63 |
+
multi_class=False,
|
| 64 |
+
num_classes=None,
|
| 65 |
+
start_index=1,
|
| 66 |
+
modality='RGB',
|
| 67 |
+
sample_by_class=False,
|
| 68 |
+
power=0,
|
| 69 |
+
dynamic_length=False):
|
| 70 |
+
super().__init__()
|
| 71 |
+
|
| 72 |
+
self.ann_file = ann_file
|
| 73 |
+
self.data_prefix = osp.realpath(
|
| 74 |
+
data_prefix) if data_prefix is not None and osp.isdir(
|
| 75 |
+
data_prefix) else data_prefix
|
| 76 |
+
self.test_mode = test_mode
|
| 77 |
+
self.multi_class = multi_class
|
| 78 |
+
self.num_classes = num_classes
|
| 79 |
+
self.start_index = start_index
|
| 80 |
+
self.modality = modality
|
| 81 |
+
self.sample_by_class = sample_by_class
|
| 82 |
+
self.power = power
|
| 83 |
+
self.dynamic_length = dynamic_length
|
| 84 |
+
|
| 85 |
+
assert not (self.multi_class and self.sample_by_class)
|
| 86 |
+
|
| 87 |
+
self.pipeline = Compose(pipeline)
|
| 88 |
+
self.video_infos = self.load_annotations()
|
| 89 |
+
if self.sample_by_class:
|
| 90 |
+
self.video_infos_by_class = self.parse_by_class()
|
| 91 |
+
|
| 92 |
+
class_prob = []
|
| 93 |
+
for _, samples in self.video_infos_by_class.items():
|
| 94 |
+
class_prob.append(len(samples) / len(self.video_infos))
|
| 95 |
+
class_prob = [x**self.power for x in class_prob]
|
| 96 |
+
|
| 97 |
+
summ = sum(class_prob)
|
| 98 |
+
class_prob = [x / summ for x in class_prob]
|
| 99 |
+
|
| 100 |
+
self.class_prob = dict(zip(self.video_infos_by_class, class_prob))
|
| 101 |
+
|
| 102 |
+
@abstractmethod
|
| 103 |
+
def load_annotations(self):
|
| 104 |
+
"""Load the annotation according to ann_file into video_infos."""
|
| 105 |
+
|
| 106 |
+
# json annotations already looks like video_infos, so for each dataset,
|
| 107 |
+
# this func should be the same
|
| 108 |
+
def load_json_annotations(self):
|
| 109 |
+
"""Load json annotation file to get video information."""
|
| 110 |
+
video_infos = mmcv.load(self.ann_file)
|
| 111 |
+
num_videos = len(video_infos)
|
| 112 |
+
path_key = 'frame_dir' if 'frame_dir' in video_infos[0] else 'filename'
|
| 113 |
+
for i in range(num_videos):
|
| 114 |
+
path_value = video_infos[i][path_key]
|
| 115 |
+
if self.data_prefix is not None:
|
| 116 |
+
path_value = osp.join(self.data_prefix, path_value)
|
| 117 |
+
video_infos[i][path_key] = path_value
|
| 118 |
+
if self.multi_class:
|
| 119 |
+
assert self.num_classes is not None
|
| 120 |
+
else:
|
| 121 |
+
assert len(video_infos[i]['label']) == 1
|
| 122 |
+
video_infos[i]['label'] = video_infos[i]['label'][0]
|
| 123 |
+
return video_infos
|
| 124 |
+
|
| 125 |
+
def parse_by_class(self):
|
| 126 |
+
video_infos_by_class = defaultdict(list)
|
| 127 |
+
for item in self.video_infos:
|
| 128 |
+
label = item['label']
|
| 129 |
+
video_infos_by_class[label].append(item)
|
| 130 |
+
return video_infos_by_class
|
| 131 |
+
|
| 132 |
+
@staticmethod
|
| 133 |
+
def label2array(num, label):
|
| 134 |
+
arr = np.zeros(num, dtype=np.float32)
|
| 135 |
+
arr[label] = 1.
|
| 136 |
+
return arr
|
| 137 |
+
|
| 138 |
+
def evaluate(self,
|
| 139 |
+
results,
|
| 140 |
+
metrics='top_k_accuracy',
|
| 141 |
+
metric_options=dict(top_k_accuracy=dict(topk=(1, 5))),
|
| 142 |
+
logger=None,
|
| 143 |
+
**deprecated_kwargs):
|
| 144 |
+
"""Perform evaluation for common datasets.
|
| 145 |
+
|
| 146 |
+
Args:
|
| 147 |
+
results (list): Output results.
|
| 148 |
+
metrics (str | sequence[str]): Metrics to be performed.
|
| 149 |
+
Defaults: 'top_k_accuracy'.
|
| 150 |
+
metric_options (dict): Dict for metric options. Options are
|
| 151 |
+
``topk`` for ``top_k_accuracy``.
|
| 152 |
+
Default: ``dict(top_k_accuracy=dict(topk=(1, 5)))``.
|
| 153 |
+
logger (logging.Logger | None): Logger for recording.
|
| 154 |
+
Default: None.
|
| 155 |
+
deprecated_kwargs (dict): Used for containing deprecated arguments.
|
| 156 |
+
See 'https://github.com/open-mmlab/mmaction2/pull/286'.
|
| 157 |
+
|
| 158 |
+
Returns:
|
| 159 |
+
dict: Evaluation results dict.
|
| 160 |
+
"""
|
| 161 |
+
# Protect ``metric_options`` since it uses mutable value as default
|
| 162 |
+
metric_options = copy.deepcopy(metric_options)
|
| 163 |
+
|
| 164 |
+
if deprecated_kwargs != {}:
|
| 165 |
+
warnings.warn(
|
| 166 |
+
'Option arguments for metrics has been changed to '
|
| 167 |
+
"`metric_options`, See 'https://github.com/open-mmlab/mmaction2/pull/286' " # noqa: E501
|
| 168 |
+
'for more details')
|
| 169 |
+
metric_options['top_k_accuracy'] = dict(
|
| 170 |
+
metric_options['top_k_accuracy'], **deprecated_kwargs)
|
| 171 |
+
|
| 172 |
+
if not isinstance(results, list):
|
| 173 |
+
raise TypeError(f'results must be a list, but got {type(results)}')
|
| 174 |
+
assert len(results) == len(self), (
|
| 175 |
+
f'The length of results is not equal to the dataset len: '
|
| 176 |
+
f'{len(results)} != {len(self)}')
|
| 177 |
+
|
| 178 |
+
metrics = metrics if isinstance(metrics, (list, tuple)) else [metrics]
|
| 179 |
+
allowed_metrics = [
|
| 180 |
+
'top_k_accuracy', 'mean_class_accuracy', 'mean_average_precision',
|
| 181 |
+
'mmit_mean_average_precision'
|
| 182 |
+
]
|
| 183 |
+
|
| 184 |
+
for metric in metrics:
|
| 185 |
+
if metric not in allowed_metrics:
|
| 186 |
+
raise KeyError(f'metric {metric} is not supported')
|
| 187 |
+
|
| 188 |
+
eval_results = OrderedDict()
|
| 189 |
+
gt_labels = [ann['label'] for ann in self.video_infos]
|
| 190 |
+
|
| 191 |
+
for metric in metrics:
|
| 192 |
+
msg = f'Evaluating {metric} ...'
|
| 193 |
+
if logger is None:
|
| 194 |
+
msg = '\n' + msg
|
| 195 |
+
print_log(msg, logger=logger)
|
| 196 |
+
|
| 197 |
+
if metric == 'top_k_accuracy':
|
| 198 |
+
topk = metric_options.setdefault('top_k_accuracy',
|
| 199 |
+
{}).setdefault(
|
| 200 |
+
'topk', (1, 5))
|
| 201 |
+
if not isinstance(topk, (int, tuple)):
|
| 202 |
+
raise TypeError('topk must be int or tuple of int, '
|
| 203 |
+
f'but got {type(topk)}')
|
| 204 |
+
if isinstance(topk, int):
|
| 205 |
+
topk = (topk, )
|
| 206 |
+
|
| 207 |
+
top_k_acc = top_k_accuracy(results, gt_labels, topk)
|
| 208 |
+
log_msg = []
|
| 209 |
+
for k, acc in zip(topk, top_k_acc):
|
| 210 |
+
eval_results[f'top{k}_acc'] = acc
|
| 211 |
+
log_msg.append(f'\ntop{k}_acc\t{acc:.4f}')
|
| 212 |
+
log_msg = ''.join(log_msg)
|
| 213 |
+
print_log(log_msg, logger=logger)
|
| 214 |
+
continue
|
| 215 |
+
|
| 216 |
+
if metric == 'mean_class_accuracy':
|
| 217 |
+
mean_acc = mean_class_accuracy(results, gt_labels)
|
| 218 |
+
eval_results['mean_class_accuracy'] = mean_acc
|
| 219 |
+
log_msg = f'\nmean_acc\t{mean_acc:.4f}'
|
| 220 |
+
print_log(log_msg, logger=logger)
|
| 221 |
+
continue
|
| 222 |
+
|
| 223 |
+
if metric in [
|
| 224 |
+
'mean_average_precision', 'mmit_mean_average_precision'
|
| 225 |
+
]:
|
| 226 |
+
gt_labels = [
|
| 227 |
+
self.label2array(self.num_classes, label)
|
| 228 |
+
for label in gt_labels
|
| 229 |
+
]
|
| 230 |
+
if metric == 'mean_average_precision':
|
| 231 |
+
mAP = mean_average_precision(results, gt_labels)
|
| 232 |
+
eval_results['mean_average_precision'] = mAP
|
| 233 |
+
log_msg = f'\nmean_average_precision\t{mAP:.4f}'
|
| 234 |
+
elif metric == 'mmit_mean_average_precision':
|
| 235 |
+
mAP = mmit_mean_average_precision(results, gt_labels)
|
| 236 |
+
eval_results['mmit_mean_average_precision'] = mAP
|
| 237 |
+
log_msg = f'\nmmit_mean_average_precision\t{mAP:.4f}'
|
| 238 |
+
print_log(log_msg, logger=logger)
|
| 239 |
+
continue
|
| 240 |
+
|
| 241 |
+
return eval_results
|
| 242 |
+
|
| 243 |
+
@staticmethod
|
| 244 |
+
def dump_results(results, out):
|
| 245 |
+
"""Dump data to json/yaml/pickle strings or files."""
|
| 246 |
+
return mmcv.dump(results, out)
|
| 247 |
+
|
| 248 |
+
def prepare_train_frames(self, idx):
|
| 249 |
+
"""Prepare the frames for training given the index."""
|
| 250 |
+
results = copy.deepcopy(self.video_infos[idx])
|
| 251 |
+
results['modality'] = self.modality
|
| 252 |
+
results['start_index'] = self.start_index
|
| 253 |
+
|
| 254 |
+
# prepare tensor in getitem
|
| 255 |
+
# If HVU, type(results['label']) is dict
|
| 256 |
+
if self.multi_class and isinstance(results['label'], list):
|
| 257 |
+
onehot = torch.zeros(self.num_classes)
|
| 258 |
+
onehot[results['label']] = 1.
|
| 259 |
+
results['label'] = onehot
|
| 260 |
+
|
| 261 |
+
return self.pipeline(results)
|
| 262 |
+
|
| 263 |
+
def prepare_test_frames(self, idx):
|
| 264 |
+
"""Prepare the frames for testing given the index."""
|
| 265 |
+
results = copy.deepcopy(self.video_infos[idx])
|
| 266 |
+
results['modality'] = self.modality
|
| 267 |
+
results['start_index'] = self.start_index
|
| 268 |
+
|
| 269 |
+
# prepare tensor in getitem
|
| 270 |
+
# If HVU, type(results['label']) is dict
|
| 271 |
+
if self.multi_class and isinstance(results['label'], list):
|
| 272 |
+
onehot = torch.zeros(self.num_classes)
|
| 273 |
+
onehot[results['label']] = 1.
|
| 274 |
+
results['label'] = onehot
|
| 275 |
+
|
| 276 |
+
return self.pipeline(results)
|
| 277 |
+
|
| 278 |
+
def __len__(self):
|
| 279 |
+
"""Get the size of the dataset."""
|
| 280 |
+
return len(self.video_infos)
|
| 281 |
+
|
| 282 |
+
def __getitem__(self, idx):
|
| 283 |
+
"""Get the sample for either training or testing given index."""
|
| 284 |
+
if self.test_mode:
|
| 285 |
+
return self.prepare_test_frames(idx)
|
| 286 |
+
|
| 287 |
+
return self.prepare_train_frames(idx)
|
TransRAC/mmaction/datasets/builder.py
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import platform
|
| 2 |
+
import random
|
| 3 |
+
from functools import partial
|
| 4 |
+
|
| 5 |
+
import numpy as np
|
| 6 |
+
from mmcv.parallel import collate
|
| 7 |
+
from mmcv.runner import get_dist_info
|
| 8 |
+
from mmcv.utils import Registry, build_from_cfg
|
| 9 |
+
from torch.utils.data import DataLoader
|
| 10 |
+
|
| 11 |
+
from .samplers import ClassSpecificDistributedSampler, DistributedSampler
|
| 12 |
+
|
| 13 |
+
if platform.system() != 'Windows':
|
| 14 |
+
# https://github.com/pytorch/pytorch/issues/973
|
| 15 |
+
import resource
|
| 16 |
+
rlimit = resource.getrlimit(resource.RLIMIT_NOFILE)
|
| 17 |
+
hard_limit = rlimit[1]
|
| 18 |
+
soft_limit = min(4096, hard_limit)
|
| 19 |
+
resource.setrlimit(resource.RLIMIT_NOFILE, (soft_limit, hard_limit))
|
| 20 |
+
|
| 21 |
+
DATASETS = Registry('dataset')
|
| 22 |
+
PIPELINES = Registry('pipeline')
|
| 23 |
+
BLENDINGS = Registry('blending')
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def build_dataset(cfg, default_args=None):
|
| 27 |
+
"""Build a dataset from config dict.
|
| 28 |
+
|
| 29 |
+
Args:
|
| 30 |
+
cfg (dict): Config dict. It should at least contain the key "type".
|
| 31 |
+
default_args (dict | None, optional): Default initialization arguments.
|
| 32 |
+
Default: None.
|
| 33 |
+
|
| 34 |
+
Returns:
|
| 35 |
+
Dataset: The constructed dataset.
|
| 36 |
+
"""
|
| 37 |
+
if cfg['type'] == 'RepeatDataset':
|
| 38 |
+
from .dataset_wrappers import RepeatDataset
|
| 39 |
+
dataset = RepeatDataset(
|
| 40 |
+
build_dataset(cfg['dataset'], default_args), cfg['times'])
|
| 41 |
+
else:
|
| 42 |
+
dataset = build_from_cfg(cfg, DATASETS, default_args)
|
| 43 |
+
return dataset
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
def build_dataloader(dataset,
|
| 47 |
+
videos_per_gpu,
|
| 48 |
+
workers_per_gpu,
|
| 49 |
+
num_gpus=1,
|
| 50 |
+
dist=True,
|
| 51 |
+
shuffle=True,
|
| 52 |
+
seed=None,
|
| 53 |
+
drop_last=False,
|
| 54 |
+
pin_memory=True,
|
| 55 |
+
**kwargs):
|
| 56 |
+
"""Build PyTorch DataLoader.
|
| 57 |
+
|
| 58 |
+
In distributed training, each GPU/process has a dataloader.
|
| 59 |
+
In non-distributed training, there is only one dataloader for all GPUs.
|
| 60 |
+
|
| 61 |
+
Args:
|
| 62 |
+
dataset (:obj:`Dataset`): A PyTorch dataset.
|
| 63 |
+
videos_per_gpu (int): Number of videos on each GPU, i.e.,
|
| 64 |
+
batch size of each GPU.
|
| 65 |
+
workers_per_gpu (int): How many subprocesses to use for data
|
| 66 |
+
loading for each GPU.
|
| 67 |
+
num_gpus (int): Number of GPUs. Only used in non-distributed
|
| 68 |
+
training. Default: 1.
|
| 69 |
+
dist (bool): Distributed training/test or not. Default: True.
|
| 70 |
+
shuffle (bool): Whether to shuffle the data at every epoch.
|
| 71 |
+
Default: True.
|
| 72 |
+
seed (int | None): Seed to be used. Default: None.
|
| 73 |
+
drop_last (bool): Whether to drop the last incomplete batch in epoch.
|
| 74 |
+
Default: False
|
| 75 |
+
pin_memory (bool): Whether to use pin_memory in DataLoader.
|
| 76 |
+
Default: True
|
| 77 |
+
kwargs (dict, optional): Any keyword argument to be used to initialize
|
| 78 |
+
DataLoader.
|
| 79 |
+
|
| 80 |
+
Returns:
|
| 81 |
+
DataLoader: A PyTorch dataloader.
|
| 82 |
+
"""
|
| 83 |
+
rank, world_size = get_dist_info()
|
| 84 |
+
sample_by_class = getattr(dataset, 'sample_by_class', False)
|
| 85 |
+
|
| 86 |
+
if dist:
|
| 87 |
+
if sample_by_class:
|
| 88 |
+
dynamic_length = getattr(dataset, 'dynamic_length', True)
|
| 89 |
+
sampler = ClassSpecificDistributedSampler(
|
| 90 |
+
dataset,
|
| 91 |
+
world_size,
|
| 92 |
+
rank,
|
| 93 |
+
dynamic_length=dynamic_length,
|
| 94 |
+
shuffle=shuffle,
|
| 95 |
+
seed=seed)
|
| 96 |
+
else:
|
| 97 |
+
sampler = DistributedSampler(
|
| 98 |
+
dataset, world_size, rank, shuffle=shuffle, seed=seed)
|
| 99 |
+
shuffle = False
|
| 100 |
+
batch_size = videos_per_gpu
|
| 101 |
+
num_workers = workers_per_gpu
|
| 102 |
+
else:
|
| 103 |
+
sampler = None
|
| 104 |
+
batch_size = num_gpus * videos_per_gpu
|
| 105 |
+
num_workers = num_gpus * workers_per_gpu
|
| 106 |
+
|
| 107 |
+
init_fn = partial(
|
| 108 |
+
worker_init_fn, num_workers=num_workers, rank=rank,
|
| 109 |
+
seed=seed) if seed is not None else None
|
| 110 |
+
|
| 111 |
+
data_loader = DataLoader(
|
| 112 |
+
dataset,
|
| 113 |
+
batch_size=batch_size,
|
| 114 |
+
sampler=sampler,
|
| 115 |
+
num_workers=num_workers,
|
| 116 |
+
collate_fn=partial(collate, samples_per_gpu=videos_per_gpu),
|
| 117 |
+
pin_memory=pin_memory,
|
| 118 |
+
shuffle=shuffle,
|
| 119 |
+
worker_init_fn=init_fn,
|
| 120 |
+
drop_last=drop_last,
|
| 121 |
+
**kwargs)
|
| 122 |
+
|
| 123 |
+
return data_loader
|
| 124 |
+
|
| 125 |
+
|
| 126 |
+
def worker_init_fn(worker_id, num_workers, rank, seed):
|
| 127 |
+
"""Init the random seed for various workers."""
|
| 128 |
+
# The seed of each worker equals to
|
| 129 |
+
# num_worker * rank + worker_id + user_seed
|
| 130 |
+
worker_seed = num_workers * rank + worker_id + seed
|
| 131 |
+
np.random.seed(worker_seed)
|
| 132 |
+
random.seed(worker_seed)
|
TransRAC/mmaction/datasets/dataset_wrappers.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .builder import DATASETS
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
@DATASETS.register_module()
|
| 5 |
+
class RepeatDataset:
|
| 6 |
+
"""A wrapper of repeated dataset.
|
| 7 |
+
|
| 8 |
+
The length of repeated dataset will be ``times`` larger than the original
|
| 9 |
+
dataset. This is useful when the data loading time is long but the dataset
|
| 10 |
+
is small. Using RepeatDataset can reduce the data loading time between
|
| 11 |
+
epochs.
|
| 12 |
+
|
| 13 |
+
Args:
|
| 14 |
+
dataset (:obj:`Dataset`): The dataset to be repeated.
|
| 15 |
+
times (int): Repeat times.
|
| 16 |
+
"""
|
| 17 |
+
|
| 18 |
+
def __init__(self, dataset, times):
|
| 19 |
+
self.dataset = dataset
|
| 20 |
+
self.times = times
|
| 21 |
+
|
| 22 |
+
self._ori_len = len(self.dataset)
|
| 23 |
+
|
| 24 |
+
def __getitem__(self, idx):
|
| 25 |
+
"""Get data."""
|
| 26 |
+
return self.dataset[idx % self._ori_len]
|
| 27 |
+
|
| 28 |
+
def __len__(self):
|
| 29 |
+
"""Length after repetition."""
|
| 30 |
+
return self.times * self._ori_len
|
TransRAC/mmaction/datasets/hvu_dataset.py
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import copy
|
| 2 |
+
import os.path as osp
|
| 3 |
+
from collections import OrderedDict
|
| 4 |
+
|
| 5 |
+
import mmcv
|
| 6 |
+
import numpy as np
|
| 7 |
+
from mmcv.utils import print_log
|
| 8 |
+
|
| 9 |
+
from ..core import mean_average_precision
|
| 10 |
+
from .base import BaseDataset
|
| 11 |
+
from .builder import DATASETS
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
@DATASETS.register_module()
|
| 15 |
+
class HVUDataset(BaseDataset):
|
| 16 |
+
"""HVU dataset, which supports the recognition tags of multiple categories.
|
| 17 |
+
Accept both video annotation files or rawframe annotation files.
|
| 18 |
+
|
| 19 |
+
The dataset loads videos or raw frames and applies specified transforms to
|
| 20 |
+
return a dict containing the frame tensors and other information.
|
| 21 |
+
|
| 22 |
+
The ann_file is a json file with multiple dictionaries, and each dictionary
|
| 23 |
+
indicates a sample video with the filename and tags, the tags are organized
|
| 24 |
+
as different categories. Example of a video dictionary:
|
| 25 |
+
|
| 26 |
+
.. code-block:: txt
|
| 27 |
+
|
| 28 |
+
{
|
| 29 |
+
'filename': 'gD_G1b0wV5I_001015_001035.mp4',
|
| 30 |
+
'label': {
|
| 31 |
+
'concept': [250, 131, 42, 51, 57, 155, 122],
|
| 32 |
+
'object': [1570, 508],
|
| 33 |
+
'event': [16],
|
| 34 |
+
'action': [180],
|
| 35 |
+
'scene': [206]
|
| 36 |
+
}
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
Example of a rawframe dictionary:
|
| 40 |
+
|
| 41 |
+
.. code-block:: txt
|
| 42 |
+
|
| 43 |
+
{
|
| 44 |
+
'frame_dir': 'gD_G1b0wV5I_001015_001035',
|
| 45 |
+
'total_frames': 61
|
| 46 |
+
'label': {
|
| 47 |
+
'concept': [250, 131, 42, 51, 57, 155, 122],
|
| 48 |
+
'object': [1570, 508],
|
| 49 |
+
'event': [16],
|
| 50 |
+
'action': [180],
|
| 51 |
+
'scene': [206]
|
| 52 |
+
}
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
Args:
|
| 57 |
+
ann_file (str): Path to the annotation file, should be a json file.
|
| 58 |
+
pipeline (list[dict | callable]): A sequence of data transforms.
|
| 59 |
+
tag_categories (list[str]): List of category names of tags.
|
| 60 |
+
tag_category_nums (list[int]): List of number of tags in each category.
|
| 61 |
+
filename_tmpl (str | None): Template for each filename. If set to None,
|
| 62 |
+
video dataset is used. Default: None.
|
| 63 |
+
**kwargs: Keyword arguments for ``BaseDataset``.
|
| 64 |
+
"""
|
| 65 |
+
|
| 66 |
+
def __init__(self,
|
| 67 |
+
ann_file,
|
| 68 |
+
pipeline,
|
| 69 |
+
tag_categories,
|
| 70 |
+
tag_category_nums,
|
| 71 |
+
filename_tmpl=None,
|
| 72 |
+
**kwargs):
|
| 73 |
+
assert len(tag_categories) == len(tag_category_nums)
|
| 74 |
+
self.tag_categories = tag_categories
|
| 75 |
+
self.tag_category_nums = tag_category_nums
|
| 76 |
+
self.filename_tmpl = filename_tmpl
|
| 77 |
+
self.num_categories = len(self.tag_categories)
|
| 78 |
+
self.num_tags = sum(self.tag_category_nums)
|
| 79 |
+
self.category2num = dict(zip(tag_categories, tag_category_nums))
|
| 80 |
+
self.start_idx = [0]
|
| 81 |
+
for i in range(self.num_categories - 1):
|
| 82 |
+
self.start_idx.append(self.start_idx[-1] +
|
| 83 |
+
self.tag_category_nums[i])
|
| 84 |
+
self.category2startidx = dict(zip(tag_categories, self.start_idx))
|
| 85 |
+
self.start_index = kwargs.pop('start_index', 0)
|
| 86 |
+
self.dataset_type = None
|
| 87 |
+
super().__init__(
|
| 88 |
+
ann_file, pipeline, start_index=self.start_index, **kwargs)
|
| 89 |
+
|
| 90 |
+
def load_annotations(self):
|
| 91 |
+
"""Load annotation file to get video information."""
|
| 92 |
+
assert self.ann_file.endswith('.json')
|
| 93 |
+
return self.load_json_annotations()
|
| 94 |
+
|
| 95 |
+
def load_json_annotations(self):
|
| 96 |
+
video_infos = mmcv.load(self.ann_file)
|
| 97 |
+
num_videos = len(video_infos)
|
| 98 |
+
|
| 99 |
+
video_info0 = video_infos[0]
|
| 100 |
+
assert ('filename' in video_info0) != ('frame_dir' in video_info0)
|
| 101 |
+
path_key = 'filename' if 'filename' in video_info0 else 'frame_dir'
|
| 102 |
+
self.dataset_type = 'video' if path_key == 'filename' else 'rawframe'
|
| 103 |
+
if self.dataset_type == 'rawframe':
|
| 104 |
+
assert self.filename_tmpl is not None
|
| 105 |
+
|
| 106 |
+
for i in range(num_videos):
|
| 107 |
+
path_value = video_infos[i][path_key]
|
| 108 |
+
if self.data_prefix is not None:
|
| 109 |
+
path_value = osp.join(self.data_prefix, path_value)
|
| 110 |
+
video_infos[i][path_key] = path_value
|
| 111 |
+
|
| 112 |
+
# We will convert label to torch tensors in the pipeline
|
| 113 |
+
video_infos[i]['categories'] = self.tag_categories
|
| 114 |
+
video_infos[i]['category_nums'] = self.tag_category_nums
|
| 115 |
+
if self.dataset_type == 'rawframe':
|
| 116 |
+
video_infos[i]['filename_tmpl'] = self.filename_tmpl
|
| 117 |
+
video_infos[i]['start_index'] = self.start_index
|
| 118 |
+
video_infos[i]['modality'] = self.modality
|
| 119 |
+
|
| 120 |
+
return video_infos
|
| 121 |
+
|
| 122 |
+
@staticmethod
|
| 123 |
+
def label2array(num, label):
|
| 124 |
+
arr = np.zeros(num, dtype=np.float32)
|
| 125 |
+
arr[label] = 1.
|
| 126 |
+
return arr
|
| 127 |
+
|
| 128 |
+
def evaluate(self,
|
| 129 |
+
results,
|
| 130 |
+
metrics='mean_average_precision',
|
| 131 |
+
metric_options=None,
|
| 132 |
+
logger=None):
|
| 133 |
+
"""Evaluation in HVU Video Dataset. We only support evaluating mAP for
|
| 134 |
+
each tag categories. Since some tag categories are missing for some
|
| 135 |
+
videos, we can not evaluate mAP for all tags.
|
| 136 |
+
|
| 137 |
+
Args:
|
| 138 |
+
results (list): Output results.
|
| 139 |
+
metrics (str | sequence[str]): Metrics to be performed.
|
| 140 |
+
Defaults: 'mean_average_precision'.
|
| 141 |
+
metric_options (dict | None): Dict for metric options.
|
| 142 |
+
Default: None.
|
| 143 |
+
logger (logging.Logger | None): Logger for recording.
|
| 144 |
+
Default: None.
|
| 145 |
+
|
| 146 |
+
Returns:
|
| 147 |
+
dict: Evaluation results dict.
|
| 148 |
+
"""
|
| 149 |
+
# Protect ``metric_options`` since it uses mutable value as default
|
| 150 |
+
metric_options = copy.deepcopy(metric_options)
|
| 151 |
+
|
| 152 |
+
if not isinstance(results, list):
|
| 153 |
+
raise TypeError(f'results must be a list, but got {type(results)}')
|
| 154 |
+
assert len(results) == len(self), (
|
| 155 |
+
f'The length of results is not equal to the dataset len: '
|
| 156 |
+
f'{len(results)} != {len(self)}')
|
| 157 |
+
|
| 158 |
+
metrics = metrics if isinstance(metrics, (list, tuple)) else [metrics]
|
| 159 |
+
|
| 160 |
+
# There should be only one metric in the metrics list:
|
| 161 |
+
# 'mean_average_precision'
|
| 162 |
+
assert len(metrics) == 1
|
| 163 |
+
metric = metrics[0]
|
| 164 |
+
assert metric == 'mean_average_precision'
|
| 165 |
+
|
| 166 |
+
gt_labels = [ann['label'] for ann in self.video_infos]
|
| 167 |
+
|
| 168 |
+
eval_results = OrderedDict()
|
| 169 |
+
|
| 170 |
+
for category in self.tag_categories:
|
| 171 |
+
|
| 172 |
+
start_idx = self.category2startidx[category]
|
| 173 |
+
num = self.category2num[category]
|
| 174 |
+
preds = [
|
| 175 |
+
result[start_idx:start_idx + num]
|
| 176 |
+
for video_idx, result in enumerate(results)
|
| 177 |
+
if category in gt_labels[video_idx]
|
| 178 |
+
]
|
| 179 |
+
gts = [
|
| 180 |
+
gt_label[category] for gt_label in gt_labels
|
| 181 |
+
if category in gt_label
|
| 182 |
+
]
|
| 183 |
+
|
| 184 |
+
gts = [self.label2array(num, item) for item in gts]
|
| 185 |
+
|
| 186 |
+
mAP = mean_average_precision(preds, gts)
|
| 187 |
+
eval_results[f'{category}_mAP'] = mAP
|
| 188 |
+
log_msg = f'\n{category}_mAP\t{mAP:.4f}'
|
| 189 |
+
print_log(log_msg, logger=logger)
|
| 190 |
+
|
| 191 |
+
return eval_results
|
TransRAC/mmaction/datasets/image_dataset.py
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .builder import DATASETS
|
| 2 |
+
from .video_dataset import VideoDataset
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
@DATASETS.register_module()
|
| 6 |
+
class ImageDataset(VideoDataset):
|
| 7 |
+
"""Image dataset for action recognition, used in the Project OmniSource.
|
| 8 |
+
|
| 9 |
+
The dataset loads image list and apply specified transforms to return a
|
| 10 |
+
dict containing the image tensors and other information. For the
|
| 11 |
+
ImageDataset
|
| 12 |
+
|
| 13 |
+
The ann_file is a text file with multiple lines, and each line indicates
|
| 14 |
+
the image path and the image label, which are split with a whitespace.
|
| 15 |
+
Example of a annotation file:
|
| 16 |
+
|
| 17 |
+
.. code-block:: txt
|
| 18 |
+
|
| 19 |
+
path/to/image1.jpg 1
|
| 20 |
+
path/to/image2.jpg 1
|
| 21 |
+
path/to/image3.jpg 2
|
| 22 |
+
path/to/image4.jpg 2
|
| 23 |
+
path/to/image5.jpg 3
|
| 24 |
+
path/to/image6.jpg 3
|
| 25 |
+
|
| 26 |
+
Example of a multi-class annotation file:
|
| 27 |
+
|
| 28 |
+
.. code-block:: txt
|
| 29 |
+
|
| 30 |
+
path/to/image1.jpg 1 3 5
|
| 31 |
+
path/to/image2.jpg 1 2
|
| 32 |
+
path/to/image3.jpg 2
|
| 33 |
+
path/to/image4.jpg 2 4 6 8
|
| 34 |
+
path/to/image5.jpg 3
|
| 35 |
+
path/to/image6.jpg 3
|
| 36 |
+
|
| 37 |
+
Args:
|
| 38 |
+
ann_file (str): Path to the annotation file.
|
| 39 |
+
pipeline (list[dict | callable]): A sequence of data transforms.
|
| 40 |
+
**kwargs: Keyword arguments for ``BaseDataset``.
|
| 41 |
+
"""
|
| 42 |
+
|
| 43 |
+
def __init__(self, ann_file, pipeline, **kwargs):
|
| 44 |
+
super().__init__(ann_file, pipeline, start_index=None, **kwargs)
|
| 45 |
+
# use `start_index=None` to indicate it is for `ImageDataset`
|
TransRAC/mmaction/datasets/pose_dataset.py
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os.path as osp
|
| 2 |
+
|
| 3 |
+
import mmcv
|
| 4 |
+
import numpy as np
|
| 5 |
+
|
| 6 |
+
from ..utils import get_root_logger
|
| 7 |
+
from .base import BaseDataset
|
| 8 |
+
from .builder import DATASETS
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
@DATASETS.register_module()
|
| 12 |
+
class PoseDataset(BaseDataset):
|
| 13 |
+
"""Pose dataset for action recognition.
|
| 14 |
+
|
| 15 |
+
The dataset loads pose and apply specified transforms to return a
|
| 16 |
+
dict containing pose information.
|
| 17 |
+
|
| 18 |
+
The ann_file is a pickle file, the json file contains a list of
|
| 19 |
+
annotations, the fields of an annotation include frame_dir(video_id),
|
| 20 |
+
total_frames, label, kp, kpscore.
|
| 21 |
+
|
| 22 |
+
Args:
|
| 23 |
+
ann_file (str): Path to the annotation file.
|
| 24 |
+
pipeline (list[dict | callable]): A sequence of data transforms.
|
| 25 |
+
valid_ratio (float | None): The valid_ratio for videos in KineticsPose.
|
| 26 |
+
For a video with n frames, it is a valid training sample only if
|
| 27 |
+
n * valid_ratio frames have human pose. None means not applicable
|
| 28 |
+
(only applicable to Kinetics Pose). Default: None.
|
| 29 |
+
box_thr (str | None): The threshold for human proposals. Only boxes
|
| 30 |
+
with confidence score larger than `box_thr` is kept. None means
|
| 31 |
+
not applicable (only applicable to Kinetics Pose [ours]). Allowed
|
| 32 |
+
choices are '0.5', '0.6', '0.7', '0.8', '0.9'. Default: None.
|
| 33 |
+
class_prob (dict | None): The per class sampling probability. If not
|
| 34 |
+
None, it will override the class_prob calculated in
|
| 35 |
+
BaseDataset.__init__(). Default: None.
|
| 36 |
+
**kwargs: Keyword arguments for ``BaseDataset``.
|
| 37 |
+
"""
|
| 38 |
+
|
| 39 |
+
def __init__(self,
|
| 40 |
+
ann_file,
|
| 41 |
+
pipeline,
|
| 42 |
+
valid_ratio=None,
|
| 43 |
+
box_thr=None,
|
| 44 |
+
class_prob=None,
|
| 45 |
+
**kwargs):
|
| 46 |
+
modality = 'Pose'
|
| 47 |
+
|
| 48 |
+
super().__init__(
|
| 49 |
+
ann_file, pipeline, start_index=0, modality=modality, **kwargs)
|
| 50 |
+
|
| 51 |
+
# box_thr, which should be a string
|
| 52 |
+
self.box_thr = box_thr
|
| 53 |
+
if self.box_thr is not None:
|
| 54 |
+
assert box_thr in ['0.5', '0.6', '0.7', '0.8', '0.9']
|
| 55 |
+
|
| 56 |
+
# Thresholding Training Examples
|
| 57 |
+
self.valid_ratio = valid_ratio
|
| 58 |
+
if self.valid_ratio is not None:
|
| 59 |
+
assert isinstance(self.valid_ratio, float)
|
| 60 |
+
if self.box_thr is None:
|
| 61 |
+
self.video_infos = self.video_infos = [
|
| 62 |
+
x for x in self.video_infos
|
| 63 |
+
if x['valid_frames'] / x['total_frames'] >= valid_ratio
|
| 64 |
+
]
|
| 65 |
+
else:
|
| 66 |
+
key = f'valid@{self.box_thr}'
|
| 67 |
+
self.video_infos = [
|
| 68 |
+
x for x in self.video_infos
|
| 69 |
+
if x[key] / x['total_frames'] >= valid_ratio
|
| 70 |
+
]
|
| 71 |
+
if self.box_thr != '0.5':
|
| 72 |
+
box_thr = float(self.box_thr)
|
| 73 |
+
for item in self.video_infos:
|
| 74 |
+
inds = [
|
| 75 |
+
i for i, score in enumerate(item['box_score'])
|
| 76 |
+
if score >= box_thr
|
| 77 |
+
]
|
| 78 |
+
item['anno_inds'] = np.array(inds)
|
| 79 |
+
|
| 80 |
+
if class_prob is not None:
|
| 81 |
+
self.class_prob = class_prob
|
| 82 |
+
|
| 83 |
+
logger = get_root_logger()
|
| 84 |
+
logger.info(f'{len(self)} videos remain after valid thresholding')
|
| 85 |
+
|
| 86 |
+
def load_annotations(self):
|
| 87 |
+
"""Load annotation file to get video information."""
|
| 88 |
+
assert self.ann_file.endswith('.pkl')
|
| 89 |
+
return self.load_pkl_annotations()
|
| 90 |
+
|
| 91 |
+
def load_pkl_annotations(self):
|
| 92 |
+
data = mmcv.load(self.ann_file)
|
| 93 |
+
|
| 94 |
+
for item in data:
|
| 95 |
+
# Sometimes we may need to load anno from the file
|
| 96 |
+
if 'filename' in item:
|
| 97 |
+
item['filename'] = osp.join(self.data_prefix, item['filename'])
|
| 98 |
+
return data
|
TransRAC/mmaction/datasets/rawframe_dataset.py
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import copy
|
| 2 |
+
import os.path as osp
|
| 3 |
+
|
| 4 |
+
import torch
|
| 5 |
+
|
| 6 |
+
from .base import BaseDataset
|
| 7 |
+
from .builder import DATASETS
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
@DATASETS.register_module()
|
| 11 |
+
class RawframeDataset(BaseDataset):
|
| 12 |
+
"""Rawframe dataset for action recognition.
|
| 13 |
+
|
| 14 |
+
The dataset loads raw frames and apply specified transforms to return a
|
| 15 |
+
dict containing the frame tensors and other information.
|
| 16 |
+
|
| 17 |
+
The ann_file is a text file with multiple lines, and each line indicates
|
| 18 |
+
the directory to frames of a video, total frames of the video and
|
| 19 |
+
the label of a video, which are split with a whitespace.
|
| 20 |
+
Example of a annotation file:
|
| 21 |
+
|
| 22 |
+
.. code-block:: txt
|
| 23 |
+
|
| 24 |
+
some/directory-1 163 1
|
| 25 |
+
some/directory-2 122 1
|
| 26 |
+
some/directory-3 258 2
|
| 27 |
+
some/directory-4 234 2
|
| 28 |
+
some/directory-5 295 3
|
| 29 |
+
some/directory-6 121 3
|
| 30 |
+
|
| 31 |
+
Example of a multi-class annotation file:
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
.. code-block:: txt
|
| 35 |
+
|
| 36 |
+
some/directory-1 163 1 3 5
|
| 37 |
+
some/directory-2 122 1 2
|
| 38 |
+
some/directory-3 258 2
|
| 39 |
+
some/directory-4 234 2 4 6 8
|
| 40 |
+
some/directory-5 295 3
|
| 41 |
+
some/directory-6 121 3
|
| 42 |
+
|
| 43 |
+
Example of a with_offset annotation file (clips from long videos), each
|
| 44 |
+
line indicates the directory to frames of a video, the index of the start
|
| 45 |
+
frame, total frames of the video clip and the label of a video clip, which
|
| 46 |
+
are split with a whitespace.
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
.. code-block:: txt
|
| 50 |
+
|
| 51 |
+
some/directory-1 12 163 3
|
| 52 |
+
some/directory-2 213 122 4
|
| 53 |
+
some/directory-3 100 258 5
|
| 54 |
+
some/directory-4 98 234 2
|
| 55 |
+
some/directory-5 0 295 3
|
| 56 |
+
some/directory-6 50 121 3
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
Args:
|
| 60 |
+
ann_file (str): Path to the annotation file.
|
| 61 |
+
pipeline (list[dict | callable]): A sequence of data transforms.
|
| 62 |
+
data_prefix (str | None): Path to a directory where videos are held.
|
| 63 |
+
Default: None.
|
| 64 |
+
test_mode (bool): Store True when building test or validation dataset.
|
| 65 |
+
Default: False.
|
| 66 |
+
filename_tmpl (str): Template for each filename.
|
| 67 |
+
Default: 'img_{:05}.jpg'.
|
| 68 |
+
with_offset (bool): Determines whether the offset information is in
|
| 69 |
+
ann_file. Default: False.
|
| 70 |
+
multi_class (bool): Determines whether it is a multi-class
|
| 71 |
+
recognition dataset. Default: False.
|
| 72 |
+
num_classes (int | None): Number of classes in the dataset.
|
| 73 |
+
Default: None.
|
| 74 |
+
modality (str): Modality of data. Support 'RGB', 'Flow'.
|
| 75 |
+
Default: 'RGB'.
|
| 76 |
+
sample_by_class (bool): Sampling by class, should be set `True` when
|
| 77 |
+
performing inter-class data balancing. Only compatible with
|
| 78 |
+
`multi_class == False`. Only applies for training. Default: False.
|
| 79 |
+
power (float): We support sampling data with the probability
|
| 80 |
+
proportional to the power of its label frequency (freq ^ power)
|
| 81 |
+
when sampling data. `power == 1` indicates uniformly sampling all
|
| 82 |
+
data; `power == 0` indicates uniformly sampling all classes.
|
| 83 |
+
Default: 0.
|
| 84 |
+
dynamic_length (bool): If the dataset length is dynamic (used by
|
| 85 |
+
ClassSpecificDistributedSampler). Default: False.
|
| 86 |
+
"""
|
| 87 |
+
|
| 88 |
+
def __init__(self,
|
| 89 |
+
ann_file,
|
| 90 |
+
pipeline,
|
| 91 |
+
data_prefix=None,
|
| 92 |
+
test_mode=False,
|
| 93 |
+
filename_tmpl='img_{:05}.jpg',
|
| 94 |
+
with_offset=False,
|
| 95 |
+
multi_class=False,
|
| 96 |
+
num_classes=None,
|
| 97 |
+
start_index=1,
|
| 98 |
+
modality='RGB',
|
| 99 |
+
sample_by_class=False,
|
| 100 |
+
power=0.,
|
| 101 |
+
dynamic_length=False):
|
| 102 |
+
self.filename_tmpl = filename_tmpl
|
| 103 |
+
self.with_offset = with_offset
|
| 104 |
+
super().__init__(
|
| 105 |
+
ann_file,
|
| 106 |
+
pipeline,
|
| 107 |
+
data_prefix,
|
| 108 |
+
test_mode,
|
| 109 |
+
multi_class,
|
| 110 |
+
num_classes,
|
| 111 |
+
start_index,
|
| 112 |
+
modality,
|
| 113 |
+
sample_by_class=sample_by_class,
|
| 114 |
+
power=power,
|
| 115 |
+
dynamic_length=dynamic_length)
|
| 116 |
+
|
| 117 |
+
def load_annotations(self):
|
| 118 |
+
"""Load annotation file to get video information."""
|
| 119 |
+
if self.ann_file.endswith('.json'):
|
| 120 |
+
return self.load_json_annotations()
|
| 121 |
+
video_infos = []
|
| 122 |
+
with open(self.ann_file, 'r') as fin:
|
| 123 |
+
for line in fin:
|
| 124 |
+
line_split = line.strip().split()
|
| 125 |
+
video_info = {}
|
| 126 |
+
idx = 0
|
| 127 |
+
# idx for frame_dir
|
| 128 |
+
frame_dir = line_split[idx]
|
| 129 |
+
if self.data_prefix is not None:
|
| 130 |
+
frame_dir = osp.join(self.data_prefix, frame_dir)
|
| 131 |
+
video_info['frame_dir'] = frame_dir
|
| 132 |
+
idx += 1
|
| 133 |
+
if self.with_offset:
|
| 134 |
+
# idx for offset and total_frames
|
| 135 |
+
video_info['offset'] = int(line_split[idx])
|
| 136 |
+
video_info['total_frames'] = int(line_split[idx + 1])
|
| 137 |
+
idx += 2
|
| 138 |
+
else:
|
| 139 |
+
# idx for total_frames
|
| 140 |
+
video_info['total_frames'] = int(line_split[idx])
|
| 141 |
+
idx += 1
|
| 142 |
+
# idx for label[s]
|
| 143 |
+
label = [int(x) for x in line_split[idx:]]
|
| 144 |
+
assert label, f'missing label in line: {line}'
|
| 145 |
+
if self.multi_class:
|
| 146 |
+
assert self.num_classes is not None
|
| 147 |
+
video_info['label'] = label
|
| 148 |
+
else:
|
| 149 |
+
assert len(label) == 1
|
| 150 |
+
video_info['label'] = label[0]
|
| 151 |
+
video_infos.append(video_info)
|
| 152 |
+
|
| 153 |
+
return video_infos
|
| 154 |
+
|
| 155 |
+
def prepare_train_frames(self, idx):
|
| 156 |
+
"""Prepare the frames for training given the index."""
|
| 157 |
+
results = copy.deepcopy(self.video_infos[idx])
|
| 158 |
+
results['filename_tmpl'] = self.filename_tmpl
|
| 159 |
+
results['modality'] = self.modality
|
| 160 |
+
results['start_index'] = self.start_index
|
| 161 |
+
|
| 162 |
+
# prepare tensor in getitem
|
| 163 |
+
if self.multi_class:
|
| 164 |
+
onehot = torch.zeros(self.num_classes)
|
| 165 |
+
onehot[results['label']] = 1.
|
| 166 |
+
results['label'] = onehot
|
| 167 |
+
|
| 168 |
+
return self.pipeline(results)
|
| 169 |
+
|
| 170 |
+
def prepare_test_frames(self, idx):
|
| 171 |
+
"""Prepare the frames for testing given the index."""
|
| 172 |
+
results = copy.deepcopy(self.video_infos[idx])
|
| 173 |
+
results['filename_tmpl'] = self.filename_tmpl
|
| 174 |
+
results['modality'] = self.modality
|
| 175 |
+
results['start_index'] = self.start_index
|
| 176 |
+
|
| 177 |
+
# prepare tensor in getitem
|
| 178 |
+
if self.multi_class:
|
| 179 |
+
onehot = torch.zeros(self.num_classes)
|
| 180 |
+
onehot[results['label']] = 1.
|
| 181 |
+
results['label'] = onehot
|
| 182 |
+
|
| 183 |
+
return self.pipeline(results)
|
TransRAC/mmaction/datasets/rawvideo_dataset.py
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import copy
|
| 2 |
+
import os.path as osp
|
| 3 |
+
import random
|
| 4 |
+
|
| 5 |
+
import mmcv
|
| 6 |
+
|
| 7 |
+
from .base import BaseDataset
|
| 8 |
+
from .builder import DATASETS
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
@DATASETS.register_module()
|
| 12 |
+
class RawVideoDataset(BaseDataset):
|
| 13 |
+
"""RawVideo dataset for action recognition, used in the Project OmniSource.
|
| 14 |
+
|
| 15 |
+
The dataset loads clips of raw videos and apply specified transforms to
|
| 16 |
+
return a dict containing the frame tensors and other information. Not that
|
| 17 |
+
for this dataset, `multi_class` should be False.
|
| 18 |
+
|
| 19 |
+
The ann_file is a text file with multiple lines, and each line indicates
|
| 20 |
+
a sample video with the filepath (without suffix), label, number of clips
|
| 21 |
+
and index of positive clips (starting from 0), which are split with a
|
| 22 |
+
whitespace. Raw videos should be first trimmed into 10 second clips,
|
| 23 |
+
organized in the following format:
|
| 24 |
+
|
| 25 |
+
.. code-block:: txt
|
| 26 |
+
|
| 27 |
+
some/path/D32_1gwq35E/part_0.mp4
|
| 28 |
+
some/path/D32_1gwq35E/part_1.mp4
|
| 29 |
+
......
|
| 30 |
+
some/path/D32_1gwq35E/part_n.mp4
|
| 31 |
+
|
| 32 |
+
Example of a annotation file:
|
| 33 |
+
|
| 34 |
+
.. code-block:: txt
|
| 35 |
+
|
| 36 |
+
some/path/D32_1gwq35E 66 10 0 1 2
|
| 37 |
+
some/path/-G-5CJ0JkKY 254 5 3 4
|
| 38 |
+
some/path/T4h1bvOd9DA 33 1 0
|
| 39 |
+
some/path/4uZ27ivBl00 341 2 0 1
|
| 40 |
+
some/path/0LfESFkfBSw 186 234 7 9 11
|
| 41 |
+
some/path/-YIsNpBEx6c 169 100 9 10 11
|
| 42 |
+
|
| 43 |
+
The first line indicates that the raw video `some/path/D32_1gwq35E` has
|
| 44 |
+
action label `66`, consists of 10 clips (from `part_0.mp4` to
|
| 45 |
+
`part_9.mp4`). The 1st, 2nd and 3rd clips are positive clips.
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
Args:
|
| 49 |
+
ann_file (str): Path to the annotation file.
|
| 50 |
+
pipeline (list[dict | callable]): A sequence of data transforms.
|
| 51 |
+
sampling_strategy (str): The strategy to sample clips from raw videos.
|
| 52 |
+
Choices are 'random' or 'positive'. Default: 'positive'.
|
| 53 |
+
clipname_tmpl (str): The template of clip name in the raw video.
|
| 54 |
+
Default: 'part_{}.mp4'.
|
| 55 |
+
**kwargs: Keyword arguments for ``BaseDataset``.
|
| 56 |
+
"""
|
| 57 |
+
|
| 58 |
+
def __init__(self,
|
| 59 |
+
ann_file,
|
| 60 |
+
pipeline,
|
| 61 |
+
clipname_tmpl='part_{}.mp4',
|
| 62 |
+
sampling_strategy='positive',
|
| 63 |
+
**kwargs):
|
| 64 |
+
super().__init__(ann_file, pipeline, start_index=0, **kwargs)
|
| 65 |
+
assert self.multi_class is False
|
| 66 |
+
self.sampling_strategy = sampling_strategy
|
| 67 |
+
self.clipname_tmpl = clipname_tmpl
|
| 68 |
+
# If positive, we should only keep those raw videos with positive
|
| 69 |
+
# clips
|
| 70 |
+
if self.sampling_strategy == 'positive':
|
| 71 |
+
self.video_infos = [
|
| 72 |
+
x for x in self.video_infos if len(x['positive_clip_inds'])
|
| 73 |
+
]
|
| 74 |
+
|
| 75 |
+
# do not support multi_class
|
| 76 |
+
def load_annotations(self):
|
| 77 |
+
"""Load annotation file to get video information."""
|
| 78 |
+
if self.ann_file.endswith('.json'):
|
| 79 |
+
return self.load_json_annotations()
|
| 80 |
+
|
| 81 |
+
video_infos = []
|
| 82 |
+
with open(self.ann_file, 'r') as fin:
|
| 83 |
+
for line in fin:
|
| 84 |
+
line_split = line.strip().split()
|
| 85 |
+
video_dir = line_split[0]
|
| 86 |
+
label = int(line_split[1])
|
| 87 |
+
num_clips = int(line_split[2])
|
| 88 |
+
positive_clip_inds = [int(ind) for ind in line_split[3:]]
|
| 89 |
+
|
| 90 |
+
if self.data_prefix is not None:
|
| 91 |
+
video_dir = osp.join(self.data_prefix, video_dir)
|
| 92 |
+
video_infos.append(
|
| 93 |
+
dict(
|
| 94 |
+
video_dir=video_dir,
|
| 95 |
+
label=label,
|
| 96 |
+
num_clips=num_clips,
|
| 97 |
+
positive_clip_inds=positive_clip_inds))
|
| 98 |
+
return video_infos
|
| 99 |
+
|
| 100 |
+
# do not support multi_class
|
| 101 |
+
def load_json_annotations(self):
|
| 102 |
+
"""Load json annotation file to get video information."""
|
| 103 |
+
video_infos = mmcv.load(self.ann_file)
|
| 104 |
+
num_videos = len(video_infos)
|
| 105 |
+
path_key = 'video_dir'
|
| 106 |
+
for i in range(num_videos):
|
| 107 |
+
if self.data_prefix is not None:
|
| 108 |
+
path_value = video_infos[i][path_key]
|
| 109 |
+
path_value = osp.join(self.data_prefix, path_value)
|
| 110 |
+
video_infos[i][path_key] = path_value
|
| 111 |
+
return video_infos
|
| 112 |
+
|
| 113 |
+
def sample_clip(self, results):
|
| 114 |
+
"""Sample a clip from the raw video given the sampling strategy."""
|
| 115 |
+
assert self.sampling_strategy in ['positive', 'random']
|
| 116 |
+
if self.sampling_strategy == 'positive':
|
| 117 |
+
assert results['positive_clip_inds']
|
| 118 |
+
ind = random.choice(results['positive_clip_inds'])
|
| 119 |
+
else:
|
| 120 |
+
ind = random.randint(0, results['num_clips'] - 1)
|
| 121 |
+
clipname = self.clipname_tmpl.format(ind)
|
| 122 |
+
|
| 123 |
+
# if the first char of self.clipname_tmpl is a letter, use osp.join;
|
| 124 |
+
# otherwise, directly concat them
|
| 125 |
+
if self.clipname_tmpl[0].isalpha():
|
| 126 |
+
filename = osp.join(results['video_dir'], clipname)
|
| 127 |
+
else:
|
| 128 |
+
filename = results['video_dir'] + clipname
|
| 129 |
+
results['filename'] = filename
|
| 130 |
+
return results
|
| 131 |
+
|
| 132 |
+
def prepare_train_frames(self, idx):
|
| 133 |
+
"""Prepare the frames for training given the index."""
|
| 134 |
+
results = copy.deepcopy(self.video_infos[idx])
|
| 135 |
+
results = self.sample_clip(results)
|
| 136 |
+
results['modality'] = self.modality
|
| 137 |
+
results['start_index'] = self.start_index
|
| 138 |
+
return self.pipeline(results)
|
| 139 |
+
|
| 140 |
+
def prepare_test_frames(self, idx):
|
| 141 |
+
"""Prepare the frames for testing given the index."""
|
| 142 |
+
results = copy.deepcopy(self.video_infos[idx])
|
| 143 |
+
results = self.sample_clip(results)
|
| 144 |
+
results['modality'] = self.modality
|
| 145 |
+
results['start_index'] = self.start_index
|
| 146 |
+
return self.pipeline(results)
|
TransRAC/mmaction/datasets/ssn_dataset.py
ADDED
|
@@ -0,0 +1,881 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import copy
|
| 2 |
+
import os.path as osp
|
| 3 |
+
import warnings
|
| 4 |
+
from collections import OrderedDict
|
| 5 |
+
|
| 6 |
+
import mmcv
|
| 7 |
+
import numpy as np
|
| 8 |
+
from torch.nn.modules.utils import _pair
|
| 9 |
+
|
| 10 |
+
from ..core import softmax
|
| 11 |
+
from ..localization import (eval_ap, load_localize_proposal_file,
|
| 12 |
+
perform_regression, temporal_iou, temporal_nms)
|
| 13 |
+
from ..utils import get_root_logger
|
| 14 |
+
from .base import BaseDataset
|
| 15 |
+
from .builder import DATASETS
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
class SSNInstance:
|
| 19 |
+
"""Proposal instance of SSN.
|
| 20 |
+
|
| 21 |
+
Args:
|
| 22 |
+
start_frame (int): Index of the proposal's start frame.
|
| 23 |
+
end_frame (int): Index of the proposal's end frame.
|
| 24 |
+
num_video_frames (int): Total frames of the video.
|
| 25 |
+
label (int | None): The category label of the proposal. Default: None.
|
| 26 |
+
best_iou (float): The highest IOU with the groundtruth instance.
|
| 27 |
+
Default: 0.
|
| 28 |
+
overlap_self (float): Percent of the proposal's own span contained
|
| 29 |
+
in a groundtruth instance. Default: 0.
|
| 30 |
+
"""
|
| 31 |
+
|
| 32 |
+
def __init__(self,
|
| 33 |
+
start_frame,
|
| 34 |
+
end_frame,
|
| 35 |
+
num_video_frames,
|
| 36 |
+
label=None,
|
| 37 |
+
best_iou=0,
|
| 38 |
+
overlap_self=0):
|
| 39 |
+
self.start_frame = start_frame
|
| 40 |
+
self.end_frame = min(end_frame, num_video_frames)
|
| 41 |
+
self.num_video_frames = num_video_frames
|
| 42 |
+
self.label = label if label is not None else -1
|
| 43 |
+
self.coverage = (end_frame - start_frame) / num_video_frames
|
| 44 |
+
self.best_iou = best_iou
|
| 45 |
+
self.overlap_self = overlap_self
|
| 46 |
+
self.loc_reg = None
|
| 47 |
+
self.size_reg = None
|
| 48 |
+
self.regression_targets = [0., 0.]
|
| 49 |
+
|
| 50 |
+
def compute_regression_targets(self, gt_list):
|
| 51 |
+
"""Compute regression targets of positive proposals.
|
| 52 |
+
|
| 53 |
+
Args:
|
| 54 |
+
gt_list (list): The list of groundtruth instances.
|
| 55 |
+
"""
|
| 56 |
+
# Find the groundtruth instance with the highest IOU.
|
| 57 |
+
ious = [
|
| 58 |
+
temporal_iou(self.start_frame, self.end_frame, gt.start_frame,
|
| 59 |
+
gt.end_frame) for gt in gt_list
|
| 60 |
+
]
|
| 61 |
+
best_gt = gt_list[np.argmax(ious)]
|
| 62 |
+
|
| 63 |
+
# interval: [start_frame, end_frame)
|
| 64 |
+
proposal_center = (self.start_frame + self.end_frame - 1) / 2
|
| 65 |
+
gt_center = (best_gt.start_frame + best_gt.end_frame - 1) / 2
|
| 66 |
+
proposal_size = self.end_frame - self.start_frame
|
| 67 |
+
gt_size = best_gt.end_frame - best_gt.start_frame
|
| 68 |
+
|
| 69 |
+
# Get regression targets:
|
| 70 |
+
# (1). Localization regression target:
|
| 71 |
+
# center shift proportional to the proposal duration
|
| 72 |
+
# (2). Duration/Size regression target:
|
| 73 |
+
# logarithm of the groundtruth duration over proposal duration
|
| 74 |
+
|
| 75 |
+
self.loc_reg = (gt_center - proposal_center) / proposal_size
|
| 76 |
+
self.size_reg = np.log(gt_size / proposal_size)
|
| 77 |
+
self.regression_targets = ([self.loc_reg, self.size_reg]
|
| 78 |
+
if self.loc_reg is not None else [0., 0.])
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
@DATASETS.register_module()
|
| 82 |
+
class SSNDataset(BaseDataset):
|
| 83 |
+
"""Proposal frame dataset for Structured Segment Networks.
|
| 84 |
+
|
| 85 |
+
Based on proposal information, the dataset loads raw frames and applies
|
| 86 |
+
specified transforms to return a dict containing the frame tensors and
|
| 87 |
+
other information.
|
| 88 |
+
|
| 89 |
+
The ann_file is a text file with multiple lines and each
|
| 90 |
+
video's information takes up several lines. This file can be a normalized
|
| 91 |
+
file with percent or standard file with specific frame indexes. If the file
|
| 92 |
+
is a normalized file, it will be converted into a standard file first.
|
| 93 |
+
|
| 94 |
+
Template information of a video in a standard file:
|
| 95 |
+
.. code-block:: txt
|
| 96 |
+
# index
|
| 97 |
+
video_id
|
| 98 |
+
num_frames
|
| 99 |
+
fps
|
| 100 |
+
num_gts
|
| 101 |
+
label, start_frame, end_frame
|
| 102 |
+
label, start_frame, end_frame
|
| 103 |
+
...
|
| 104 |
+
num_proposals
|
| 105 |
+
label, best_iou, overlap_self, start_frame, end_frame
|
| 106 |
+
label, best_iou, overlap_self, start_frame, end_frame
|
| 107 |
+
...
|
| 108 |
+
|
| 109 |
+
Example of a standard annotation file:
|
| 110 |
+
.. code-block:: txt
|
| 111 |
+
# 0
|
| 112 |
+
video_validation_0000202
|
| 113 |
+
5666
|
| 114 |
+
1
|
| 115 |
+
3
|
| 116 |
+
8 130 185
|
| 117 |
+
8 832 1136
|
| 118 |
+
8 1303 1381
|
| 119 |
+
5
|
| 120 |
+
8 0.0620 0.0620 790 5671
|
| 121 |
+
8 0.1656 0.1656 790 2619
|
| 122 |
+
8 0.0833 0.0833 3945 5671
|
| 123 |
+
8 0.0960 0.0960 4173 5671
|
| 124 |
+
8 0.0614 0.0614 3327 5671
|
| 125 |
+
|
| 126 |
+
Args:
|
| 127 |
+
ann_file (str): Path to the annotation file.
|
| 128 |
+
pipeline (list[dict | callable]): A sequence of data transforms.
|
| 129 |
+
train_cfg (dict): Config for training.
|
| 130 |
+
test_cfg (dict): Config for testing.
|
| 131 |
+
data_prefix (str): Path to a directory where videos are held.
|
| 132 |
+
test_mode (bool): Store True when building test or validation dataset.
|
| 133 |
+
Default: False.
|
| 134 |
+
filename_tmpl (str): Template for each filename.
|
| 135 |
+
Default: 'img_{:05}.jpg'.
|
| 136 |
+
start_index (int): Specify a start index for frames in consideration of
|
| 137 |
+
different filename format. Default: 1.
|
| 138 |
+
modality (str): Modality of data. Support 'RGB', 'Flow'.
|
| 139 |
+
Default: 'RGB'.
|
| 140 |
+
video_centric (bool): Whether to sample proposals just from
|
| 141 |
+
this video or sample proposals randomly from the entire dataset.
|
| 142 |
+
Default: True.
|
| 143 |
+
reg_normalize_constants (list): Regression target normalized constants,
|
| 144 |
+
including mean and standard deviation of location and duration.
|
| 145 |
+
body_segments (int): Number of segments in course period.
|
| 146 |
+
Default: 5.
|
| 147 |
+
aug_segments (list[int]): Number of segments in starting and
|
| 148 |
+
ending period. Default: (2, 2).
|
| 149 |
+
aug_ratio (int | float | tuple[int | float]): The ratio of the length
|
| 150 |
+
of augmentation to that of the proposal. Defualt: (0.5, 0.5).
|
| 151 |
+
clip_len (int): Frames of each sampled output clip.
|
| 152 |
+
Default: 1.
|
| 153 |
+
frame_interval (int): Temporal interval of adjacent sampled frames.
|
| 154 |
+
Default: 1.
|
| 155 |
+
filter_gt (bool): Whether to filter videos with no annotation
|
| 156 |
+
during training. Default: True.
|
| 157 |
+
use_regression (bool): Whether to perform regression. Default: True.
|
| 158 |
+
verbose (bool): Whether to print full information or not.
|
| 159 |
+
Default: False.
|
| 160 |
+
"""
|
| 161 |
+
|
| 162 |
+
def __init__(self,
|
| 163 |
+
ann_file,
|
| 164 |
+
pipeline,
|
| 165 |
+
train_cfg,
|
| 166 |
+
test_cfg,
|
| 167 |
+
data_prefix,
|
| 168 |
+
test_mode=False,
|
| 169 |
+
filename_tmpl='img_{:05d}.jpg',
|
| 170 |
+
start_index=1,
|
| 171 |
+
modality='RGB',
|
| 172 |
+
video_centric=True,
|
| 173 |
+
reg_normalize_constants=None,
|
| 174 |
+
body_segments=5,
|
| 175 |
+
aug_segments=(2, 2),
|
| 176 |
+
aug_ratio=(0.5, 0.5),
|
| 177 |
+
clip_len=1,
|
| 178 |
+
frame_interval=1,
|
| 179 |
+
filter_gt=True,
|
| 180 |
+
use_regression=True,
|
| 181 |
+
verbose=False):
|
| 182 |
+
self.logger = get_root_logger()
|
| 183 |
+
super().__init__(
|
| 184 |
+
ann_file,
|
| 185 |
+
pipeline,
|
| 186 |
+
data_prefix=data_prefix,
|
| 187 |
+
test_mode=test_mode,
|
| 188 |
+
start_index=start_index,
|
| 189 |
+
modality=modality)
|
| 190 |
+
self.train_cfg = train_cfg
|
| 191 |
+
self.test_cfg = test_cfg
|
| 192 |
+
self.assigner = train_cfg.ssn.assigner
|
| 193 |
+
self.sampler = train_cfg.ssn.sampler
|
| 194 |
+
self.evaluater = test_cfg.ssn.evaluater
|
| 195 |
+
self.verbose = verbose
|
| 196 |
+
self.filename_tmpl = filename_tmpl
|
| 197 |
+
|
| 198 |
+
if filter_gt or not test_mode:
|
| 199 |
+
valid_inds = [
|
| 200 |
+
i for i, video_info in enumerate(self.video_infos)
|
| 201 |
+
if len(video_info['gts']) > 0
|
| 202 |
+
]
|
| 203 |
+
self.logger.info(f'{len(valid_inds)} out of {len(self.video_infos)} '
|
| 204 |
+
f'videos are valid.')
|
| 205 |
+
self.video_infos = [self.video_infos[i] for i in valid_inds]
|
| 206 |
+
|
| 207 |
+
# construct three pools:
|
| 208 |
+
# 1. Positive(Foreground)
|
| 209 |
+
# 2. Background
|
| 210 |
+
# 3. Incomplete
|
| 211 |
+
self.positive_pool = []
|
| 212 |
+
self.background_pool = []
|
| 213 |
+
self.incomplete_pool = []
|
| 214 |
+
self.construct_proposal_pools()
|
| 215 |
+
|
| 216 |
+
if reg_normalize_constants is None:
|
| 217 |
+
self.reg_norm_consts = self._compute_reg_normalize_constants()
|
| 218 |
+
else:
|
| 219 |
+
self.reg_norm_consts = reg_normalize_constants
|
| 220 |
+
self.video_centric = video_centric
|
| 221 |
+
self.body_segments = body_segments
|
| 222 |
+
self.aug_segments = aug_segments
|
| 223 |
+
self.aug_ratio = _pair(aug_ratio)
|
| 224 |
+
if not mmcv.is_tuple_of(self.aug_ratio, (int, float)):
|
| 225 |
+
raise TypeError(f'aug_ratio should be int, float'
|
| 226 |
+
f'or tuple of int and float, '
|
| 227 |
+
f'but got {type(aug_ratio)}')
|
| 228 |
+
assert len(self.aug_ratio) == 2
|
| 229 |
+
|
| 230 |
+
total_ratio = (
|
| 231 |
+
self.sampler.positive_ratio + self.sampler.background_ratio +
|
| 232 |
+
self.sampler.incomplete_ratio)
|
| 233 |
+
self.positive_per_video = int(
|
| 234 |
+
self.sampler.num_per_video *
|
| 235 |
+
(self.sampler.positive_ratio / total_ratio))
|
| 236 |
+
self.background_per_video = int(
|
| 237 |
+
self.sampler.num_per_video *
|
| 238 |
+
(self.sampler.background_ratio / total_ratio))
|
| 239 |
+
self.incomplete_per_video = (
|
| 240 |
+
self.sampler.num_per_video - self.positive_per_video -
|
| 241 |
+
self.background_per_video)
|
| 242 |
+
|
| 243 |
+
self.test_interval = self.test_cfg.ssn.sampler.test_interval
|
| 244 |
+
# number of consecutive frames
|
| 245 |
+
self.clip_len = clip_len
|
| 246 |
+
# number of steps (sparse sampling for efficiency of io)
|
| 247 |
+
self.frame_interval = frame_interval
|
| 248 |
+
|
| 249 |
+
# test mode or not
|
| 250 |
+
self.filter_gt = filter_gt
|
| 251 |
+
self.use_regression = use_regression
|
| 252 |
+
self.test_mode = test_mode
|
| 253 |
+
|
| 254 |
+
# yapf: disable
|
| 255 |
+
if self.verbose:
|
| 256 |
+
self.logger.info(f"""
|
| 257 |
+
SSNDataset: proposal file {self.proposal_file} parsed.
|
| 258 |
+
|
| 259 |
+
There are {len(self.positive_pool) + len(self.background_pool) +
|
| 260 |
+
len(self.incomplete_pool)} usable proposals from {len(self.video_infos)} videos.
|
| 261 |
+
{len(self.positive_pool)} positive proposals
|
| 262 |
+
{len(self.incomplete_pool)} incomplete proposals
|
| 263 |
+
{len(self.background_pool)} background proposals
|
| 264 |
+
|
| 265 |
+
Sample config:
|
| 266 |
+
FG/BG/INCOMP: {self.positive_per_video}/{self.background_per_video}/{self.incomplete_per_video} # noqa:E501
|
| 267 |
+
Video Centric: {self.video_centric}
|
| 268 |
+
|
| 269 |
+
Regression Normalization Constants:
|
| 270 |
+
Location: mean {self.reg_norm_consts[0][0]:.05f} std {self.reg_norm_consts[1][0]:.05f} # noqa: E501
|
| 271 |
+
Duration: mean {self.reg_norm_consts[0][1]:.05f} std {self.reg_norm_consts[1][1]:.05f} # noqa: E501
|
| 272 |
+
""")
|
| 273 |
+
# yapf: enable
|
| 274 |
+
else:
|
| 275 |
+
self.logger.info(
|
| 276 |
+
f'SSNDataset: proposal file {self.proposal_file} parsed.')
|
| 277 |
+
|
| 278 |
+
def load_annotations(self):
|
| 279 |
+
"""Load annotation file to get video information."""
|
| 280 |
+
video_infos = []
|
| 281 |
+
if 'normalized_' in self.ann_file:
|
| 282 |
+
self.proposal_file = self.ann_file.replace('normalized_', '')
|
| 283 |
+
if not osp.exists(self.proposal_file):
|
| 284 |
+
raise Exception(f'Please refer to `$MMACTION2/tools/data` to'
|
| 285 |
+
f'denormalize {self.ann_file}.')
|
| 286 |
+
else:
|
| 287 |
+
self.proposal_file = self.ann_file
|
| 288 |
+
proposal_infos = load_localize_proposal_file(self.proposal_file)
|
| 289 |
+
# proposal_info:[video_id, num_frames, gt_list, proposal_list]
|
| 290 |
+
# gt_list member: [label, start_frame, end_frame]
|
| 291 |
+
# proposal_list member: [label, best_iou, overlap_self,
|
| 292 |
+
# start_frame, end_frame]
|
| 293 |
+
for proposal_info in proposal_infos:
|
| 294 |
+
if self.data_prefix is not None:
|
| 295 |
+
frame_dir = osp.join(self.data_prefix, proposal_info[0])
|
| 296 |
+
num_frames = int(proposal_info[1])
|
| 297 |
+
# gts:start, end, num_frames, class_label, tIoU=1
|
| 298 |
+
gts = []
|
| 299 |
+
for x in proposal_info[2]:
|
| 300 |
+
if int(x[2]) > int(x[1]) and int(x[1]) < num_frames:
|
| 301 |
+
ssn_instance = SSNInstance(
|
| 302 |
+
int(x[1]),
|
| 303 |
+
int(x[2]),
|
| 304 |
+
num_frames,
|
| 305 |
+
label=int(x[0]),
|
| 306 |
+
best_iou=1.0)
|
| 307 |
+
gts.append(ssn_instance)
|
| 308 |
+
# proposals:start, end, num_frames, class_label
|
| 309 |
+
# tIoU=best_iou, overlap_self
|
| 310 |
+
proposals = []
|
| 311 |
+
for x in proposal_info[3]:
|
| 312 |
+
if int(x[4]) > int(x[3]) and int(x[3]) < num_frames:
|
| 313 |
+
ssn_instance = SSNInstance(
|
| 314 |
+
int(x[3]),
|
| 315 |
+
int(x[4]),
|
| 316 |
+
num_frames,
|
| 317 |
+
label=int(x[0]),
|
| 318 |
+
best_iou=float(x[1]),
|
| 319 |
+
overlap_self=float(x[2]))
|
| 320 |
+
proposals.append(ssn_instance)
|
| 321 |
+
video_infos.append(
|
| 322 |
+
dict(
|
| 323 |
+
frame_dir=frame_dir,
|
| 324 |
+
video_id=proposal_info[0],
|
| 325 |
+
total_frames=num_frames,
|
| 326 |
+
gts=gts,
|
| 327 |
+
proposals=proposals))
|
| 328 |
+
return video_infos
|
| 329 |
+
|
| 330 |
+
def results_to_detections(self, results, top_k=2000, **kwargs):
|
| 331 |
+
"""Convert prediction results into detections.
|
| 332 |
+
|
| 333 |
+
Args:
|
| 334 |
+
results (list): Prediction results.
|
| 335 |
+
top_k (int): Number of top results. Default: 2000.
|
| 336 |
+
|
| 337 |
+
Returns:
|
| 338 |
+
list: Detection results.
|
| 339 |
+
"""
|
| 340 |
+
num_classes = results[0]['activity_scores'].shape[1] - 1
|
| 341 |
+
detections = [dict() for _ in range(num_classes)]
|
| 342 |
+
|
| 343 |
+
for idx in range(len(self)):
|
| 344 |
+
video_id = self.video_infos[idx]['video_id']
|
| 345 |
+
relative_proposals = results[idx]['relative_proposal_list']
|
| 346 |
+
if len(relative_proposals[0].shape) == 3:
|
| 347 |
+
relative_proposals = np.squeeze(relative_proposals, 0)
|
| 348 |
+
|
| 349 |
+
activity_scores = results[idx]['activity_scores']
|
| 350 |
+
completeness_scores = results[idx]['completeness_scores']
|
| 351 |
+
regression_scores = results[idx]['bbox_preds']
|
| 352 |
+
if regression_scores is None:
|
| 353 |
+
regression_scores = np.zeros(
|
| 354 |
+
(len(relative_proposals), num_classes, 2),
|
| 355 |
+
dtype=np.float32)
|
| 356 |
+
regression_scores = regression_scores.reshape((-1, num_classes, 2))
|
| 357 |
+
|
| 358 |
+
if top_k <= 0:
|
| 359 |
+
combined_scores = (
|
| 360 |
+
softmax(activity_scores[:, 1:], dim=1) *
|
| 361 |
+
np.exp(completeness_scores))
|
| 362 |
+
for i in range(num_classes):
|
| 363 |
+
center_scores = regression_scores[:, i, 0][:, None]
|
| 364 |
+
duration_scores = regression_scores[:, i, 1][:, None]
|
| 365 |
+
detections[i][video_id] = np.concatenate(
|
| 366 |
+
(relative_proposals, combined_scores[:, i][:, None],
|
| 367 |
+
center_scores, duration_scores),
|
| 368 |
+
axis=1)
|
| 369 |
+
else:
|
| 370 |
+
combined_scores = (
|
| 371 |
+
softmax(activity_scores[:, 1:], dim=1) *
|
| 372 |
+
np.exp(completeness_scores))
|
| 373 |
+
keep_idx = np.argsort(combined_scores.ravel())[-top_k:]
|
| 374 |
+
for k in keep_idx:
|
| 375 |
+
class_idx = k % num_classes
|
| 376 |
+
proposal_idx = k // num_classes
|
| 377 |
+
new_item = [
|
| 378 |
+
relative_proposals[proposal_idx, 0],
|
| 379 |
+
relative_proposals[proposal_idx,
|
| 380 |
+
1], combined_scores[proposal_idx,
|
| 381 |
+
class_idx],
|
| 382 |
+
regression_scores[proposal_idx, class_idx,
|
| 383 |
+
0], regression_scores[proposal_idx,
|
| 384 |
+
class_idx, 1]
|
| 385 |
+
]
|
| 386 |
+
if video_id not in detections[class_idx]:
|
| 387 |
+
detections[class_idx][video_id] = np.array([new_item])
|
| 388 |
+
else:
|
| 389 |
+
detections[class_idx][video_id] = np.vstack(
|
| 390 |
+
[detections[class_idx][video_id], new_item])
|
| 391 |
+
|
| 392 |
+
return detections
|
| 393 |
+
|
| 394 |
+
def evaluate(self,
|
| 395 |
+
results,
|
| 396 |
+
metrics='mAP',
|
| 397 |
+
metric_options=dict(mAP=dict(eval_dataset='thumos14')),
|
| 398 |
+
logger=None,
|
| 399 |
+
**deprecated_kwargs):
|
| 400 |
+
"""Evaluation in SSN proposal dataset.
|
| 401 |
+
|
| 402 |
+
Args:
|
| 403 |
+
results (list[dict]): Output results.
|
| 404 |
+
metrics (str | sequence[str]): Metrics to be performed.
|
| 405 |
+
Defaults: 'mAP'.
|
| 406 |
+
metric_options (dict): Dict for metric options. Options are
|
| 407 |
+
``eval_dataset`` for ``mAP``.
|
| 408 |
+
Default: ``dict(mAP=dict(eval_dataset='thumos14'))``.
|
| 409 |
+
logger (logging.Logger | None): Logger for recording.
|
| 410 |
+
Default: None.
|
| 411 |
+
deprecated_kwargs (dict): Used for containing deprecated arguments.
|
| 412 |
+
See 'https://github.com/open-mmlab/mmaction2/pull/286'.
|
| 413 |
+
|
| 414 |
+
Returns:
|
| 415 |
+
dict: Evaluation results for evaluation metrics.
|
| 416 |
+
"""
|
| 417 |
+
# Protect ``metric_options`` since it uses mutable value as default
|
| 418 |
+
metric_options = copy.deepcopy(metric_options)
|
| 419 |
+
|
| 420 |
+
if deprecated_kwargs != {}:
|
| 421 |
+
warnings.warn(
|
| 422 |
+
'Option arguments for metrics has been changed to '
|
| 423 |
+
"`metric_options`, See 'https://github.com/open-mmlab/mmaction2/pull/286' " # noqa: E501
|
| 424 |
+
'for more details')
|
| 425 |
+
metric_options['mAP'] = dict(metric_options['mAP'],
|
| 426 |
+
**deprecated_kwargs)
|
| 427 |
+
|
| 428 |
+
if not isinstance(results, list):
|
| 429 |
+
raise TypeError(f'results must be a list, but got {type(results)}')
|
| 430 |
+
assert len(results) == len(self), (
|
| 431 |
+
f'The length of results is not equal to the dataset len: '
|
| 432 |
+
f'{len(results)} != {len(self)}')
|
| 433 |
+
|
| 434 |
+
metrics = metrics if isinstance(metrics, (list, tuple)) else [metrics]
|
| 435 |
+
allowed_metrics = ['mAP']
|
| 436 |
+
for metric in metrics:
|
| 437 |
+
if metric not in allowed_metrics:
|
| 438 |
+
raise KeyError(f'metric {metric} is not supported')
|
| 439 |
+
|
| 440 |
+
detections = self.results_to_detections(results, **self.evaluater)
|
| 441 |
+
|
| 442 |
+
if self.use_regression:
|
| 443 |
+
self.logger.info('Performing location regression')
|
| 444 |
+
for class_idx, _ in enumerate(detections):
|
| 445 |
+
detections[class_idx] = {
|
| 446 |
+
k: perform_regression(v)
|
| 447 |
+
for k, v in detections[class_idx].items()
|
| 448 |
+
}
|
| 449 |
+
self.logger.info('Regression finished')
|
| 450 |
+
|
| 451 |
+
self.logger.info('Performing NMS')
|
| 452 |
+
for class_idx, _ in enumerate(detections):
|
| 453 |
+
detections[class_idx] = {
|
| 454 |
+
k: temporal_nms(v, self.evaluater.nms)
|
| 455 |
+
for k, v in detections[class_idx].items()
|
| 456 |
+
}
|
| 457 |
+
self.logger.info('NMS finished')
|
| 458 |
+
|
| 459 |
+
# get gts
|
| 460 |
+
all_gts = self.get_all_gts()
|
| 461 |
+
for class_idx, _ in enumerate(detections):
|
| 462 |
+
if class_idx not in all_gts:
|
| 463 |
+
all_gts[class_idx] = dict()
|
| 464 |
+
|
| 465 |
+
# get predictions
|
| 466 |
+
plain_detections = {}
|
| 467 |
+
for class_idx, _ in enumerate(detections):
|
| 468 |
+
detection_list = []
|
| 469 |
+
for video, dets in detections[class_idx].items():
|
| 470 |
+
detection_list.extend([[video, class_idx] + x[:3]
|
| 471 |
+
for x in dets.tolist()])
|
| 472 |
+
plain_detections[class_idx] = detection_list
|
| 473 |
+
|
| 474 |
+
eval_results = OrderedDict()
|
| 475 |
+
for metric in metrics:
|
| 476 |
+
if metric == 'mAP':
|
| 477 |
+
eval_dataset = metric_options.setdefault('mAP', {}).setdefault(
|
| 478 |
+
'eval_dataset', 'thumos14')
|
| 479 |
+
if eval_dataset == 'thumos14':
|
| 480 |
+
iou_range = np.arange(0.1, 1.0, .1)
|
| 481 |
+
ap_values = eval_ap(plain_detections, all_gts, iou_range)
|
| 482 |
+
map_ious = ap_values.mean(axis=0)
|
| 483 |
+
self.logger.info('Evaluation finished')
|
| 484 |
+
|
| 485 |
+
for iou, map_iou in zip(iou_range, map_ious):
|
| 486 |
+
eval_results[f'mAP@{iou:.02f}'] = map_iou
|
| 487 |
+
|
| 488 |
+
return eval_results
|
| 489 |
+
|
| 490 |
+
def construct_proposal_pools(self):
|
| 491 |
+
"""Construct positve proposal pool, incomplete proposal pool and
|
| 492 |
+
background proposal pool of the entire dataset."""
|
| 493 |
+
for video_info in self.video_infos:
|
| 494 |
+
positives = self.get_positives(
|
| 495 |
+
video_info['gts'], video_info['proposals'],
|
| 496 |
+
self.assigner.positive_iou_threshold,
|
| 497 |
+
self.sampler.add_gt_as_proposals)
|
| 498 |
+
self.positive_pool.extend([(video_info['video_id'], proposal)
|
| 499 |
+
for proposal in positives])
|
| 500 |
+
|
| 501 |
+
incompletes, backgrounds = self.get_negatives(
|
| 502 |
+
video_info['proposals'],
|
| 503 |
+
self.assigner.incomplete_iou_threshold,
|
| 504 |
+
self.assigner.background_iou_threshold,
|
| 505 |
+
self.assigner.background_coverage_threshold,
|
| 506 |
+
self.assigner.incomplete_overlap_threshold)
|
| 507 |
+
self.incomplete_pool.extend([(video_info['video_id'], proposal)
|
| 508 |
+
for proposal in incompletes])
|
| 509 |
+
self.background_pool.extend([video_info['video_id'], proposal]
|
| 510 |
+
for proposal in backgrounds)
|
| 511 |
+
|
| 512 |
+
def get_all_gts(self):
|
| 513 |
+
"""Fetch groundtruth instances of the entire dataset."""
|
| 514 |
+
gts = {}
|
| 515 |
+
for video_info in self.video_infos:
|
| 516 |
+
video = video_info['video_id']
|
| 517 |
+
for gt in video_info['gts']:
|
| 518 |
+
class_idx = gt.label - 1
|
| 519 |
+
# gt_info: [relative_start, relative_end]
|
| 520 |
+
gt_info = [
|
| 521 |
+
gt.start_frame / video_info['total_frames'],
|
| 522 |
+
gt.end_frame / video_info['total_frames']
|
| 523 |
+
]
|
| 524 |
+
gts.setdefault(class_idx, {}).setdefault(video,
|
| 525 |
+
[]).append(gt_info)
|
| 526 |
+
|
| 527 |
+
return gts
|
| 528 |
+
|
| 529 |
+
@staticmethod
|
| 530 |
+
def get_positives(gts, proposals, positive_threshold, with_gt=True):
|
| 531 |
+
"""Get positive/foreground proposals.
|
| 532 |
+
|
| 533 |
+
Args:
|
| 534 |
+
gts (list): List of groundtruth instances(:obj:`SSNInstance`).
|
| 535 |
+
proposals (list): List of proposal instances(:obj:`SSNInstance`).
|
| 536 |
+
positive_threshold (float): Minimum threshold of overlap of
|
| 537 |
+
positive/foreground proposals and groundtruths.
|
| 538 |
+
with_gt (bool): Whether to include groundtruth instances in
|
| 539 |
+
positive proposals. Default: True.
|
| 540 |
+
|
| 541 |
+
Returns:
|
| 542 |
+
list[:obj:`SSNInstance`]: (positives), positives is a list
|
| 543 |
+
comprised of positive proposal instances.
|
| 544 |
+
"""
|
| 545 |
+
positives = [
|
| 546 |
+
proposal for proposal in proposals
|
| 547 |
+
if proposal.best_iou > positive_threshold
|
| 548 |
+
]
|
| 549 |
+
|
| 550 |
+
if with_gt:
|
| 551 |
+
positives.extend(gts)
|
| 552 |
+
|
| 553 |
+
for proposal in positives:
|
| 554 |
+
proposal.compute_regression_targets(gts)
|
| 555 |
+
|
| 556 |
+
return positives
|
| 557 |
+
|
| 558 |
+
@staticmethod
|
| 559 |
+
def get_negatives(proposals,
|
| 560 |
+
incomplete_iou_threshold,
|
| 561 |
+
background_iou_threshold,
|
| 562 |
+
background_coverage_threshold=0.01,
|
| 563 |
+
incomplete_overlap_threshold=0.7):
|
| 564 |
+
"""Get negative proposals, including incomplete proposals and
|
| 565 |
+
background proposals.
|
| 566 |
+
|
| 567 |
+
Args:
|
| 568 |
+
proposals (list): List of proposal instances(:obj:`SSNInstance`).
|
| 569 |
+
incomplete_iou_threshold (float): Maximum threshold of overlap
|
| 570 |
+
of incomplete proposals and groundtruths.
|
| 571 |
+
background_iou_threshold (float): Maximum threshold of overlap
|
| 572 |
+
of background proposals and groundtruths.
|
| 573 |
+
background_coverage_threshold (float): Minimum coverage
|
| 574 |
+
of background proposals in video duration. Default: 0.01.
|
| 575 |
+
incomplete_overlap_threshold (float): Minimum percent of incomplete
|
| 576 |
+
proposals' own span contained in a groundtruth instance.
|
| 577 |
+
Default: 0.7.
|
| 578 |
+
|
| 579 |
+
Returns:
|
| 580 |
+
list[:obj:`SSNInstance`]: (incompletes, backgrounds), incompletes
|
| 581 |
+
and backgrounds are lists comprised of incomplete
|
| 582 |
+
proposal instances and background proposal instances.
|
| 583 |
+
"""
|
| 584 |
+
incompletes = []
|
| 585 |
+
backgrounds = []
|
| 586 |
+
|
| 587 |
+
for proposal in proposals:
|
| 588 |
+
if (proposal.best_iou < incomplete_iou_threshold
|
| 589 |
+
and proposal.overlap_self > incomplete_overlap_threshold):
|
| 590 |
+
incompletes.append(proposal)
|
| 591 |
+
elif (proposal.best_iou < background_iou_threshold
|
| 592 |
+
and proposal.coverage > background_coverage_threshold):
|
| 593 |
+
backgrounds.append(proposal)
|
| 594 |
+
|
| 595 |
+
return incompletes, backgrounds
|
| 596 |
+
|
| 597 |
+
def _video_centric_sampling(self, record):
|
| 598 |
+
"""Sample proposals from the this video instance.
|
| 599 |
+
|
| 600 |
+
Args:
|
| 601 |
+
record (dict): Information of the video instance(video_info[idx]).
|
| 602 |
+
key: frame_dir, video_id, total_frames,
|
| 603 |
+
gts: List of groundtruth instances(:obj:`SSNInstance`).
|
| 604 |
+
proposals: List of proposal instances(:obj:`SSNInstance`).
|
| 605 |
+
"""
|
| 606 |
+
positives = self.get_positives(record['gts'], record['proposals'],
|
| 607 |
+
self.assigner.positive_iou_threshold,
|
| 608 |
+
self.sampler.add_gt_as_proposals)
|
| 609 |
+
incompletes, backgrounds = self.get_negatives(
|
| 610 |
+
record['proposals'], self.assigner.incomplete_iou_threshold,
|
| 611 |
+
self.assigner.background_iou_threshold,
|
| 612 |
+
self.assigner.background_coverage_threshold,
|
| 613 |
+
self.assigner.incomplete_overlap_threshold)
|
| 614 |
+
|
| 615 |
+
def sample_video_proposals(proposal_type, video_id, video_pool,
|
| 616 |
+
num_requested_proposals, dataset_pool):
|
| 617 |
+
"""This method will sample proposals from the this video pool. If
|
| 618 |
+
the video pool is empty, it will fetch from the dataset pool
|
| 619 |
+
(collect proposal of the entire dataset).
|
| 620 |
+
|
| 621 |
+
Args:
|
| 622 |
+
proposal_type (int): Type id of proposal.
|
| 623 |
+
Positive/Foreground: 0
|
| 624 |
+
Negative:
|
| 625 |
+
Incomplete: 1
|
| 626 |
+
Background: 2
|
| 627 |
+
video_id (str): Name of the video.
|
| 628 |
+
video_pool (list): Pool comprised of proposals in this video.
|
| 629 |
+
num_requested_proposals (int): Number of proposals
|
| 630 |
+
to be sampled.
|
| 631 |
+
dataset_pool (list): Proposals of the entire dataset.
|
| 632 |
+
|
| 633 |
+
Returns:
|
| 634 |
+
list[(str, :obj:`SSNInstance`), int]:
|
| 635 |
+
video_id (str): Name of the video.
|
| 636 |
+
:obj:`SSNInstance`: Instance of class SSNInstance.
|
| 637 |
+
proposal_type (int): Type of proposal.
|
| 638 |
+
"""
|
| 639 |
+
|
| 640 |
+
if len(video_pool) == 0:
|
| 641 |
+
idx = np.random.choice(
|
| 642 |
+
len(dataset_pool), num_requested_proposals, replace=False)
|
| 643 |
+
return [(dataset_pool[x], proposal_type) for x in idx]
|
| 644 |
+
|
| 645 |
+
replicate = len(video_pool) < num_requested_proposals
|
| 646 |
+
idx = np.random.choice(
|
| 647 |
+
len(video_pool), num_requested_proposals, replace=replicate)
|
| 648 |
+
return [((video_id, video_pool[x]), proposal_type) for x in idx]
|
| 649 |
+
|
| 650 |
+
out_proposals = []
|
| 651 |
+
out_proposals.extend(
|
| 652 |
+
sample_video_proposals(0, record['video_id'], positives,
|
| 653 |
+
self.positive_per_video,
|
| 654 |
+
self.positive_pool))
|
| 655 |
+
out_proposals.extend(
|
| 656 |
+
sample_video_proposals(1, record['video_id'], incompletes,
|
| 657 |
+
self.incomplete_per_video,
|
| 658 |
+
self.incomplete_pool))
|
| 659 |
+
out_proposals.extend(
|
| 660 |
+
sample_video_proposals(2, record['video_id'], backgrounds,
|
| 661 |
+
self.background_per_video,
|
| 662 |
+
self.background_pool))
|
| 663 |
+
|
| 664 |
+
return out_proposals
|
| 665 |
+
|
| 666 |
+
def _random_sampling(self):
|
| 667 |
+
"""Randomly sample proposals from the entire dataset."""
|
| 668 |
+
out_proposals = []
|
| 669 |
+
|
| 670 |
+
positive_idx = np.random.choice(
|
| 671 |
+
len(self.positive_pool),
|
| 672 |
+
self.positive_per_video,
|
| 673 |
+
replace=len(self.positive_pool) < self.positive_per_video)
|
| 674 |
+
out_proposals.extend([(self.positive_pool[x], 0)
|
| 675 |
+
for x in positive_idx])
|
| 676 |
+
incomplete_idx = np.random.choice(
|
| 677 |
+
len(self.incomplete_pool),
|
| 678 |
+
self.incomplete_per_video,
|
| 679 |
+
replace=len(self.incomplete_pool) < self.incomplete_per_video)
|
| 680 |
+
out_proposals.extend([(self.incomplete_pool[x], 1)
|
| 681 |
+
for x in incomplete_idx])
|
| 682 |
+
background_idx = np.random.choice(
|
| 683 |
+
len(self.background_pool),
|
| 684 |
+
self.background_per_video,
|
| 685 |
+
replace=len(self.background_pool) < self.background_per_video)
|
| 686 |
+
out_proposals.extend([(self.background_pool[x], 2)
|
| 687 |
+
for x in background_idx])
|
| 688 |
+
|
| 689 |
+
return out_proposals
|
| 690 |
+
|
| 691 |
+
def _get_stage(self, proposal, num_frames):
|
| 692 |
+
"""Fetch the scale factor of starting and ending stage and get the
|
| 693 |
+
stage split.
|
| 694 |
+
|
| 695 |
+
Args:
|
| 696 |
+
proposal (:obj:`SSNInstance`): Proposal instance.
|
| 697 |
+
num_frames (int): Total frames of the video.
|
| 698 |
+
|
| 699 |
+
Returns:
|
| 700 |
+
tuple[float, float, list]: (starting_scale_factor,
|
| 701 |
+
ending_scale_factor, stage_split), starting_scale_factor is
|
| 702 |
+
the ratio of the effective sampling length to augment length
|
| 703 |
+
in starting stage, ending_scale_factor is the ratio of the
|
| 704 |
+
effective sampling length to augment length in ending stage,
|
| 705 |
+
stage_split is ending segment id of starting, course and
|
| 706 |
+
ending stage.
|
| 707 |
+
"""
|
| 708 |
+
# proposal interval: [start_frame, end_frame)
|
| 709 |
+
start_frame = proposal.start_frame
|
| 710 |
+
end_frame = proposal.end_frame
|
| 711 |
+
ori_clip_len = self.clip_len * self.frame_interval
|
| 712 |
+
|
| 713 |
+
duration = end_frame - start_frame
|
| 714 |
+
assert duration != 0
|
| 715 |
+
|
| 716 |
+
valid_starting = max(0,
|
| 717 |
+
start_frame - int(duration * self.aug_ratio[0]))
|
| 718 |
+
valid_ending = min(num_frames - ori_clip_len + 1,
|
| 719 |
+
end_frame - 1 + int(duration * self.aug_ratio[1]))
|
| 720 |
+
|
| 721 |
+
valid_starting_length = start_frame - valid_starting - ori_clip_len
|
| 722 |
+
valid_ending_length = (valid_ending - end_frame + 1) - ori_clip_len
|
| 723 |
+
|
| 724 |
+
starting_scale_factor = ((valid_starting_length + ori_clip_len + 1) /
|
| 725 |
+
(duration * self.aug_ratio[0]))
|
| 726 |
+
ending_scale_factor = (valid_ending_length + ori_clip_len + 1) / (
|
| 727 |
+
duration * self.aug_ratio[1])
|
| 728 |
+
|
| 729 |
+
aug_start, aug_end = self.aug_segments
|
| 730 |
+
stage_split = [
|
| 731 |
+
aug_start, aug_start + self.body_segments,
|
| 732 |
+
aug_start + self.body_segments + aug_end
|
| 733 |
+
]
|
| 734 |
+
|
| 735 |
+
return starting_scale_factor, ending_scale_factor, stage_split
|
| 736 |
+
|
| 737 |
+
def _compute_reg_normalize_constants(self):
|
| 738 |
+
"""Compute regression target normalized constants."""
|
| 739 |
+
if self.verbose:
|
| 740 |
+
self.logger.info('Compute regression target normalized constants')
|
| 741 |
+
targets = []
|
| 742 |
+
for video_info in self.video_infos:
|
| 743 |
+
positives = self.get_positives(
|
| 744 |
+
video_info['gts'], video_info['proposals'],
|
| 745 |
+
self.assigner.positive_iou_threshold, False)
|
| 746 |
+
for positive in positives:
|
| 747 |
+
targets.append(list(positive.regression_targets))
|
| 748 |
+
|
| 749 |
+
return np.array((np.mean(targets, axis=0), np.std(targets, axis=0)))
|
| 750 |
+
|
| 751 |
+
def prepare_train_frames(self, idx):
|
| 752 |
+
"""Prepare the frames for training given the index."""
|
| 753 |
+
results = copy.deepcopy(self.video_infos[idx])
|
| 754 |
+
results['filename_tmpl'] = self.filename_tmpl
|
| 755 |
+
results['modality'] = self.modality
|
| 756 |
+
results['start_index'] = self.start_index
|
| 757 |
+
|
| 758 |
+
if self.video_centric:
|
| 759 |
+
# yapf: disable
|
| 760 |
+
results['out_proposals'] = self._video_centric_sampling(self.video_infos[idx]) # noqa: E501
|
| 761 |
+
# yapf: enable
|
| 762 |
+
else:
|
| 763 |
+
results['out_proposals'] = self._random_sampling()
|
| 764 |
+
|
| 765 |
+
out_proposal_scale_factor = []
|
| 766 |
+
out_proposal_type = []
|
| 767 |
+
out_proposal_labels = []
|
| 768 |
+
out_proposal_reg_targets = []
|
| 769 |
+
|
| 770 |
+
for _, proposal in enumerate(results['out_proposals']):
|
| 771 |
+
# proposal: [(video_id, SSNInstance), proposal_type]
|
| 772 |
+
num_frames = proposal[0][1].num_video_frames
|
| 773 |
+
|
| 774 |
+
(starting_scale_factor, ending_scale_factor,
|
| 775 |
+
_) = self._get_stage(proposal[0][1], num_frames)
|
| 776 |
+
|
| 777 |
+
# proposal[1]: Type id of proposal.
|
| 778 |
+
# Positive/Foreground: 0
|
| 779 |
+
# Negative:
|
| 780 |
+
# Incomplete: 1
|
| 781 |
+
# Background: 2
|
| 782 |
+
|
| 783 |
+
# Positivte/Foreground proposal
|
| 784 |
+
if proposal[1] == 0:
|
| 785 |
+
label = proposal[0][1].label
|
| 786 |
+
# Incomplete proposal
|
| 787 |
+
elif proposal[1] == 1:
|
| 788 |
+
label = proposal[0][1].label
|
| 789 |
+
# Background proposal
|
| 790 |
+
elif proposal[1] == 2:
|
| 791 |
+
label = 0
|
| 792 |
+
else:
|
| 793 |
+
raise ValueError(f'Proposal type should be 0, 1, or 2,'
|
| 794 |
+
f'but got {proposal[1]}')
|
| 795 |
+
out_proposal_scale_factor.append(
|
| 796 |
+
[starting_scale_factor, ending_scale_factor])
|
| 797 |
+
if not isinstance(label, int):
|
| 798 |
+
raise TypeError(f'proposal_label must be an int,'
|
| 799 |
+
f'but got {type(label)}')
|
| 800 |
+
out_proposal_labels.append(label)
|
| 801 |
+
out_proposal_type.append(proposal[1])
|
| 802 |
+
|
| 803 |
+
reg_targets = proposal[0][1].regression_targets
|
| 804 |
+
if proposal[1] == 0:
|
| 805 |
+
# Normalize regression targets of positive proposals.
|
| 806 |
+
reg_targets = ((reg_targets[0] - self.reg_norm_consts[0][0]) /
|
| 807 |
+
self.reg_norm_consts[1][0],
|
| 808 |
+
(reg_targets[1] - self.reg_norm_consts[0][1]) /
|
| 809 |
+
self.reg_norm_consts[1][1])
|
| 810 |
+
out_proposal_reg_targets.append(reg_targets)
|
| 811 |
+
|
| 812 |
+
results['reg_targets'] = np.array(
|
| 813 |
+
out_proposal_reg_targets, dtype=np.float32)
|
| 814 |
+
results['proposal_scale_factor'] = np.array(
|
| 815 |
+
out_proposal_scale_factor, dtype=np.float32)
|
| 816 |
+
results['proposal_labels'] = np.array(out_proposal_labels)
|
| 817 |
+
results['proposal_type'] = np.array(out_proposal_type)
|
| 818 |
+
|
| 819 |
+
return self.pipeline(results)
|
| 820 |
+
|
| 821 |
+
def prepare_test_frames(self, idx):
|
| 822 |
+
"""Prepare the frames for testing given the index."""
|
| 823 |
+
results = copy.deepcopy(self.video_infos[idx])
|
| 824 |
+
results['filename_tmpl'] = self.filename_tmpl
|
| 825 |
+
results['modality'] = self.modality
|
| 826 |
+
results['start_index'] = self.start_index
|
| 827 |
+
|
| 828 |
+
proposals = results['proposals']
|
| 829 |
+
num_frames = results['total_frames']
|
| 830 |
+
ori_clip_len = self.clip_len * self.frame_interval
|
| 831 |
+
frame_ticks = np.arange(
|
| 832 |
+
0, num_frames - ori_clip_len, self.test_interval, dtype=int) + 1
|
| 833 |
+
|
| 834 |
+
num_sampled_frames = len(frame_ticks)
|
| 835 |
+
|
| 836 |
+
if len(proposals) == 0:
|
| 837 |
+
proposals.append(SSNInstance(0, num_frames - 1, num_frames))
|
| 838 |
+
|
| 839 |
+
relative_proposal_list = []
|
| 840 |
+
proposal_tick_list = []
|
| 841 |
+
scale_factor_list = []
|
| 842 |
+
|
| 843 |
+
for proposal in proposals:
|
| 844 |
+
relative_proposal = (proposal.start_frame / num_frames,
|
| 845 |
+
proposal.end_frame / num_frames)
|
| 846 |
+
relative_duration = relative_proposal[1] - relative_proposal[0]
|
| 847 |
+
relative_starting_duration = relative_duration * self.aug_ratio[0]
|
| 848 |
+
relative_ending_duration = relative_duration * self.aug_ratio[1]
|
| 849 |
+
relative_starting = (
|
| 850 |
+
relative_proposal[0] - relative_starting_duration)
|
| 851 |
+
relative_ending = relative_proposal[1] + relative_ending_duration
|
| 852 |
+
|
| 853 |
+
real_relative_starting = max(0.0, relative_starting)
|
| 854 |
+
real_relative_ending = min(1.0, relative_ending)
|
| 855 |
+
|
| 856 |
+
starting_scale_factor = (
|
| 857 |
+
(relative_proposal[0] - real_relative_starting) /
|
| 858 |
+
relative_starting_duration)
|
| 859 |
+
ending_scale_factor = (
|
| 860 |
+
(real_relative_ending - relative_proposal[1]) /
|
| 861 |
+
relative_ending_duration)
|
| 862 |
+
|
| 863 |
+
proposal_ranges = (real_relative_starting, *relative_proposal,
|
| 864 |
+
real_relative_ending)
|
| 865 |
+
proposal_ticks = (np.array(proposal_ranges) *
|
| 866 |
+
num_sampled_frames).astype(np.int32)
|
| 867 |
+
|
| 868 |
+
relative_proposal_list.append(relative_proposal)
|
| 869 |
+
proposal_tick_list.append(proposal_ticks)
|
| 870 |
+
scale_factor_list.append(
|
| 871 |
+
(starting_scale_factor, ending_scale_factor))
|
| 872 |
+
|
| 873 |
+
results['relative_proposal_list'] = np.array(
|
| 874 |
+
relative_proposal_list, dtype=np.float32)
|
| 875 |
+
results['scale_factor_list'] = np.array(
|
| 876 |
+
scale_factor_list, dtype=np.float32)
|
| 877 |
+
results['proposal_tick_list'] = np.array(
|
| 878 |
+
proposal_tick_list, dtype=np.int32)
|
| 879 |
+
results['reg_norm_consts'] = self.reg_norm_consts
|
| 880 |
+
|
| 881 |
+
return self.pipeline(results)
|
TransRAC/mmaction/datasets/video_dataset.py
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os.path as osp
|
| 2 |
+
|
| 3 |
+
from .base import BaseDataset
|
| 4 |
+
from .builder import DATASETS
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
@DATASETS.register_module()
|
| 8 |
+
class VideoDataset(BaseDataset):
|
| 9 |
+
"""Video dataset for action recognition.
|
| 10 |
+
|
| 11 |
+
The dataset loads raw videos and apply specified transforms to return a
|
| 12 |
+
dict containing the frame tensors and other information.
|
| 13 |
+
|
| 14 |
+
The ann_file is a text file with multiple lines, and each line indicates
|
| 15 |
+
a sample video with the filepath and label, which are split with a
|
| 16 |
+
whitespace. Example of a annotation file:
|
| 17 |
+
|
| 18 |
+
.. code-block:: txt
|
| 19 |
+
|
| 20 |
+
some/path/000.mp4 1
|
| 21 |
+
some/path/001.mp4 1
|
| 22 |
+
some/path/002.mp4 2
|
| 23 |
+
some/path/003.mp4 2
|
| 24 |
+
some/path/004.mp4 3
|
| 25 |
+
some/path/005.mp4 3
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
Args:
|
| 29 |
+
ann_file (str): Path to the annotation file.
|
| 30 |
+
pipeline (list[dict | callable]): A sequence of data transforms.
|
| 31 |
+
start_index (int): Specify a start index for frames in consideration of
|
| 32 |
+
different filename format. However, when taking videos as input,
|
| 33 |
+
it should be set to 0, since frames loaded from videos count
|
| 34 |
+
from 0. Default: 0.
|
| 35 |
+
**kwargs: Keyword arguments for ``BaseDataset``.
|
| 36 |
+
"""
|
| 37 |
+
|
| 38 |
+
def __init__(self, ann_file, pipeline, start_index=0, **kwargs):
|
| 39 |
+
super().__init__(ann_file, pipeline, start_index=start_index, **kwargs)
|
| 40 |
+
|
| 41 |
+
def load_annotations(self):
|
| 42 |
+
"""Load annotation file to get video information."""
|
| 43 |
+
if self.ann_file.endswith('.json'):
|
| 44 |
+
return self.load_json_annotations()
|
| 45 |
+
|
| 46 |
+
video_infos = []
|
| 47 |
+
with open(self.ann_file, 'r') as fin:
|
| 48 |
+
for line in fin:
|
| 49 |
+
line_split = line.strip().split()
|
| 50 |
+
if self.multi_class:
|
| 51 |
+
assert self.num_classes is not None
|
| 52 |
+
filename, label = line_split[0], line_split[1:]
|
| 53 |
+
label = list(map(int, label))
|
| 54 |
+
else:
|
| 55 |
+
filename, label = line_split
|
| 56 |
+
label = int(label)
|
| 57 |
+
if self.data_prefix is not None:
|
| 58 |
+
filename = osp.join(self.data_prefix, filename)
|
| 59 |
+
video_infos.append(dict(filename=filename, label=label))
|
| 60 |
+
return video_infos
|
TransRAC/mmaction/localization/__init__.py
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .bsn_utils import generate_bsp_feature, generate_candidate_proposals
|
| 2 |
+
from .proposal_utils import soft_nms, temporal_iop, temporal_iou
|
| 3 |
+
from .ssn_utils import (eval_ap, load_localize_proposal_file,
|
| 4 |
+
perform_regression, temporal_nms)
|
| 5 |
+
|
| 6 |
+
__all__ = [
|
| 7 |
+
'generate_candidate_proposals', 'generate_bsp_feature', 'temporal_iop',
|
| 8 |
+
'temporal_iou', 'soft_nms', 'load_localize_proposal_file',
|
| 9 |
+
'perform_regression', 'temporal_nms', 'eval_ap'
|
| 10 |
+
]
|
TransRAC/mmaction/localization/bsn_utils.py
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os.path as osp
|
| 2 |
+
|
| 3 |
+
import numpy as np
|
| 4 |
+
|
| 5 |
+
from .proposal_utils import temporal_iop, temporal_iou
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
def generate_candidate_proposals(video_list,
|
| 9 |
+
video_infos,
|
| 10 |
+
tem_results_dir,
|
| 11 |
+
temporal_scale,
|
| 12 |
+
peak_threshold,
|
| 13 |
+
tem_results_ext='.csv',
|
| 14 |
+
result_dict=None):
|
| 15 |
+
"""Generate Candidate Proposals with given temporal evalutation results.
|
| 16 |
+
Each proposal file will contain:
|
| 17 |
+
'tmin,tmax,tmin_score,tmax_score,score,match_iou,match_ioa'.
|
| 18 |
+
|
| 19 |
+
Args:
|
| 20 |
+
video_list (list[int]): List of video indexs to generate proposals.
|
| 21 |
+
video_infos (list[dict]): List of video_info dict that contains
|
| 22 |
+
'video_name', 'duration_frame', 'duration_second',
|
| 23 |
+
'feature_frame', and 'annotations'.
|
| 24 |
+
tem_results_dir (str): Directory to load temporal evaluation
|
| 25 |
+
results.
|
| 26 |
+
temporal_scale (int): The number (scale) on temporal axis.
|
| 27 |
+
peak_threshold (float): The threshold for proposal generation.
|
| 28 |
+
tem_results_ext (str): File extension for temporal evaluation
|
| 29 |
+
model output. Default: '.csv'.
|
| 30 |
+
result_dict (dict | None): The dict to save the results. Default: None.
|
| 31 |
+
|
| 32 |
+
Returns:
|
| 33 |
+
dict: A dict contains video_name as keys and proposal list as value.
|
| 34 |
+
If result_dict is not None, save the results to it.
|
| 35 |
+
"""
|
| 36 |
+
if tem_results_ext != '.csv':
|
| 37 |
+
raise NotImplementedError('Only support csv format now.')
|
| 38 |
+
|
| 39 |
+
tscale = temporal_scale
|
| 40 |
+
tgap = 1. / tscale
|
| 41 |
+
proposal_dict = {}
|
| 42 |
+
for video_index in video_list:
|
| 43 |
+
video_name = video_infos[video_index]['video_name']
|
| 44 |
+
tem_path = osp.join(tem_results_dir, video_name + tem_results_ext)
|
| 45 |
+
tem_results = np.loadtxt(
|
| 46 |
+
tem_path, dtype=np.float32, delimiter=',', skiprows=1)
|
| 47 |
+
start_scores = tem_results[:, 1]
|
| 48 |
+
end_scores = tem_results[:, 2]
|
| 49 |
+
|
| 50 |
+
max_start = max(start_scores)
|
| 51 |
+
max_end = max(end_scores)
|
| 52 |
+
|
| 53 |
+
start_bins = np.zeros(len(start_scores))
|
| 54 |
+
start_bins[[0, -1]] = 1
|
| 55 |
+
end_bins = np.zeros(len(end_scores))
|
| 56 |
+
end_bins[[0, -1]] = 1
|
| 57 |
+
for idx in range(1, tscale - 1):
|
| 58 |
+
if start_scores[idx] > start_scores[
|
| 59 |
+
idx + 1] and start_scores[idx] > start_scores[idx - 1]:
|
| 60 |
+
start_bins[idx] = 1
|
| 61 |
+
elif start_scores[idx] > (peak_threshold * max_start):
|
| 62 |
+
start_bins[idx] = 1
|
| 63 |
+
if end_scores[idx] > end_scores[
|
| 64 |
+
idx + 1] and end_scores[idx] > end_scores[idx - 1]:
|
| 65 |
+
end_bins[idx] = 1
|
| 66 |
+
elif end_scores[idx] > (peak_threshold * max_end):
|
| 67 |
+
end_bins[idx] = 1
|
| 68 |
+
|
| 69 |
+
tmin_list = []
|
| 70 |
+
tmin_score_list = []
|
| 71 |
+
tmax_list = []
|
| 72 |
+
tmax_score_list = []
|
| 73 |
+
for idx in range(tscale):
|
| 74 |
+
if start_bins[idx] == 1:
|
| 75 |
+
tmin_list.append(tgap / 2 + tgap * idx)
|
| 76 |
+
tmin_score_list.append(start_scores[idx])
|
| 77 |
+
if end_bins[idx] == 1:
|
| 78 |
+
tmax_list.append(tgap / 2 + tgap * idx)
|
| 79 |
+
tmax_score_list.append(end_scores[idx])
|
| 80 |
+
|
| 81 |
+
new_props = []
|
| 82 |
+
for tmax, tmax_score in zip(tmax_list, tmax_score_list):
|
| 83 |
+
for tmin, tmin_score in zip(tmin_list, tmin_score_list):
|
| 84 |
+
if tmin >= tmax:
|
| 85 |
+
break
|
| 86 |
+
new_props.append([tmin, tmax, tmin_score, tmax_score])
|
| 87 |
+
|
| 88 |
+
new_props = np.stack(new_props)
|
| 89 |
+
|
| 90 |
+
score = (new_props[:, 2] * new_props[:, 3]).reshape(-1, 1)
|
| 91 |
+
new_props = np.concatenate((new_props, score), axis=1)
|
| 92 |
+
|
| 93 |
+
new_props = new_props[new_props[:, -1].argsort()[::-1]]
|
| 94 |
+
video_info = video_infos[video_index]
|
| 95 |
+
video_frame = video_info['duration_frame']
|
| 96 |
+
video_second = video_info['duration_second']
|
| 97 |
+
feature_frame = video_info['feature_frame']
|
| 98 |
+
corrected_second = float(feature_frame) / video_frame * video_second
|
| 99 |
+
|
| 100 |
+
gt_tmins = []
|
| 101 |
+
gt_tmaxs = []
|
| 102 |
+
for annotations in video_info['annotations']:
|
| 103 |
+
gt_tmins.append(annotations['segment'][0] / corrected_second)
|
| 104 |
+
gt_tmaxs.append(annotations['segment'][1] / corrected_second)
|
| 105 |
+
|
| 106 |
+
new_iou_list = []
|
| 107 |
+
new_ioa_list = []
|
| 108 |
+
for new_prop in new_props:
|
| 109 |
+
new_iou = max(
|
| 110 |
+
temporal_iou(new_prop[0], new_prop[1], gt_tmins, gt_tmaxs))
|
| 111 |
+
new_ioa = max(
|
| 112 |
+
temporal_iop(new_prop[0], new_prop[1], gt_tmins, gt_tmaxs))
|
| 113 |
+
new_iou_list.append(new_iou)
|
| 114 |
+
new_ioa_list.append(new_ioa)
|
| 115 |
+
|
| 116 |
+
new_iou_list = np.array(new_iou_list).reshape(-1, 1)
|
| 117 |
+
new_ioa_list = np.array(new_ioa_list).reshape(-1, 1)
|
| 118 |
+
new_props = np.concatenate((new_props, new_iou_list), axis=1)
|
| 119 |
+
new_props = np.concatenate((new_props, new_ioa_list), axis=1)
|
| 120 |
+
proposal_dict[video_name] = new_props
|
| 121 |
+
if result_dict is not None:
|
| 122 |
+
result_dict[video_name] = new_props
|
| 123 |
+
return proposal_dict
|
| 124 |
+
|
| 125 |
+
|
| 126 |
+
def generate_bsp_feature(video_list,
|
| 127 |
+
video_infos,
|
| 128 |
+
tem_results_dir,
|
| 129 |
+
pgm_proposals_dir,
|
| 130 |
+
top_k=1000,
|
| 131 |
+
bsp_boundary_ratio=0.2,
|
| 132 |
+
num_sample_start=8,
|
| 133 |
+
num_sample_end=8,
|
| 134 |
+
num_sample_action=16,
|
| 135 |
+
num_sample_interp=3,
|
| 136 |
+
tem_results_ext='.csv',
|
| 137 |
+
pgm_proposal_ext='.csv',
|
| 138 |
+
result_dict=None):
|
| 139 |
+
"""Generate Boundary-Sensitive Proposal Feature with given proposals.
|
| 140 |
+
|
| 141 |
+
Args:
|
| 142 |
+
video_list (list[int]): List of video indexs to generate bsp_feature.
|
| 143 |
+
video_infos (list[dict]): List of video_info dict that contains
|
| 144 |
+
'video_name'.
|
| 145 |
+
tem_results_dir (str): Directory to load temporal evaluation
|
| 146 |
+
results.
|
| 147 |
+
pgm_proposals_dir (str): Directory to load proposals.
|
| 148 |
+
top_k (int): Number of proposals to be considered. Default: 1000
|
| 149 |
+
bsp_boundary_ratio (float): Ratio for proposal boundary
|
| 150 |
+
(start/end). Default: 0.2.
|
| 151 |
+
num_sample_start (int): Num of samples for actionness in
|
| 152 |
+
start region. Default: 8.
|
| 153 |
+
num_sample_end (int): Num of samples for actionness in end region.
|
| 154 |
+
Default: 8.
|
| 155 |
+
num_sample_action (int): Num of samples for actionness in center
|
| 156 |
+
region. Default: 16.
|
| 157 |
+
num_sample_interp (int): Num of samples for interpolation for
|
| 158 |
+
each sample point. Default: 3.
|
| 159 |
+
tem_results_ext (str): File extension for temporal evaluation
|
| 160 |
+
model output. Default: '.csv'.
|
| 161 |
+
pgm_proposal_ext (str): File extension for proposals. Default: '.csv'.
|
| 162 |
+
result_dict (dict | None): The dict to save the results. Default: None.
|
| 163 |
+
|
| 164 |
+
Returns:
|
| 165 |
+
bsp_feature_dict (dict): A dict contains video_name as keys and
|
| 166 |
+
bsp_feature as value. If result_dict is not None, save the
|
| 167 |
+
results to it.
|
| 168 |
+
"""
|
| 169 |
+
if tem_results_ext != '.csv' or pgm_proposal_ext != '.csv':
|
| 170 |
+
raise NotImplementedError('Only support csv format now.')
|
| 171 |
+
|
| 172 |
+
bsp_feature_dict = {}
|
| 173 |
+
for video_index in video_list:
|
| 174 |
+
video_name = video_infos[video_index]['video_name']
|
| 175 |
+
|
| 176 |
+
# Load temporal evaluation results
|
| 177 |
+
tem_path = osp.join(tem_results_dir, video_name + tem_results_ext)
|
| 178 |
+
tem_results = np.loadtxt(
|
| 179 |
+
tem_path, dtype=np.float32, delimiter=',', skiprows=1)
|
| 180 |
+
score_action = tem_results[:, 0]
|
| 181 |
+
seg_tmins = tem_results[:, 3]
|
| 182 |
+
seg_tmaxs = tem_results[:, 4]
|
| 183 |
+
video_scale = len(tem_results)
|
| 184 |
+
video_gap = seg_tmaxs[0] - seg_tmins[0]
|
| 185 |
+
video_extend = int(video_scale / 4 + 10)
|
| 186 |
+
|
| 187 |
+
# Load proposals results
|
| 188 |
+
proposal_path = osp.join(pgm_proposals_dir,
|
| 189 |
+
video_name + pgm_proposal_ext)
|
| 190 |
+
pgm_proposals = np.loadtxt(
|
| 191 |
+
proposal_path, dtype=np.float32, delimiter=',', skiprows=1)
|
| 192 |
+
pgm_proposals = pgm_proposals[:top_k]
|
| 193 |
+
|
| 194 |
+
# Generate temporal sample points
|
| 195 |
+
boundary_zeros = np.zeros([video_extend])
|
| 196 |
+
score_action = np.concatenate(
|
| 197 |
+
(boundary_zeros, score_action, boundary_zeros))
|
| 198 |
+
begin_tp = []
|
| 199 |
+
middle_tp = []
|
| 200 |
+
end_tp = []
|
| 201 |
+
for i in range(video_extend):
|
| 202 |
+
begin_tp.append(-video_gap / 2 -
|
| 203 |
+
(video_extend - 1 - i) * video_gap)
|
| 204 |
+
end_tp.append(video_gap / 2 + seg_tmaxs[-1] + i * video_gap)
|
| 205 |
+
for i in range(video_scale):
|
| 206 |
+
middle_tp.append(video_gap / 2 + i * video_gap)
|
| 207 |
+
t_points = begin_tp + middle_tp + end_tp
|
| 208 |
+
|
| 209 |
+
bsp_feature = []
|
| 210 |
+
for pgm_proposal in pgm_proposals:
|
| 211 |
+
tmin = pgm_proposal[0]
|
| 212 |
+
tmax = pgm_proposal[1]
|
| 213 |
+
|
| 214 |
+
tlen = tmax - tmin
|
| 215 |
+
# Temporal range for start
|
| 216 |
+
tmin_0 = tmin - tlen * bsp_boundary_ratio
|
| 217 |
+
tmin_1 = tmin + tlen * bsp_boundary_ratio
|
| 218 |
+
# Temporal range for end
|
| 219 |
+
tmax_0 = tmax - tlen * bsp_boundary_ratio
|
| 220 |
+
tmax_1 = tmax + tlen * bsp_boundary_ratio
|
| 221 |
+
|
| 222 |
+
# Generate features at start boundary
|
| 223 |
+
tlen_start = (tmin_1 - tmin_0) / (num_sample_start - 1)
|
| 224 |
+
tlen_start_sample = tlen_start / num_sample_interp
|
| 225 |
+
t_new = [
|
| 226 |
+
tmin_0 - tlen_start / 2 + tlen_start_sample * i
|
| 227 |
+
for i in range(num_sample_start * num_sample_interp + 1)
|
| 228 |
+
]
|
| 229 |
+
y_new_start_action = np.interp(t_new, t_points, score_action)
|
| 230 |
+
y_new_start = [
|
| 231 |
+
np.mean(y_new_start_action[i * num_sample_interp:(i + 1) *
|
| 232 |
+
num_sample_interp + 1])
|
| 233 |
+
for i in range(num_sample_start)
|
| 234 |
+
]
|
| 235 |
+
# Generate features at end boundary
|
| 236 |
+
tlen_end = (tmax_1 - tmax_0) / (num_sample_end - 1)
|
| 237 |
+
tlen_end_sample = tlen_end / num_sample_interp
|
| 238 |
+
t_new = [
|
| 239 |
+
tmax_0 - tlen_end / 2 + tlen_end_sample * i
|
| 240 |
+
for i in range(num_sample_end * num_sample_interp + 1)
|
| 241 |
+
]
|
| 242 |
+
y_new_end_action = np.interp(t_new, t_points, score_action)
|
| 243 |
+
y_new_end = [
|
| 244 |
+
np.mean(y_new_end_action[i * num_sample_interp:(i + 1) *
|
| 245 |
+
num_sample_interp + 1])
|
| 246 |
+
for i in range(num_sample_end)
|
| 247 |
+
]
|
| 248 |
+
# Generate features for action
|
| 249 |
+
tlen_action = (tmax - tmin) / (num_sample_action - 1)
|
| 250 |
+
tlen_action_sample = tlen_action / num_sample_interp
|
| 251 |
+
t_new = [
|
| 252 |
+
tmin - tlen_action / 2 + tlen_action_sample * i
|
| 253 |
+
for i in range(num_sample_action * num_sample_interp + 1)
|
| 254 |
+
]
|
| 255 |
+
y_new_action = np.interp(t_new, t_points, score_action)
|
| 256 |
+
y_new_action = [
|
| 257 |
+
np.mean(y_new_action[i * num_sample_interp:(i + 1) *
|
| 258 |
+
num_sample_interp + 1])
|
| 259 |
+
for i in range(num_sample_action)
|
| 260 |
+
]
|
| 261 |
+
feature = np.concatenate([y_new_action, y_new_start, y_new_end])
|
| 262 |
+
bsp_feature.append(feature)
|
| 263 |
+
bsp_feature = np.array(bsp_feature)
|
| 264 |
+
bsp_feature_dict[video_name] = bsp_feature
|
| 265 |
+
if result_dict is not None:
|
| 266 |
+
result_dict[video_name] = bsp_feature
|
| 267 |
+
return bsp_feature_dict
|
TransRAC/mmaction/localization/proposal_utils.py
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
def temporal_iou(proposal_min, proposal_max, gt_min, gt_max):
|
| 5 |
+
"""Compute IoU score between a groundtruth bbox and the proposals.
|
| 6 |
+
|
| 7 |
+
Args:
|
| 8 |
+
proposal_min (list[float]): List of temporal anchor min.
|
| 9 |
+
proposal_max (list[float]): List of temporal anchor max.
|
| 10 |
+
gt_min (float): Groundtruth temporal box min.
|
| 11 |
+
gt_max (float): Groundtruth temporal box max.
|
| 12 |
+
|
| 13 |
+
Returns:
|
| 14 |
+
list[float]: List of iou scores.
|
| 15 |
+
"""
|
| 16 |
+
len_anchors = proposal_max - proposal_min
|
| 17 |
+
int_tmin = np.maximum(proposal_min, gt_min)
|
| 18 |
+
int_tmax = np.minimum(proposal_max, gt_max)
|
| 19 |
+
inter_len = np.maximum(int_tmax - int_tmin, 0.)
|
| 20 |
+
union_len = len_anchors - inter_len + gt_max - gt_min
|
| 21 |
+
jaccard = np.divide(inter_len, union_len)
|
| 22 |
+
return jaccard
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def temporal_iop(proposal_min, proposal_max, gt_min, gt_max):
|
| 26 |
+
"""Compute IoP score between a groundtruth bbox and the proposals.
|
| 27 |
+
|
| 28 |
+
Compute the IoP which is defined as the overlap ratio with
|
| 29 |
+
groundtruth proportional to the duration of this proposal.
|
| 30 |
+
|
| 31 |
+
Args:
|
| 32 |
+
proposal_min (list[float]): List of temporal anchor min.
|
| 33 |
+
proposal_max (list[float]): List of temporal anchor max.
|
| 34 |
+
gt_min (float): Groundtruth temporal box min.
|
| 35 |
+
gt_max (float): Groundtruth temporal box max.
|
| 36 |
+
|
| 37 |
+
Returns:
|
| 38 |
+
list[float]: List of intersection over anchor scores.
|
| 39 |
+
"""
|
| 40 |
+
len_anchors = np.array(proposal_max - proposal_min)
|
| 41 |
+
int_tmin = np.maximum(proposal_min, gt_min)
|
| 42 |
+
int_tmax = np.minimum(proposal_max, gt_max)
|
| 43 |
+
inter_len = np.maximum(int_tmax - int_tmin, 0.)
|
| 44 |
+
scores = np.divide(inter_len, len_anchors)
|
| 45 |
+
return scores
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
def soft_nms(proposals, alpha, low_threshold, high_threshold, top_k):
|
| 49 |
+
"""Soft NMS for temporal proposals.
|
| 50 |
+
|
| 51 |
+
Args:
|
| 52 |
+
proposals (np.ndarray): Proposals generated by network.
|
| 53 |
+
alpha (float): Alpha value of Gaussian decaying function.
|
| 54 |
+
low_threshold (float): Low threshold for soft nms.
|
| 55 |
+
high_threshold (float): High threshold for soft nms.
|
| 56 |
+
top_k (int): Top k values to be considered.
|
| 57 |
+
|
| 58 |
+
Returns:
|
| 59 |
+
np.ndarray: The updated proposals.
|
| 60 |
+
"""
|
| 61 |
+
proposals = proposals[proposals[:, -1].argsort()[::-1]]
|
| 62 |
+
tstart = list(proposals[:, 0])
|
| 63 |
+
tend = list(proposals[:, 1])
|
| 64 |
+
tscore = list(proposals[:, -1])
|
| 65 |
+
rstart = []
|
| 66 |
+
rend = []
|
| 67 |
+
rscore = []
|
| 68 |
+
|
| 69 |
+
while len(tscore) > 0 and len(rscore) <= top_k:
|
| 70 |
+
max_index = np.argmax(tscore)
|
| 71 |
+
max_width = tend[max_index] - tstart[max_index]
|
| 72 |
+
iou_list = temporal_iou(tstart[max_index], tend[max_index],
|
| 73 |
+
np.array(tstart), np.array(tend))
|
| 74 |
+
iou_exp_list = np.exp(-np.square(iou_list) / alpha)
|
| 75 |
+
|
| 76 |
+
for idx, _ in enumerate(tscore):
|
| 77 |
+
if idx != max_index:
|
| 78 |
+
current_iou = iou_list[idx]
|
| 79 |
+
if current_iou > low_threshold + (high_threshold -
|
| 80 |
+
low_threshold) * max_width:
|
| 81 |
+
tscore[idx] = tscore[idx] * iou_exp_list[idx]
|
| 82 |
+
|
| 83 |
+
rstart.append(tstart[max_index])
|
| 84 |
+
rend.append(tend[max_index])
|
| 85 |
+
rscore.append(tscore[max_index])
|
| 86 |
+
tstart.pop(max_index)
|
| 87 |
+
tend.pop(max_index)
|
| 88 |
+
tscore.pop(max_index)
|
| 89 |
+
|
| 90 |
+
rstart = np.array(rstart).reshape(-1, 1)
|
| 91 |
+
rend = np.array(rend).reshape(-1, 1)
|
| 92 |
+
rscore = np.array(rscore).reshape(-1, 1)
|
| 93 |
+
new_proposals = np.concatenate((rstart, rend, rscore), axis=1)
|
| 94 |
+
return new_proposals
|
TransRAC/mmaction/localization/ssn_utils.py
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from itertools import groupby
|
| 2 |
+
|
| 3 |
+
import numpy as np
|
| 4 |
+
|
| 5 |
+
from ..core import average_precision_at_temporal_iou
|
| 6 |
+
from . import temporal_iou
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
def load_localize_proposal_file(filename):
|
| 10 |
+
"""Load the proposal file and split it into many parts which contain one
|
| 11 |
+
video's information separately.
|
| 12 |
+
|
| 13 |
+
Args:
|
| 14 |
+
filename(str): Path to the proposal file.
|
| 15 |
+
|
| 16 |
+
Returns:
|
| 17 |
+
list: List of all videos' information.
|
| 18 |
+
"""
|
| 19 |
+
lines = list(open(filename))
|
| 20 |
+
|
| 21 |
+
# Split the proposal file into many parts which contain one video's
|
| 22 |
+
# information separately.
|
| 23 |
+
groups = groupby(lines, lambda x: x.startswith('#'))
|
| 24 |
+
|
| 25 |
+
video_infos = [[x.strip() for x in list(g)] for k, g in groups if not k]
|
| 26 |
+
|
| 27 |
+
def parse_group(video_info):
|
| 28 |
+
"""Parse the video's information.
|
| 29 |
+
|
| 30 |
+
Template information of a video in a standard file:
|
| 31 |
+
# index
|
| 32 |
+
video_id
|
| 33 |
+
num_frames
|
| 34 |
+
fps
|
| 35 |
+
num_gts
|
| 36 |
+
label, start_frame, end_frame
|
| 37 |
+
label, start_frame, end_frame
|
| 38 |
+
...
|
| 39 |
+
num_proposals
|
| 40 |
+
label, best_iou, overlap_self, start_frame, end_frame
|
| 41 |
+
label, best_iou, overlap_self, start_frame, end_frame
|
| 42 |
+
...
|
| 43 |
+
|
| 44 |
+
Example of a standard annotation file:
|
| 45 |
+
|
| 46 |
+
.. code-block:: txt
|
| 47 |
+
|
| 48 |
+
# 0
|
| 49 |
+
video_validation_0000202
|
| 50 |
+
5666
|
| 51 |
+
1
|
| 52 |
+
3
|
| 53 |
+
8 130 185
|
| 54 |
+
8 832 1136
|
| 55 |
+
8 1303 1381
|
| 56 |
+
5
|
| 57 |
+
8 0.0620 0.0620 790 5671
|
| 58 |
+
8 0.1656 0.1656 790 2619
|
| 59 |
+
8 0.0833 0.0833 3945 5671
|
| 60 |
+
8 0.0960 0.0960 4173 5671
|
| 61 |
+
8 0.0614 0.0614 3327 5671
|
| 62 |
+
|
| 63 |
+
Args:
|
| 64 |
+
video_info (list): Information of the video.
|
| 65 |
+
|
| 66 |
+
Returns:
|
| 67 |
+
tuple[str, int, list, list]:
|
| 68 |
+
video_id (str): Name of the video.
|
| 69 |
+
num_frames (int): Number of frames in the video.
|
| 70 |
+
gt_boxes (list): List of the information of gt boxes.
|
| 71 |
+
proposal_boxes (list): List of the information of
|
| 72 |
+
proposal boxes.
|
| 73 |
+
"""
|
| 74 |
+
offset = 0
|
| 75 |
+
video_id = video_info[offset]
|
| 76 |
+
offset += 1
|
| 77 |
+
|
| 78 |
+
num_frames = int(float(video_info[1]) * float(video_info[2]))
|
| 79 |
+
num_gts = int(video_info[3])
|
| 80 |
+
offset = 4
|
| 81 |
+
|
| 82 |
+
gt_boxes = [x.split() for x in video_info[offset:offset + num_gts]]
|
| 83 |
+
offset += num_gts
|
| 84 |
+
num_proposals = int(video_info[offset])
|
| 85 |
+
offset += 1
|
| 86 |
+
proposal_boxes = [
|
| 87 |
+
x.split() for x in video_info[offset:offset + num_proposals]
|
| 88 |
+
]
|
| 89 |
+
|
| 90 |
+
return video_id, num_frames, gt_boxes, proposal_boxes
|
| 91 |
+
|
| 92 |
+
return [parse_group(video_info) for video_info in video_infos]
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
def perform_regression(detections):
|
| 96 |
+
"""Perform regression on detection results.
|
| 97 |
+
|
| 98 |
+
Args:
|
| 99 |
+
detections (list): Detection results before regression.
|
| 100 |
+
|
| 101 |
+
Returns:
|
| 102 |
+
list: Detection results after regression.
|
| 103 |
+
"""
|
| 104 |
+
starts = detections[:, 0]
|
| 105 |
+
ends = detections[:, 1]
|
| 106 |
+
centers = (starts + ends) / 2
|
| 107 |
+
durations = ends - starts
|
| 108 |
+
|
| 109 |
+
new_centers = centers + durations * detections[:, 3]
|
| 110 |
+
new_durations = durations * np.exp(detections[:, 4])
|
| 111 |
+
|
| 112 |
+
new_detections = np.concatenate(
|
| 113 |
+
(np.clip(new_centers - new_durations / 2, 0,
|
| 114 |
+
1)[:, None], np.clip(new_centers + new_durations / 2, 0,
|
| 115 |
+
1)[:, None], detections[:, 2:]),
|
| 116 |
+
axis=1)
|
| 117 |
+
return new_detections
|
| 118 |
+
|
| 119 |
+
|
| 120 |
+
def temporal_nms(detections, threshold):
|
| 121 |
+
"""Parse the video's information.
|
| 122 |
+
|
| 123 |
+
Args:
|
| 124 |
+
detections (list): Detection results before NMS.
|
| 125 |
+
threshold (float): Threshold of NMS.
|
| 126 |
+
|
| 127 |
+
Returns:
|
| 128 |
+
list: Detection results after NMS.
|
| 129 |
+
"""
|
| 130 |
+
starts = detections[:, 0]
|
| 131 |
+
ends = detections[:, 1]
|
| 132 |
+
scores = detections[:, 2]
|
| 133 |
+
|
| 134 |
+
order = scores.argsort()[::-1]
|
| 135 |
+
|
| 136 |
+
keep = []
|
| 137 |
+
while order.size > 0:
|
| 138 |
+
i = order[0]
|
| 139 |
+
keep.append(i)
|
| 140 |
+
ious = temporal_iou(starts[order[1:]], ends[order[1:]], starts[i],
|
| 141 |
+
ends[i])
|
| 142 |
+
idxs = np.where(ious <= threshold)[0]
|
| 143 |
+
order = order[idxs + 1]
|
| 144 |
+
|
| 145 |
+
return detections[keep, :]
|
| 146 |
+
|
| 147 |
+
|
| 148 |
+
def eval_ap(detections, gt_by_cls, iou_range):
|
| 149 |
+
"""Evaluate average precisions.
|
| 150 |
+
|
| 151 |
+
Args:
|
| 152 |
+
detections (dict): Results of detections.
|
| 153 |
+
gt_by_cls (dict): Information of groudtruth.
|
| 154 |
+
iou_range (list): Ranges of iou.
|
| 155 |
+
|
| 156 |
+
Returns:
|
| 157 |
+
list: Average precision values of classes at ious.
|
| 158 |
+
"""
|
| 159 |
+
ap_values = np.zeros((len(detections), len(iou_range)))
|
| 160 |
+
|
| 161 |
+
for iou_idx, min_overlap in enumerate(iou_range):
|
| 162 |
+
for class_idx, _ in enumerate(detections):
|
| 163 |
+
ap = average_precision_at_temporal_iou(gt_by_cls[class_idx],
|
| 164 |
+
detections[class_idx],
|
| 165 |
+
[min_overlap])
|
| 166 |
+
ap_values[class_idx, iou_idx] = ap
|
| 167 |
+
|
| 168 |
+
return ap_values
|
TransRAC/mmaction/utils/decorators.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from types import MethodType
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
def import_module_error_func(module_name):
|
| 5 |
+
"""When a function is imported incorrectly due to a missing module, raise
|
| 6 |
+
an import error when the function is called."""
|
| 7 |
+
|
| 8 |
+
def decorate(func):
|
| 9 |
+
|
| 10 |
+
def new_func(*args, **kwargs):
|
| 11 |
+
raise ImportError(
|
| 12 |
+
f'Please install {module_name} to use {func.__name__}.')
|
| 13 |
+
|
| 14 |
+
return new_func
|
| 15 |
+
|
| 16 |
+
return decorate
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
def import_module_error_class(module_name):
|
| 20 |
+
"""When a class is imported incorrectly due to a missing module, raise an
|
| 21 |
+
import error when the class is instantiated."""
|
| 22 |
+
|
| 23 |
+
def decorate(cls):
|
| 24 |
+
|
| 25 |
+
def import_error_init(*args, **kwargs):
|
| 26 |
+
raise ImportError(
|
| 27 |
+
f'Please install {module_name} to use {cls.__name__}.')
|
| 28 |
+
|
| 29 |
+
cls.__init__ = MethodType(import_error_init, cls)
|
| 30 |
+
return cls
|
| 31 |
+
|
| 32 |
+
return decorate
|
TransRAC/mmaction/utils/logger.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import logging
|
| 2 |
+
|
| 3 |
+
from mmcv.utils import get_logger
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
def get_root_logger(log_file=None, log_level=logging.INFO):
|
| 7 |
+
"""Use ``get_logger`` method in mmcv to get the root logger.
|
| 8 |
+
|
| 9 |
+
The logger will be initialized if it has not been initialized. By default a
|
| 10 |
+
StreamHandler will be added. If ``log_file`` is specified, a FileHandler
|
| 11 |
+
will also be added. The name of the root logger is the top-level package
|
| 12 |
+
name, e.g., "mmaction".
|
| 13 |
+
|
| 14 |
+
Args:
|
| 15 |
+
log_file (str | None): The log filename. If specified, a FileHandler
|
| 16 |
+
will be added to the root logger.
|
| 17 |
+
log_level (int): The root logger level. Note that only the process of
|
| 18 |
+
rank 0 is affected, while other processes will set the level to
|
| 19 |
+
"Error" and be silent most of the time.
|
| 20 |
+
|
| 21 |
+
Returns:
|
| 22 |
+
:obj:`logging.Logger`: The root logger.
|
| 23 |
+
"""
|
| 24 |
+
return get_logger(__name__.split('.')[0], log_file, log_level)
|
TransRAC/mmaction/utils/optimizer.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from mmcv.runner import OptimizerHook, HOOKS
|
| 2 |
+
try:
|
| 3 |
+
import apex
|
| 4 |
+
except:
|
| 5 |
+
print('apex is not installed')
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
@HOOKS.register_module()
|
| 9 |
+
class DistOptimizerHook(OptimizerHook):
|
| 10 |
+
"""Optimizer hook for distributed training."""
|
| 11 |
+
|
| 12 |
+
def __init__(self, update_interval=1, grad_clip=None, coalesce=True, bucket_size_mb=-1, use_fp16=False):
|
| 13 |
+
self.grad_clip = grad_clip
|
| 14 |
+
self.coalesce = coalesce
|
| 15 |
+
self.bucket_size_mb = bucket_size_mb
|
| 16 |
+
self.update_interval = update_interval
|
| 17 |
+
self.use_fp16 = use_fp16
|
| 18 |
+
|
| 19 |
+
def before_run(self, runner):
|
| 20 |
+
runner.optimizer.zero_grad()
|
| 21 |
+
|
| 22 |
+
def after_train_iter(self, runner):
|
| 23 |
+
runner.outputs['loss'] /= self.update_interval
|
| 24 |
+
if self.use_fp16:
|
| 25 |
+
with apex.amp.scale_loss(runner.outputs['loss'], runner.optimizer) as scaled_loss:
|
| 26 |
+
scaled_loss.backward()
|
| 27 |
+
else:
|
| 28 |
+
runner.outputs['loss'].backward()
|
| 29 |
+
if self.every_n_iters(runner, self.update_interval):
|
| 30 |
+
if self.grad_clip is not None:
|
| 31 |
+
self.clip_grads(runner.model.parameters())
|
| 32 |
+
runner.optimizer.step()
|
| 33 |
+
runner.optimizer.zero_grad()
|
TransRAC/mmaction/version.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Open-MMLab. All rights reserved.
|
| 2 |
+
|
| 3 |
+
__version__ = '0.15.0'
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
def parse_version_info(version_str):
|
| 7 |
+
version_info = []
|
| 8 |
+
for x in version_str.split('.'):
|
| 9 |
+
if x.isdigit():
|
| 10 |
+
version_info.append(int(x))
|
| 11 |
+
elif x.find('rc') != -1:
|
| 12 |
+
patch_version = x.split('rc')
|
| 13 |
+
version_info.append(int(patch_version[0]))
|
| 14 |
+
version_info.append(f'rc{patch_version[1]}')
|
| 15 |
+
return tuple(version_info)
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
version_info = parse_version_info(__version__)
|
TransRAC/mmcv_custom/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
# -*- coding: utf-8 -*-
|
TransRAC/models/TransRAC.py
ADDED
|
@@ -0,0 +1,254 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""TransRAC network"""
|
| 2 |
+
from mmcv import Config
|
| 3 |
+
from mmaction.models import build_model
|
| 4 |
+
from mmcv.runner import load_checkpoint
|
| 5 |
+
import torch
|
| 6 |
+
import torch.nn as nn
|
| 7 |
+
import math
|
| 8 |
+
from torch.cuda.amp import autocast
|
| 9 |
+
import numpy as np
|
| 10 |
+
import torch.nn.functional as F
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
class attention(nn.Module):
|
| 14 |
+
"""Scaled dot-product attention mechanism."""
|
| 15 |
+
|
| 16 |
+
def __init__(self, scale=64, att_dropout=None):
|
| 17 |
+
super().__init__()
|
| 18 |
+
# self.dropout = nn.Dropout(attention_dropout)
|
| 19 |
+
self.softmax = nn.Softmax(dim=-1)
|
| 20 |
+
self.dropout = nn.Dropout(att_dropout)
|
| 21 |
+
self.scale = scale
|
| 22 |
+
|
| 23 |
+
def forward(self, q, k, v, attn_mask=None):
|
| 24 |
+
# q: [B, head, F, model_dim]
|
| 25 |
+
scores = torch.matmul(q, k.transpose(-2, -1)) / math.sqrt(self.scale) # [B,Head, F, F]
|
| 26 |
+
if attn_mask:
|
| 27 |
+
scores = scores.masked_fill_(attn_mask, -np.inf)
|
| 28 |
+
scores = self.softmax(scores)
|
| 29 |
+
scores = self.dropout(scores) # [B,head, F, F]
|
| 30 |
+
# context = torch.matmul(scores, v) # output
|
| 31 |
+
return scores # [B,head,F, F]
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
class Similarity_matrix(nn.Module):
|
| 35 |
+
''' buliding similarity matrix by self-attention mechanism '''
|
| 36 |
+
|
| 37 |
+
def __init__(self, num_heads=4, model_dim=512):
|
| 38 |
+
super().__init__()
|
| 39 |
+
|
| 40 |
+
# self.dim_per_head = model_dim // num_heads
|
| 41 |
+
self.num_heads = num_heads
|
| 42 |
+
self.model_dim = model_dim
|
| 43 |
+
self.input_size = 512
|
| 44 |
+
self.linear_q = nn.Linear(self.input_size, model_dim)
|
| 45 |
+
self.linear_k = nn.Linear(self.input_size, model_dim)
|
| 46 |
+
self.linear_v = nn.Linear(self.input_size, model_dim)
|
| 47 |
+
|
| 48 |
+
self.attention = attention(att_dropout=0)
|
| 49 |
+
# self.out = nn.Linear(model_dim, model_dim)
|
| 50 |
+
# self.layer_norm = nn.LayerNorm(model_dim)
|
| 51 |
+
|
| 52 |
+
def forward(self, query, key, value, attn_mask=None):
|
| 53 |
+
batch_size = query.size(0)
|
| 54 |
+
# dim_per_head = self.dim_per_head
|
| 55 |
+
num_heads = self.num_heads
|
| 56 |
+
# linear projection
|
| 57 |
+
query = self.linear_q(query) # [B,F,model_dim]
|
| 58 |
+
key = self.linear_k(key)
|
| 59 |
+
value = self.linear_v(value)
|
| 60 |
+
# split by heads
|
| 61 |
+
# [B,F,model_dim] -> [B,F,num_heads,per_head]->[B,num_heads,F,per_head]
|
| 62 |
+
query = query.reshape(batch_size, -1, num_heads, self.model_dim // self.num_heads).transpose(1, 2)
|
| 63 |
+
key = key.reshape(batch_size, -1, num_heads, self.model_dim // self.num_heads).transpose(1, 2)
|
| 64 |
+
value = value.reshape(batch_size, -1, num_heads, self.model_dim // self.num_heads).transpose(1, 2)
|
| 65 |
+
# similar_matrix :[B,H,F,F ]
|
| 66 |
+
matrix = self.attention(query, key, value, attn_mask)
|
| 67 |
+
|
| 68 |
+
return matrix
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
class PositionalEncoding(nn.Module):
|
| 72 |
+
def __init__(self, d_model, dropout=0.1, max_len=5000):
|
| 73 |
+
super(PositionalEncoding, self).__init__()
|
| 74 |
+
self.dropout = nn.Dropout(p=dropout)
|
| 75 |
+
|
| 76 |
+
pe = torch.zeros(max_len, d_model)
|
| 77 |
+
position = torch.arange(0, max_len, dtype=torch.float).unsqueeze(1)
|
| 78 |
+
div_term = torch.exp(torch.arange(0, d_model, 2).float() * (-math.log(10000.0) / d_model))
|
| 79 |
+
pe[:, 0::2] = torch.sin(position * div_term)
|
| 80 |
+
pe[:, 1::2] = torch.cos(position * div_term)
|
| 81 |
+
pe = pe.unsqueeze(0).transpose(0, 1)
|
| 82 |
+
self.register_buffer('pe', pe)
|
| 83 |
+
|
| 84 |
+
def forward(self, x):
|
| 85 |
+
x = x + self.pe[:x.size(0), :]
|
| 86 |
+
x = self.dropout(x)
|
| 87 |
+
return x
|
| 88 |
+
|
| 89 |
+
|
| 90 |
+
class TransEncoder(nn.Module):
|
| 91 |
+
'''standard transformer encoder'''
|
| 92 |
+
|
| 93 |
+
def __init__(self, d_model, n_head, dim_ff, dropout=0.0, num_layers=1, num_frames=64):
|
| 94 |
+
super(TransEncoder, self).__init__()
|
| 95 |
+
self.pos_encoder = PositionalEncoding(d_model, 0.1, num_frames)
|
| 96 |
+
|
| 97 |
+
encoder_layer = nn.TransformerEncoderLayer(d_model=d_model,
|
| 98 |
+
nhead=n_head,
|
| 99 |
+
dim_feedforward=dim_ff,
|
| 100 |
+
dropout=dropout,
|
| 101 |
+
activation='relu')
|
| 102 |
+
encoder_norm = nn.LayerNorm(d_model)
|
| 103 |
+
self.trans_encoder = nn.TransformerEncoder(encoder_layer, num_layers, encoder_norm)
|
| 104 |
+
|
| 105 |
+
def forward(self, src):
|
| 106 |
+
src = self.pos_encoder(src)
|
| 107 |
+
e_op = self.trans_encoder(src)
|
| 108 |
+
return e_op
|
| 109 |
+
|
| 110 |
+
|
| 111 |
+
class Prediction(nn.Module):
|
| 112 |
+
''' predict the density map with densenet '''
|
| 113 |
+
|
| 114 |
+
def __init__(self, input_dim, n_hidden_1, n_hidden_2, out_dim):
|
| 115 |
+
super(Prediction, self).__init__()
|
| 116 |
+
self.layers = nn.Sequential(
|
| 117 |
+
nn.Linear(input_dim, n_hidden_1),
|
| 118 |
+
nn.LayerNorm(n_hidden_1),
|
| 119 |
+
nn.Dropout(p=0.25, inplace=False),
|
| 120 |
+
nn.ReLU(True),
|
| 121 |
+
nn.Linear(n_hidden_1, n_hidden_2),
|
| 122 |
+
nn.ReLU(True),
|
| 123 |
+
nn.Dropout(p=0.25, inplace=False),
|
| 124 |
+
nn.Linear(n_hidden_2, out_dim)
|
| 125 |
+
)
|
| 126 |
+
|
| 127 |
+
def forward(self, x):
|
| 128 |
+
x = self.layers(x)
|
| 129 |
+
return x
|
| 130 |
+
|
| 131 |
+
|
| 132 |
+
class TransferModel(nn.Module):
|
| 133 |
+
def __init__(self, config, checkpoint, num_frames, scales, OPEN=False):
|
| 134 |
+
super(TransferModel, self).__init__()
|
| 135 |
+
self.num_frames = num_frames
|
| 136 |
+
self.config = config
|
| 137 |
+
self.checkpoint = checkpoint
|
| 138 |
+
self.scales = scales
|
| 139 |
+
self.OPEN = OPEN
|
| 140 |
+
|
| 141 |
+
self.backbone = self.load_model() # load pretrain model
|
| 142 |
+
|
| 143 |
+
self.Replication_padding1 = nn.ConstantPad3d((0, 0, 0, 0, 1, 1), 0)
|
| 144 |
+
self.Replication_padding2 = nn.ConstantPad3d((0, 0, 0, 0, 2, 2), 0)
|
| 145 |
+
self.Replication_padding4 = nn.ConstantPad3d((0, 0, 0, 0, 4, 4), 0)
|
| 146 |
+
|
| 147 |
+
self.conv3D = nn.Conv3d(in_channels=768,
|
| 148 |
+
out_channels=512,
|
| 149 |
+
kernel_size=3,
|
| 150 |
+
padding=(3, 1, 1),
|
| 151 |
+
dilation=(3, 1, 1))
|
| 152 |
+
|
| 153 |
+
self.bn1 = nn.BatchNorm3d(512)
|
| 154 |
+
self.SpatialPooling = nn.MaxPool3d(kernel_size=(1, 7, 7))
|
| 155 |
+
|
| 156 |
+
self.sims = Similarity_matrix()
|
| 157 |
+
self.conv3x3 = nn.Conv2d(in_channels=4 * len(self.scales), # num_head*scale_num
|
| 158 |
+
out_channels=32,
|
| 159 |
+
kernel_size=3,
|
| 160 |
+
padding=1)
|
| 161 |
+
|
| 162 |
+
self.bn2 = nn.BatchNorm2d(32)
|
| 163 |
+
|
| 164 |
+
self.dropout1 = nn.Dropout(0.25)
|
| 165 |
+
self.input_projection = nn.Linear(self.num_frames * 32, 512) # 线性投射层
|
| 166 |
+
self.ln1 = nn.LayerNorm(512)
|
| 167 |
+
|
| 168 |
+
self.transEncoder = TransEncoder(d_model=512, n_head=4, dropout=0.2, dim_ff=512, num_layers=1,
|
| 169 |
+
num_frames=self.num_frames)
|
| 170 |
+
self.FC = Prediction(512, 512, 256, 1) #
|
| 171 |
+
|
| 172 |
+
def load_model(self):
|
| 173 |
+
# # # load pretrained model of video swin transformer using mmaction and mmcv API
|
| 174 |
+
cfg = Config.fromfile(self.config)
|
| 175 |
+
model = build_model(cfg.model, train_cfg=cfg.get('train_cfg'), test_cfg=cfg.get('test_cfg'))
|
| 176 |
+
|
| 177 |
+
# # # load hyperparameters by mmcv api
|
| 178 |
+
load_checkpoint(model, self.checkpoint, map_location='cpu')
|
| 179 |
+
backbone = model.backbone
|
| 180 |
+
|
| 181 |
+
# # # load hyperparameters by pytorch
|
| 182 |
+
# loaded_ckpt = torch.load(self.checkpoint)
|
| 183 |
+
# backbone = model.backbone
|
| 184 |
+
# net_dict = backbone.state_dict()
|
| 185 |
+
# state_dict = {k: v for k, v in loaded_ckpt.items() if k in net_dict.keys()}
|
| 186 |
+
# net_dict.update(state_dict)
|
| 187 |
+
# backbone.load_state_dict(net_dict, strict=False)
|
| 188 |
+
|
| 189 |
+
print('--------- backbone loaded ------------')
|
| 190 |
+
|
| 191 |
+
return backbone
|
| 192 |
+
|
| 193 |
+
def forward(self, x):
|
| 194 |
+
# x: tensor([batch_size, channel, temporal_dim, height, width])
|
| 195 |
+
with autocast():
|
| 196 |
+
batch_size, c, num_frames, h, w = x.shape
|
| 197 |
+
# scales = [1,4,8]
|
| 198 |
+
### We currently only support 1, 4, 8 flames. If you want to add more scale, you can change the part and don't forget padding.
|
| 199 |
+
multi_scales = []
|
| 200 |
+
for scale in self.scales:
|
| 201 |
+
if scale == 4:
|
| 202 |
+
x = self.Replication_padding2(x)
|
| 203 |
+
crops = [x[:, :, i:i + scale, :, :] for i in
|
| 204 |
+
range(0, self.num_frames - scale + scale // 2 * 2, max(scale // 2, 1))]
|
| 205 |
+
elif scale == 8:
|
| 206 |
+
x = self.Replication_padding4(x)
|
| 207 |
+
crops = [x[:, :, i:i + scale, :, :] for i in
|
| 208 |
+
range(0, self.num_frames - scale + scale // 2 * 2, max(scale // 2, 1))]
|
| 209 |
+
else:
|
| 210 |
+
crops = [x[:, :, i:i + 1, :, :] for i in range(0, self.num_frames)]
|
| 211 |
+
|
| 212 |
+
slice = []
|
| 213 |
+
## feature extract with video SwinTransformer
|
| 214 |
+
if not self.OPEN:
|
| 215 |
+
with torch.no_grad():
|
| 216 |
+
for crop in crops:
|
| 217 |
+
crop = self.backbone(crop) # ->[batch_size, 768, scale/2(up), 7, 7]
|
| 218 |
+
slice.append(crop)
|
| 219 |
+
else: # train the feature extractor (video SwinTransformer backbone)
|
| 220 |
+
for crop in crops:
|
| 221 |
+
crop = self.backbone(crop) # ->[batch_size, 768, scale/2(up), 7, 7]
|
| 222 |
+
slice.append(crop)
|
| 223 |
+
|
| 224 |
+
x_scale = torch.cat(slice, dim=2) # -> [b,768,f,size,size]
|
| 225 |
+
x_scale = F.relu(self.bn1(self.conv3D(x_scale))) # ->[b,512,f,7,7]
|
| 226 |
+
# print(x_scale.shape)
|
| 227 |
+
x_scale = self.SpatialPooling(x_scale) # ->[b,512,f,1,1]
|
| 228 |
+
x_scale = x_scale.squeeze(3).squeeze(3) # -> [b,512,f]
|
| 229 |
+
x_scale = x_scale.transpose(1, 2) # -> [b,f,512]
|
| 230 |
+
|
| 231 |
+
# -------- similarity matrix ---------
|
| 232 |
+
x_sims = F.relu(self.sims(x_scale, x_scale, x_scale)) # -> [b,4,f,f]
|
| 233 |
+
multi_scales.append(x_sims)
|
| 234 |
+
|
| 235 |
+
x = torch.cat(multi_scales, dim=1) # [B,4*scale_num,f,f]
|
| 236 |
+
## x are the similarity matrixs
|
| 237 |
+
x_matrix = x
|
| 238 |
+
x = F.relu(self.bn2(self.conv3x3(x))) # [b,32,f,f]
|
| 239 |
+
x = self.dropout1(x)
|
| 240 |
+
|
| 241 |
+
x = x.permute(0, 2, 3, 1) # [b,f,f,32]
|
| 242 |
+
# --------- transformer encoder ------
|
| 243 |
+
x = x.flatten(start_dim=2) # ->[b,f,32*f]
|
| 244 |
+
x = F.relu(self.input_projection(x)) # ->[b,f, 512]
|
| 245 |
+
x = self.ln1(x)
|
| 246 |
+
|
| 247 |
+
x = x.transpose(0, 1) # [f,b,512]
|
| 248 |
+
x = self.transEncoder(x) #
|
| 249 |
+
x = x.transpose(0, 1) # ->[b,f, 512]
|
| 250 |
+
|
| 251 |
+
x = self.FC(x) # ->[b,f,1]
|
| 252 |
+
x = x.squeeze(2)
|
| 253 |
+
|
| 254 |
+
return x, x_matrix
|
TransRAC/models/__init__.py
ADDED
|
File without changes
|
TransRAC/open_set/new_test.csv
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
TransRAC/open_set/new_train.csv
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
TransRAC/open_set/new_valid.csv
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
,Unnamed: 0,type,name,count,L1,L2,L3,L4,L5,L6,L7,L8,L9,L10,L11,L12,L13,L14,L15,L16,L17,L18,L19,L20,L21,L22,L23,L24,L25,L26,L27,L28,L29,L30,L31,L32,L33,L34,L35,L36,L37,L38,L39,L40,L41,L42,L43,L44,L45,L46,L47,L48,L49,L50,L51,L52,L53,L54,L55,L56,L57,L58,L59,L60,L61,L62,L63,L64,L65,L66,L67,L68,L69,L70,L71,L72,L73,L74,L75,L76,L77,L78,L79,L80,L81,L82,L83,L84,L85,L86,L87,L88,L89,L90,L91,L92,L93,L94,L95,L96,L97,L98,L99,L100,L101,L102,L103,L104,L105,L106,L107,L108,L109,L110,L111,L112,L113,L114,L115,L116,L117,L118,L119,L120,L121,L122,L123,L124,L125,L126,L127,L128,L129,L130,L131,L132,L133,L134,L135,L136,L137,L138,L139,L140,L141,L142,L143,L144,L145,L146,L147,L148,L149,L150,L151,L152,L153,L154,L155,L156,L157,L158,L159,L160,L161,L162,L163,L164,L165,L166,L167,L168,L169,L170,L171,L172,L173,L174,L175,L176,L177,L178,L179,L180,L181,L182,L183,L184,L185,L186,L187,L188,L189,L190,L191,L192,L193,L194,L195,L196,L197,L198,L199,L200,L201,L202,L203,L204,L205,L206,L207,L208,L209,L210,L211,L212,L213,L214,L215,L216,L217,L218,L219,L220,L221,L222,L223,L224,L225,L226,L227,L228,L229,L230,L231,L232,L233,L234,L235,L236,L237,L238,L239,L240,L241,L242,L243,L244,L245,L246,L247,L248,L249,L250,L251,L252,L253,L254,L255,L256,L257,L258,L259,L260,L261,L262,L263,L264,L265,L266,L267,L268,L269,L270,L271,L272,L273,L274,L275,L276,L277,L278,L279,L280,L281,L282,L283,L284,L285,L286,L287,L288,L289,L290,L291,L292,L293,L294,L295,L296,L297,L298,L299,L300,L301,L302
|
| 2 |
+
0,0,frontraise,train951.mp4,4,6,72,72.0,132.0,132.0,204.0,204.0,271.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 3 |
+
1,1,frontraise,train952.mp4,10,13,62,62.0,103.0,103.0,126.0,126.0,146.0,146.0,171.0,171.0,194.0,194.0,215.0,215.0,239.0,239.0,260.0,260.0,281.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 4 |
+
4,4,front_raise,stu5_11.mp4,6,104,179,179.0,255.0,255.0,330.0,426.0,501.0,703.0,789.0,790.0,853.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 5 |
+
7,7,front_raise,stu4_14.mp4,5,6,87,87.0,177.0,177.0,276.0,276.0,377.0,377.0,490.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 6 |
+
28,28,frontraise,train940.mp4,5,45,94,94.0,148.0,148.0,201.0,201.0,255.0,255.0,299.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 7 |
+
33,33,front_raise,stu9_22.mp4,6,39,124,124.0,207.0,207.0,277.0,566.0,659.0,659.0,750.0,750.0,851.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 8 |
+
35,35,front_raise,stu1_14.mp4,3,15,227,227.0,403.0,403.0,540.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 9 |
+
39,39,frontraise,train987.mp4,7,0,36,36.0,78.0,78.0,118.0,118.0,160.0,160.0,204.0,204.0,244.0,244.0,279.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 10 |
+
42,43,front_raise,stu7_18.mp4,5,40,207,208.0,337.0,338.0,399.0,402.0,466.0,555.0,663.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 11 |
+
51,52,front_raise,stu6_8.mp4,6,19,110,111.0,191.0,198.0,346.0,382.0,553.0,579.0,702.0,702.0,822.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 12 |
+
54,55,front_raise,stu9_26.mp4,3,142,239,239.0,339.0,339.0,436.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 13 |
+
64,66,front_raise,stu2_20.mp4,13,79,145,145.0,198.0,198.0,251.0,251.0,304.0,304.0,360.0,360.0,412.0,412.0,466.0,466.0,518.0,518.0,572.0,572.0,629.0,630.0,679.0,679.0,736.0,736.0,789.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 14 |
+
65,67,front_raise,stu10_16.mp4,6,22,94,94.0,147.0,147.0,197.0,197.0,249.0,249.0,300.0,300.0,343.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 15 |
+
83,85,frontraise,test586.mp4,7,19,56,56.0,99.0,99.0,136.0,136.0,178.0,178.0,220.0,220.0,257.0,257.0,298.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 16 |
+
89,91,front_raise,stu1_20.mp4,3,36,105,105.0,186.0,186.0,265.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 17 |
+
93,95,front_raise,stu3_9.mp4,12,24,84,84.0,145.0,145.0,210.0,210.0,278.0,278.0,350.0,350.0,422.0,422.0,492.0,492.0,562.0,562.0,630.0,630.0,708.0,708.0,784.0,784.0,854.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 18 |
+
117,119,front_raise,stu6_6.mp4,9,5,309,309.0,455.0,455.0,562.0,563.0,663.0,663.0,770.0,772.0,867.0,867.0,977.0,977.0,1102.0,1107.0,1204.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 19 |
+
127,129,front_raise,stu7_21.mp4,7,39,129,129.0,213.0,215.0,314.0,316.0,406.0,407.0,504.0,551.0,606.0,608.0,672.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 20 |
+
130,132,front_raise,stu10_23.mp4,9,39,87,87.0,138.0,138.0,188.0,188.0,237.0,365.0,441.0,441.0,489.0,489.0,536.0,536.0,578.0,578.0,615.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 21 |
+
138,140,front_raise,stu2_16.mp4,2,336,513,513.0,709.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 22 |
+
140,142,front_raise,stu9_25.mp4,1,87,202,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 23 |
+
147,149,frontraise,train954.mp4,6,3,47,47.0,91.0,91.0,142.0,142.0,195.0,195.0,243.0,243.0,286.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 24 |
+
150,152,front_raise,stu8_17.mp4,23,69,121,121.0,170.0,170.0,219.0,219.0,269.0,269.0,319.0,319.0,369.0,369.0,417.0,417.0,456.0,457.0,505.0,505.0,554.0,554.0,603.0,603.0,646.0,646.0,694.0,694.0,762.0,762.0,803.0,803.0,856.0,856.0,908.0,908.0,951.0,951.0,1006.0,1006.0,1056.0,1056.0,1106.0,1106.0,1157.0,1157.0,1199.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 25 |
+
161,163,front_raise,stu10_18.mp4,4,63,457,457.0,724.0,725.0,833.0,833.0,934.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 26 |
+
162,164,front_raise,stu6_10.mp4,3,57,167,174.0,298.0,304.0,405.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 27 |
+
164,166,frontraise,train1005.mp4,4,1,60,60.0,131.0,131.0,193.0,193.0,273.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 28 |
+
167,169,front_raise,stu9_20.mp4,7,64,147,147.0,225.0,225.0,290.0,291.0,376.0,376.0,429.0,429.0,485.0,600.0,712.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 29 |
+
173,175,front_raise,stu4_13.mp4,6,4,65,65.0,135.0,135.0,209.0,211.0,286.0,286.0,365.0,366.0,437.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 30 |
+
174,176,front_raise,stu5_13.mp4,4,0,60,60.0,129.0,129.0,188.0,188.0,263.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 31 |
+
176,178,front_raise,stu10_15.mp4,17,14,73,73.0,127.0,135.0,180.0,180.0,226.0,226.0,268.0,268.0,313.0,313.0,356.0,356.0,404.0,404.0,448.0,448.0,526.0,526.0,569.0,570.0,620.0,620.0,661.0,661.0,710.0,710.0,752.0,752.0,797.0,798.0,844.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 32 |
+
195,197,front_raise,stu3_11.mp4,3,17,100,100.0,176.0,176.0,234.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 33 |
+
201,203,frontraise,test531.mp4,4,0,79,79.0,151.0,151.0,221.0,222.0,290.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 34 |
+
203,205,front_raise,stu2_17.mp4,5,48,147,147.0,298.0,298.0,428.0,428.0,559.0,561.0,695.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 35 |
+
204,206,front_raise,stu9_16.mp4,6,46,127,128.0,202.0,202.0,285.0,285.0,361.0,361.0,480.0,480.0,604.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 36 |
+
206,208,front_raise,stu1_15.mp4,1,59,215,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 37 |
+
207,209,front_raise,stu7_23.mp4,4,0,100,100.0,201.0,203.0,318.0,318.0,418.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 38 |
+
219,222,front_raise,stu9_27.mp4,17,6,60,61.0,111.0,198.0,241.0,241.0,279.0,280.0,316.0,316.0,359.0,359.0,399.0,399.0,436.0,437.0,478.0,478.0,516.0,516.0,558.0,559.0,599.0,599.0,636.0,636.0,676.0,676.0,720.0,720.0,759.0,759.0,808.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 39 |
+
229,232,frontraise,val359.mp4,1,3,225,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 40 |
+
238,241,front_raise,stu5_14.mp4,9,0,75,75.0,157.0,157.0,242.0,242.0,331.0,331.0,411.0,411.0,497.0,497.0,574.0,574.0,654.0,654.0,723.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 41 |
+
247,250,front_raise,stu7_15.mp4,4,1,107,108.0,209.0,209.0,318.0,319.0,431.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 42 |
+
256,260,frontraise,test558.mp4,2,0,127,128.0,255.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 43 |
+
262,266,front_raise,stu4_9.mp4,4,59,254,275.0,407.0,408.0,548.0,549.0,651.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 44 |
+
265,269,frontraise,train929.mp4,4,2,49,49.0,108.0,108.0,162.0,162.0,220.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 45 |
+
274,279,front_raise,stu8_22.mp4,10,4,50,51.0,94.0,94.0,133.0,133.0,172.0,172.0,211.0,211.0,245.0,329.0,390.0,390.0,436.0,436.0,499.0,499.0,550.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 46 |
+
276,281,front_raise,stu1_16.mp4,3,616,711,711.0,763.0,763.0,839.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 47 |
+
277,282,front_raise,stu3_17.mp4,5,7,83,83.0,157.0,157.0,222.0,241.0,313.0,313.0,377.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 48 |
+
287,292,front_raise,stu4_10.mp4,13,5,42,42.0,92.0,93.0,145.0,145.0,194.0,194.0,243.0,243.0,296.0,297.0,340.0,341.0,387.0,387.0,439.0,440.0,491.0,492.0,534.0,535.0,584.0,586.0,624.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 49 |
+
290,295,front_raise,stu2_19.mp4,5,29,148,148.0,272.0,272.0,387.0,387.0,489.0,490.0,595.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 50 |
+
293,298,front_raise,stu1_21.mp4,4,190,281,281.0,371.0,371.0,548.0,548.0,719.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 51 |
+
311,316,frontraise,test557.mp4,3,24,111,113.0,212.0,213.0,299.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 52 |
+
317,322,frontraise,test570.mp4,3,0,95,95.0,177.0,177.0,262.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 53 |
+
325,330,front_raise,stu8_18.mp4,9,45,126,127.0,199.0,199.0,275.0,275.0,358.0,358.0,435.0,435.0,504.0,504.0,578.0,578.0,653.0,653.0,719.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 54 |
+
338,343,front_raise,stu2_18.mp4,39,59,104,104.0,147.0,147.0,199.0,199.0,241.0,241.0,287.0,287.0,335.0,335.0,379.0,379.0,427.0,427.0,472.0,472.0,517.0,742.0,761.0,761.0,776.0,777.0,791.0,792.0,807.0,807.0,823.0,823.0,838.0,838.0,854.0,854.0,870.0,870.0,887.0,887.0,901.0,1018.0,1036.0,1036.0,1050.0,1050.0,1064.0,1064.0,1078.0,1078.0,1091.0,1092.0,1105.0,1105.0,1120.0,1120.0,1137.0,1137.0,1153.0,1153.0,1165.0,1304.0,1319.0,1319.0,1335.0,1335.0,1349.0,1349.0,1363.0,1363.0,1377.0,1377.0,1392.0,1392.0,1406.0,1406.0,1422.0,1422.0,1434.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 55 |
+
342,347,front_raise,stu8_21.mp4,3,45,146,146.0,239.0,239.0,327.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 56 |
+
348,353,frontraise,test577.mp4,3,6,80,80.0,160.0,160.0,264.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 57 |
+
351,356,front_raise,stu9_14.mp4,11,30,77,77.0,132.0,132.0,174.0,174.0,217.0,217.0,288.0,288.0,337.0,337.0,385.0,463.0,500.0,500.0,536.0,536.0,569.0,569.0,597.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 58 |
+
375,380,frontraise,test584.mp4,5,5,39,39.0,79.0,79.0,133.0,133.0,177.0,177.0,215.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 59 |
+
378,383,front_raise,stu10_21.mp4,2,66,146,146.0,242.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 60 |
+
395,400,front_raise,stu9_23.mp4,5,34,163,195.0,330.0,331.0,446.0,446.0,553.0,566.0,669.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 61 |
+
401,406,front_raise,stu10_20.mp4,8,28,212,212.0,327.0,327.0,458.0,458.0,558.0,558.0,647.0,647.0,737.0,737.0,828.0,828.0,913.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 62 |
+
403,408,front_raise,stu10_14.mp4,14,49,135,135.0,212.0,212.0,289.0,290.0,363.0,364.0,432.0,432.0,498.0,498.0,564.0,564.0,647.0,647.0,723.0,723.0,805.0,805.0,900.0,900.0,981.0,981.0,1071.0,1071.0,1154.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 63 |
+
427,432,frontraise,train1022.mp4,3,52,125,125.0,197.0,197.0,264.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 64 |
+
454,459,front_raise,stu6_11.mp4,22,107,127,127.0,146.0,146.0,162.0,162.0,180.0,180.0,198.0,198.0,219.0,219.0,238.0,238.0,264.0,264.0,288.0,288.0,313.0,331.0,410.0,520.0,568.0,568.0,656.0,656.0,733.0,734.0,828.0,828.0,922.0,922.0,964.0,965.0,1002.0,1002.0,1033.0,1033.0,1070.0,1070.0,1114.0,1114.0,1174.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 65 |
+
455,460,front_raise,stu6_14.mp4,4,47,112,112.0,190.0,392.0,478.0,478.0,563.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 66 |
+
459,465,front_raise,stu3_14.mp4,3,3,136,136.0,199.0,199.0,256.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 67 |
+
460,466,frontraise,train941.mp4,4,39,108,108.0,171.0,171.0,229.0,229.0,289.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 68 |
+
462,468,frontraise,test541.mp4,3,0,112,112.0,204.0,204.0,299.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 69 |
+
469,475,frontraise,train970.mp4,5,40,88,88.0,138.0,138.0,188.0,188.0,236.0,236.0,285.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 70 |
+
485,491,front_raise,stu3_8.mp4,6,66,127,127.0,190.0,190.0,257.0,257.0,322.0,322.0,390.0,390.0,448.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 71 |
+
489,495,front_raise,stu10_22.mp4,4,96,226,226.0,345.0,345.0,456.0,456.0,554.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 72 |
+
492,498,front_raise,stu1_18.mp4,9,40,123,123.0,195.0,195.0,263.0,263.0,336.0,336.0,427.0,427.0,515.0,515.0,611.0,611.0,681.0,681.0,753.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 73 |
+
494,500,front_raise,stu2_22.mp4,7,26,56,56.0,95.0,95.0,135.0,135.0,175.0,176.0,215.0,216.0,255.0,255.0,292.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 74 |
+
498,504,front_raise,stu2_24.mp4,5,0,124,124.0,290.0,290.0,464.0,465.0,672.0,673.0,869.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 75 |
+
500,506,frontraise,train960.mp4,2,1,106,106.0,199.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 76 |
+
508,514,front_raise,stu9_24.mp4,2,71,168,168.0,269.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 77 |
+
515,521,front_raise,stu6_12.mp4,4,21,97,97.0,177.0,177.0,267.0,267.0,348.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 78 |
+
522,528,frontraise,test528.mp4,2,0,168,168.0,281.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 79 |
+
525,532,front_raise,stu7_20.mp4,6,27,218,223.0,356.0,357.0,457.0,459.0,530.0,533.0,640.0,640.0,727.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 80 |
+
534,541,front_raise,stu6_9.mp4,18,5,100,106.0,210.0,222.0,317.0,334.0,428.0,430.0,512.0,515.0,612.0,615.0,713.0,713.0,798.0,800.0,912.0,912.0,995.0,995.0,1085.0,1085.0,1167.0,1167.0,1267.0,1268.0,1353.0,1353.0,1451.0,1452.0,1555.0,1557.0,1651.0,1654.0,1747.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 81 |
+
546,553,front_raise,stu8_20.mp4,8,5,128,128.0,222.0,222.0,331.0,331.0,426.0,426.0,523.0,523.0,633.0,633.0,743.0,743.0,839.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 82 |
+
556,563,front_raise,stu7_22.mp4,2,179,278,288.0,473.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 83 |
+
559,566,front_raise,stu9_21.mp4,16,349,422,443.0,521.0,522.0,597.0,597.0,667.0,667.0,730.0,730.0,807.0,807.0,876.0,876.0,936.0,937.0,1013.0,1013.0,1098.0,1098.0,1186.0,1187.0,1258.0,1258.0,1337.0,1338.0,1436.0,1617.0,1681.0,1727.0,1792.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 84 |
+
565,572,front_raise,stu9_12.mp4,7,0,45,45.0,91.0,91.0,131.0,131.0,176.0,199.0,240.0,240.0,286.0,286.0,334.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 85 |
+
569,576,frontraise,test545.mp4,1,0,204,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 86 |
+
580,590,frontraise,train1000.mp4,3,49,141,141.0,238.0,238.0,293.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 87 |
+
595,605,front_raise,stu5_15.mp4,18,56,109,109.0,177.0,177.0,234.0,234.0,322.0,384.0,463.0,463.0,533.0,533.0,622.0,622.0,707.0,728.0,791.0,791.0,856.0,856.0,927.0,976.0,1040.0,1040.0,1096.0,1096.0,1151.0,1151.0,1192.0,1192.0,1262.0,1262.0,1339.0,1340.0,1420.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 88 |
+
610,621,front_raise,stu8_19.mp4,9,45,86,86.0,120.0,120.0,154.0,154.0,187.0,187.0,224.0,224.0,256.0,256.0,291.0,291.0,322.0,322.0,351.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 89 |
+
628,639,front_raise,stu9_15.mp4,3,0,184,184.0,362.0,363.0,529.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 90 |
+
629,640,frontraise,test587.mp4,3,7,115,115.0,217.0,217.0,299.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 91 |
+
635,646,front_raise,stu3_12.mp4,2,155,389,389.0,548.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 92 |
+
636,647,front_raise,stu4_8.mp4,3,40,134,137.0,271.0,304.0,424.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 93 |
+
12,13,front_raise,stu10_17.mp4,11,6,105,105.0,177.0,177.0,242.0,242.0,317.0,317.0,392.0,392.0,461.0,461.0,541.0,541.0,616.0,616.0,690.0,691.0,769.0,769.0,852.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 94 |
+
20,21,front_raise,stu10_24.mp4,14,60,97,97.0,131.0,131.0,162.0,162.0,194.0,243.0,292.0,292.0,326.0,326.0,361.0,361.0,398.0,755.0,791.0,791.0,828.0,828.0,858.0,858.0,892.0,892.0,923.0,923.0,961.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 95 |
+
43,44,frontraise,test538.mp4,5,8,64,64.0,122.0,122.0,172.0,172.0,223.0,224.0,268.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 96 |
+
49,50,frontraise,test536.mp4,6,21,69,69.0,112.0,112.0,158.0,158.0,203.0,203.0,245.0,245.0,288.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 97 |
+
55,56,front_raise,stu5_12.mp4,5,34,152,152.0,255.0,255.0,352.0,352.0,468.0,468.0,582.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 98 |
+
56,57,front_raise,stu9_17.mp4,15,45,111,111.0,168.0,192.0,251.0,251.0,320.0,320.0,386.0,386.0,441.0,447.0,511.0,511.0,566.0,566.0,635.0,859.0,932.0,932.0,991.0,991.0,1044.0,1045.0,1098.0,1098.0,1159.0,1159.0,1215.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 99 |
+
64,65,front_raise,stu3_10.mp4,5,33,95,95.0,149.0,149.0,199.0,199.0,255.0,255.0,310.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 100 |
+
80,81,front_raise,stu4_12.mp4,4,33,158,159.0,261.0,262.0,376.0,376.0,465.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 101 |
+
82,83,front_raise,stu7_17.mp4,5,14,100,100.0,195.0,195.0,297.0,299.0,388.0,390.0,467.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 102 |
+
99,100,front_raise,stu10_19.mp4,8,24,88,88.0,153.0,153.0,221.0,221.0,287.0,287.0,354.0,354.0,425.0,425.0,495.0,495.0,570.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 103 |
+
107,108,frontraise,test534.mp4,3,1,37,37.0,81.0,81.0,130.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 104 |
+
109,110,front_raise,stu6_13.mp4,9,28,104,104.0,182.0,182.0,265.0,265.0,329.0,329.0,390.0,390.0,447.0,447.0,509.0,509.0,579.0,579.0,624.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 105 |
+
111,112,front_raise,stu8_23.mp4,3,55,133,133.0,194.0,194.0,265.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 106 |
+
119,120,frontraise,test539.mp4,2,4,125,125.0,240.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 107 |
+
120,121,front_raise,stu2_14.mp4,7,0,40,40.0,89.0,132.0,246.0,246.0,349.0,349.0,441.0,441.0,539.0,539.0,633.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 108 |
+
127,128,front_raise,stu3_15.mp4,4,336,486,486.0,555.0,556.0,642.0,642.0,715.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 109 |
+
128,129,front_raise,stu3_18.mp4,16,1,40,40.0,77.0,77.0,117.0,117.0,151.0,151.0,187.0,187.0,222.0,380.0,418.0,418.0,453.0,453.0,490.0,490.0,529.0,538.0,590.0,823.0,863.0,863.0,900.0,900.0,936.0,936.0,975.0,975.0,1017.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 110 |
+
129,130,front_raise,stu5_16.mp4,10,80,136,136.0,215.0,215.0,284.0,284.0,354.0,354.0,430.0,430.0,499.0,499.0,561.0,561.0,633.0,633.0,713.0,713.0,778.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 111 |
+
1,2,front_raise,stu2_15.mp4,8,80,147,147.0,203.0,203.0,264.0,264.0,321.0,321.0,382.0,382.0,443.0,443.0,512.0,512.0,578.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 112 |
+
13,14,front_raise,stu2_23.mp4,6,6,113,113.0,221.0,221.0,316.0,316.0,398.0,398.0,495.0,495.0,586.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 113 |
+
15,16,front_raise,stu6_7.mp4,5,6,50,50.0,94.0,94.0,130.0,130.0,167.0,167.0,204.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 114 |
+
16,17,front_raise,stu9_18.mp4,23,28,72,121.0,166.0,166.0,204.0,339.0,384.0,384.0,423.0,423.0,462.0,463.0,508.0,508.0,554.0,554.0,602.0,649.0,690.0,690.0,730.0,730.0,769.0,769.0,813.0,841.0,889.0,889.0,935.0,1031.0,1073.0,1073.0,1122.0,1122.0,1169.0,1169.0,1213.0,1246.0,1336.0,1336.0,1423.0,1423.0,1517.0,1517.0,1606.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 115 |
+
18,19,front_raise,stu7_19.mp4,4,15,314,318.0,512.0,515.0,688.0,690.0,853.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 116 |
+
19,20,front_raise,stu7_16.mp4,4,0,62,63.0,114.0,114.0,163.0,163.0,211.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 117 |
+
23,24,front_raise,stu5_17.mp4,4,0,51,51.0,136.0,136.0,215.0,215.0,289.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 118 |
+
26,27,front_raise,stu2_21.mp4,4,8,112,112.0,237.0,237.0,376.0,376.0,511.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 119 |
+
40,41,front_raise,stu1_17.mp4,6,5,39,39.0,85.0,85.0,129.0,129.0,174.0,222.0,264.0,264.0,318.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 120 |
+
43,44,frontraise,train1025.mp4,3,0,102,102.0,214.0,214.0,299.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 121 |
+
57,58,frontraise,test571.mp4,3,0,94,94.0,190.0,191.0,283.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 122 |
+
68,69,front_raise,stu3_16.mp4,5,32,157,270.0,464.0,464.0,583.0,583.0,691.0,692.0,830.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 123 |
+
70,71,front_raise,stu1_19.mp4,3,89,176,202.0,280.0,280.0,348.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 124 |
+
77,78,front_raise,stu4_11.mp4,4,25,141,143.0,241.0,243.0,343.0,345.0,447.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 125 |
+
82,83,front_raise,stu9_13.mp4,2,60,138,363.0,445.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 126 |
+
93,96,front_raise,stu10_13.mp4,3,11,314,314.0,509.0,509.0,719.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 127 |
+
94,98,front_raise,stu9_19.mp4,7,34,119,119.0,191.0,191.0,273.0,273.0,364.0,364.0,436.0,436.0,542.0,542.0,632.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 128 |
+
101,105,frontraise,train990.mp4,4,87,133,134.0,175.0,176.0,224.0,226.0,271.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
| 129 |
+
103,107,frontraise,train1010.mp4,6,25,68,68.0,115.0,115.0,158.0,158.0,200.0,200.0,241.0,241.0,281.0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|