Merge remote-tracking branch 'upstream/main'
This commit is contained in:
@@ -1,592 +0,0 @@
|
||||
import os
|
||||
from typing import List, Union
|
||||
import numpy as np
|
||||
import math
|
||||
import time
|
||||
import heapq
|
||||
import torch
|
||||
from torch import Tensor, nn
|
||||
from torch.distributions.distribution import Distribution
|
||||
from transformers import AutoModelForSeq2SeqLM, T5ForConditionalGeneration, T5Tokenizer, AutoTokenizer, GPT2LMHeadModel, GPT2Tokenizer
|
||||
import random
|
||||
from typing import Optional
|
||||
from .tools.token_emb import NewTokenEmb
|
||||
|
||||
|
||||
class MLM(nn.Module):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
model_path: str,
|
||||
model_type: str = "t5",
|
||||
stage: str = "lm_pretrain",
|
||||
new_token_type: str = "insert",
|
||||
motion_codebook_size: int = 512,
|
||||
framerate: float = 20.0,
|
||||
down_t: int = 4,
|
||||
predict_ratio: float = 0.2,
|
||||
inbetween_ratio: float = 0.25,
|
||||
max_length: int = 256,
|
||||
lora: bool = False,
|
||||
quota_ratio: float = 0.5,
|
||||
noise_density: float = 0.15,
|
||||
mean_noise_span_length: int = 3,
|
||||
**kwargs,
|
||||
) -> None:
|
||||
|
||||
super().__init__()
|
||||
|
||||
# Parameters
|
||||
self.m_codebook_size = motion_codebook_size
|
||||
self.max_length = max_length
|
||||
self.framerate = framerate
|
||||
self.down_t = down_t
|
||||
self.predict_ratio = predict_ratio
|
||||
self.inbetween_ratio = inbetween_ratio
|
||||
self.noise_density = noise_density
|
||||
self.mean_noise_span_length = mean_noise_span_length
|
||||
self.quota_ratio = quota_ratio
|
||||
self.stage = stage
|
||||
|
||||
# Instantiate language model
|
||||
self.tokenizer = AutoTokenizer.from_pretrained(model_path, legacy=True)
|
||||
if model_type == "t5":
|
||||
self.language_model = T5ForConditionalGeneration.from_pretrained(
|
||||
model_path)
|
||||
self.lm_type = 'encdec'
|
||||
elif model_type == "gpt2":
|
||||
self.language_model = GPT2LMHeadModel.from_pretrained(model_path)
|
||||
self.lm_type = 'dec'
|
||||
else:
|
||||
raise ValueError("type must be either seq2seq or conditional")
|
||||
|
||||
if self.lm_type == 'dec':
|
||||
self.tokenizer.pad_token = self.tokenizer.eos_token
|
||||
|
||||
# Add motion tokens
|
||||
self.tokenizer.add_tokens(
|
||||
[f'<motion_id_{i}>' for i in range(self.m_codebook_size + 3)])
|
||||
|
||||
if new_token_type == "insert":
|
||||
self.language_model.resize_token_embeddings(len(self.tokenizer))
|
||||
elif new_token_type == "mlp":
|
||||
shared = NewTokenEmb(self.language_model.shared,
|
||||
self.m_codebook_size + 3)
|
||||
# lm_head = NewTokenEmb(self.language_model.lm_head,
|
||||
# self.m_codebook_size + 3)
|
||||
self.language_model.resize_token_embeddings(len(self.tokenizer))
|
||||
self.language_model.shared = shared
|
||||
# self.language_model.lm_head = lm_head
|
||||
|
||||
# Lora
|
||||
if lora:
|
||||
from peft import LoraConfig, TaskType, get_peft_model, get_peft_model_state_dict
|
||||
from peft.utils.other import fsdp_auto_wrap_policy
|
||||
peft_config = LoraConfig(
|
||||
bias="none",
|
||||
task_type="CAUSAL_LM",
|
||||
# inference_mode=False,
|
||||
r=8,
|
||||
lora_alpha=16,
|
||||
lora_dropout=0.05)
|
||||
self.language_model = get_peft_model(self.language_model,
|
||||
peft_config)
|
||||
|
||||
def forward(self, texts: List[str], motion_tokens: Tensor,
|
||||
lengths: List[int], tasks: dict):
|
||||
if self.lm_type == 'encdec':
|
||||
return self.forward_encdec(texts, motion_tokens, lengths, tasks)
|
||||
elif self.lm_type == 'dec':
|
||||
return self.forward_dec(texts, motion_tokens, lengths, tasks)
|
||||
else:
|
||||
raise NotImplementedError("Only conditional_multitask supported")
|
||||
|
||||
def forward_encdec(
|
||||
self,
|
||||
texts: List[str],
|
||||
motion_tokens: Tensor,
|
||||
lengths: List[int],
|
||||
tasks: dict,
|
||||
):
|
||||
|
||||
# Tensor to string
|
||||
motion_strings = self.motion_token_to_string(motion_tokens, lengths)
|
||||
|
||||
# Supervised or unsupervised
|
||||
# condition = random.choice(
|
||||
# ['text', 'motion', 'supervised', 'supervised', 'supervised'])
|
||||
condition = random.choice(['supervised', 'supervised', 'supervised'])
|
||||
|
||||
if condition == 'text':
|
||||
inputs = texts
|
||||
outputs = texts
|
||||
elif condition == 'motion':
|
||||
inputs = motion_strings
|
||||
outputs = motion_strings
|
||||
else:
|
||||
inputs, outputs = self.template_fulfill(tasks, lengths,
|
||||
motion_strings, texts)
|
||||
|
||||
# Tokenize
|
||||
source_encoding = self.tokenizer(inputs,
|
||||
padding='max_length',
|
||||
max_length=self.max_length,
|
||||
truncation=True,
|
||||
return_attention_mask=True,
|
||||
add_special_tokens=True,
|
||||
return_tensors="pt")
|
||||
|
||||
source_attention_mask = source_encoding.attention_mask.to(
|
||||
motion_tokens.device)
|
||||
source_input_ids = source_encoding.input_ids.to(motion_tokens.device)
|
||||
|
||||
if condition in ['text', 'motion']:
|
||||
batch_size, expandend_input_length = source_input_ids.shape
|
||||
mask_indices = np.asarray([
|
||||
self.random_spans_noise_mask(expandend_input_length)
|
||||
for i in range(batch_size)
|
||||
])
|
||||
target_mask = ~mask_indices
|
||||
input_ids_sentinel = self.create_sentinel_ids(
|
||||
mask_indices.astype(np.int8))
|
||||
target_sentinel = self.create_sentinel_ids(
|
||||
target_mask.astype(np.int8))
|
||||
|
||||
labels_input_ids = self.filter_input_ids(source_input_ids,
|
||||
target_sentinel)
|
||||
source_input_ids = self.filter_input_ids(source_input_ids,
|
||||
input_ids_sentinel)
|
||||
|
||||
else:
|
||||
target_inputs = self.tokenizer(outputs,
|
||||
padding='max_length',
|
||||
max_length=self.max_length,
|
||||
truncation=True,
|
||||
return_attention_mask=True,
|
||||
add_special_tokens=True,
|
||||
return_tensors="pt")
|
||||
|
||||
labels_input_ids = target_inputs.input_ids.to(motion_tokens.device)
|
||||
lables_attention_mask = target_inputs.attention_mask.to(
|
||||
motion_tokens.device)
|
||||
|
||||
labels_input_ids[labels_input_ids == 0] = -100
|
||||
outputs = self.language_model(
|
||||
input_ids=source_input_ids,
|
||||
attention_mask=source_attention_mask
|
||||
if condition == 'supervised' else None,
|
||||
labels=labels_input_ids,
|
||||
decoder_attention_mask=lables_attention_mask
|
||||
if condition == 'supervised' else None,
|
||||
)
|
||||
|
||||
return outputs
|
||||
|
||||
def forward_dec(
|
||||
self,
|
||||
texts: List[str],
|
||||
motion_tokens: Tensor,
|
||||
lengths: List[int],
|
||||
tasks: dict,
|
||||
):
|
||||
self.tokenizer.padding_side = "right"
|
||||
|
||||
# Tensor to string
|
||||
motion_strings = self.motion_token_to_string(motion_tokens, lengths)
|
||||
|
||||
# Supervised or unsupervised
|
||||
condition = random.choice(
|
||||
['text', 'motion', 'supervised', 'supervised', 'supervised'])
|
||||
|
||||
if condition == 'text':
|
||||
labels = texts
|
||||
elif condition == 'motion':
|
||||
labels = motion_strings
|
||||
else:
|
||||
inputs, outputs = self.template_fulfill(tasks, lengths,
|
||||
motion_strings, texts)
|
||||
labels = []
|
||||
for i in range(len(inputs)):
|
||||
labels.append(inputs[i] + ' \n ' + outputs[i] +
|
||||
self.tokenizer.eos_token)
|
||||
|
||||
# Tokenize
|
||||
inputs = self.tokenizer(labels,
|
||||
padding='max_length',
|
||||
max_length=self.max_length,
|
||||
truncation=True,
|
||||
return_attention_mask=True,
|
||||
return_tensors="pt")
|
||||
|
||||
labels_input_ids = inputs.input_ids.to(motion_tokens.device)
|
||||
lables_attention_mask = inputs.attention_mask.to(motion_tokens.device)
|
||||
|
||||
# print(labels_input_ids[0:5])
|
||||
|
||||
outputs = self.language_model(input_ids=labels_input_ids,
|
||||
attention_mask=lables_attention_mask,
|
||||
labels=inputs["input_ids"])
|
||||
|
||||
return outputs
|
||||
|
||||
def generate_direct(self,
|
||||
texts: List[str],
|
||||
max_length: int = 256,
|
||||
num_beams: int = 1,
|
||||
do_sample: bool = True,
|
||||
bad_words_ids: List[int] = None):
|
||||
|
||||
# Device
|
||||
self.device = self.language_model.device
|
||||
|
||||
# Tokenize
|
||||
if self.lm_type == 'dec':
|
||||
texts = [text + " \n " for text in texts]
|
||||
|
||||
source_encoding = self.tokenizer(texts,
|
||||
padding='max_length',
|
||||
max_length=self.max_length,
|
||||
truncation=True,
|
||||
return_attention_mask=True,
|
||||
add_special_tokens=True,
|
||||
return_tensors="pt")
|
||||
|
||||
source_input_ids = source_encoding.input_ids.to(self.device)
|
||||
source_attention_mask = source_encoding.attention_mask.to(self.device)
|
||||
|
||||
if self.lm_type == 'encdec':
|
||||
outputs = self.language_model.generate(
|
||||
source_input_ids,
|
||||
max_length=max_length,
|
||||
num_beams=num_beams,
|
||||
do_sample=do_sample,
|
||||
bad_words_ids=bad_words_ids,
|
||||
)
|
||||
elif self.lm_type == 'dec':
|
||||
outputs = self.language_model.generate(
|
||||
input_ids=source_input_ids,
|
||||
attention_mask=source_attention_mask,
|
||||
pad_token_id=self.tokenizer.pad_token_id,
|
||||
do_sample=do_sample,
|
||||
max_new_tokens=max_length)
|
||||
self.tokenizer.padding_side = 'left'
|
||||
|
||||
outputs_string = self.tokenizer.batch_decode(outputs,
|
||||
skip_special_tokens=True)
|
||||
|
||||
print(texts[:2])
|
||||
print(outputs_string[:2])
|
||||
|
||||
outputs_tokens, cleaned_text = self.motion_string_to_token(
|
||||
outputs_string)
|
||||
|
||||
return outputs_tokens, cleaned_text
|
||||
|
||||
def generate_conditional(self,
|
||||
texts: Optional[List[str]] = None,
|
||||
motion_tokens: Optional[Tensor] = None,
|
||||
lengths: Optional[List[int]] = None,
|
||||
task: str = "t2m",
|
||||
with_len: bool = False,
|
||||
stage: str = 'train',
|
||||
tasks: dict = None):
|
||||
|
||||
self.device = self.language_model.device
|
||||
|
||||
if task in ["t2m", "m2m", "pred", "inbetween"]:
|
||||
|
||||
if task == "t2m":
|
||||
assert texts is not None
|
||||
motion_strings = [''] * len(texts)
|
||||
if not with_len:
|
||||
if tasks is None:
|
||||
tasks = [{
|
||||
'input':
|
||||
['Generate motion: <Caption_Placeholder>'],
|
||||
'output': ['']
|
||||
}] * len(texts)
|
||||
|
||||
lengths = [0] * len(texts)
|
||||
else:
|
||||
tasks = [{
|
||||
'input': [
|
||||
'Generate motion with <Frame_Placeholder> frames: <Caption_Placeholder>'
|
||||
],
|
||||
'output': ['']
|
||||
}] * len(texts)
|
||||
|
||||
elif task == "pred":
|
||||
assert motion_tokens is not None and lengths is not None
|
||||
texts = [''] * len(lengths)
|
||||
tasks = [{
|
||||
'input': ['Predict motion: <Motion_Placeholder_s1>'],
|
||||
'output': ['']
|
||||
}] * len(lengths)
|
||||
|
||||
motion_strings_old = self.motion_token_to_string(
|
||||
motion_tokens, lengths)
|
||||
motion_strings = []
|
||||
for i, length in enumerate(lengths):
|
||||
split = length // 5
|
||||
motion_strings.append(
|
||||
'>'.join(motion_strings_old[i].split('>')[:split]) +
|
||||
'>')
|
||||
|
||||
elif task == "inbetween":
|
||||
assert motion_tokens is not None and lengths is not None
|
||||
texts = [''] * len(lengths)
|
||||
tasks = [{
|
||||
'input': [
|
||||
"Complete the masked motion: <Motion_Placeholder_Masked>"
|
||||
],
|
||||
'output': ['']
|
||||
}] * len(lengths)
|
||||
motion_strings = self.motion_token_to_string(
|
||||
motion_tokens, lengths)
|
||||
|
||||
inputs, outputs = self.template_fulfill(tasks, lengths,
|
||||
motion_strings, texts,
|
||||
stage)
|
||||
|
||||
outputs_tokens, cleaned_text = self.generate_direct(inputs,
|
||||
max_length=128,
|
||||
num_beams=1,
|
||||
do_sample=True)
|
||||
|
||||
return outputs_tokens
|
||||
|
||||
elif task == "m2t":
|
||||
assert motion_tokens is not None and lengths is not None
|
||||
|
||||
motion_strings = self.motion_token_to_string(
|
||||
motion_tokens, lengths)
|
||||
|
||||
if not with_len:
|
||||
tasks = [{
|
||||
'input': ['Generate text: <Motion_Placeholder>'],
|
||||
'output': ['']
|
||||
}] * len(lengths)
|
||||
else:
|
||||
tasks = [{
|
||||
'input': [
|
||||
'Generate text with <Frame_Placeholder> frames: <Motion_Placeholder>'
|
||||
],
|
||||
'output': ['']
|
||||
}] * len(lengths)
|
||||
|
||||
texts = [''] * len(lengths)
|
||||
|
||||
inputs, outputs = self.template_fulfill(tasks, lengths,
|
||||
motion_strings, texts)
|
||||
outputs_tokens, cleaned_text = self.generate_direct(
|
||||
inputs,
|
||||
max_length=40,
|
||||
num_beams=1,
|
||||
do_sample=False,
|
||||
# bad_words_ids=self.bad_words_ids
|
||||
)
|
||||
return cleaned_text
|
||||
|
||||
def motion_token_to_string(self, motion_token: Tensor, lengths: List[int]):
|
||||
motion_string = []
|
||||
for i in range(len(motion_token)):
|
||||
motion_i = motion_token[i].cpu(
|
||||
) if motion_token[i].device.type == 'cuda' else motion_token[i]
|
||||
motion_list = motion_i.tolist()[:lengths[i]]
|
||||
motion_string.append(
|
||||
(f'<motion_id_{self.m_codebook_size}>' +
|
||||
''.join([f'<motion_id_{int(i)}>' for i in motion_list]) +
|
||||
f'<motion_id_{self.m_codebook_size + 1}>'))
|
||||
return motion_string
|
||||
|
||||
def motion_token_list_to_string(self, motion_token: Tensor):
|
||||
motion_string = []
|
||||
for i in range(len(motion_token)):
|
||||
motion_i = motion_token[i].cpu(
|
||||
) if motion_token[i].device.type == 'cuda' else motion_token[i]
|
||||
motion_list = motion_i.tolist()
|
||||
motion_string.append(
|
||||
(f'<motion_id_{self.m_codebook_size}>' +
|
||||
''.join([f'<motion_id_{int(i)}>' for i in motion_list]) +
|
||||
f'<motion_id_{self.m_codebook_size + 1}>'))
|
||||
return motion_string
|
||||
|
||||
def motion_string_to_token(self, motion_string: List[str]):
|
||||
motion_tokens = []
|
||||
output_string = []
|
||||
for i in range(len(motion_string)):
|
||||
string = self.get_middle_str(
|
||||
motion_string[i], f'<motion_id_{self.m_codebook_size}>',
|
||||
f'<motion_id_{self.m_codebook_size + 1}>')
|
||||
string_list = string.split('><')
|
||||
token_list = [
|
||||
int(i.split('_')[-1].replace('>', ''))
|
||||
for i in string_list[1:-1]
|
||||
]
|
||||
if len(token_list) == 0:
|
||||
token_list = [0]
|
||||
token_list_padded = torch.tensor(token_list,
|
||||
dtype=int).to(self.device)
|
||||
motion_tokens.append(token_list_padded)
|
||||
output_string.append(motion_string[i].replace(
|
||||
string, '<Motion_Placeholder>'))
|
||||
|
||||
return motion_tokens, output_string
|
||||
|
||||
def placeholder_fulfill(self, prompt: str, length: int, motion_string: str,
|
||||
text: str):
|
||||
|
||||
seconds = math.floor(length / self.framerate)
|
||||
motion_splited = motion_string.split('>')
|
||||
token_length = length / self.down_t
|
||||
predict_head = int(token_length * self.predict_ratio + 1)
|
||||
masked_head = int(token_length * self.inbetween_ratio + 1)
|
||||
masked_tail = int(token_length * (1 - self.inbetween_ratio) + 1)
|
||||
|
||||
motion_predict_head = '>'.join(
|
||||
motion_splited[:predict_head]
|
||||
) + f'><motion_id_{self.m_codebook_size+1}>'
|
||||
motion_predict_last = f'<motion_id_{self.m_codebook_size}>' + '>'.join(
|
||||
motion_splited[predict_head:])
|
||||
|
||||
motion_masked = '>'.join(
|
||||
motion_splited[:masked_head]
|
||||
) + '>' + f'<motion_id_{self.m_codebook_size+2}>' * (
|
||||
masked_tail - masked_head) + '>'.join(motion_splited[masked_tail:])
|
||||
|
||||
if random.random() < self.quota_ratio:
|
||||
text = f'\"{text}\"'
|
||||
|
||||
prompt = prompt.replace('<Caption_Placeholder>', text).replace(
|
||||
'<Motion_Placeholder>',
|
||||
motion_string).replace('<Frame_Placeholder>', f'{length}').replace(
|
||||
'<Second_Placeholder>', '%.1f' % seconds).replace(
|
||||
'<Motion_Placeholder_s1>', motion_predict_head).replace(
|
||||
'<Motion_Placeholder_s2>',
|
||||
motion_predict_last).replace(
|
||||
'<Motion_Placeholder_Masked>', motion_masked)
|
||||
|
||||
return prompt
|
||||
|
||||
def template_fulfill(self,
|
||||
tasks,
|
||||
lengths,
|
||||
motion_strings,
|
||||
texts,
|
||||
stage='test'):
|
||||
inputs = []
|
||||
outputs = []
|
||||
for i in range(len(lengths)):
|
||||
input_template = random.choice(tasks[i]['input'])
|
||||
output_template = random.choice(tasks[i]['output'])
|
||||
length = lengths[i]
|
||||
inputs.append(
|
||||
self.placeholder_fulfill(input_template, length,
|
||||
motion_strings[i], texts[i]))
|
||||
outputs.append(
|
||||
self.placeholder_fulfill(output_template, length,
|
||||
motion_strings[i], texts[i]))
|
||||
|
||||
return inputs, outputs
|
||||
|
||||
def get_middle_str(self, content, startStr, endStr):
|
||||
try:
|
||||
startIndex = content.index(startStr)
|
||||
if startIndex >= 0:
|
||||
startIndex += len(startStr)
|
||||
endIndex = content.index(endStr)
|
||||
except:
|
||||
return f'<motion_id_{self.m_codebook_size}><motion_id_0><motion_id_{self.m_codebook_size+1}>'
|
||||
|
||||
return f'<motion_id_{self.m_codebook_size}>' + content[
|
||||
startIndex:endIndex] + f'<motion_id_{self.m_codebook_size+1}>'
|
||||
|
||||
def random_spans_noise_mask(self, length):
|
||||
# From https://github.com/google-research/text-to-text-transfer-transformer/blob/84f8bcc14b5f2c03de51bd3587609ba8f6bbd1cd/t5/data/preprocessors.py
|
||||
|
||||
orig_length = length
|
||||
|
||||
num_noise_tokens = int(np.round(length * self.noise_density))
|
||||
# avoid degeneracy by ensuring positive numbers of noise and nonnoise tokens.
|
||||
num_noise_tokens = min(max(num_noise_tokens, 1), length - 1)
|
||||
num_noise_spans = int(
|
||||
np.round(num_noise_tokens / self.mean_noise_span_length))
|
||||
|
||||
# avoid degeneracy by ensuring positive number of noise spans
|
||||
num_noise_spans = max(num_noise_spans, 1)
|
||||
num_nonnoise_tokens = length - num_noise_tokens
|
||||
|
||||
# pick the lengths of the noise spans and the non-noise spans
|
||||
def _random_segmentation(num_items, num_segments):
|
||||
"""Partition a sequence of items randomly into non-empty segments.
|
||||
Args:
|
||||
num_items: an integer scalar > 0
|
||||
num_segments: an integer scalar in [1, num_items]
|
||||
Returns:
|
||||
a Tensor with shape [num_segments] containing positive integers that add
|
||||
up to num_items
|
||||
"""
|
||||
mask_indices = np.arange(num_items - 1) < (num_segments - 1)
|
||||
np.random.shuffle(mask_indices)
|
||||
first_in_segment = np.pad(mask_indices, [[1, 0]])
|
||||
segment_id = np.cumsum(first_in_segment)
|
||||
# count length of sub segments assuming that list is sorted
|
||||
_, segment_length = np.unique(segment_id, return_counts=True)
|
||||
return segment_length
|
||||
|
||||
noise_span_lengths = _random_segmentation(num_noise_tokens,
|
||||
num_noise_spans)
|
||||
nonnoise_span_lengths = _random_segmentation(num_nonnoise_tokens,
|
||||
num_noise_spans)
|
||||
|
||||
interleaved_span_lengths = np.reshape(
|
||||
np.stack([nonnoise_span_lengths, noise_span_lengths], axis=1),
|
||||
[num_noise_spans * 2],
|
||||
)
|
||||
span_starts = np.cumsum(interleaved_span_lengths)[:-1]
|
||||
span_start_indicator = np.zeros((length, ), dtype=np.int8)
|
||||
span_start_indicator[span_starts] = True
|
||||
span_num = np.cumsum(span_start_indicator)
|
||||
is_noise = np.equal(span_num % 2, 1)
|
||||
|
||||
return is_noise[:orig_length]
|
||||
|
||||
def create_sentinel_ids(self, mask_indices):
|
||||
# From https://github.com/huggingface/transformers/blob/main/examples/flax/language-modeling/run_t5_mlm_flax.py
|
||||
start_indices = mask_indices - np.roll(mask_indices, 1,
|
||||
axis=-1) * mask_indices
|
||||
start_indices[:, 0] = mask_indices[:, 0]
|
||||
|
||||
sentinel_ids = np.where(start_indices != 0,
|
||||
np.cumsum(start_indices, axis=-1),
|
||||
start_indices)
|
||||
sentinel_ids = np.where(sentinel_ids != 0,
|
||||
(len(self.tokenizer) - sentinel_ids), 0)
|
||||
sentinel_ids -= mask_indices - start_indices
|
||||
|
||||
return sentinel_ids
|
||||
|
||||
def filter_input_ids(self, input_ids, sentinel_ids):
|
||||
# From https://github.com/huggingface/transformers/blob/main/examples/flax/language-modeling/run_t5_mlm_flax.py
|
||||
batch_size = input_ids.shape[0]
|
||||
|
||||
input_ids_full = np.where(sentinel_ids != 0, sentinel_ids,
|
||||
input_ids.to('cpu'))
|
||||
|
||||
# input_ids tokens and sentinel tokens are >= 0, tokens < 0 are
|
||||
# masked tokens coming after sentinel tokens and should be removed
|
||||
input_ids = input_ids_full[input_ids_full >= 0].reshape(
|
||||
(batch_size, -1))
|
||||
input_ids = np.concatenate(
|
||||
[
|
||||
input_ids,
|
||||
np.full((batch_size, 1),
|
||||
self.tokenizer.eos_token_id,
|
||||
dtype=np.int32),
|
||||
],
|
||||
axis=-1,
|
||||
)
|
||||
|
||||
input_ids = torch.tensor(input_ids, device=self.device)
|
||||
|
||||
return input_ids
|
||||
@@ -1,190 +0,0 @@
|
||||
# Partially from https://github.com/Mael-zys/T2M-GPT
|
||||
|
||||
from typing import List, Optional, Union
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
from torch import Tensor, nn
|
||||
from torch.distributions.distribution import Distribution
|
||||
from .tools.resnet import Resnet1D
|
||||
from .tools.quantize_cnn import QuantizeEMAReset, Quantizer, QuantizeEMA, QuantizeReset
|
||||
from collections import OrderedDict
|
||||
|
||||
|
||||
class VQVae(nn.Module):
|
||||
|
||||
def __init__(self,
|
||||
nfeats: int,
|
||||
quantizer: str = "ema_reset",
|
||||
code_num=512,
|
||||
code_dim=512,
|
||||
output_emb_width=512,
|
||||
down_t=3,
|
||||
stride_t=2,
|
||||
width=512,
|
||||
depth=3,
|
||||
dilation_growth_rate=3,
|
||||
norm=None,
|
||||
activation: str = "relu",
|
||||
**kwargs) -> None:
|
||||
|
||||
super().__init__()
|
||||
|
||||
self.code_dim = code_dim
|
||||
|
||||
self.encoder = Encoder(nfeats,
|
||||
output_emb_width,
|
||||
down_t,
|
||||
stride_t,
|
||||
width,
|
||||
depth,
|
||||
dilation_growth_rate,
|
||||
activation=activation,
|
||||
norm=norm)
|
||||
|
||||
self.decoder = Decoder(nfeats,
|
||||
output_emb_width,
|
||||
down_t,
|
||||
stride_t,
|
||||
width,
|
||||
depth,
|
||||
dilation_growth_rate,
|
||||
activation=activation,
|
||||
norm=norm)
|
||||
|
||||
if quantizer == "ema_reset":
|
||||
self.quantizer = QuantizeEMAReset(code_num, code_dim, mu=0.99)
|
||||
elif quantizer == "orig":
|
||||
self.quantizer = Quantizer(code_num, code_dim, beta=1.0)
|
||||
elif quantizer == "ema":
|
||||
self.quantizer = QuantizeEMA(code_num, code_dim, mu=0.99)
|
||||
elif quantizer == "reset":
|
||||
self.quantizer = QuantizeReset(code_num, code_dim)
|
||||
|
||||
def preprocess(self, x):
|
||||
# (bs, T, Jx3) -> (bs, Jx3, T)
|
||||
x = x.permute(0, 2, 1)
|
||||
return x
|
||||
|
||||
def postprocess(self, x):
|
||||
# (bs, Jx3, T) -> (bs, T, Jx3)
|
||||
x = x.permute(0, 2, 1)
|
||||
return x
|
||||
|
||||
def forward(self, features: Tensor):
|
||||
# Preprocess
|
||||
x_in = self.preprocess(features)
|
||||
|
||||
# Encode
|
||||
x_encoder = self.encoder(x_in)
|
||||
|
||||
# quantization
|
||||
x_quantized, loss, perplexity = self.quantizer(x_encoder)
|
||||
|
||||
# decoder
|
||||
x_decoder = self.decoder(x_quantized)
|
||||
x_out = self.postprocess(x_decoder)
|
||||
|
||||
return x_out, loss, perplexity
|
||||
|
||||
def encode(
|
||||
self,
|
||||
features: Tensor,
|
||||
) -> Union[Tensor, Distribution]:
|
||||
|
||||
N, T, _ = features.shape
|
||||
x_in = self.preprocess(features)
|
||||
x_encoder = self.encoder(x_in)
|
||||
x_encoder = self.postprocess(x_encoder)
|
||||
x_encoder = x_encoder.contiguous().view(-1,
|
||||
x_encoder.shape[-1]) # (NT, C)
|
||||
code_idx = self.quantizer.quantize(x_encoder)
|
||||
code_idx = code_idx.view(N, -1)
|
||||
|
||||
# latent, dist
|
||||
return code_idx, None
|
||||
|
||||
def decode(self, z: Tensor):
|
||||
|
||||
x_d = self.quantizer.dequantize(z)
|
||||
x_d = x_d.view(1, -1, self.code_dim).permute(0, 2, 1).contiguous()
|
||||
|
||||
# decoder
|
||||
x_decoder = self.decoder(x_d)
|
||||
x_out = self.postprocess(x_decoder)
|
||||
return x_out
|
||||
|
||||
|
||||
class Encoder(nn.Module):
|
||||
|
||||
def __init__(self,
|
||||
input_emb_width=3,
|
||||
output_emb_width=512,
|
||||
down_t=3,
|
||||
stride_t=2,
|
||||
width=512,
|
||||
depth=3,
|
||||
dilation_growth_rate=3,
|
||||
activation='relu',
|
||||
norm=None):
|
||||
super().__init__()
|
||||
|
||||
blocks = []
|
||||
filter_t, pad_t = stride_t * 2, stride_t // 2
|
||||
blocks.append(nn.Conv1d(input_emb_width, width, 3, 1, 1))
|
||||
blocks.append(nn.ReLU())
|
||||
|
||||
for i in range(down_t):
|
||||
input_dim = width
|
||||
block = nn.Sequential(
|
||||
nn.Conv1d(input_dim, width, filter_t, stride_t, pad_t),
|
||||
Resnet1D(width,
|
||||
depth,
|
||||
dilation_growth_rate,
|
||||
activation=activation,
|
||||
norm=norm),
|
||||
)
|
||||
blocks.append(block)
|
||||
blocks.append(nn.Conv1d(width, output_emb_width, 3, 1, 1))
|
||||
self.model = nn.Sequential(*blocks)
|
||||
|
||||
def forward(self, x):
|
||||
return self.model(x)
|
||||
|
||||
|
||||
class Decoder(nn.Module):
|
||||
|
||||
def __init__(self,
|
||||
input_emb_width=3,
|
||||
output_emb_width=512,
|
||||
down_t=3,
|
||||
stride_t=2,
|
||||
width=512,
|
||||
depth=3,
|
||||
dilation_growth_rate=3,
|
||||
activation='relu',
|
||||
norm=None):
|
||||
super().__init__()
|
||||
blocks = []
|
||||
|
||||
filter_t, pad_t = stride_t * 2, stride_t // 2
|
||||
blocks.append(nn.Conv1d(output_emb_width, width, 3, 1, 1))
|
||||
blocks.append(nn.ReLU())
|
||||
for i in range(down_t):
|
||||
out_dim = width
|
||||
block = nn.Sequential(
|
||||
Resnet1D(width,
|
||||
depth,
|
||||
dilation_growth_rate,
|
||||
reverse_dilation=True,
|
||||
activation=activation,
|
||||
norm=norm), nn.Upsample(scale_factor=2,
|
||||
mode='nearest'),
|
||||
nn.Conv1d(width, out_dim, 3, 1, 1))
|
||||
blocks.append(block)
|
||||
blocks.append(nn.Conv1d(width, width, 3, 1, 1))
|
||||
blocks.append(nn.ReLU())
|
||||
blocks.append(nn.Conv1d(width, input_emb_width, 3, 1, 1))
|
||||
self.model = nn.Sequential(*blocks)
|
||||
|
||||
def forward(self, x):
|
||||
return self.model(x)
|
||||
@@ -1,111 +0,0 @@
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
from torch.nn.utils.rnn import pack_padded_sequence
|
||||
|
||||
|
||||
class MovementConvEncoder(nn.Module):
|
||||
def __init__(self, input_size, hidden_size, output_size):
|
||||
super(MovementConvEncoder, self).__init__()
|
||||
self.main = nn.Sequential(
|
||||
nn.Conv1d(input_size, hidden_size, 4, 2, 1),
|
||||
nn.Dropout(0.2, inplace=True),
|
||||
nn.LeakyReLU(0.2, inplace=True),
|
||||
nn.Conv1d(hidden_size, output_size, 4, 2, 1),
|
||||
nn.Dropout(0.2, inplace=True),
|
||||
nn.LeakyReLU(0.2, inplace=True),
|
||||
)
|
||||
self.out_net = nn.Linear(output_size, output_size)
|
||||
# self.main.apply(init_weight)
|
||||
# self.out_net.apply(init_weight)
|
||||
|
||||
def forward(self, inputs):
|
||||
inputs = inputs.permute(0, 2, 1)
|
||||
outputs = self.main(inputs).permute(0, 2, 1)
|
||||
# print(outputs.shape)
|
||||
return self.out_net(outputs)
|
||||
|
||||
|
||||
class MotionEncoderBiGRUCo(nn.Module):
|
||||
def __init__(self, input_size, hidden_size, output_size):
|
||||
super(MotionEncoderBiGRUCo, self).__init__()
|
||||
|
||||
self.input_emb = nn.Linear(input_size, hidden_size)
|
||||
self.gru = nn.GRU(
|
||||
hidden_size, hidden_size, batch_first=True, bidirectional=True
|
||||
)
|
||||
self.output_net = nn.Sequential(
|
||||
nn.Linear(hidden_size * 2, hidden_size),
|
||||
nn.LayerNorm(hidden_size),
|
||||
nn.LeakyReLU(0.2, inplace=True),
|
||||
nn.Linear(hidden_size, output_size),
|
||||
)
|
||||
|
||||
# self.input_emb.apply(init_weight)
|
||||
# self.output_net.apply(init_weight)
|
||||
self.hidden_size = hidden_size
|
||||
self.hidden = nn.Parameter(
|
||||
torch.randn((2, 1, self.hidden_size), requires_grad=True)
|
||||
)
|
||||
|
||||
# input(batch_size, seq_len, dim)
|
||||
def forward(self, inputs, m_lens):
|
||||
num_samples = inputs.shape[0]
|
||||
|
||||
input_embs = self.input_emb(inputs)
|
||||
hidden = self.hidden.repeat(1, num_samples, 1)
|
||||
|
||||
cap_lens = m_lens.data.tolist()
|
||||
|
||||
# emb = pack_padded_sequence(input=input_embs, lengths=cap_lens, batch_first=True)
|
||||
emb = input_embs
|
||||
|
||||
gru_seq, gru_last = self.gru(emb, hidden)
|
||||
|
||||
gru_last = torch.cat([gru_last[0], gru_last[1]], dim=-1)
|
||||
|
||||
return self.output_net(gru_last)
|
||||
|
||||
|
||||
class TextEncoderBiGRUCo(nn.Module):
|
||||
def __init__(self, word_size, pos_size, hidden_size, output_size):
|
||||
super(TextEncoderBiGRUCo, self).__init__()
|
||||
|
||||
self.pos_emb = nn.Linear(pos_size, word_size)
|
||||
self.input_emb = nn.Linear(word_size, hidden_size)
|
||||
self.gru = nn.GRU(
|
||||
hidden_size, hidden_size, batch_first=True, bidirectional=True
|
||||
)
|
||||
self.output_net = nn.Sequential(
|
||||
nn.Linear(hidden_size * 2, hidden_size),
|
||||
nn.LayerNorm(hidden_size),
|
||||
nn.LeakyReLU(0.2, inplace=True),
|
||||
nn.Linear(hidden_size, output_size),
|
||||
)
|
||||
|
||||
# self.input_emb.apply(init_weight)
|
||||
# self.pos_emb.apply(init_weight)
|
||||
# self.output_net.apply(init_weight)
|
||||
# self.linear2.apply(init_weight)
|
||||
# self.batch_size = batch_size
|
||||
self.hidden_size = hidden_size
|
||||
self.hidden = nn.Parameter(
|
||||
torch.randn((2, 1, self.hidden_size), requires_grad=True)
|
||||
)
|
||||
|
||||
# input(batch_size, seq_len, dim)
|
||||
def forward(self, word_embs, pos_onehot, cap_lens):
|
||||
num_samples = word_embs.shape[0]
|
||||
|
||||
pos_embs = self.pos_emb(pos_onehot)
|
||||
inputs = word_embs + pos_embs
|
||||
input_embs = self.input_emb(inputs)
|
||||
hidden = self.hidden.repeat(1, num_samples, 1)
|
||||
|
||||
cap_lens = cap_lens.data.tolist()
|
||||
emb = pack_padded_sequence(input=input_embs, lengths=cap_lens, batch_first=True)
|
||||
|
||||
gru_seq, gru_last = self.gru(emb, hidden)
|
||||
|
||||
gru_last = torch.cat([gru_last[0], gru_last[1]], dim=-1)
|
||||
|
||||
return self.output_net(gru_last)
|
||||
@@ -1,322 +0,0 @@
|
||||
# This file is taken from signjoey repository
|
||||
import math
|
||||
|
||||
import torch
|
||||
from torch import Tensor, nn
|
||||
|
||||
|
||||
def get_activation(activation_type):
|
||||
if activation_type == "relu":
|
||||
return nn.ReLU()
|
||||
elif activation_type == "relu6":
|
||||
return nn.ReLU6()
|
||||
elif activation_type == "prelu":
|
||||
return nn.PReLU()
|
||||
elif activation_type == "selu":
|
||||
return nn.SELU()
|
||||
elif activation_type == "celu":
|
||||
return nn.CELU()
|
||||
elif activation_type == "gelu":
|
||||
return nn.GELU()
|
||||
elif activation_type == "sigmoid":
|
||||
return nn.Sigmoid()
|
||||
elif activation_type == "softplus":
|
||||
return nn.Softplus()
|
||||
elif activation_type == "softshrink":
|
||||
return nn.Softshrink()
|
||||
elif activation_type == "softsign":
|
||||
return nn.Softsign()
|
||||
elif activation_type == "tanh":
|
||||
return nn.Tanh()
|
||||
elif activation_type == "tanhshrink":
|
||||
return nn.Tanhshrink()
|
||||
else:
|
||||
raise ValueError("Unknown activation type {}".format(activation_type))
|
||||
|
||||
|
||||
class MaskedNorm(nn.Module):
|
||||
"""
|
||||
Original Code from:
|
||||
https://discuss.pytorch.org/t/batchnorm-for-different-sized-samples-in-batch/44251/8
|
||||
"""
|
||||
|
||||
def __init__(self, norm_type, num_groups, num_features):
|
||||
super().__init__()
|
||||
self.norm_type = norm_type
|
||||
if self.norm_type == "batch":
|
||||
self.norm = nn.BatchNorm1d(num_features=num_features)
|
||||
elif self.norm_type == "group":
|
||||
self.norm = nn.GroupNorm(num_groups=num_groups, num_channels=num_features)
|
||||
elif self.norm_type == "layer":
|
||||
self.norm = nn.LayerNorm(normalized_shape=num_features)
|
||||
else:
|
||||
raise ValueError("Unsupported Normalization Layer")
|
||||
|
||||
self.num_features = num_features
|
||||
|
||||
def forward(self, x: Tensor, mask: Tensor):
|
||||
if self.training:
|
||||
reshaped = x.reshape([-1, self.num_features])
|
||||
reshaped_mask = mask.reshape([-1, 1]) > 0
|
||||
selected = torch.masked_select(reshaped, reshaped_mask).reshape(
|
||||
[-1, self.num_features]
|
||||
)
|
||||
batch_normed = self.norm(selected)
|
||||
scattered = reshaped.masked_scatter(reshaped_mask, batch_normed)
|
||||
return scattered.reshape([x.shape[0], -1, self.num_features])
|
||||
else:
|
||||
reshaped = x.reshape([-1, self.num_features])
|
||||
batched_normed = self.norm(reshaped)
|
||||
return batched_normed.reshape([x.shape[0], -1, self.num_features])
|
||||
|
||||
|
||||
# TODO (Cihan): Spatial and Word Embeddings are pretty much the same
|
||||
# We might as well convert them into a single module class.
|
||||
# Only difference is the lut vs linear layers.
|
||||
class Embeddings(nn.Module):
|
||||
|
||||
"""
|
||||
Simple embeddings class
|
||||
"""
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def __init__(
|
||||
self,
|
||||
embedding_dim: int = 64,
|
||||
num_heads: int = 8,
|
||||
scale: bool = False,
|
||||
scale_factor: float = None,
|
||||
norm_type: str = None,
|
||||
activation_type: str = None,
|
||||
vocab_size: int = 0,
|
||||
padding_idx: int = 1,
|
||||
freeze: bool = False,
|
||||
**kwargs
|
||||
):
|
||||
"""
|
||||
Create new embeddings for the vocabulary.
|
||||
Use scaling for the Transformer.
|
||||
|
||||
:param embedding_dim:
|
||||
:param scale:
|
||||
:param vocab_size:
|
||||
:param padding_idx:
|
||||
:param freeze: freeze the embeddings during training
|
||||
"""
|
||||
super().__init__()
|
||||
|
||||
self.embedding_dim = embedding_dim
|
||||
self.vocab_size = vocab_size
|
||||
self.lut = nn.Embedding(vocab_size, self.embedding_dim, padding_idx=padding_idx)
|
||||
|
||||
self.norm_type = norm_type
|
||||
if self.norm_type:
|
||||
self.norm = MaskedNorm(
|
||||
norm_type=norm_type, num_groups=num_heads, num_features=embedding_dim
|
||||
)
|
||||
|
||||
self.activation_type = activation_type
|
||||
if self.activation_type:
|
||||
self.activation = get_activation(activation_type)
|
||||
|
||||
self.scale = scale
|
||||
if self.scale:
|
||||
if scale_factor:
|
||||
self.scale_factor = scale_factor
|
||||
else:
|
||||
self.scale_factor = math.sqrt(self.embedding_dim)
|
||||
|
||||
if freeze:
|
||||
freeze_params(self)
|
||||
|
||||
# pylint: disable=arguments-differ
|
||||
def forward(self, x: Tensor, mask: Tensor = None) -> Tensor:
|
||||
"""
|
||||
Perform lookup for input `x` in the embedding table.
|
||||
|
||||
:param mask: token masks
|
||||
:param x: index in the vocabulary
|
||||
:return: embedded representation for `x`
|
||||
"""
|
||||
|
||||
x = self.lut(x)
|
||||
|
||||
if self.norm_type:
|
||||
x = self.norm(x, mask)
|
||||
|
||||
if self.activation_type:
|
||||
x = self.activation(x)
|
||||
|
||||
if self.scale:
|
||||
return x * self.scale_factor
|
||||
else:
|
||||
return x
|
||||
|
||||
def __repr__(self):
|
||||
return "%s(embedding_dim=%d, vocab_size=%d)" % (
|
||||
self.__class__.__name__,
|
||||
self.embedding_dim,
|
||||
self.vocab_size,
|
||||
)
|
||||
|
||||
|
||||
class SpatialEmbeddings(nn.Module):
|
||||
|
||||
"""
|
||||
Simple Linear Projection Layer
|
||||
(For encoder outputs to predict glosses)
|
||||
"""
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def __init__(
|
||||
self,
|
||||
embedding_dim: int,
|
||||
input_size: int,
|
||||
num_heads: int,
|
||||
freeze: bool = False,
|
||||
norm_type: str = "batch",
|
||||
activation_type: str = "softsign",
|
||||
scale: bool = False,
|
||||
scale_factor: float = None,
|
||||
**kwargs
|
||||
):
|
||||
"""
|
||||
Create new embeddings for the vocabulary.
|
||||
Use scaling for the Transformer.
|
||||
|
||||
:param embedding_dim:
|
||||
:param input_size:
|
||||
:param freeze: freeze the embeddings during training
|
||||
"""
|
||||
super().__init__()
|
||||
|
||||
self.embedding_dim = embedding_dim
|
||||
self.input_size = input_size
|
||||
self.ln = nn.Linear(self.input_size, self.embedding_dim)
|
||||
|
||||
self.norm_type = norm_type
|
||||
if self.norm_type:
|
||||
self.norm = MaskedNorm(
|
||||
norm_type=norm_type, num_groups=num_heads, num_features=embedding_dim
|
||||
)
|
||||
|
||||
self.activation_type = activation_type
|
||||
if self.activation_type:
|
||||
self.activation = get_activation(activation_type)
|
||||
|
||||
self.scale = scale
|
||||
if self.scale:
|
||||
if scale_factor:
|
||||
self.scale_factor = scale_factor
|
||||
else:
|
||||
self.scale_factor = math.sqrt(self.embedding_dim)
|
||||
|
||||
if freeze:
|
||||
freeze_params(self)
|
||||
|
||||
# pylint: disable=arguments-differ
|
||||
def forward(self, x: Tensor, mask: Tensor) -> Tensor:
|
||||
"""
|
||||
:param mask: frame masks
|
||||
:param x: input frame features
|
||||
:return: embedded representation for `x`
|
||||
"""
|
||||
|
||||
x = self.ln(x)
|
||||
|
||||
if self.norm_type:
|
||||
x = self.norm(x, mask)
|
||||
|
||||
if self.activation_type:
|
||||
x = self.activation(x)
|
||||
|
||||
if self.scale:
|
||||
return x * self.scale_factor
|
||||
else:
|
||||
return x
|
||||
|
||||
def __repr__(self):
|
||||
return "%s(embedding_dim=%d, input_size=%d)" % (
|
||||
self.__class__.__name__,
|
||||
self.embedding_dim,
|
||||
self.input_size,
|
||||
)
|
||||
|
||||
def get_timestep_embedding(
|
||||
timesteps: torch.Tensor,
|
||||
embedding_dim: int,
|
||||
flip_sin_to_cos: bool = False,
|
||||
downscale_freq_shift: float = 1,
|
||||
scale: float = 1,
|
||||
max_period: int = 10000,
|
||||
):
|
||||
"""
|
||||
This matches the implementation in Denoising Diffusion Probabilistic Models: Create sinusoidal timestep embeddings.
|
||||
|
||||
:param timesteps: a 1-D Tensor of N indices, one per batch element.
|
||||
These may be fractional.
|
||||
:param embedding_dim: the dimension of the output. :param max_period: controls the minimum frequency of the
|
||||
embeddings. :return: an [N x dim] Tensor of positional embeddings.
|
||||
"""
|
||||
assert len(timesteps.shape) == 1, "Timesteps should be a 1d-array"
|
||||
|
||||
half_dim = embedding_dim // 2
|
||||
exponent = -math.log(max_period) * torch.arange(
|
||||
start=0, end=half_dim, dtype=torch.float32, device=timesteps.device
|
||||
)
|
||||
exponent = exponent / (half_dim - downscale_freq_shift)
|
||||
|
||||
emb = torch.exp(exponent)
|
||||
emb = timesteps[:, None].float() * emb[None, :]
|
||||
|
||||
# scale embeddings
|
||||
emb = scale * emb
|
||||
|
||||
# concat sine and cosine embeddings
|
||||
emb = torch.cat([torch.sin(emb), torch.cos(emb)], dim=-1)
|
||||
|
||||
# flip sine and cosine embeddings
|
||||
if flip_sin_to_cos:
|
||||
emb = torch.cat([emb[:, half_dim:], emb[:, :half_dim]], dim=-1)
|
||||
|
||||
# zero pad
|
||||
if embedding_dim % 2 == 1:
|
||||
emb = torch.nn.functional.pad(emb, (0, 1, 0, 0))
|
||||
return emb
|
||||
|
||||
|
||||
class TimestepEmbedding(nn.Module):
|
||||
def __init__(self, channel: int, time_embed_dim: int, act_fn: str = "silu"):
|
||||
super().__init__()
|
||||
|
||||
self.linear_1 = nn.Linear(channel, time_embed_dim)
|
||||
self.act = None
|
||||
if act_fn == "silu":
|
||||
self.act = nn.SiLU()
|
||||
self.linear_2 = nn.Linear(time_embed_dim, time_embed_dim)
|
||||
|
||||
def forward(self, sample):
|
||||
sample = self.linear_1(sample)
|
||||
|
||||
if self.act is not None:
|
||||
sample = self.act(sample)
|
||||
|
||||
sample = self.linear_2(sample)
|
||||
return sample
|
||||
|
||||
|
||||
class Timesteps(nn.Module):
|
||||
def __init__(self, num_channels: int, flip_sin_to_cos: bool, downscale_freq_shift: float):
|
||||
super().__init__()
|
||||
self.num_channels = num_channels
|
||||
self.flip_sin_to_cos = flip_sin_to_cos
|
||||
self.downscale_freq_shift = downscale_freq_shift
|
||||
|
||||
def forward(self, timesteps):
|
||||
t_emb = get_timestep_embedding(
|
||||
timesteps,
|
||||
self.num_channels,
|
||||
flip_sin_to_cos=self.flip_sin_to_cos,
|
||||
downscale_freq_shift=self.downscale_freq_shift,
|
||||
)
|
||||
return t_emb
|
||||
@@ -1,414 +0,0 @@
|
||||
import numpy as np
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
|
||||
class QuantizeEMAReset(nn.Module):
|
||||
def __init__(self, nb_code, code_dim, mu):
|
||||
super().__init__()
|
||||
self.nb_code = nb_code
|
||||
self.code_dim = code_dim
|
||||
self.mu = mu
|
||||
self.reset_codebook()
|
||||
|
||||
def reset_codebook(self):
|
||||
self.init = False
|
||||
self.code_sum = None
|
||||
self.code_count = None
|
||||
device = "cuda" if torch.cuda.is_available() else "cpu"
|
||||
self.register_buffer('codebook', torch.zeros(self.nb_code, self.code_dim).to(device))
|
||||
|
||||
def _tile(self, x):
|
||||
nb_code_x, code_dim = x.shape
|
||||
if nb_code_x < self.nb_code:
|
||||
n_repeats = (self.nb_code + nb_code_x - 1) // nb_code_x
|
||||
std = 0.01 / np.sqrt(code_dim)
|
||||
out = x.repeat(n_repeats, 1)
|
||||
out = out + torch.randn_like(out) * std
|
||||
else :
|
||||
out = x
|
||||
return out
|
||||
|
||||
def init_codebook(self, x):
|
||||
out = self._tile(x)
|
||||
self.codebook = out[:self.nb_code]
|
||||
self.code_sum = self.codebook.clone()
|
||||
self.code_count = torch.ones(self.nb_code, device=self.codebook.device)
|
||||
self.init = True
|
||||
|
||||
@torch.no_grad()
|
||||
def compute_perplexity(self, code_idx) :
|
||||
# Calculate new centres
|
||||
code_onehot = torch.zeros(self.nb_code, code_idx.shape[0], device=code_idx.device) # nb_code, N * L
|
||||
code_onehot.scatter_(0, code_idx.view(1, code_idx.shape[0]), 1)
|
||||
|
||||
code_count = code_onehot.sum(dim=-1) # nb_code
|
||||
prob = code_count / torch.sum(code_count)
|
||||
perplexity = torch.exp(-torch.sum(prob * torch.log(prob + 1e-7)))
|
||||
return perplexity
|
||||
|
||||
@torch.no_grad()
|
||||
def update_codebook(self, x, code_idx):
|
||||
|
||||
code_onehot = torch.zeros(self.nb_code, x.shape[0], device=x.device) # nb_code, N * L
|
||||
code_onehot.scatter_(0, code_idx.view(1, x.shape[0]), 1)
|
||||
|
||||
code_sum = torch.matmul(code_onehot, x) # nb_code, w
|
||||
code_count = code_onehot.sum(dim=-1) # nb_code
|
||||
|
||||
out = self._tile(x)
|
||||
code_rand = out[:self.nb_code]
|
||||
|
||||
# Update centres
|
||||
self.code_sum = self.mu * self.code_sum + (1. - self.mu) * code_sum # w, nb_code
|
||||
self.code_count = self.mu * self.code_count + (1. - self.mu) * code_count # nb_code
|
||||
|
||||
usage = (self.code_count.view(self.nb_code, 1) >= 1.0).float()
|
||||
code_update = self.code_sum.view(self.nb_code, self.code_dim) / self.code_count.view(self.nb_code, 1)
|
||||
|
||||
self.codebook = usage * code_update + (1 - usage) * code_rand
|
||||
prob = code_count / torch.sum(code_count)
|
||||
perplexity = torch.exp(-torch.sum(prob * torch.log(prob + 1e-7)))
|
||||
|
||||
|
||||
return perplexity
|
||||
|
||||
def preprocess(self, x):
|
||||
# NCT -> NTC -> [NT, C]
|
||||
x = x.permute(0, 2, 1).contiguous()
|
||||
x = x.view(-1, x.shape[-1])
|
||||
return x
|
||||
|
||||
def quantize(self, x):
|
||||
# Calculate latent code x_l
|
||||
k_w = self.codebook.t()
|
||||
distance = torch.sum(x ** 2, dim=-1, keepdim=True) - 2 * torch.matmul(x, k_w) + torch.sum(k_w ** 2, dim=0,
|
||||
keepdim=True) # (N * L, b)
|
||||
_, code_idx = torch.min(distance, dim=-1)
|
||||
return code_idx
|
||||
|
||||
def dequantize(self, code_idx):
|
||||
x = F.embedding(code_idx, self.codebook)
|
||||
return x
|
||||
|
||||
|
||||
def forward(self, x):
|
||||
N, width, T = x.shape
|
||||
|
||||
# Preprocess
|
||||
x = self.preprocess(x)
|
||||
|
||||
# Init codebook if not inited
|
||||
if self.training and not self.init:
|
||||
self.init_codebook(x)
|
||||
|
||||
# quantize and dequantize through bottleneck
|
||||
code_idx = self.quantize(x)
|
||||
x_d = self.dequantize(code_idx)
|
||||
|
||||
# Update embeddings
|
||||
if self.training:
|
||||
perplexity = self.update_codebook(x, code_idx)
|
||||
else :
|
||||
perplexity = self.compute_perplexity(code_idx)
|
||||
|
||||
# Loss
|
||||
commit_loss = F.mse_loss(x, x_d.detach())
|
||||
|
||||
# Passthrough
|
||||
x_d = x + (x_d - x).detach()
|
||||
|
||||
# Postprocess
|
||||
x_d = x_d.view(N, T, -1).permute(0, 2, 1).contiguous() #(N, DIM, T)
|
||||
|
||||
return x_d, commit_loss, perplexity
|
||||
|
||||
|
||||
|
||||
class Quantizer(nn.Module):
|
||||
def __init__(self, n_e, e_dim, beta):
|
||||
super(Quantizer, self).__init__()
|
||||
|
||||
self.e_dim = e_dim
|
||||
self.n_e = n_e
|
||||
self.beta = beta
|
||||
|
||||
self.embedding = nn.Embedding(self.n_e, self.e_dim)
|
||||
self.embedding.weight.data.uniform_(-1.0 / self.n_e, 1.0 / self.n_e)
|
||||
|
||||
def forward(self, z):
|
||||
|
||||
N, width, T = z.shape
|
||||
z = self.preprocess(z)
|
||||
assert z.shape[-1] == self.e_dim
|
||||
z_flattened = z.contiguous().view(-1, self.e_dim)
|
||||
|
||||
# B x V
|
||||
d = torch.sum(z_flattened ** 2, dim=1, keepdim=True) + \
|
||||
torch.sum(self.embedding.weight**2, dim=1) - 2 * \
|
||||
torch.matmul(z_flattened, self.embedding.weight.t())
|
||||
# B x 1
|
||||
min_encoding_indices = torch.argmin(d, dim=1)
|
||||
z_q = self.embedding(min_encoding_indices).view(z.shape)
|
||||
|
||||
# compute loss for embedding
|
||||
loss = torch.mean((z_q - z.detach())**2) + self.beta * \
|
||||
torch.mean((z_q.detach() - z)**2)
|
||||
|
||||
# preserve gradients
|
||||
z_q = z + (z_q - z).detach()
|
||||
z_q = z_q.view(N, T, -1).permute(0, 2, 1).contiguous() #(N, DIM, T)
|
||||
|
||||
min_encodings = F.one_hot(min_encoding_indices, self.n_e).type(z.dtype)
|
||||
e_mean = torch.mean(min_encodings, dim=0)
|
||||
perplexity = torch.exp(-torch.sum(e_mean*torch.log(e_mean + 1e-10)))
|
||||
return z_q, loss, perplexity
|
||||
|
||||
def quantize(self, z):
|
||||
|
||||
assert z.shape[-1] == self.e_dim
|
||||
|
||||
# B x V
|
||||
d = torch.sum(z ** 2, dim=1, keepdim=True) + \
|
||||
torch.sum(self.embedding.weight ** 2, dim=1) - 2 * \
|
||||
torch.matmul(z, self.embedding.weight.t())
|
||||
# B x 1
|
||||
min_encoding_indices = torch.argmin(d, dim=1)
|
||||
return min_encoding_indices
|
||||
|
||||
def dequantize(self, indices):
|
||||
|
||||
index_flattened = indices.view(-1)
|
||||
z_q = self.embedding(index_flattened)
|
||||
z_q = z_q.view(indices.shape + (self.e_dim, )).contiguous()
|
||||
return z_q
|
||||
|
||||
def preprocess(self, x):
|
||||
# NCT -> NTC -> [NT, C]
|
||||
x = x.permute(0, 2, 1).contiguous()
|
||||
x = x.view(-1, x.shape[-1])
|
||||
return x
|
||||
|
||||
|
||||
|
||||
class QuantizeReset(nn.Module):
|
||||
def __init__(self, nb_code, code_dim):
|
||||
super().__init__()
|
||||
self.nb_code = nb_code
|
||||
self.code_dim = code_dim
|
||||
self.reset_codebook()
|
||||
self.codebook = nn.Parameter(torch.randn(nb_code, code_dim))
|
||||
|
||||
def reset_codebook(self):
|
||||
self.init = False
|
||||
self.code_count = None
|
||||
|
||||
def _tile(self, x):
|
||||
nb_code_x, code_dim = x.shape
|
||||
if nb_code_x < self.nb_code:
|
||||
n_repeats = (self.nb_code + nb_code_x - 1) // nb_code_x
|
||||
std = 0.01 / np.sqrt(code_dim)
|
||||
out = x.repeat(n_repeats, 1)
|
||||
out = out + torch.randn_like(out) * std
|
||||
else :
|
||||
out = x
|
||||
return out
|
||||
|
||||
def init_codebook(self, x):
|
||||
out = self._tile(x)
|
||||
self.codebook = nn.Parameter(out[:self.nb_code])
|
||||
self.code_count = torch.ones(self.nb_code, device=self.codebook.device)
|
||||
self.init = True
|
||||
|
||||
@torch.no_grad()
|
||||
def compute_perplexity(self, code_idx) :
|
||||
# Calculate new centres
|
||||
code_onehot = torch.zeros(self.nb_code, code_idx.shape[0], device=code_idx.device) # nb_code, N * L
|
||||
code_onehot.scatter_(0, code_idx.view(1, code_idx.shape[0]), 1)
|
||||
|
||||
code_count = code_onehot.sum(dim=-1) # nb_code
|
||||
prob = code_count / torch.sum(code_count)
|
||||
perplexity = torch.exp(-torch.sum(prob * torch.log(prob + 1e-7)))
|
||||
return perplexity
|
||||
|
||||
def update_codebook(self, x, code_idx):
|
||||
|
||||
code_onehot = torch.zeros(self.nb_code, x.shape[0], device=x.device) # nb_code, N * L
|
||||
code_onehot.scatter_(0, code_idx.view(1, x.shape[0]), 1)
|
||||
|
||||
code_count = code_onehot.sum(dim=-1) # nb_code
|
||||
|
||||
out = self._tile(x)
|
||||
code_rand = out[:self.nb_code]
|
||||
|
||||
# Update centres
|
||||
self.code_count = code_count # nb_code
|
||||
usage = (self.code_count.view(self.nb_code, 1) >= 1.0).float()
|
||||
|
||||
self.codebook.data = usage * self.codebook.data + (1 - usage) * code_rand
|
||||
prob = code_count / torch.sum(code_count)
|
||||
perplexity = torch.exp(-torch.sum(prob * torch.log(prob + 1e-7)))
|
||||
|
||||
|
||||
return perplexity
|
||||
|
||||
def preprocess(self, x):
|
||||
# NCT -> NTC -> [NT, C]
|
||||
x = x.permute(0, 2, 1).contiguous()
|
||||
x = x.view(-1, x.shape[-1])
|
||||
return x
|
||||
|
||||
def quantize(self, x):
|
||||
# Calculate latent code x_l
|
||||
k_w = self.codebook.t()
|
||||
distance = torch.sum(x ** 2, dim=-1, keepdim=True) - 2 * torch.matmul(x, k_w) + torch.sum(k_w ** 2, dim=0,
|
||||
keepdim=True) # (N * L, b)
|
||||
_, code_idx = torch.min(distance, dim=-1)
|
||||
return code_idx
|
||||
|
||||
def dequantize(self, code_idx):
|
||||
x = F.embedding(code_idx, self.codebook)
|
||||
return x
|
||||
|
||||
|
||||
def forward(self, x):
|
||||
N, width, T = x.shape
|
||||
# Preprocess
|
||||
x = self.preprocess(x)
|
||||
# Init codebook if not inited
|
||||
if self.training and not self.init:
|
||||
self.init_codebook(x)
|
||||
# quantize and dequantize through bottleneck
|
||||
code_idx = self.quantize(x)
|
||||
x_d = self.dequantize(code_idx)
|
||||
# Update embeddings
|
||||
if self.training:
|
||||
perplexity = self.update_codebook(x, code_idx)
|
||||
else :
|
||||
perplexity = self.compute_perplexity(code_idx)
|
||||
|
||||
# Loss
|
||||
commit_loss = F.mse_loss(x, x_d.detach())
|
||||
|
||||
# Passthrough
|
||||
x_d = x + (x_d - x).detach()
|
||||
|
||||
# Postprocess
|
||||
x_d = x_d.view(N, T, -1).permute(0, 2, 1).contiguous() #(N, DIM, T)
|
||||
|
||||
return x_d, commit_loss, perplexity
|
||||
|
||||
|
||||
class QuantizeEMA(nn.Module):
|
||||
def __init__(self, nb_code, code_dim, mu):
|
||||
super().__init__()
|
||||
self.nb_code = nb_code
|
||||
self.code_dim = code_dim
|
||||
self.mu = mu
|
||||
self.reset_codebook()
|
||||
|
||||
def reset_codebook(self):
|
||||
self.init = False
|
||||
self.code_sum = None
|
||||
self.code_count = None
|
||||
self.register_buffer('codebook', torch.zeros(self.nb_code, self.code_dim).cuda())
|
||||
|
||||
def _tile(self, x):
|
||||
nb_code_x, code_dim = x.shape
|
||||
if nb_code_x < self.nb_code:
|
||||
n_repeats = (self.nb_code + nb_code_x - 1) // nb_code_x
|
||||
std = 0.01 / np.sqrt(code_dim)
|
||||
out = x.repeat(n_repeats, 1)
|
||||
out = out + torch.randn_like(out) * std
|
||||
else :
|
||||
out = x
|
||||
return out
|
||||
|
||||
def init_codebook(self, x):
|
||||
out = self._tile(x)
|
||||
self.codebook = out[:self.nb_code]
|
||||
self.code_sum = self.codebook.clone()
|
||||
self.code_count = torch.ones(self.nb_code, device=self.codebook.device)
|
||||
self.init = True
|
||||
|
||||
@torch.no_grad()
|
||||
def compute_perplexity(self, code_idx) :
|
||||
# Calculate new centres
|
||||
code_onehot = torch.zeros(self.nb_code, code_idx.shape[0], device=code_idx.device) # nb_code, N * L
|
||||
code_onehot.scatter_(0, code_idx.view(1, code_idx.shape[0]), 1)
|
||||
|
||||
code_count = code_onehot.sum(dim=-1) # nb_code
|
||||
prob = code_count / torch.sum(code_count)
|
||||
perplexity = torch.exp(-torch.sum(prob * torch.log(prob + 1e-7)))
|
||||
return perplexity
|
||||
|
||||
@torch.no_grad()
|
||||
def update_codebook(self, x, code_idx):
|
||||
|
||||
code_onehot = torch.zeros(self.nb_code, x.shape[0], device=x.device) # nb_code, N * L
|
||||
code_onehot.scatter_(0, code_idx.view(1, x.shape[0]), 1)
|
||||
|
||||
code_sum = torch.matmul(code_onehot, x) # nb_code, w
|
||||
code_count = code_onehot.sum(dim=-1) # nb_code
|
||||
|
||||
# Update centres
|
||||
self.code_sum = self.mu * self.code_sum + (1. - self.mu) * code_sum # w, nb_code
|
||||
self.code_count = self.mu * self.code_count + (1. - self.mu) * code_count # nb_code
|
||||
|
||||
code_update = self.code_sum.view(self.nb_code, self.code_dim) / self.code_count.view(self.nb_code, 1)
|
||||
|
||||
self.codebook = code_update
|
||||
prob = code_count / torch.sum(code_count)
|
||||
perplexity = torch.exp(-torch.sum(prob * torch.log(prob + 1e-7)))
|
||||
|
||||
return perplexity
|
||||
|
||||
def preprocess(self, x):
|
||||
# NCT -> NTC -> [NT, C]
|
||||
x = x.permute(0, 2, 1).contiguous()
|
||||
x = x.view(-1, x.shape[-1])
|
||||
return x
|
||||
|
||||
def quantize(self, x):
|
||||
# Calculate latent code x_l
|
||||
k_w = self.codebook.t()
|
||||
distance = torch.sum(x ** 2, dim=-1, keepdim=True) - 2 * torch.matmul(x, k_w) + torch.sum(k_w ** 2, dim=0,
|
||||
keepdim=True) # (N * L, b)
|
||||
_, code_idx = torch.min(distance, dim=-1)
|
||||
return code_idx
|
||||
|
||||
def dequantize(self, code_idx):
|
||||
x = F.embedding(code_idx, self.codebook)
|
||||
return x
|
||||
|
||||
|
||||
def forward(self, x):
|
||||
N, width, T = x.shape
|
||||
|
||||
# Preprocess
|
||||
x = self.preprocess(x)
|
||||
|
||||
# Init codebook if not inited
|
||||
if self.training and not self.init:
|
||||
self.init_codebook(x)
|
||||
|
||||
# quantize and dequantize through bottleneck
|
||||
code_idx = self.quantize(x)
|
||||
x_d = self.dequantize(code_idx)
|
||||
|
||||
# Update embeddings
|
||||
if self.training:
|
||||
perplexity = self.update_codebook(x, code_idx)
|
||||
else :
|
||||
perplexity = self.compute_perplexity(code_idx)
|
||||
|
||||
# Loss
|
||||
commit_loss = F.mse_loss(x, x_d.detach())
|
||||
|
||||
# Passthrough
|
||||
x_d = x + (x_d - x).detach()
|
||||
|
||||
# Postprocess
|
||||
x_d = x_d.view(N, T, -1).permute(0, 2, 1).contiguous() #(N, DIM, T)
|
||||
|
||||
return x_d, commit_loss, perplexity
|
||||
@@ -1,82 +0,0 @@
|
||||
import torch.nn as nn
|
||||
import torch
|
||||
|
||||
class nonlinearity(nn.Module):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def forward(self, x):
|
||||
# swish
|
||||
return x * torch.sigmoid(x)
|
||||
|
||||
class ResConv1DBlock(nn.Module):
|
||||
def __init__(self, n_in, n_state, dilation=1, activation='silu', norm=None, dropout=None):
|
||||
super().__init__()
|
||||
padding = dilation
|
||||
self.norm = norm
|
||||
if norm == "LN":
|
||||
self.norm1 = nn.LayerNorm(n_in)
|
||||
self.norm2 = nn.LayerNorm(n_in)
|
||||
elif norm == "GN":
|
||||
self.norm1 = nn.GroupNorm(num_groups=32, num_channels=n_in, eps=1e-6, affine=True)
|
||||
self.norm2 = nn.GroupNorm(num_groups=32, num_channels=n_in, eps=1e-6, affine=True)
|
||||
elif norm == "BN":
|
||||
self.norm1 = nn.BatchNorm1d(num_features=n_in, eps=1e-6, affine=True)
|
||||
self.norm2 = nn.BatchNorm1d(num_features=n_in, eps=1e-6, affine=True)
|
||||
|
||||
else:
|
||||
self.norm1 = nn.Identity()
|
||||
self.norm2 = nn.Identity()
|
||||
|
||||
if activation == "relu":
|
||||
self.activation1 = nn.ReLU()
|
||||
self.activation2 = nn.ReLU()
|
||||
|
||||
elif activation == "silu":
|
||||
self.activation1 = nonlinearity()
|
||||
self.activation2 = nonlinearity()
|
||||
|
||||
elif activation == "gelu":
|
||||
self.activation1 = nn.GELU()
|
||||
self.activation2 = nn.GELU()
|
||||
|
||||
|
||||
|
||||
self.conv1 = nn.Conv1d(n_in, n_state, 3, 1, padding, dilation)
|
||||
self.conv2 = nn.Conv1d(n_state, n_in, 1, 1, 0,)
|
||||
|
||||
|
||||
def forward(self, x):
|
||||
x_orig = x
|
||||
if self.norm == "LN":
|
||||
x = self.norm1(x.transpose(-2, -1))
|
||||
x = self.activation1(x.transpose(-2, -1))
|
||||
else:
|
||||
x = self.norm1(x)
|
||||
x = self.activation1(x)
|
||||
|
||||
x = self.conv1(x)
|
||||
|
||||
if self.norm == "LN":
|
||||
x = self.norm2(x.transpose(-2, -1))
|
||||
x = self.activation2(x.transpose(-2, -1))
|
||||
else:
|
||||
x = self.norm2(x)
|
||||
x = self.activation2(x)
|
||||
|
||||
x = self.conv2(x)
|
||||
x = x + x_orig
|
||||
return x
|
||||
|
||||
class Resnet1D(nn.Module):
|
||||
def __init__(self, n_in, n_depth, dilation_growth_rate=1, reverse_dilation=True, activation='relu', norm=None):
|
||||
super().__init__()
|
||||
|
||||
blocks = [ResConv1DBlock(n_in, n_in, dilation=dilation_growth_rate ** depth, activation=activation, norm=norm) for depth in range(n_depth)]
|
||||
if reverse_dilation:
|
||||
blocks = blocks[::-1]
|
||||
|
||||
self.model = nn.Sequential(*blocks)
|
||||
|
||||
def forward(self, x):
|
||||
return self.model(x)
|
||||
@@ -1,73 +0,0 @@
|
||||
|
||||
from torch import Tensor, nn
|
||||
|
||||
class NewTokenEmb(nn.Module):
|
||||
"""
|
||||
For adding new tokens to a pretrained model
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
old_embeddings: nn.Embedding,
|
||||
new_num_tokens: int = None) -> None:
|
||||
|
||||
super().__init__()
|
||||
|
||||
self.num_tokens = old_embeddings.num_embeddings + new_num_tokens
|
||||
self.old_num_tokens = old_embeddings.num_embeddings
|
||||
self.new_num_tokens = new_num_tokens
|
||||
self.embedding_dim = old_embeddings.embedding_dim
|
||||
|
||||
# For text embeddings
|
||||
self.text_embeddings = nn.Embedding(
|
||||
self.num_tokens,
|
||||
self.embedding_dim,
|
||||
device=old_embeddings.weight.device,
|
||||
dtype=old_embeddings.weight.dtype)
|
||||
with torch.no_grad():
|
||||
self.text_embeddings.weight.data[:old_embeddings.
|
||||
num_embeddings] = old_embeddings.weight.data
|
||||
self.text_embeddings.weight.data[
|
||||
self.old_num_tokens:] = torch.zeros(
|
||||
self.new_num_tokens,
|
||||
self.embedding_dim,
|
||||
dtype=old_embeddings.weight.dtype,
|
||||
device=old_embeddings.weight.device)
|
||||
self.text_embeddings.weight.requires_grad_(False)
|
||||
|
||||
# For motion embeddings
|
||||
self.motion_embeddings = nn.Embedding(
|
||||
new_num_tokens,
|
||||
self.embedding_dim,
|
||||
device=old_embeddings.weight.device,
|
||||
dtype=old_embeddings.weight.dtype)
|
||||
with torch.no_grad():
|
||||
self.motion_embeddings.weight.data[:self.
|
||||
old_num_tokens] = torch.zeros(
|
||||
new_num_tokens,
|
||||
self.embedding_dim,
|
||||
dtype=old_embeddings.weight.
|
||||
dtype,
|
||||
device=old_embeddings.
|
||||
weight.device)
|
||||
self.word2motionProj = nn.Linear(self.old_num_tokens, new_num_tokens)
|
||||
|
||||
def forward(self, input: Tensor) -> Tensor:
|
||||
|
||||
with torch.no_grad():
|
||||
self.motion_embeddings.weight.data[:self.
|
||||
old_num_tokens] = torch.zeros(
|
||||
self.new_num_tokens,
|
||||
self.embedding_dim,
|
||||
dtype=self.motion_embeddings
|
||||
.weight.dtype,
|
||||
device=self.
|
||||
motion_embeddings.weight.
|
||||
device)
|
||||
|
||||
self.motion_embeddings.weight.data[
|
||||
self.old_num_tokens:] = self.word2motionProj(
|
||||
self.text_embeddings.weight.data[:self.old_num_tokens].permute(
|
||||
1, 0)).permute(1, 0)
|
||||
|
||||
return self.text_embeddings(input) + self.motion_embeddings(input)
|
||||
|
||||
@@ -1,285 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import math
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
from torch import Tensor
|
||||
|
||||
# Took from https://github.com/joeynmt/joeynmt/blob/fb66afcbe1beef9acd59283bcc084c4d4c1e6343/joeynmt/transformer_layers.py
|
||||
|
||||
|
||||
# pylint: disable=arguments-differ
|
||||
class MultiHeadedAttention(nn.Module):
|
||||
"""
|
||||
Multi-Head Attention module from "Attention is All You Need"
|
||||
|
||||
Implementation modified from OpenNMT-py.
|
||||
https://github.com/OpenNMT/OpenNMT-py
|
||||
"""
|
||||
|
||||
def __init__(self, num_heads: int, size: int, dropout: float = 0.1):
|
||||
"""
|
||||
Create a multi-headed attention layer.
|
||||
:param num_heads: the number of heads
|
||||
:param size: model size (must be divisible by num_heads)
|
||||
:param dropout: probability of dropping a unit
|
||||
"""
|
||||
super().__init__()
|
||||
|
||||
assert size % num_heads == 0
|
||||
|
||||
self.head_size = head_size = size // num_heads
|
||||
self.model_size = size
|
||||
self.num_heads = num_heads
|
||||
|
||||
self.k_layer = nn.Linear(size, num_heads * head_size)
|
||||
self.v_layer = nn.Linear(size, num_heads * head_size)
|
||||
self.q_layer = nn.Linear(size, num_heads * head_size)
|
||||
|
||||
self.output_layer = nn.Linear(size, size)
|
||||
self.softmax = nn.Softmax(dim=-1)
|
||||
self.dropout = nn.Dropout(dropout)
|
||||
|
||||
def forward(self, k: Tensor, v: Tensor, q: Tensor, mask: Tensor = None):
|
||||
"""
|
||||
Computes multi-headed attention.
|
||||
|
||||
:param k: keys [B, M, D] with M being the sentence length.
|
||||
:param v: values [B, M, D]
|
||||
:param q: query [B, M, D]
|
||||
:param mask: optional mask [B, 1, M] or [B, M, M]
|
||||
:return:
|
||||
"""
|
||||
batch_size = k.size(0)
|
||||
num_heads = self.num_heads
|
||||
|
||||
# project the queries (q), keys (k), and values (v)
|
||||
k = self.k_layer(k)
|
||||
v = self.v_layer(v)
|
||||
q = self.q_layer(q)
|
||||
|
||||
# reshape q, k, v for our computation to [batch_size, num_heads, ..]
|
||||
k = k.view(batch_size, -1, num_heads, self.head_size).transpose(1, 2)
|
||||
v = v.view(batch_size, -1, num_heads, self.head_size).transpose(1, 2)
|
||||
q = q.view(batch_size, -1, num_heads, self.head_size).transpose(1, 2)
|
||||
|
||||
# compute scores
|
||||
q = q / math.sqrt(self.head_size)
|
||||
|
||||
# batch x num_heads x query_len x key_len
|
||||
scores = torch.matmul(q, k.transpose(2, 3))
|
||||
# torch.Size([48, 8, 183, 183])
|
||||
|
||||
# apply the mask (if we have one)
|
||||
# we add a dimension for the heads to it below: [B, 1, 1, M]
|
||||
if mask is not None:
|
||||
scores = scores.masked_fill(~mask.unsqueeze(1), float('-inf'))
|
||||
|
||||
# apply attention dropout and compute context vectors.
|
||||
attention = self.softmax(scores)
|
||||
attention = self.dropout(attention)
|
||||
# torch.Size([48, 8, 183, 183]) [bs, nheads, time, time] (for decoding)
|
||||
|
||||
# v: torch.Size([48, 8, 183, 32]) (32 is 256/8)
|
||||
# get context vector (select values with attention) and reshape
|
||||
# back to [B, M, D]
|
||||
context = torch.matmul(attention, v) # torch.Size([48, 8, 183, 32])
|
||||
context = context.transpose(1, 2).contiguous().view(
|
||||
batch_size, -1, num_heads * self.head_size)
|
||||
# torch.Size([48, 183, 256]) put back to 256 (combine the heads)
|
||||
|
||||
output = self.output_layer(context)
|
||||
# torch.Size([48, 183, 256]): 1 output per time step
|
||||
|
||||
return output
|
||||
|
||||
|
||||
# pylint: disable=arguments-differ
|
||||
class PositionwiseFeedForward(nn.Module):
|
||||
"""
|
||||
Position-wise Feed-forward layer
|
||||
Projects to ff_size and then back down to input_size.
|
||||
"""
|
||||
|
||||
def __init__(self, input_size, ff_size, dropout=0.1):
|
||||
"""
|
||||
Initializes position-wise feed-forward layer.
|
||||
:param input_size: dimensionality of the input.
|
||||
:param ff_size: dimensionality of intermediate representation
|
||||
:param dropout:
|
||||
"""
|
||||
super().__init__()
|
||||
self.layer_norm = nn.LayerNorm(input_size, eps=1e-6)
|
||||
self.pwff_layer = nn.Sequential(
|
||||
nn.Linear(input_size, ff_size),
|
||||
nn.ReLU(),
|
||||
nn.Dropout(dropout),
|
||||
nn.Linear(ff_size, input_size),
|
||||
nn.Dropout(dropout),
|
||||
)
|
||||
|
||||
def forward(self, x):
|
||||
x_norm = self.layer_norm(x)
|
||||
return self.pwff_layer(x_norm) + x
|
||||
|
||||
|
||||
# pylint: disable=arguments-differ
|
||||
class PositionalEncoding(nn.Module):
|
||||
"""
|
||||
Pre-compute position encodings (PE).
|
||||
In forward pass, this adds the position-encodings to the
|
||||
input for as many time steps as necessary.
|
||||
|
||||
Implementation based on OpenNMT-py.
|
||||
https://github.com/OpenNMT/OpenNMT-py
|
||||
"""
|
||||
|
||||
def __init__(self, size: int = 0, max_len: int = 5000):
|
||||
"""
|
||||
Positional Encoding with maximum length max_len
|
||||
:param size:
|
||||
:param max_len:
|
||||
:param dropout:
|
||||
"""
|
||||
if size % 2 != 0:
|
||||
raise ValueError("Cannot use sin/cos positional encoding with "
|
||||
"odd dim (got dim={:d})".format(size))
|
||||
pe = torch.zeros(max_len, size)
|
||||
position = torch.arange(0, max_len).unsqueeze(1)
|
||||
div_term = torch.exp((torch.arange(0, size, 2, dtype=torch.float) *
|
||||
-(math.log(10000.0) / size)))
|
||||
pe[:, 0::2] = torch.sin(position.float() * div_term)
|
||||
pe[:, 1::2] = torch.cos(position.float() * div_term)
|
||||
pe = pe.unsqueeze(0) # shape: [1, size, max_len]
|
||||
super().__init__()
|
||||
self.register_buffer('pe', pe)
|
||||
self.dim = size
|
||||
|
||||
def forward(self, emb):
|
||||
"""Embed inputs.
|
||||
Args:
|
||||
emb (FloatTensor): Sequence of word vectors
|
||||
``(seq_len, batch_size, self.dim)``
|
||||
"""
|
||||
# Add position encodings
|
||||
return emb + self.pe[:, :emb.size(1)]
|
||||
|
||||
|
||||
class TransformerEncoderLayer(nn.Module):
|
||||
"""
|
||||
One Transformer encoder layer has a Multi-head attention layer plus
|
||||
a position-wise feed-forward layer.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
size: int = 0,
|
||||
ff_size: int = 0,
|
||||
num_heads: int = 0,
|
||||
dropout: float = 0.1):
|
||||
"""
|
||||
A single Transformer layer.
|
||||
:param size:
|
||||
:param ff_size:
|
||||
:param num_heads:
|
||||
:param dropout:
|
||||
"""
|
||||
super().__init__()
|
||||
|
||||
self.layer_norm = nn.LayerNorm(size, eps=1e-6)
|
||||
self.src_src_att = MultiHeadedAttention(num_heads,
|
||||
size,
|
||||
dropout=dropout)
|
||||
self.feed_forward = PositionwiseFeedForward(size,
|
||||
ff_size=ff_size,
|
||||
dropout=dropout)
|
||||
self.dropout = nn.Dropout(dropout)
|
||||
self.size = size
|
||||
|
||||
# pylint: disable=arguments-differ
|
||||
def forward(self, x: Tensor, mask: Tensor) -> Tensor:
|
||||
"""
|
||||
Forward pass for a single transformer encoder layer.
|
||||
First applies layer norm, then self attention,
|
||||
then dropout with residual connection (adding the input to the result),
|
||||
and then a position-wise feed-forward layer.
|
||||
|
||||
:param x: layer input
|
||||
:param mask: input mask
|
||||
:return: output tensor
|
||||
"""
|
||||
x_norm = self.layer_norm(x)
|
||||
h = self.src_src_att(x_norm, x_norm, x_norm, mask)
|
||||
h = self.dropout(h) + x
|
||||
o = self.feed_forward(h)
|
||||
return o
|
||||
|
||||
|
||||
class TransformerDecoderLayer(nn.Module):
|
||||
"""
|
||||
Transformer decoder layer.
|
||||
|
||||
Consists of self-attention, source-attention, and feed-forward.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
size: int = 0,
|
||||
ff_size: int = 0,
|
||||
num_heads: int = 0,
|
||||
dropout: float = 0.1):
|
||||
"""
|
||||
Represents a single Transformer decoder layer.
|
||||
|
||||
It attends to the source representation and the previous decoder states.
|
||||
|
||||
:param size: model dimensionality
|
||||
:param ff_size: size of the feed-forward intermediate layer
|
||||
:param num_heads: number of heads
|
||||
:param dropout: dropout to apply to input
|
||||
"""
|
||||
super().__init__()
|
||||
self.size = size
|
||||
|
||||
self.trg_trg_att = MultiHeadedAttention(num_heads,
|
||||
size,
|
||||
dropout=dropout)
|
||||
self.src_trg_att = MultiHeadedAttention(num_heads,
|
||||
size,
|
||||
dropout=dropout)
|
||||
|
||||
self.feed_forward = PositionwiseFeedForward(size,
|
||||
ff_size=ff_size,
|
||||
dropout=dropout)
|
||||
|
||||
self.x_layer_norm = nn.LayerNorm(size, eps=1e-6)
|
||||
self.dec_layer_norm = nn.LayerNorm(size, eps=1e-6)
|
||||
|
||||
self.dropout = nn.Dropout(dropout)
|
||||
|
||||
# pylint: disable=arguments-differ
|
||||
def forward(self,
|
||||
x: Tensor = None,
|
||||
memory: Tensor = None,
|
||||
src_mask: Tensor = None,
|
||||
trg_mask: Tensor = None) -> Tensor:
|
||||
"""
|
||||
Forward pass of a single Transformer decoder layer.
|
||||
|
||||
:param x: inputs
|
||||
:param memory: source representations
|
||||
:param src_mask: source mask
|
||||
:param trg_mask: target mask (so as to not condition on future steps)
|
||||
:return: output tensor
|
||||
"""
|
||||
# decoder/target self-attention
|
||||
x_norm = self.x_layer_norm(x) # torch.Size([48, 183, 256])
|
||||
h1 = self.trg_trg_att(x_norm, x_norm, x_norm, mask=trg_mask)
|
||||
h1 = self.dropout(h1) + x
|
||||
|
||||
# source-target attention
|
||||
h1_norm = self.dec_layer_norm(
|
||||
h1) # torch.Size([48, 183, 256]) (same for memory)
|
||||
h2 = self.src_trg_att(memory, memory, h1_norm, mask=src_mask)
|
||||
|
||||
# final position-wise feed-forward layer
|
||||
o = self.feed_forward(self.dropout(h2) + h1)
|
||||
|
||||
return o
|
||||
@@ -1,200 +0,0 @@
|
||||
import os
|
||||
from pytorch_lightning import LightningModule, Trainer
|
||||
from pytorch_lightning.callbacks import Callback, RichProgressBar, ModelCheckpoint
|
||||
|
||||
|
||||
def build_callbacks(cfg, logger=None, phase='test', **kwargs):
|
||||
callbacks = []
|
||||
logger = logger
|
||||
|
||||
# Rich Progress Bar
|
||||
callbacks.append(progressBar())
|
||||
|
||||
# Checkpoint Callback
|
||||
if phase == 'train':
|
||||
callbacks.extend(getCheckpointCallback(cfg, logger=logger, **kwargs))
|
||||
|
||||
return callbacks
|
||||
|
||||
def getCheckpointCallback(cfg, logger=None, **kwargs):
|
||||
callbacks = []
|
||||
# Logging
|
||||
metric_monitor = {
|
||||
"loss_total": "total/train",
|
||||
"Train_jf": "recons/text2jfeats/train",
|
||||
"Val_jf": "recons/text2jfeats/val",
|
||||
"Train_rf": "recons/text2rfeats/train",
|
||||
"Val_rf": "recons/text2rfeats/val",
|
||||
"APE root": "Metrics/APE_root",
|
||||
"APE mean pose": "Metrics/APE_mean_pose",
|
||||
"AVE root": "Metrics/AVE_root",
|
||||
"AVE mean pose": "Metrics/AVE_mean_pose",
|
||||
"R_TOP_1": "Metrics/R_precision_top_1",
|
||||
"R_TOP_2": "Metrics/R_precision_top_2",
|
||||
"R_TOP_3": "Metrics/R_precision_top_3",
|
||||
"gt_R_TOP_3": "Metrics/gt_R_precision_top_3",
|
||||
"FID": "Metrics/FID",
|
||||
"gt_FID": "Metrics/gt_FID",
|
||||
"Diversity": "Metrics/Diversity",
|
||||
"MM dist": "Metrics/Matching_score",
|
||||
"Accuracy": "Metrics/accuracy",
|
||||
}
|
||||
callbacks.append(
|
||||
progressLogger(logger,metric_monitor=metric_monitor,log_every_n_steps=1))
|
||||
|
||||
# Save 10 latest checkpoints
|
||||
checkpointParams = {
|
||||
'dirpath': os.path.join(cfg.FOLDER_EXP, "checkpoints"),
|
||||
'filename': "{epoch}",
|
||||
'monitor': "step",
|
||||
'mode': "max",
|
||||
'every_n_epochs': cfg.LOGGER.VAL_EVERY_STEPS,
|
||||
'save_top_k': 8,
|
||||
'save_last': True,
|
||||
'save_on_train_epoch_end': True
|
||||
}
|
||||
callbacks.append(ModelCheckpoint(**checkpointParams))
|
||||
|
||||
# Save checkpoint every n*10 epochs
|
||||
checkpointParams.update({
|
||||
'every_n_epochs':
|
||||
cfg.LOGGER.VAL_EVERY_STEPS * 10,
|
||||
'save_top_k':
|
||||
-1,
|
||||
'save_last':
|
||||
False
|
||||
})
|
||||
callbacks.append(ModelCheckpoint(**checkpointParams))
|
||||
|
||||
metrics = cfg.METRIC.TYPE
|
||||
metric_monitor_map = {
|
||||
'TemosMetric': {
|
||||
'Metrics/APE_root': {
|
||||
'abbr': 'APEroot',
|
||||
'mode': 'min'
|
||||
},
|
||||
},
|
||||
'TM2TMetrics': {
|
||||
'Metrics/FID': {
|
||||
'abbr': 'FID',
|
||||
'mode': 'min'
|
||||
},
|
||||
'Metrics/R_precision_top_3': {
|
||||
'abbr': 'R3',
|
||||
'mode': 'max'
|
||||
}
|
||||
},
|
||||
'MRMetrics': {
|
||||
'Metrics/MPJPE': {
|
||||
'abbr': 'MPJPE',
|
||||
'mode': 'min'
|
||||
}
|
||||
},
|
||||
'HUMANACTMetrics': {
|
||||
'Metrics/Accuracy': {
|
||||
'abbr': 'Accuracy',
|
||||
'mode': 'max'
|
||||
}
|
||||
},
|
||||
'UESTCMetrics': {
|
||||
'Metrics/Accuracy': {
|
||||
'abbr': 'Accuracy',
|
||||
'mode': 'max'
|
||||
}
|
||||
},
|
||||
'UncondMetrics': {
|
||||
'Metrics/FID': {
|
||||
'abbr': 'FID',
|
||||
'mode': 'min'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
checkpointParams.update({
|
||||
'every_n_epochs': cfg.LOGGER.VAL_EVERY_STEPS,
|
||||
'save_top_k': 1,
|
||||
})
|
||||
|
||||
for metric in metrics:
|
||||
if metric in metric_monitor_map.keys():
|
||||
metric_monitors = metric_monitor_map[metric]
|
||||
|
||||
# Delete R3 if training VAE
|
||||
if cfg.TRAIN.STAGE == 'vae' and metric == 'TM2TMetrics':
|
||||
del metric_monitors['Metrics/R_precision_top_3']
|
||||
|
||||
for metric_monitor in metric_monitors:
|
||||
checkpointParams.update({
|
||||
'filename':
|
||||
metric_monitor_map[metric][metric_monitor]['mode']
|
||||
+ "-" +
|
||||
metric_monitor_map[metric][metric_monitor]['abbr']
|
||||
+ "{ep}",
|
||||
'monitor':
|
||||
metric_monitor,
|
||||
'mode':
|
||||
metric_monitor_map[metric][metric_monitor]['mode'],
|
||||
})
|
||||
callbacks.append(
|
||||
ModelCheckpoint(**checkpointParams))
|
||||
return callbacks
|
||||
|
||||
class progressBar(RichProgressBar):
|
||||
def __init__(self, ):
|
||||
super().__init__()
|
||||
|
||||
def get_metrics(self, trainer, model):
|
||||
# Don't show the version number
|
||||
items = super().get_metrics(trainer, model)
|
||||
items.pop("v_num", None)
|
||||
return items
|
||||
|
||||
class progressLogger(Callback):
|
||||
def __init__(self,
|
||||
logger,
|
||||
metric_monitor: dict,
|
||||
precision: int = 3,
|
||||
log_every_n_steps: int = 1):
|
||||
# Metric to monitor
|
||||
self.logger = logger
|
||||
self.metric_monitor = metric_monitor
|
||||
self.precision = precision
|
||||
self.log_every_n_steps = log_every_n_steps
|
||||
|
||||
def on_train_start(self, trainer: Trainer, pl_module: LightningModule,
|
||||
**kwargs) -> None:
|
||||
self.logger.info("Training started")
|
||||
|
||||
def on_train_end(self, trainer: Trainer, pl_module: LightningModule,
|
||||
**kwargs) -> None:
|
||||
self.logger.info("Training done")
|
||||
|
||||
def on_validation_epoch_end(self, trainer: Trainer,
|
||||
pl_module: LightningModule, **kwargs) -> None:
|
||||
if trainer.sanity_checking:
|
||||
self.logger.info("Sanity checking ok.")
|
||||
|
||||
def on_train_epoch_end(self,
|
||||
trainer: Trainer,
|
||||
pl_module: LightningModule,
|
||||
padding=False,
|
||||
**kwargs) -> None:
|
||||
metric_format = f"{{:.{self.precision}e}}"
|
||||
line = f"Epoch {trainer.current_epoch}"
|
||||
if padding:
|
||||
line = f"{line:>{len('Epoch xxxx')}}" # Right padding
|
||||
|
||||
if trainer.current_epoch % self.log_every_n_steps == 0:
|
||||
metrics_str = []
|
||||
|
||||
losses_dict = trainer.callback_metrics
|
||||
for metric_name, dico_name in self.metric_monitor.items():
|
||||
if dico_name in losses_dict:
|
||||
metric = losses_dict[dico_name].item()
|
||||
metric = metric_format.format(metric)
|
||||
metric = f"{metric_name} {metric}"
|
||||
metrics_str.append(metric)
|
||||
|
||||
line = line + ": " + " ".join(metrics_str)
|
||||
|
||||
self.logger.info(line)
|
||||
-217
@@ -1,217 +0,0 @@
|
||||
import importlib
|
||||
from argparse import ArgumentParser
|
||||
from omegaconf import OmegaConf
|
||||
from os.path import join as pjoin
|
||||
import os
|
||||
import glob
|
||||
|
||||
|
||||
def get_module_config(cfg, filepath="./configs"):
|
||||
"""
|
||||
Load yaml config files from subfolders
|
||||
"""
|
||||
|
||||
yamls = glob.glob(pjoin(filepath, '*', '*.yaml'))
|
||||
yamls = [y.replace(filepath, '') for y in yamls]
|
||||
for yaml in yamls:
|
||||
nodes = yaml.replace('.yaml', '').replace('/', '.')
|
||||
nodes = nodes[1:] if nodes[0] == '.' else nodes
|
||||
OmegaConf.update(cfg, nodes, OmegaConf.load('./configs' + yaml))
|
||||
|
||||
return cfg
|
||||
|
||||
|
||||
def get_obj_from_str(string, reload=False):
|
||||
"""
|
||||
Get object from string
|
||||
"""
|
||||
package_directory_name = os.path.basename(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
module, cls = string.rsplit(".", 1)
|
||||
if reload:
|
||||
module_imp = importlib.import_module(module)
|
||||
importlib.reload(module_imp)
|
||||
return getattr(importlib.import_module(module, package=package_directory_name), cls)
|
||||
|
||||
|
||||
def instantiate_from_config(config):
|
||||
"""
|
||||
Instantiate object from config
|
||||
"""
|
||||
if not "target" in config:
|
||||
raise KeyError("Expected key `target` to instantiate.")
|
||||
return get_obj_from_str(config["target"])(**config.get("params", dict()))
|
||||
|
||||
|
||||
def resume_config(cfg: OmegaConf):
|
||||
"""
|
||||
Resume model and wandb
|
||||
"""
|
||||
|
||||
if cfg.TRAIN.RESUME:
|
||||
resume = cfg.TRAIN.RESUME
|
||||
if os.path.exists(resume):
|
||||
# Checkpoints
|
||||
cfg.TRAIN.PRETRAINED = pjoin(resume, "checkpoints", "last.ckpt")
|
||||
# Wandb
|
||||
wandb_files = os.listdir(pjoin(resume, "wandb", "latest-run"))
|
||||
wandb_run = [item for item in wandb_files if "run-" in item][0]
|
||||
cfg.LOGGER.WANDB.params.id = wandb_run.replace("run-","").replace(".wandb", "")
|
||||
else:
|
||||
raise ValueError("Resume path is not right.")
|
||||
|
||||
return cfg
|
||||
|
||||
def parse_args(phase="train"):
|
||||
"""
|
||||
Parse arguments and load config files
|
||||
"""
|
||||
|
||||
parser = ArgumentParser()
|
||||
group = parser.add_argument_group("Training options")
|
||||
|
||||
# Assets
|
||||
group.add_argument(
|
||||
"--cfg_assets",
|
||||
type=str,
|
||||
required=False,
|
||||
default="./configs/assets.yaml",
|
||||
help="config file for asset paths",
|
||||
)
|
||||
|
||||
# Default config
|
||||
if phase in ["train", "test"]:
|
||||
cfg_defualt = "./configs/default.yaml"
|
||||
elif phase == "render":
|
||||
cfg_defualt = "./configs/render.yaml"
|
||||
elif phase == "webui":
|
||||
cfg_defualt = "./configs/webui.yaml"
|
||||
|
||||
group.add_argument(
|
||||
"--cfg",
|
||||
type=str,
|
||||
required=False,
|
||||
default=cfg_defualt,
|
||||
help="config file",
|
||||
)
|
||||
|
||||
# Parse for each phase
|
||||
if phase in ["train", "test"]:
|
||||
group.add_argument("--batch_size",
|
||||
type=int,
|
||||
required=False,
|
||||
help="training batch size")
|
||||
group.add_argument("--num_nodes",
|
||||
type=int,
|
||||
required=False,
|
||||
help="number of nodes")
|
||||
group.add_argument("--device",
|
||||
type=int,
|
||||
nargs="+",
|
||||
required=False,
|
||||
help="training device")
|
||||
group.add_argument("--task",
|
||||
type=str,
|
||||
required=False,
|
||||
help="evaluation task type")
|
||||
group.add_argument("--nodebug",
|
||||
action="store_true",
|
||||
required=False,
|
||||
help="debug or not")
|
||||
|
||||
|
||||
if phase == "demo":
|
||||
group.add_argument(
|
||||
"--example",
|
||||
type=str,
|
||||
required=False,
|
||||
help="input text and lengths with txt format",
|
||||
)
|
||||
group.add_argument(
|
||||
"--out_dir",
|
||||
type=str,
|
||||
required=False,
|
||||
help="output dir",
|
||||
)
|
||||
group.add_argument("--task",
|
||||
type=str,
|
||||
required=False,
|
||||
help="evaluation task type")
|
||||
|
||||
if phase == "render":
|
||||
group.add_argument("--npy",
|
||||
type=str,
|
||||
required=False,
|
||||
default=None,
|
||||
help="npy motion files")
|
||||
group.add_argument("--dir",
|
||||
type=str,
|
||||
required=False,
|
||||
default=None,
|
||||
help="npy motion folder")
|
||||
group.add_argument("--fps",
|
||||
type=int,
|
||||
required=False,
|
||||
default=30,
|
||||
help="render fps")
|
||||
group.add_argument(
|
||||
"--mode",
|
||||
type=str,
|
||||
required=False,
|
||||
default="sequence",
|
||||
help="render target: video, sequence, frame",
|
||||
)
|
||||
|
||||
params = parser.parse_args()
|
||||
|
||||
# Load yaml config files
|
||||
OmegaConf.register_new_resolver("eval", eval)
|
||||
cfg_assets = OmegaConf.load(params.cfg_assets)
|
||||
cfg_base = OmegaConf.load(pjoin(cfg_assets.CONFIG_FOLDER, 'default.yaml'))
|
||||
cfg_exp = OmegaConf.merge(cfg_base, OmegaConf.load(params.cfg))
|
||||
if not cfg_exp.FULL_CONFIG:
|
||||
cfg_exp = get_module_config(cfg_exp, cfg_assets.CONFIG_FOLDER)
|
||||
cfg = OmegaConf.merge(cfg_exp, cfg_assets)
|
||||
|
||||
# Update config with arguments
|
||||
if phase in ["train", "test"]:
|
||||
cfg.TRAIN.BATCH_SIZE = params.batch_size if params.batch_size else cfg.TRAIN.BATCH_SIZE
|
||||
cfg.DEVICE = params.device if params.device else cfg.DEVICE
|
||||
cfg.NUM_NODES = params.num_nodes if params.num_nodes else cfg.NUM_NODES
|
||||
cfg.model.params.task = params.task if params.task else cfg.model.params.task
|
||||
cfg.DEBUG = not params.nodebug if params.nodebug is not None else cfg.DEBUG
|
||||
|
||||
# Force no debug in test
|
||||
if phase == "test":
|
||||
cfg.DEBUG = False
|
||||
cfg.DEVICE = [0]
|
||||
print("Force no debugging and one gpu when testing")
|
||||
|
||||
if phase == "demo":
|
||||
cfg.DEMO.RENDER = params.render
|
||||
cfg.DEMO.FRAME_RATE = params.frame_rate
|
||||
cfg.DEMO.EXAMPLE = params.example
|
||||
cfg.DEMO.TASK = params.task
|
||||
cfg.TEST.FOLDER = params.out_dir if params.out_dir else cfg.TEST.FOLDER
|
||||
os.makedirs(cfg.TEST.FOLDER, exist_ok=True)
|
||||
|
||||
if phase == "render":
|
||||
if params.npy:
|
||||
cfg.RENDER.NPY = params.npy
|
||||
cfg.RENDER.INPUT_MODE = "npy"
|
||||
if params.dir:
|
||||
cfg.RENDER.DIR = params.dir
|
||||
cfg.RENDER.INPUT_MODE = "dir"
|
||||
if params.fps:
|
||||
cfg.RENDER.FPS = float(params.fps)
|
||||
cfg.RENDER.MODE = params.mode
|
||||
|
||||
# Debug mode
|
||||
if cfg.DEBUG:
|
||||
cfg.NAME = "debug--" + cfg.NAME
|
||||
cfg.LOGGER.WANDB.params.offline = True
|
||||
cfg.LOGGER.VAL_EVERY_STEPS = 1
|
||||
|
||||
# Resume config
|
||||
cfg = resume_config(cfg)
|
||||
|
||||
return cfg
|
||||
@@ -1,175 +0,0 @@
|
||||
NAME: Webui # Experiment name
|
||||
DEBUG: False # Debug mode
|
||||
ACCELERATOR: 'cpu' # Devices optioncal: “cpu”, “gpu”, “tpu”, “ipu”, “hpu”, “mps, “auto”
|
||||
DEVICE: [0] # Index of gpus eg. [0] or [0,1,2,3]
|
||||
|
||||
# Training configuration
|
||||
TRAIN:
|
||||
#---------------------------------
|
||||
STAGE: lm_instruct
|
||||
DATASETS: ['humanml3d'] # Training datasets
|
||||
SPLIT: 'train' # Training split name
|
||||
NUM_WORKERS: 32 # Number of workers
|
||||
BATCH_SIZE: 16 # Size of batches
|
||||
START_EPOCH: 0 # Start epochMMOTIONENCODER
|
||||
END_EPOCH: 99999 # End epoch
|
||||
ABLATION:
|
||||
pkeep: 0.5
|
||||
OPTIM:
|
||||
TYPE: AdamW # Optimizer type
|
||||
LR: 2e-4 # Learning rate
|
||||
WEIGHT_DECAY: 0.0
|
||||
LR_SCHEDULER: [100, 200, 300, 400]
|
||||
GAMMA: 0.8
|
||||
|
||||
LR_SCHEDULER:
|
||||
target: CosineAnnealingLR
|
||||
params:
|
||||
T_max: ${eval:${LOGGER.VAL_EVERY_STEPS} * 100}
|
||||
eta_min: 1e-6
|
||||
|
||||
# Evaluating Configuration
|
||||
EVAL:
|
||||
DATASETS: ['humanml3d'] # Evaluating datasets
|
||||
BATCH_SIZE: 32 # Evaluating Batch size
|
||||
NUM_WORKERS: 8 # Validation Batch size
|
||||
SPLIT: test
|
||||
|
||||
# Test Configuration
|
||||
TEST:
|
||||
CHECKPOINTS: checkpoints/MotionGPT-base/motiongpt_s3_h3d.ckpt
|
||||
DATASETS: ['humanml3d'] # training datasets
|
||||
SPLIT: test
|
||||
BATCH_SIZE: 32 # training Batch size
|
||||
MEAN: False
|
||||
NUM_SAMPLES: 1
|
||||
FACT: 1
|
||||
|
||||
# Datasets Configuration
|
||||
DATASET:
|
||||
target: .mGPT.data.HumanML3D.HumanML3DDataModule
|
||||
JOINT_TYPE: 'humanml3d' # join type
|
||||
CODE_PATH: 'VQBEST'
|
||||
TASK_ROOT: .deps/mGPT_instructions
|
||||
TASK_PATH: ''
|
||||
SMPL_PATH: .deps/smpl
|
||||
TRANSFORM_PATH: .deps/transforms/
|
||||
WORD_VERTILIZER_PATH: .deps/glove/
|
||||
NFEATS: 263
|
||||
KIT:
|
||||
ROOT: .datasets/kit-ml # KIT directory
|
||||
SPLIT_ROOT: .datasets/kit-ml # KIT splits directory
|
||||
MEAN_STD_PATH: .deps/t2m/
|
||||
MAX_MOTION_LEN: 196
|
||||
MIN_MOTION_LEN: 24
|
||||
MAX_TEXT_LEN: 20
|
||||
PICK_ONE_TEXT: true
|
||||
FRAME_RATE: 12.5
|
||||
UNIT_LEN: 4
|
||||
HUMANML3D:
|
||||
ROOT: .datasets/humanml3d # HumanML3D directory
|
||||
SPLIT_ROOT: .datasets/humanml3d # HumanML3D splits directory
|
||||
ASSETS_ROOT: .assets/meta/
|
||||
MEAN_STD_PATH: .deps/t2m/
|
||||
MAX_MOTION_LEN: 196
|
||||
MIN_MOTION_LEN: 40
|
||||
MAX_TEXT_LEN: 20
|
||||
PICK_ONE_TEXT: true
|
||||
FRAME_RATE: 20.0
|
||||
UNIT_LEN: 4
|
||||
STD_TEXT: False
|
||||
|
||||
ABLATION:
|
||||
# For MotionGPT
|
||||
use_length: False
|
||||
predict_ratio: 0.2
|
||||
inbetween_ratio: 0.25
|
||||
image_size: 256
|
||||
# For Motion-latent-diffusion
|
||||
VAE_TYPE: 'actor' # vae ablation: actor or mcross
|
||||
VAE_ARCH: 'encoder_decoder' # mdiffusion vae architecture
|
||||
PE_TYPE: 'actor' # mdiffusion mld or actor
|
||||
DIFF_PE_TYPE: 'actor' # mdiffusion mld or actor
|
||||
SKIP_CONNECT: False # skip connection for denoiser va
|
||||
MLP_DIST: False # use linear to expand mean and std rather expand token nums
|
||||
IS_DIST: False # Mcross distribution kl
|
||||
PREDICT_EPSILON: True # noise or motion
|
||||
|
||||
METRIC:
|
||||
TYPE: ['TM2TMetrics']
|
||||
TM2T:
|
||||
t2m_path: .deps/t2m/ # path for tm2t evaluator
|
||||
TASK: 't2m'
|
||||
FORCE_IN_METER: True
|
||||
DIST_SYNC_ON_STEP: True
|
||||
MM_NUM_SAMPLES: 100 # Number of samples for multimodal test
|
||||
MM_NUM_REPEATS: 30 # Number of repeats for multimodal test
|
||||
MM_NUM_TIMES: 10 # Number of times to repeat the multimodal test
|
||||
DIVERSITY_TIMES: 300 # Number of times to repeat the diversity test
|
||||
|
||||
# Losses Configuration
|
||||
LOSS:
|
||||
TYPE: t2mgpt # Losses type
|
||||
LAMBDA_FEATURE: 1.0
|
||||
LAMBDA_VELOCITY: 0.5
|
||||
LAMBDA_COMMIT: 0.02
|
||||
LAMBDA_CLS: 1.0
|
||||
LAMBDA_M2T2M: 1.0
|
||||
LAMBDA_T2M2T: 10.0
|
||||
ABLATION:
|
||||
RECONS_LOSS: 'l1_smooth'
|
||||
LAMBDA_REC: 1.0 # Lambda for reconstruction losses
|
||||
LAMBDA_JOINT: 1.0 # Lambda for joint losses
|
||||
|
||||
LAMBDA_LATENT: 1e-5 # Lambda for latent losses
|
||||
LAMBDA_KL: 1e-5 # Lambda for kl losses
|
||||
LAMBDA_GEN: 1.0 # Lambda for text-motion generation losses
|
||||
LAMBDA_CROSS: 1.0 # Lambda for cross-reconstruction losses
|
||||
LAMBDA_CYCLE: 1.0 # Lambda for cycle losses
|
||||
LAMBDA_PRIOR: 0.0 # Lambda for diffusion prior losses
|
||||
|
||||
# Model Configuration
|
||||
model:
|
||||
target: .mGPT.models.mgpt.MotionGPT
|
||||
params:
|
||||
condition: 'text'
|
||||
task: 't2m'
|
||||
lm:
|
||||
target: .mGPT.archs.mgpt_lm.MLM
|
||||
params:
|
||||
model_type: t5
|
||||
model_path: google/flan-t5-base
|
||||
stage: ${TRAIN.STAGE}
|
||||
motion_codebook_size: 512
|
||||
ablation: ${ABLATION}
|
||||
motion_vae:
|
||||
target: .mGPT.archs.mgpt_vq.VQVae
|
||||
params:
|
||||
quantizer: 'ema_reset'
|
||||
code_num: 512
|
||||
code_dim: 512
|
||||
output_emb_width: 512
|
||||
down_t: 2
|
||||
stride_t: 2
|
||||
width: 512
|
||||
depth: 3
|
||||
dilation_growth_rate: 3
|
||||
norm: None
|
||||
activation: 'relu'
|
||||
nfeats: ${DATASET.NFEATS}
|
||||
ablation: ${ABLATION}
|
||||
|
||||
# Related parameters
|
||||
stage: ${TRAIN.STAGE}
|
||||
debug: ${DEBUG}
|
||||
codebook_size: 512
|
||||
metrics_dict: ${METRIC.TYPE}
|
||||
|
||||
# Logger configuration
|
||||
LOGGER:
|
||||
LOG_EVERY_STEPS: 5
|
||||
VAL_EVERY_STEPS: 10
|
||||
TENSORBOARD: True
|
||||
wandb:
|
||||
params:
|
||||
project: null
|
||||
@@ -1,118 +0,0 @@
|
||||
import numpy as np
|
||||
import torch
|
||||
from os.path import join as pjoin
|
||||
import os
|
||||
from .humanml.utils.word_vectorizer import WordVectorizer
|
||||
from .humanml.scripts.motion_process import (process_file, recover_from_ric)
|
||||
from . import BASEDataModule
|
||||
from .humanml import Text2MotionDatasetEval, Text2MotionDataset, Text2MotionDatasetCB, MotionDataset, MotionDatasetVQ, Text2MotionDatasetToken, Text2MotionDatasetM2T
|
||||
from .utils import humanml3d_collate
|
||||
script_directory = os.path.dirname(os.path.abspath(__file__))
|
||||
class HumanML3DDataModule(BASEDataModule):
|
||||
def __init__(self, cfg, **kwargs):
|
||||
|
||||
super().__init__(collate_fn=humanml3d_collate)
|
||||
self.cfg = cfg
|
||||
self.save_hyperparameters(logger=False)
|
||||
|
||||
# Basic info of the dataset
|
||||
cfg.DATASET.JOINT_TYPE = 'humanml3d'
|
||||
self.name = "humanml3d"
|
||||
self.njoints = 22
|
||||
|
||||
# Path to the dataset
|
||||
data_root = cfg.DATASET.HUMANML3D.ROOT
|
||||
assets_root = cfg.DATASET.HUMANML3D.ASSETS_ROOT
|
||||
self.hparams.data_root = data_root
|
||||
self.hparams.text_dir = pjoin(data_root, "texts")
|
||||
self.hparams.motion_dir = pjoin(data_root, 'new_joint_vecs')
|
||||
|
||||
# Mean and std of the dataset
|
||||
self.hparams.mean = np.load(pjoin(script_directory, "mean.npy"))
|
||||
self.hparams.std = np.load(pjoin(script_directory, "std.npy"))
|
||||
|
||||
# Mean and std for fair evaluation
|
||||
self.hparams.mean_eval = np.load(pjoin(script_directory, "mean_eval.npy"))
|
||||
self.hparams.std_eval = np.load(pjoin(script_directory, "std_eval.npy"))
|
||||
|
||||
# Length of the dataset
|
||||
self.hparams.max_motion_length = cfg.DATASET.HUMANML3D.MAX_MOTION_LEN
|
||||
self.hparams.min_motion_length = cfg.DATASET.HUMANML3D.MIN_MOTION_LEN
|
||||
self.hparams.max_text_len = cfg.DATASET.HUMANML3D.MAX_TEXT_LEN
|
||||
self.hparams.unit_length = cfg.DATASET.HUMANML3D.UNIT_LEN
|
||||
|
||||
# Additional parameters
|
||||
self.hparams.debug = cfg.DEBUG
|
||||
self.hparams.stage = cfg.TRAIN.STAGE
|
||||
|
||||
# Dataset switch
|
||||
self.DatasetEval = Text2MotionDatasetEval
|
||||
|
||||
if cfg.TRAIN.STAGE == "vae":
|
||||
if cfg.model.params.motion_vae.target.split('.')[-1].lower() == "vqvae":
|
||||
self.hparams.win_size = 64
|
||||
self.Dataset = MotionDatasetVQ
|
||||
else:
|
||||
self.Dataset = MotionDataset
|
||||
elif 'lm' in cfg.TRAIN.STAGE:
|
||||
self.hparams.code_path = cfg.DATASET.CODE_PATH
|
||||
self.hparams.task_path = cfg.DATASET.TASK_PATH
|
||||
self.hparams.std_text = cfg.DATASET.HUMANML3D.STD_TEXT
|
||||
self.Dataset = Text2MotionDatasetCB
|
||||
elif cfg.TRAIN.STAGE == "token":
|
||||
self.Dataset = Text2MotionDatasetToken
|
||||
self.DatasetEval = Text2MotionDatasetToken
|
||||
elif cfg.TRAIN.STAGE == "m2t":
|
||||
self.Dataset = Text2MotionDatasetM2T
|
||||
self.DatasetEval = Text2MotionDatasetM2T
|
||||
else:
|
||||
self.Dataset = Text2MotionDataset
|
||||
|
||||
# Get additional info of the dataset
|
||||
self.nfeats = 263
|
||||
cfg.DATASET.NFEATS = self.nfeats
|
||||
|
||||
|
||||
def feats2joints(self, features):
|
||||
mean = torch.tensor(self.hparams.mean).to(features)
|
||||
std = torch.tensor(self.hparams.std).to(features)
|
||||
features = features * std + mean
|
||||
return recover_from_ric(features, self.njoints)
|
||||
|
||||
def joints2feats(self, features):
|
||||
features = process_file(features, self.njoints)[0]
|
||||
return features
|
||||
|
||||
def normalize(self, features):
|
||||
mean = torch.tensor(self.hparams.mean).to(features)
|
||||
std = torch.tensor(self.hparams.std).to(features)
|
||||
features = (features - mean) / std
|
||||
return features
|
||||
|
||||
def denormalize(self, features):
|
||||
mean = torch.tensor(self.hparams.mean).to(features)
|
||||
std = torch.tensor(self.hparams.std).to(features)
|
||||
features = features * std + mean
|
||||
return features
|
||||
|
||||
def renorm4t2m(self, features):
|
||||
# renorm to t2m norms for using t2m evaluators
|
||||
ori_mean = torch.tensor(self.hparams.mean).to(features)
|
||||
ori_std = torch.tensor(self.hparams.std).to(features)
|
||||
eval_mean = torch.tensor(self.hparams.mean_eval).to(features)
|
||||
eval_std = torch.tensor(self.hparams.std_eval).to(features)
|
||||
features = features * ori_std + ori_mean
|
||||
features = (features - eval_mean) / eval_std
|
||||
return features
|
||||
|
||||
def mm_mode(self, mm_on=True):
|
||||
if mm_on:
|
||||
self.is_mm = True
|
||||
self.name_list = self.test_dataset.name_list
|
||||
self.mm_list = np.random.choice(self.name_list,
|
||||
self.cfg.METRIC.MM_NUM_SAMPLES,
|
||||
replace=False)
|
||||
self.test_dataset.name_list = self.mm_list
|
||||
else:
|
||||
self.is_mm = False
|
||||
self.test_dataset.name_list = self.name_list
|
||||
@@ -1,88 +0,0 @@
|
||||
import numpy as np
|
||||
import torch
|
||||
from os.path import join as pjoin
|
||||
from .humanml.utils.word_vectorizer import WordVectorizer
|
||||
from .humanml.scripts.motion_process import (process_file, recover_from_ric)
|
||||
from .HumanML3D import HumanML3DDataModule
|
||||
from .humanml import Text2MotionDatasetEval, Text2MotionDataset, Text2MotionDatasetCB, MotionDataset, MotionDatasetVQ, Text2MotionDatasetToken
|
||||
|
||||
|
||||
class KitDataModule(HumanML3DDataModule):
|
||||
def __init__(self, cfg, **kwargs):
|
||||
|
||||
super().__init__(cfg, **kwargs)
|
||||
|
||||
# Basic info of the dataset
|
||||
self.name = "kit"
|
||||
self.njoints = 21
|
||||
|
||||
# Path to the dataset
|
||||
data_root = cfg.DATASET.KIT.ROOT
|
||||
self.hparams.data_root = data_root
|
||||
self.hparams.text_dir = pjoin(data_root, "texts")
|
||||
self.hparams.motion_dir = pjoin(data_root, 'new_joint_vecs')
|
||||
|
||||
# Mean and std of the dataset
|
||||
dis_data_root = pjoin(cfg.DATASET.KIT.MEAN_STD_PATH, 'kit',
|
||||
"VQVAEV3_CB1024_CMT_H1024_NRES3", "meta")
|
||||
self.hparams.mean = np.load(pjoin(dis_data_root, "mean.npy"))
|
||||
self.hparams.std = np.load(pjoin(dis_data_root, "std.npy"))
|
||||
|
||||
# Mean and std for fair evaluation
|
||||
dis_data_root_eval = pjoin(cfg.DATASET.KIT.MEAN_STD_PATH, 't2m',
|
||||
"Comp_v6_KLD005", "meta")
|
||||
self.hparams.mean_eval = np.load(pjoin(dis_data_root_eval, "mean.npy"))
|
||||
self.hparams.std_eval = np.load(pjoin(dis_data_root_eval, "std.npy"))
|
||||
|
||||
# Length of the dataset
|
||||
self.hparams.max_motion_length = cfg.DATASET.KIT.MAX_MOTION_LEN
|
||||
self.hparams.min_motion_length = cfg.DATASET.KIT.MIN_MOTION_LEN
|
||||
self.hparams.max_text_len = cfg.DATASET.KIT.MAX_TEXT_LEN
|
||||
self.hparams.unit_length = cfg.DATASET.KIT.UNIT_LEN
|
||||
|
||||
# Get additional info of the dataset
|
||||
self._sample_set = self.get_sample_set(overrides={"split": "test", "tiny": True})
|
||||
self.nfeats = self._sample_set.nfeats
|
||||
cfg.DATASET.NFEATS = self.nfeats
|
||||
|
||||
def feats2joints(self, features):
|
||||
mean = torch.tensor(self.hparams.mean).to(features)
|
||||
std = torch.tensor(self.hparams.std).to(features)
|
||||
features = features * std + mean
|
||||
return recover_from_ric(features, self.njoints)
|
||||
|
||||
def joints2feats(self, features):
|
||||
features = process_file(features, self.njoints)[0]
|
||||
# mean = torch.tensor(self.hparams.mean).to(features)
|
||||
# std = torch.tensor(self.hparams.std).to(features)
|
||||
# features = (features - mean) / std
|
||||
return features
|
||||
|
||||
def normalize(self, features):
|
||||
mean = torch.tensor(self.hparams.mean).to(features)
|
||||
std = torch.tensor(self.hparams.std).to(features)
|
||||
features = (features - mean) / std
|
||||
return features
|
||||
|
||||
def renorm4t2m(self, features):
|
||||
# renorm to t2m norms for using t2m evaluators
|
||||
ori_mean = torch.tensor(self.hparams.mean).to(features)
|
||||
ori_std = torch.tensor(self.hparams.std).to(features)
|
||||
eval_mean = torch.tensor(self.hparams.mean_eval).to(features)
|
||||
eval_std = torch.tensor(self.hparams.std_eval).to(features)
|
||||
features = features * ori_std + ori_mean
|
||||
features = (features - eval_mean) / eval_std
|
||||
return features
|
||||
|
||||
def mm_mode(self, mm_on=True):
|
||||
# random select samples for mm
|
||||
if mm_on:
|
||||
self.is_mm = True
|
||||
self.name_list = self.test_dataset.name_list
|
||||
self.mm_list = np.random.choice(self.name_list,
|
||||
self.cfg.METRIC.MM_NUM_SAMPLES,
|
||||
replace=False)
|
||||
self.test_dataset.name_list = self.mm_list
|
||||
else:
|
||||
self.is_mm = False
|
||||
self.test_dataset.name_list = self.name_list
|
||||
@@ -1,103 +0,0 @@
|
||||
import pytorch_lightning as pl
|
||||
from torch.utils.data import DataLoader
|
||||
|
||||
|
||||
class BASEDataModule(pl.LightningDataModule):
|
||||
def __init__(self, collate_fn):
|
||||
super().__init__()
|
||||
|
||||
self.dataloader_options = {"collate_fn": collate_fn}
|
||||
self.persistent_workers = True
|
||||
self.is_mm = False
|
||||
|
||||
self._train_dataset = None
|
||||
self._val_dataset = None
|
||||
self._test_dataset = None
|
||||
|
||||
def get_sample_set(self, overrides={}):
|
||||
sample_params = self.hparams.copy()
|
||||
sample_params.update(overrides)
|
||||
return self.DatasetEval(**sample_params)
|
||||
|
||||
@property
|
||||
def train_dataset(self):
|
||||
if self._train_dataset is None:
|
||||
self._train_dataset = self.Dataset(split=self.cfg.TRAIN.SPLIT,
|
||||
**self.hparams)
|
||||
return self._train_dataset
|
||||
|
||||
@property
|
||||
def val_dataset(self):
|
||||
if self._val_dataset is None:
|
||||
params = self.hparams.copy()
|
||||
params['code_path'] = None
|
||||
params['split'] = self.cfg.EVAL.SPLIT
|
||||
self._val_dataset = self.DatasetEval(**params)
|
||||
return self._val_dataset
|
||||
|
||||
@property
|
||||
def test_dataset(self):
|
||||
if self._test_dataset is None:
|
||||
# self._test_dataset = self.DatasetEval(split=self.cfg.TEST.SPLIT,
|
||||
# **self.hparams)
|
||||
params = self.hparams.copy()
|
||||
params['code_path'] = None
|
||||
params['split'] = self.cfg.TEST.SPLIT
|
||||
self._test_dataset = self.DatasetEval( **params)
|
||||
return self._test_dataset
|
||||
|
||||
def setup(self, stage=None):
|
||||
# Use the getter the first time to load the data
|
||||
if stage in (None, "fit"):
|
||||
_ = self.train_dataset
|
||||
_ = self.val_dataset
|
||||
if stage in (None, "test"):
|
||||
_ = self.test_dataset
|
||||
|
||||
def train_dataloader(self):
|
||||
dataloader_options = self.dataloader_options.copy()
|
||||
dataloader_options["batch_size"] = self.cfg.TRAIN.BATCH_SIZE
|
||||
dataloader_options["num_workers"] = self.cfg.TRAIN.NUM_WORKERS
|
||||
return DataLoader(
|
||||
self.train_dataset,
|
||||
shuffle=False,
|
||||
persistent_workers=True,
|
||||
**dataloader_options,
|
||||
)
|
||||
|
||||
def predict_dataloader(self):
|
||||
dataloader_options = self.dataloader_options.copy()
|
||||
dataloader_options[
|
||||
"batch_size"] = 1 if self.is_mm else self.cfg.TEST.BATCH_SIZE
|
||||
dataloader_options["num_workers"] = self.cfg.TEST.NUM_WORKERS
|
||||
dataloader_options["shuffle"] = False
|
||||
return DataLoader(
|
||||
self.test_dataset,
|
||||
persistent_workers=True,
|
||||
**dataloader_options,
|
||||
)
|
||||
|
||||
def val_dataloader(self):
|
||||
# overrides batch_size and num_workers
|
||||
dataloader_options = self.dataloader_options.copy()
|
||||
dataloader_options["batch_size"] = self.cfg.EVAL.BATCH_SIZE
|
||||
dataloader_options["num_workers"] = self.cfg.EVAL.NUM_WORKERS
|
||||
dataloader_options["shuffle"] = False
|
||||
return DataLoader(
|
||||
self.val_dataset,
|
||||
persistent_workers=True,
|
||||
**dataloader_options,
|
||||
)
|
||||
|
||||
def test_dataloader(self):
|
||||
# overrides batch_size and num_workers
|
||||
dataloader_options = self.dataloader_options.copy()
|
||||
dataloader_options[
|
||||
"batch_size"] = 1 if self.is_mm else self.cfg.TEST.BATCH_SIZE
|
||||
dataloader_options["num_workers"] = self.cfg.TEST.NUM_WORKERS
|
||||
dataloader_options["shuffle"] = False
|
||||
return DataLoader(
|
||||
self.test_dataset,
|
||||
persistent_workers=True,
|
||||
**dataloader_options,
|
||||
)
|
||||
@@ -1,15 +0,0 @@
|
||||
from omegaconf import OmegaConf
|
||||
from os.path import join as pjoin
|
||||
from ..config import instantiate_from_config
|
||||
|
||||
|
||||
def build_data(cfg, phase="train"):
|
||||
data_config = OmegaConf.to_container(cfg.DATASET, resolve=True)
|
||||
data_config['params'] = {'cfg': cfg, 'phase': phase}
|
||||
if isinstance(data_config['target'], str):
|
||||
return instantiate_from_config(data_config)
|
||||
elif isinstance(data_config['target'], list):
|
||||
data_config_tmp = data_config.copy()
|
||||
data_config_tmp['params']['dataModules'] = data_config['target']
|
||||
data_config_tmp['target'] = 'mGPT.data.Concat.ConcatDataModule'
|
||||
return instantiate_from_config(data_config)
|
||||
@@ -1 +0,0 @@
|
||||
This code is based on https://github.com/EricGuo5513/text-to-motion.git
|
||||
@@ -1,7 +0,0 @@
|
||||
from .dataset_t2m import Text2MotionDataset
|
||||
from .dataset_t2m_eval import Text2MotionDatasetEval
|
||||
from .dataset_t2m_cb import Text2MotionDatasetCB
|
||||
from .dataset_t2m_token import Text2MotionDatasetToken
|
||||
from .dataset_t2m_m2t import Text2MotionDatasetM2T
|
||||
from .dataset_m import MotionDataset
|
||||
from .dataset_m_vq import MotionDatasetVQ
|
||||
@@ -1,423 +0,0 @@
|
||||
# Copyright (c) 2018-present, Facebook, Inc.
|
||||
# All rights reserved.
|
||||
#
|
||||
# This source code is licensed under the license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
#
|
||||
|
||||
import torch
|
||||
import numpy as np
|
||||
|
||||
_EPS4 = np.finfo(float).eps * 4.0
|
||||
|
||||
_FLOAT_EPS = np.finfo(np.float64).eps
|
||||
|
||||
# PyTorch-backed implementations
|
||||
def qinv(q):
|
||||
assert q.shape[-1] == 4, 'q must be a tensor of shape (*, 4)'
|
||||
mask = torch.ones_like(q)
|
||||
mask[..., 1:] = -mask[..., 1:]
|
||||
return q * mask
|
||||
|
||||
|
||||
def qinv_np(q):
|
||||
assert q.shape[-1] == 4, 'q must be a tensor of shape (*, 4)'
|
||||
return qinv(torch.from_numpy(q).float()).numpy()
|
||||
|
||||
|
||||
def qnormalize(q):
|
||||
assert q.shape[-1] == 4, 'q must be a tensor of shape (*, 4)'
|
||||
return q / torch.norm(q, dim=-1, keepdim=True)
|
||||
|
||||
|
||||
def qmul(q, r):
|
||||
"""
|
||||
Multiply quaternion(s) q with quaternion(s) r.
|
||||
Expects two equally-sized tensors of shape (*, 4), where * denotes any number of dimensions.
|
||||
Returns q*r as a tensor of shape (*, 4).
|
||||
"""
|
||||
assert q.shape[-1] == 4
|
||||
assert r.shape[-1] == 4
|
||||
|
||||
original_shape = q.shape
|
||||
|
||||
# Compute outer product
|
||||
terms = torch.bmm(r.view(-1, 4, 1), q.view(-1, 1, 4))
|
||||
|
||||
w = terms[:, 0, 0] - terms[:, 1, 1] - terms[:, 2, 2] - terms[:, 3, 3]
|
||||
x = terms[:, 0, 1] + terms[:, 1, 0] - terms[:, 2, 3] + terms[:, 3, 2]
|
||||
y = terms[:, 0, 2] + terms[:, 1, 3] + terms[:, 2, 0] - terms[:, 3, 1]
|
||||
z = terms[:, 0, 3] - terms[:, 1, 2] + terms[:, 2, 1] + terms[:, 3, 0]
|
||||
return torch.stack((w, x, y, z), dim=1).view(original_shape)
|
||||
|
||||
|
||||
def qrot(q, v):
|
||||
"""
|
||||
Rotate vector(s) v about the rotation described by quaternion(s) q.
|
||||
Expects a tensor of shape (*, 4) for q and a tensor of shape (*, 3) for v,
|
||||
where * denotes any number of dimensions.
|
||||
Returns a tensor of shape (*, 3).
|
||||
"""
|
||||
assert q.shape[-1] == 4
|
||||
assert v.shape[-1] == 3
|
||||
assert q.shape[:-1] == v.shape[:-1]
|
||||
|
||||
original_shape = list(v.shape)
|
||||
# print(q.shape)
|
||||
q = q.contiguous().view(-1, 4)
|
||||
v = v.contiguous().view(-1, 3)
|
||||
|
||||
qvec = q[:, 1:]
|
||||
uv = torch.cross(qvec, v, dim=1)
|
||||
uuv = torch.cross(qvec, uv, dim=1)
|
||||
return (v + 2 * (q[:, :1] * uv + uuv)).view(original_shape)
|
||||
|
||||
|
||||
def qeuler(q, order, epsilon=0, deg=True):
|
||||
"""
|
||||
Convert quaternion(s) q to Euler angles.
|
||||
Expects a tensor of shape (*, 4), where * denotes any number of dimensions.
|
||||
Returns a tensor of shape (*, 3).
|
||||
"""
|
||||
assert q.shape[-1] == 4
|
||||
|
||||
original_shape = list(q.shape)
|
||||
original_shape[-1] = 3
|
||||
q = q.view(-1, 4)
|
||||
|
||||
q0 = q[:, 0]
|
||||
q1 = q[:, 1]
|
||||
q2 = q[:, 2]
|
||||
q3 = q[:, 3]
|
||||
|
||||
if order == 'xyz':
|
||||
x = torch.atan2(2 * (q0 * q1 - q2 * q3), 1 - 2 * (q1 * q1 + q2 * q2))
|
||||
y = torch.asin(torch.clamp(2 * (q1 * q3 + q0 * q2), -1 + epsilon, 1 - epsilon))
|
||||
z = torch.atan2(2 * (q0 * q3 - q1 * q2), 1 - 2 * (q2 * q2 + q3 * q3))
|
||||
elif order == 'yzx':
|
||||
x = torch.atan2(2 * (q0 * q1 - q2 * q3), 1 - 2 * (q1 * q1 + q3 * q3))
|
||||
y = torch.atan2(2 * (q0 * q2 - q1 * q3), 1 - 2 * (q2 * q2 + q3 * q3))
|
||||
z = torch.asin(torch.clamp(2 * (q1 * q2 + q0 * q3), -1 + epsilon, 1 - epsilon))
|
||||
elif order == 'zxy':
|
||||
x = torch.asin(torch.clamp(2 * (q0 * q1 + q2 * q3), -1 + epsilon, 1 - epsilon))
|
||||
y = torch.atan2(2 * (q0 * q2 - q1 * q3), 1 - 2 * (q1 * q1 + q2 * q2))
|
||||
z = torch.atan2(2 * (q0 * q3 - q1 * q2), 1 - 2 * (q1 * q1 + q3 * q3))
|
||||
elif order == 'xzy':
|
||||
x = torch.atan2(2 * (q0 * q1 + q2 * q3), 1 - 2 * (q1 * q1 + q3 * q3))
|
||||
y = torch.atan2(2 * (q0 * q2 + q1 * q3), 1 - 2 * (q2 * q2 + q3 * q3))
|
||||
z = torch.asin(torch.clamp(2 * (q0 * q3 - q1 * q2), -1 + epsilon, 1 - epsilon))
|
||||
elif order == 'yxz':
|
||||
x = torch.asin(torch.clamp(2 * (q0 * q1 - q2 * q3), -1 + epsilon, 1 - epsilon))
|
||||
y = torch.atan2(2 * (q1 * q3 + q0 * q2), 1 - 2 * (q1 * q1 + q2 * q2))
|
||||
z = torch.atan2(2 * (q1 * q2 + q0 * q3), 1 - 2 * (q1 * q1 + q3 * q3))
|
||||
elif order == 'zyx':
|
||||
x = torch.atan2(2 * (q0 * q1 + q2 * q3), 1 - 2 * (q1 * q1 + q2 * q2))
|
||||
y = torch.asin(torch.clamp(2 * (q0 * q2 - q1 * q3), -1 + epsilon, 1 - epsilon))
|
||||
z = torch.atan2(2 * (q0 * q3 + q1 * q2), 1 - 2 * (q2 * q2 + q3 * q3))
|
||||
else:
|
||||
raise
|
||||
|
||||
if deg:
|
||||
return torch.stack((x, y, z), dim=1).view(original_shape) * 180 / np.pi
|
||||
else:
|
||||
return torch.stack((x, y, z), dim=1).view(original_shape)
|
||||
|
||||
|
||||
# Numpy-backed implementations
|
||||
|
||||
def qmul_np(q, r):
|
||||
q = torch.from_numpy(q).contiguous().float()
|
||||
r = torch.from_numpy(r).contiguous().float()
|
||||
return qmul(q, r).numpy()
|
||||
|
||||
|
||||
def qrot_np(q, v):
|
||||
q = torch.from_numpy(q).contiguous().float()
|
||||
v = torch.from_numpy(v).contiguous().float()
|
||||
return qrot(q, v).numpy()
|
||||
|
||||
|
||||
def qeuler_np(q, order, epsilon=0, use_gpu=False):
|
||||
if use_gpu:
|
||||
q = torch.from_numpy(q).cuda().float()
|
||||
return qeuler(q, order, epsilon).cpu().numpy()
|
||||
else:
|
||||
q = torch.from_numpy(q).contiguous().float()
|
||||
return qeuler(q, order, epsilon).numpy()
|
||||
|
||||
|
||||
def qfix(q):
|
||||
"""
|
||||
Enforce quaternion continuity across the time dimension by selecting
|
||||
the representation (q or -q) with minimal distance (or, equivalently, maximal dot product)
|
||||
between two consecutive frames.
|
||||
|
||||
Expects a tensor of shape (L, J, 4), where L is the sequence length and J is the number of joints.
|
||||
Returns a tensor of the same shape.
|
||||
"""
|
||||
assert len(q.shape) == 3
|
||||
assert q.shape[-1] == 4
|
||||
|
||||
result = q.copy()
|
||||
dot_products = np.sum(q[1:] * q[:-1], axis=2)
|
||||
mask = dot_products < 0
|
||||
mask = (np.cumsum(mask, axis=0) % 2).astype(bool)
|
||||
result[1:][mask] *= -1
|
||||
return result
|
||||
|
||||
|
||||
def euler2quat(e, order, deg=True):
|
||||
"""
|
||||
Convert Euler angles to quaternions.
|
||||
"""
|
||||
assert e.shape[-1] == 3
|
||||
|
||||
original_shape = list(e.shape)
|
||||
original_shape[-1] = 4
|
||||
|
||||
e = e.view(-1, 3)
|
||||
|
||||
## if euler angles in degrees
|
||||
if deg:
|
||||
e = e * np.pi / 180.
|
||||
|
||||
x = e[:, 0]
|
||||
y = e[:, 1]
|
||||
z = e[:, 2]
|
||||
|
||||
rx = torch.stack((torch.cos(x / 2), torch.sin(x / 2), torch.zeros_like(x), torch.zeros_like(x)), dim=1)
|
||||
ry = torch.stack((torch.cos(y / 2), torch.zeros_like(y), torch.sin(y / 2), torch.zeros_like(y)), dim=1)
|
||||
rz = torch.stack((torch.cos(z / 2), torch.zeros_like(z), torch.zeros_like(z), torch.sin(z / 2)), dim=1)
|
||||
|
||||
result = None
|
||||
for coord in order:
|
||||
if coord == 'x':
|
||||
r = rx
|
||||
elif coord == 'y':
|
||||
r = ry
|
||||
elif coord == 'z':
|
||||
r = rz
|
||||
else:
|
||||
raise
|
||||
if result is None:
|
||||
result = r
|
||||
else:
|
||||
result = qmul(result, r)
|
||||
|
||||
# Reverse antipodal representation to have a non-negative "w"
|
||||
if order in ['xyz', 'yzx', 'zxy']:
|
||||
result *= -1
|
||||
|
||||
return result.view(original_shape)
|
||||
|
||||
|
||||
def expmap_to_quaternion(e):
|
||||
"""
|
||||
Convert axis-angle rotations (aka exponential maps) to quaternions.
|
||||
Stable formula from "Practical Parameterization of Rotations Using the Exponential Map".
|
||||
Expects a tensor of shape (*, 3), where * denotes any number of dimensions.
|
||||
Returns a tensor of shape (*, 4).
|
||||
"""
|
||||
assert e.shape[-1] == 3
|
||||
|
||||
original_shape = list(e.shape)
|
||||
original_shape[-1] = 4
|
||||
e = e.reshape(-1, 3)
|
||||
|
||||
theta = np.linalg.norm(e, axis=1).reshape(-1, 1)
|
||||
w = np.cos(0.5 * theta).reshape(-1, 1)
|
||||
xyz = 0.5 * np.sinc(0.5 * theta / np.pi) * e
|
||||
return np.concatenate((w, xyz), axis=1).reshape(original_shape)
|
||||
|
||||
|
||||
def euler_to_quaternion(e, order):
|
||||
"""
|
||||
Convert Euler angles to quaternions.
|
||||
"""
|
||||
assert e.shape[-1] == 3
|
||||
|
||||
original_shape = list(e.shape)
|
||||
original_shape[-1] = 4
|
||||
|
||||
e = e.reshape(-1, 3)
|
||||
|
||||
x = e[:, 0]
|
||||
y = e[:, 1]
|
||||
z = e[:, 2]
|
||||
|
||||
rx = np.stack((np.cos(x / 2), np.sin(x / 2), np.zeros_like(x), np.zeros_like(x)), axis=1)
|
||||
ry = np.stack((np.cos(y / 2), np.zeros_like(y), np.sin(y / 2), np.zeros_like(y)), axis=1)
|
||||
rz = np.stack((np.cos(z / 2), np.zeros_like(z), np.zeros_like(z), np.sin(z / 2)), axis=1)
|
||||
|
||||
result = None
|
||||
for coord in order:
|
||||
if coord == 'x':
|
||||
r = rx
|
||||
elif coord == 'y':
|
||||
r = ry
|
||||
elif coord == 'z':
|
||||
r = rz
|
||||
else:
|
||||
raise
|
||||
if result is None:
|
||||
result = r
|
||||
else:
|
||||
result = qmul_np(result, r)
|
||||
|
||||
# Reverse antipodal representation to have a non-negative "w"
|
||||
if order in ['xyz', 'yzx', 'zxy']:
|
||||
result *= -1
|
||||
|
||||
return result.reshape(original_shape)
|
||||
|
||||
|
||||
def quaternion_to_matrix(quaternions):
|
||||
"""
|
||||
Convert rotations given as quaternions to rotation matrices.
|
||||
Args:
|
||||
quaternions: quaternions with real part first,
|
||||
as tensor of shape (..., 4).
|
||||
Returns:
|
||||
Rotation matrices as tensor of shape (..., 3, 3).
|
||||
"""
|
||||
r, i, j, k = torch.unbind(quaternions, -1)
|
||||
two_s = 2.0 / (quaternions * quaternions).sum(-1)
|
||||
|
||||
o = torch.stack(
|
||||
(
|
||||
1 - two_s * (j * j + k * k),
|
||||
two_s * (i * j - k * r),
|
||||
two_s * (i * k + j * r),
|
||||
two_s * (i * j + k * r),
|
||||
1 - two_s * (i * i + k * k),
|
||||
two_s * (j * k - i * r),
|
||||
two_s * (i * k - j * r),
|
||||
two_s * (j * k + i * r),
|
||||
1 - two_s * (i * i + j * j),
|
||||
),
|
||||
-1,
|
||||
)
|
||||
return o.reshape(quaternions.shape[:-1] + (3, 3))
|
||||
|
||||
|
||||
def quaternion_to_matrix_np(quaternions):
|
||||
q = torch.from_numpy(quaternions).contiguous().float()
|
||||
return quaternion_to_matrix(q).numpy()
|
||||
|
||||
|
||||
def quaternion_to_cont6d_np(quaternions):
|
||||
rotation_mat = quaternion_to_matrix_np(quaternions)
|
||||
cont_6d = np.concatenate([rotation_mat[..., 0], rotation_mat[..., 1]], axis=-1)
|
||||
return cont_6d
|
||||
|
||||
|
||||
def quaternion_to_cont6d(quaternions):
|
||||
rotation_mat = quaternion_to_matrix(quaternions)
|
||||
cont_6d = torch.cat([rotation_mat[..., 0], rotation_mat[..., 1]], dim=-1)
|
||||
return cont_6d
|
||||
|
||||
|
||||
def cont6d_to_matrix(cont6d):
|
||||
assert cont6d.shape[-1] == 6, "The last dimension must be 6"
|
||||
x_raw = cont6d[..., 0:3]
|
||||
y_raw = cont6d[..., 3:6]
|
||||
|
||||
x = x_raw / torch.norm(x_raw, dim=-1, keepdim=True)
|
||||
z = torch.cross(x, y_raw, dim=-1)
|
||||
z = z / torch.norm(z, dim=-1, keepdim=True)
|
||||
|
||||
y = torch.cross(z, x, dim=-1)
|
||||
|
||||
x = x[..., None]
|
||||
y = y[..., None]
|
||||
z = z[..., None]
|
||||
|
||||
mat = torch.cat([x, y, z], dim=-1)
|
||||
return mat
|
||||
|
||||
|
||||
def cont6d_to_matrix_np(cont6d):
|
||||
q = torch.from_numpy(cont6d).contiguous().float()
|
||||
return cont6d_to_matrix(q).numpy()
|
||||
|
||||
|
||||
def qpow(q0, t, dtype=torch.float):
|
||||
''' q0 : tensor of quaternions
|
||||
t: tensor of powers
|
||||
'''
|
||||
q0 = qnormalize(q0)
|
||||
theta0 = torch.acos(q0[..., 0])
|
||||
|
||||
## if theta0 is close to zero, add epsilon to avoid NaNs
|
||||
mask = (theta0 <= 10e-10) * (theta0 >= -10e-10)
|
||||
theta0 = (1 - mask) * theta0 + mask * 10e-10
|
||||
v0 = q0[..., 1:] / torch.sin(theta0).view(-1, 1)
|
||||
|
||||
if isinstance(t, torch.Tensor):
|
||||
q = torch.zeros(t.shape + q0.shape)
|
||||
theta = t.view(-1, 1) * theta0.view(1, -1)
|
||||
else: ## if t is a number
|
||||
q = torch.zeros(q0.shape)
|
||||
theta = t * theta0
|
||||
|
||||
q[..., 0] = torch.cos(theta)
|
||||
q[..., 1:] = v0 * torch.sin(theta).unsqueeze(-1)
|
||||
|
||||
return q.to(dtype)
|
||||
|
||||
|
||||
def qslerp(q0, q1, t):
|
||||
'''
|
||||
q0: starting quaternion
|
||||
q1: ending quaternion
|
||||
t: array of points along the way
|
||||
|
||||
Returns:
|
||||
Tensor of Slerps: t.shape + q0.shape
|
||||
'''
|
||||
|
||||
q0 = qnormalize(q0)
|
||||
q1 = qnormalize(q1)
|
||||
q_ = qpow(qmul(q1, qinv(q0)), t)
|
||||
|
||||
return qmul(q_,
|
||||
q0.contiguous().view(torch.Size([1] * len(t.shape)) + q0.shape).expand(t.shape + q0.shape).contiguous())
|
||||
|
||||
|
||||
def qbetween(v0, v1):
|
||||
'''
|
||||
find the quaternion used to rotate v0 to v1
|
||||
'''
|
||||
assert v0.shape[-1] == 3, 'v0 must be of the shape (*, 3)'
|
||||
assert v1.shape[-1] == 3, 'v1 must be of the shape (*, 3)'
|
||||
|
||||
v = torch.cross(v0, v1)
|
||||
w = torch.sqrt((v0 ** 2).sum(dim=-1, keepdim=True) * (v1 ** 2).sum(dim=-1, keepdim=True)) + (v0 * v1).sum(dim=-1,
|
||||
keepdim=True)
|
||||
return qnormalize(torch.cat([w, v], dim=-1))
|
||||
|
||||
|
||||
def qbetween_np(v0, v1):
|
||||
'''
|
||||
find the quaternion used to rotate v0 to v1
|
||||
'''
|
||||
assert v0.shape[-1] == 3, 'v0 must be of the shape (*, 3)'
|
||||
assert v1.shape[-1] == 3, 'v1 must be of the shape (*, 3)'
|
||||
|
||||
v0 = torch.from_numpy(v0).float()
|
||||
v1 = torch.from_numpy(v1).float()
|
||||
return qbetween(v0, v1).numpy()
|
||||
|
||||
|
||||
def lerp(p0, p1, t):
|
||||
if not isinstance(t, torch.Tensor):
|
||||
t = torch.Tensor([t])
|
||||
|
||||
new_shape = t.shape + p0.shape
|
||||
new_view_t = t.shape + torch.Size([1] * len(p0.shape))
|
||||
new_view_p = torch.Size([1] * len(t.shape)) + p0.shape
|
||||
p0 = p0.view(new_view_p).expand(new_shape)
|
||||
p1 = p1.view(new_view_p).expand(new_shape)
|
||||
t = t.view(new_view_t).expand(new_shape)
|
||||
|
||||
return p0 + t * (p1 - p0)
|
||||
@@ -1,199 +0,0 @@
|
||||
from .quaternion import *
|
||||
import scipy.ndimage.filters as filters
|
||||
|
||||
class Skeleton(object):
|
||||
def __init__(self, offset, kinematic_tree, device):
|
||||
self.device = device
|
||||
self._raw_offset_np = offset.numpy()
|
||||
self._raw_offset = offset.clone().detach().to(device).float()
|
||||
self._kinematic_tree = kinematic_tree
|
||||
self._offset = None
|
||||
self._parents = [0] * len(self._raw_offset)
|
||||
self._parents[0] = -1
|
||||
for chain in self._kinematic_tree:
|
||||
for j in range(1, len(chain)):
|
||||
self._parents[chain[j]] = chain[j-1]
|
||||
|
||||
def njoints(self):
|
||||
return len(self._raw_offset)
|
||||
|
||||
def offset(self):
|
||||
return self._offset
|
||||
|
||||
def set_offset(self, offsets):
|
||||
self._offset = offsets.clone().detach().to(self.device).float()
|
||||
|
||||
def kinematic_tree(self):
|
||||
return self._kinematic_tree
|
||||
|
||||
def parents(self):
|
||||
return self._parents
|
||||
|
||||
# joints (batch_size, joints_num, 3)
|
||||
def get_offsets_joints_batch(self, joints):
|
||||
assert len(joints.shape) == 3
|
||||
_offsets = self._raw_offset.expand(joints.shape[0], -1, -1).clone()
|
||||
for i in range(1, self._raw_offset.shape[0]):
|
||||
_offsets[:, i] = torch.norm(joints[:, i] - joints[:, self._parents[i]], p=2, dim=1)[:, None] * _offsets[:, i]
|
||||
|
||||
self._offset = _offsets.detach()
|
||||
return _offsets
|
||||
|
||||
# joints (joints_num, 3)
|
||||
def get_offsets_joints(self, joints):
|
||||
assert len(joints.shape) == 2
|
||||
_offsets = self._raw_offset.clone()
|
||||
for i in range(1, self._raw_offset.shape[0]):
|
||||
# print(joints.shape)
|
||||
_offsets[i] = torch.norm(joints[i] - joints[self._parents[i]], p=2, dim=0) * _offsets[i]
|
||||
|
||||
self._offset = _offsets.detach()
|
||||
return _offsets
|
||||
|
||||
# face_joint_idx should follow the order of right hip, left hip, right shoulder, left shoulder
|
||||
# joints (batch_size, joints_num, 3)
|
||||
def inverse_kinematics_np(self, joints, face_joint_idx, smooth_forward=False):
|
||||
assert len(face_joint_idx) == 4
|
||||
'''Get Forward Direction'''
|
||||
l_hip, r_hip, sdr_r, sdr_l = face_joint_idx
|
||||
across1 = joints[:, r_hip] - joints[:, l_hip]
|
||||
across2 = joints[:, sdr_r] - joints[:, sdr_l]
|
||||
across = across1 + across2
|
||||
across = across / np.sqrt((across**2).sum(axis=-1))[:, np.newaxis]
|
||||
# print(across1.shape, across2.shape)
|
||||
|
||||
# forward (batch_size, 3)
|
||||
forward = np.cross(np.array([[0, 1, 0]]), across, axis=-1)
|
||||
if smooth_forward:
|
||||
forward = filters.gaussian_filter1d(forward, 20, axis=0, mode='nearest')
|
||||
# forward (batch_size, 3)
|
||||
forward = forward / np.sqrt((forward**2).sum(axis=-1))[..., np.newaxis]
|
||||
|
||||
'''Get Root Rotation'''
|
||||
target = np.array([[0,0,1]]).repeat(len(forward), axis=0)
|
||||
root_quat = qbetween_np(forward, target)
|
||||
|
||||
'''Inverse Kinematics'''
|
||||
# quat_params (batch_size, joints_num, 4)
|
||||
# print(joints.shape[:-1])
|
||||
quat_params = np.zeros(joints.shape[:-1] + (4,))
|
||||
# print(quat_params.shape)
|
||||
root_quat[0] = np.array([[1.0, 0.0, 0.0, 0.0]])
|
||||
quat_params[:, 0] = root_quat
|
||||
# quat_params[0, 0] = np.array([[1.0, 0.0, 0.0, 0.0]])
|
||||
for chain in self._kinematic_tree:
|
||||
R = root_quat
|
||||
for j in range(len(chain) - 1):
|
||||
# (batch, 3)
|
||||
u = self._raw_offset_np[chain[j+1]][np.newaxis,...].repeat(len(joints), axis=0)
|
||||
# print(u.shape)
|
||||
# (batch, 3)
|
||||
v = joints[:, chain[j+1]] - joints[:, chain[j]]
|
||||
v = v / np.sqrt((v**2).sum(axis=-1))[:, np.newaxis]
|
||||
# print(u.shape, v.shape)
|
||||
rot_u_v = qbetween_np(u, v)
|
||||
|
||||
R_loc = qmul_np(qinv_np(R), rot_u_v)
|
||||
|
||||
quat_params[:,chain[j + 1], :] = R_loc
|
||||
R = qmul_np(R, R_loc)
|
||||
|
||||
return quat_params
|
||||
|
||||
# Be sure root joint is at the beginning of kinematic chains
|
||||
def forward_kinematics(self, quat_params, root_pos, skel_joints=None, do_root_R=True):
|
||||
# quat_params (batch_size, joints_num, 4)
|
||||
# joints (batch_size, joints_num, 3)
|
||||
# root_pos (batch_size, 3)
|
||||
if skel_joints is not None:
|
||||
offsets = self.get_offsets_joints_batch(skel_joints)
|
||||
if len(self._offset.shape) == 2:
|
||||
offsets = self._offset.expand(quat_params.shape[0], -1, -1)
|
||||
joints = torch.zeros(quat_params.shape[:-1] + (3,)).to(self.device)
|
||||
joints[:, 0] = root_pos
|
||||
for chain in self._kinematic_tree:
|
||||
if do_root_R:
|
||||
R = quat_params[:, 0]
|
||||
else:
|
||||
R = torch.tensor([[1.0, 0.0, 0.0, 0.0]]).expand(len(quat_params), -1).detach().to(self.device)
|
||||
for i in range(1, len(chain)):
|
||||
R = qmul(R, quat_params[:, chain[i]])
|
||||
offset_vec = offsets[:, chain[i]]
|
||||
joints[:, chain[i]] = qrot(R, offset_vec) + joints[:, chain[i-1]]
|
||||
return joints
|
||||
|
||||
# Be sure root joint is at the beginning of kinematic chains
|
||||
def forward_kinematics_np(self, quat_params, root_pos, skel_joints=None, do_root_R=True):
|
||||
# quat_params (batch_size, joints_num, 4)
|
||||
# joints (batch_size, joints_num, 3)
|
||||
# root_pos (batch_size, 3)
|
||||
if skel_joints is not None:
|
||||
skel_joints = torch.from_numpy(skel_joints)
|
||||
offsets = self.get_offsets_joints_batch(skel_joints)
|
||||
if len(self._offset.shape) == 2:
|
||||
offsets = self._offset.expand(quat_params.shape[0], -1, -1)
|
||||
offsets = offsets.numpy()
|
||||
joints = np.zeros(quat_params.shape[:-1] + (3,))
|
||||
joints[:, 0] = root_pos
|
||||
for chain in self._kinematic_tree:
|
||||
if do_root_R:
|
||||
R = quat_params[:, 0]
|
||||
else:
|
||||
R = np.array([[1.0, 0.0, 0.0, 0.0]]).repeat(len(quat_params), axis=0)
|
||||
for i in range(1, len(chain)):
|
||||
R = qmul_np(R, quat_params[:, chain[i]])
|
||||
offset_vec = offsets[:, chain[i]]
|
||||
joints[:, chain[i]] = qrot_np(R, offset_vec) + joints[:, chain[i - 1]]
|
||||
return joints
|
||||
|
||||
def forward_kinematics_cont6d_np(self, cont6d_params, root_pos, skel_joints=None, do_root_R=True):
|
||||
# cont6d_params (batch_size, joints_num, 6)
|
||||
# joints (batch_size, joints_num, 3)
|
||||
# root_pos (batch_size, 3)
|
||||
if skel_joints is not None:
|
||||
skel_joints = torch.from_numpy(skel_joints)
|
||||
offsets = self.get_offsets_joints_batch(skel_joints)
|
||||
if len(self._offset.shape) == 2:
|
||||
offsets = self._offset.expand(cont6d_params.shape[0], -1, -1)
|
||||
offsets = offsets.numpy()
|
||||
joints = np.zeros(cont6d_params.shape[:-1] + (3,))
|
||||
joints[:, 0] = root_pos
|
||||
for chain in self._kinematic_tree:
|
||||
if do_root_R:
|
||||
matR = cont6d_to_matrix_np(cont6d_params[:, 0])
|
||||
else:
|
||||
matR = np.eye(3)[np.newaxis, :].repeat(len(cont6d_params), axis=0)
|
||||
for i in range(1, len(chain)):
|
||||
matR = np.matmul(matR, cont6d_to_matrix_np(cont6d_params[:, chain[i]]))
|
||||
offset_vec = offsets[:, chain[i]][..., np.newaxis]
|
||||
# print(matR.shape, offset_vec.shape)
|
||||
joints[:, chain[i]] = np.matmul(matR, offset_vec).squeeze(-1) + joints[:, chain[i-1]]
|
||||
return joints
|
||||
|
||||
def forward_kinematics_cont6d(self, cont6d_params, root_pos, skel_joints=None, do_root_R=True):
|
||||
# cont6d_params (batch_size, joints_num, 6)
|
||||
# joints (batch_size, joints_num, 3)
|
||||
# root_pos (batch_size, 3)
|
||||
if skel_joints is not None:
|
||||
# skel_joints = torch.from_numpy(skel_joints)
|
||||
offsets = self.get_offsets_joints_batch(skel_joints)
|
||||
if len(self._offset.shape) == 2:
|
||||
offsets = self._offset.expand(cont6d_params.shape[0], -1, -1)
|
||||
joints = torch.zeros(cont6d_params.shape[:-1] + (3,)).to(cont6d_params.device)
|
||||
joints[..., 0, :] = root_pos
|
||||
for chain in self._kinematic_tree:
|
||||
if do_root_R:
|
||||
matR = cont6d_to_matrix(cont6d_params[:, 0])
|
||||
else:
|
||||
matR = torch.eye(3).expand((len(cont6d_params), -1, -1)).detach().to(cont6d_params.device)
|
||||
for i in range(1, len(chain)):
|
||||
matR = torch.matmul(matR, cont6d_to_matrix(cont6d_params[:, chain[i]]))
|
||||
offset_vec = offsets[:, chain[i]].unsqueeze(-1)
|
||||
# print(matR.shape, offset_vec.shape)
|
||||
joints[:, chain[i]] = torch.matmul(matR, offset_vec).squeeze(-1) + joints[:, chain[i-1]]
|
||||
return joints
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,156 +0,0 @@
|
||||
import os
|
||||
import rich
|
||||
import random
|
||||
import pickle
|
||||
import codecs as cs
|
||||
import numpy as np
|
||||
from torch.utils import data
|
||||
from rich.progress import track
|
||||
from os.path import join as pjoin
|
||||
|
||||
|
||||
class MotionDataset(data.Dataset):
|
||||
def __init__(
|
||||
self,
|
||||
data_root,
|
||||
split,
|
||||
mean,
|
||||
std,
|
||||
max_motion_length=196,
|
||||
min_motion_length=20,
|
||||
unit_length=4,
|
||||
fps=20,
|
||||
tmpFile=True,
|
||||
tiny=False,
|
||||
debug=False,
|
||||
**kwargs,
|
||||
):
|
||||
|
||||
# restrian the length of motion and text
|
||||
self.max_motion_length = max_motion_length
|
||||
self.min_motion_length = min_motion_length
|
||||
self.unit_length = unit_length
|
||||
|
||||
# Data mean and std
|
||||
self.mean = mean
|
||||
self.std = std
|
||||
|
||||
# Data path
|
||||
split_file = pjoin(data_root, split + '.txt')
|
||||
motion_dir = pjoin(data_root, 'new_joint_vecs')
|
||||
text_dir = pjoin(data_root, 'texts')
|
||||
|
||||
# Data id list
|
||||
self.id_list = []
|
||||
with cs.open(split_file, "r") as f:
|
||||
for line in f.readlines():
|
||||
self.id_list.append(line.strip())
|
||||
|
||||
# Debug mode
|
||||
if tiny or debug:
|
||||
enumerator = enumerate(
|
||||
track(
|
||||
self.id_list,
|
||||
f"Loading HumanML3D {split}",
|
||||
))
|
||||
maxdata = 100
|
||||
subset = '_tiny'
|
||||
else:
|
||||
enumerator = enumerate(self.id_list)
|
||||
maxdata = 1e10
|
||||
subset = ''
|
||||
|
||||
new_name_list = []
|
||||
motion_dict = {}
|
||||
|
||||
# Fast loading
|
||||
if os.path.exists(pjoin(data_root, f'tmp/{split}{subset}_motion.pkl')):
|
||||
with rich.progress.open(pjoin(data_root, f'tmp/{split}{subset}_motion.pkl'),
|
||||
'rb', description=f"Loading HumanML3D {split}") as file:
|
||||
motion_dict = pickle.load(file)
|
||||
with open(pjoin(data_root, f'tmp/{split}{subset}_index.pkl'), 'rb') as file:
|
||||
new_name_list = pickle.load(file)
|
||||
else:
|
||||
for idx, name in enumerator:
|
||||
if len(new_name_list) > maxdata:
|
||||
break
|
||||
try:
|
||||
motion = [np.load(pjoin(motion_dir, name + ".npy"))]
|
||||
|
||||
# Read text
|
||||
with cs.open(pjoin(text_dir, name + '.txt')) as f:
|
||||
text_data = []
|
||||
flag = False
|
||||
lines = f.readlines()
|
||||
|
||||
for line in lines:
|
||||
try:
|
||||
line_split = line.strip().split('#')
|
||||
f_tag = float(line_split[2])
|
||||
to_tag = float(line_split[3])
|
||||
f_tag = 0.0 if np.isnan(f_tag) else f_tag
|
||||
to_tag = 0.0 if np.isnan(to_tag) else to_tag
|
||||
|
||||
if f_tag == 0.0 and to_tag == 0.0:
|
||||
flag = True
|
||||
else:
|
||||
motion_new = [tokens[int(f_tag*fps/unit_length) : int(to_tag*fps/unit_length)] for tokens in motion if int(f_tag*fps/unit_length) < int(to_tag*fps/unit_length)]
|
||||
|
||||
if len(motion_new) == 0:
|
||||
continue
|
||||
new_name = '%s_%f_%f'%(name, f_tag, to_tag)
|
||||
|
||||
motion_dict[new_name] = {
|
||||
'motion': motion_new,
|
||||
"length": [len(m[0]) for m in motion_new]}
|
||||
new_name_list.append(new_name)
|
||||
except:
|
||||
pass
|
||||
|
||||
if flag:
|
||||
motion_dict[name] = {
|
||||
'motion': motion,
|
||||
"length": [len(m[0]) for m in motion]}
|
||||
new_name_list.append(name)
|
||||
except:
|
||||
pass
|
||||
|
||||
if tmpFile:
|
||||
os.makedirs(pjoin(data_root, 'tmp'), exist_ok=True)
|
||||
|
||||
with open(pjoin(data_root, f'tmp/{split}{subset}_motion.pkl'),'wb') as file:
|
||||
pickle.dump(motion_dict, file)
|
||||
with open(pjoin(data_root, f'tmp/{split}{subset}_index.pkl'), 'wb') as file:
|
||||
pickle.dump(new_name_list, file)
|
||||
|
||||
self.motion_dict = motion_dict
|
||||
self.name_list = new_name_list
|
||||
self.nfeats = motion_dict[new_name_list[0]]['motion'][0].shape[1]
|
||||
|
||||
def __len__(self):
|
||||
return len(self.name_list)
|
||||
|
||||
def __getitem__(self, item):
|
||||
data = self.motion_dict[self.name_list[item]]
|
||||
motion_list, m_length = data["motion"], data["length"]
|
||||
|
||||
# Randomly select a motion
|
||||
motion = random.choice(motion_list)
|
||||
|
||||
# Crop the motions in to times of 4, and introduce small variations
|
||||
if self.unit_length < 10:
|
||||
coin2 = np.random.choice(["single", "single", "double"])
|
||||
else:
|
||||
coin2 = "single"
|
||||
|
||||
if coin2 == "double":
|
||||
m_length = (m_length // self.unit_length - 1) * self.unit_length
|
||||
elif coin2 == "single":
|
||||
m_length = (m_length // self.unit_length) * self.unit_length
|
||||
idx = random.randint(0, len(motion) - m_length)
|
||||
motion = motion[idx:idx + m_length]
|
||||
|
||||
# Z Normalization
|
||||
motion = (motion - self.mean) / self.std
|
||||
|
||||
return None, motion, m_length, None, None, None, None,
|
||||
@@ -1,54 +0,0 @@
|
||||
import random
|
||||
import codecs as cs
|
||||
import numpy as np
|
||||
from torch.utils import data
|
||||
from rich.progress import track
|
||||
from os.path import join as pjoin
|
||||
from .dataset_m import MotionDataset
|
||||
from .dataset_t2m import Text2MotionDataset
|
||||
|
||||
|
||||
class MotionDatasetVQ(Text2MotionDataset):
|
||||
def __init__(
|
||||
self,
|
||||
data_root,
|
||||
split,
|
||||
mean,
|
||||
std,
|
||||
max_motion_length,
|
||||
min_motion_length,
|
||||
win_size,
|
||||
unit_length=4,
|
||||
fps=20,
|
||||
tmpFile=True,
|
||||
tiny=False,
|
||||
debug=False,
|
||||
**kwargs,
|
||||
):
|
||||
super().__init__(data_root, split, mean, std, max_motion_length,
|
||||
min_motion_length, unit_length, fps, tmpFile, tiny,
|
||||
debug, **kwargs)
|
||||
|
||||
# Filter out the motions that are too short
|
||||
self.window_size = win_size
|
||||
name_list = list(self.name_list)
|
||||
for name in self.name_list:
|
||||
motion = self.data_dict[name]["motion"]
|
||||
if motion.shape[0] < self.window_size:
|
||||
name_list.remove(name)
|
||||
self.data_dict.pop(name)
|
||||
self.name_list = name_list
|
||||
|
||||
def __len__(self):
|
||||
return len(self.name_list)
|
||||
|
||||
def __getitem__(self, item):
|
||||
idx = self.pointer + item
|
||||
data = self.data_dict[self.name_list[idx]]
|
||||
motion, length = data["motion"], data["length"]
|
||||
|
||||
idx = random.randint(0, motion.shape[0] - self.window_size)
|
||||
motion = motion[idx:idx + self.window_size]
|
||||
motion = (motion - self.mean) / self.std
|
||||
|
||||
return None, motion, length, None, None, None, None,
|
||||
@@ -1,211 +0,0 @@
|
||||
import os
|
||||
import rich
|
||||
import random
|
||||
import pickle
|
||||
import codecs as cs
|
||||
import numpy as np
|
||||
from torch.utils import data
|
||||
from rich.progress import track
|
||||
from os.path import join as pjoin
|
||||
|
||||
|
||||
class Text2MotionDataset(data.Dataset):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
data_root,
|
||||
split,
|
||||
mean,
|
||||
std,
|
||||
max_motion_length=196,
|
||||
min_motion_length=40,
|
||||
unit_length=4,
|
||||
fps=20,
|
||||
tmpFile=True,
|
||||
tiny=False,
|
||||
debug=False,
|
||||
**kwargs,
|
||||
):
|
||||
|
||||
# restrian the length of motion and text
|
||||
self.max_length = 20
|
||||
self.max_motion_length = max_motion_length
|
||||
self.min_motion_length = min_motion_length
|
||||
self.unit_length = unit_length
|
||||
|
||||
# Data mean and std
|
||||
self.mean = mean
|
||||
self.std = std
|
||||
|
||||
# Data path
|
||||
split_file = pjoin(data_root, split + '.txt')
|
||||
motion_dir = pjoin(data_root, 'new_joint_vecs')
|
||||
text_dir = pjoin(data_root, 'texts')
|
||||
|
||||
# Data id list
|
||||
self.id_list = []
|
||||
with cs.open(split_file, "r") as f:
|
||||
for line in f.readlines():
|
||||
self.id_list.append(line.strip())
|
||||
|
||||
# Debug mode
|
||||
if tiny or debug:
|
||||
enumerator = enumerate(self.id_list)
|
||||
maxdata = 100
|
||||
subset = '_tiny'
|
||||
else:
|
||||
enumerator = enumerate(
|
||||
track(
|
||||
self.id_list,
|
||||
f"Loading HumanML3D {split}",
|
||||
))
|
||||
maxdata = 1e10
|
||||
subset = ''
|
||||
|
||||
new_name_list = []
|
||||
length_list = []
|
||||
data_dict = {}
|
||||
|
||||
# Fast loading
|
||||
if os.path.exists(pjoin(data_root, f'tmp/{split}{subset}_data.pkl')):
|
||||
if tiny or debug:
|
||||
with open(pjoin(data_root, f'tmp/{split}{subset}_data.pkl'),
|
||||
'rb') as file:
|
||||
data_dict = pickle.load(file)
|
||||
else:
|
||||
with rich.progress.open(
|
||||
pjoin(data_root, f'tmp/{split}{subset}_data.pkl'),
|
||||
'rb',
|
||||
description=f"Loading HumanML3D {split}") as file:
|
||||
data_dict = pickle.load(file)
|
||||
with open(pjoin(data_root, f'tmp/{split}{subset}_index.pkl'),
|
||||
'rb') as file:
|
||||
name_list = pickle.load(file)
|
||||
for name in new_name_list:
|
||||
length_list.append(data_dict[name]['length'])
|
||||
|
||||
else:
|
||||
for idx, name in enumerator:
|
||||
if len(new_name_list) > maxdata:
|
||||
break
|
||||
try:
|
||||
motion = np.load(pjoin(motion_dir, name + ".npy"))
|
||||
if (len(motion)) < self.min_motion_length or (len(motion)
|
||||
>= 200):
|
||||
continue
|
||||
|
||||
# Read text
|
||||
text_data = []
|
||||
flag = False
|
||||
with cs.open(pjoin(text_dir, name + '.txt')) as f:
|
||||
lines = f.readlines()
|
||||
for line in lines:
|
||||
text_dict = {}
|
||||
line_split = line.strip().split('#')
|
||||
caption = line_split[0]
|
||||
t_tokens = line_split[1].split(' ')
|
||||
f_tag = float(line_split[2])
|
||||
to_tag = float(line_split[3])
|
||||
f_tag = 0.0 if np.isnan(f_tag) else f_tag
|
||||
to_tag = 0.0 if np.isnan(to_tag) else to_tag
|
||||
|
||||
text_dict['caption'] = caption
|
||||
text_dict['tokens'] = t_tokens
|
||||
if f_tag == 0.0 and to_tag == 0.0:
|
||||
flag = True
|
||||
text_data.append(text_dict)
|
||||
else:
|
||||
motion_new = motion[int(f_tag *
|
||||
fps):int(to_tag * fps)]
|
||||
if (len(motion_new)
|
||||
) < self.min_motion_length or (
|
||||
len(motion_new) >= 200):
|
||||
continue
|
||||
new_name = random.choice(
|
||||
'ABCDEFGHIJKLMNOPQRSTUVW') + '_' + name
|
||||
while new_name in new_name_list:
|
||||
new_name = random.choice(
|
||||
'ABCDEFGHIJKLMNOPQRSTUVW') + '_' + name
|
||||
name_count = 1
|
||||
while new_name in data_dict:
|
||||
new_name += '_' + name_count
|
||||
name_count += 1
|
||||
data_dict[new_name] = {
|
||||
'motion': motion_new,
|
||||
"length": len(motion_new),
|
||||
'text': [text_dict]
|
||||
}
|
||||
new_name_list.append(new_name)
|
||||
length_list.append(len(motion_new))
|
||||
|
||||
if flag:
|
||||
data_dict[name] = {
|
||||
'motion': motion,
|
||||
"length": len(motion),
|
||||
'text': text_data
|
||||
}
|
||||
new_name_list.append(name)
|
||||
length_list.append(len(motion))
|
||||
except:
|
||||
pass
|
||||
|
||||
name_list, length_list = zip(
|
||||
*sorted(zip(new_name_list, length_list), key=lambda x: x[1]))
|
||||
|
||||
if tmpFile:
|
||||
os.makedirs(pjoin(data_root, 'tmp'), exist_ok=True)
|
||||
with open(pjoin(data_root, f'tmp/{split}{subset}_data.pkl'),
|
||||
'wb') as file:
|
||||
pickle.dump(data_dict, file)
|
||||
with open(pjoin(data_root, f'tmp/{split}{subset}_index.pkl'),
|
||||
'wb') as file:
|
||||
pickle.dump(name_list, file)
|
||||
|
||||
self.length_arr = np.array(length_list)
|
||||
self.data_dict = data_dict
|
||||
self.name_list = name_list
|
||||
self.nfeats = data_dict[name_list[0]]['motion'].shape[1]
|
||||
self.reset_max_len(self.max_length)
|
||||
|
||||
def reset_max_len(self, length):
|
||||
assert length <= self.max_motion_length
|
||||
self.pointer = np.searchsorted(self.length_arr, length)
|
||||
print("Pointer Pointing at %d" % self.pointer)
|
||||
self.max_length = length
|
||||
|
||||
def __len__(self):
|
||||
return len(self.name_list) - self.pointer
|
||||
|
||||
def __getitem__(self, item):
|
||||
idx = self.pointer + item
|
||||
data = self.data_dict[self.name_list[idx]]
|
||||
motion, m_length, text_list = data["motion"], data["length"], data[
|
||||
"text"]
|
||||
|
||||
# Randomly select a caption
|
||||
text_data = random.choice(text_list)
|
||||
caption = text_data["caption"]
|
||||
|
||||
all_captions = [
|
||||
' '.join([token.split('/')[0] for token in text_dic['tokens']])
|
||||
for text_dic in text_list
|
||||
]
|
||||
|
||||
# Crop the motions in to times of 4, and introduce small variations
|
||||
if self.unit_length < 10:
|
||||
coin2 = np.random.choice(["single", "single", "double"])
|
||||
else:
|
||||
coin2 = "single"
|
||||
|
||||
if coin2 == "double":
|
||||
m_length = (m_length // self.unit_length - 1) * self.unit_length
|
||||
elif coin2 == "single":
|
||||
m_length = (m_length // self.unit_length) * self.unit_length
|
||||
|
||||
idx = random.randint(0, len(motion) - m_length)
|
||||
motion = motion[idx:idx + m_length]
|
||||
|
||||
# Z Normalization
|
||||
motion = (motion - self.mean) / self.std
|
||||
|
||||
return caption, motion, m_length, None, None, None, None, all_captions
|
||||
@@ -1,211 +0,0 @@
|
||||
import rich
|
||||
import random
|
||||
import pickle
|
||||
import os
|
||||
import numpy as np
|
||||
import codecs as cs
|
||||
from torch.utils import data
|
||||
from os.path import join as pjoin
|
||||
from rich.progress import track
|
||||
import json
|
||||
import spacy
|
||||
|
||||
class Text2MotionDatasetCB(data.Dataset):
|
||||
def __init__(
|
||||
self,
|
||||
data_root,
|
||||
split,
|
||||
mean,
|
||||
std,
|
||||
max_motion_length=196,
|
||||
min_motion_length=20,
|
||||
unit_length=4,
|
||||
fps=20,
|
||||
tmpFile=True,
|
||||
tiny=False,
|
||||
debug=False,
|
||||
stage='lm_pretrain',
|
||||
code_path='VQVAE',
|
||||
task_path=None,
|
||||
std_text=False,
|
||||
**kwargs,
|
||||
):
|
||||
self.tiny = tiny
|
||||
self.unit_length = unit_length
|
||||
|
||||
# Data mean and std
|
||||
self.mean = mean
|
||||
self.std = std
|
||||
|
||||
# Data path
|
||||
split = 'train'
|
||||
split_file = pjoin(data_root, split + '.txt')
|
||||
motion_dir = pjoin(data_root, code_path)
|
||||
text_dir = pjoin(data_root, 'texts')
|
||||
|
||||
if task_path:
|
||||
instructions = task_path
|
||||
elif stage == 'lm_pretrain':
|
||||
instructions = pjoin(data_root, 'template_pretrain.json')
|
||||
elif stage in ['lm_instruct', "lm_rl"]:
|
||||
instructions = pjoin(data_root, 'template_instructions.json')
|
||||
else:
|
||||
raise NotImplementedError(f"stage {stage} not implemented")
|
||||
|
||||
# Data id list
|
||||
self.id_list = []
|
||||
with cs.open(split_file, "r") as f:
|
||||
for line in f.readlines():
|
||||
self.id_list.append(line.strip())
|
||||
|
||||
# Debug mode
|
||||
if tiny or debug:
|
||||
enumerator = enumerate(self.id_list)
|
||||
maxdata = 100
|
||||
subset = '_tiny'
|
||||
else:
|
||||
enumerator = enumerate(
|
||||
track(
|
||||
self.id_list,
|
||||
f"Loading HumanML3D {split}",
|
||||
))
|
||||
maxdata = 1e10
|
||||
subset = ''
|
||||
|
||||
new_name_list = []
|
||||
data_dict = {}
|
||||
|
||||
# Fast loading
|
||||
for i, name in enumerator:
|
||||
if len(new_name_list) > maxdata:
|
||||
break
|
||||
try:
|
||||
# Load motion tokens
|
||||
m_token_list = np.load(pjoin(motion_dir, f'{name}.npy'))
|
||||
# Read text
|
||||
with cs.open(pjoin(text_dir, name + '.txt')) as f:
|
||||
text_data = []
|
||||
flag = False
|
||||
lines = f.readlines()
|
||||
|
||||
for line in lines:
|
||||
try:
|
||||
text_dict = {}
|
||||
line_split = line.strip().split('#')
|
||||
caption = line_split[0]
|
||||
t_tokens = line_split[1].split(' ')
|
||||
f_tag = float(line_split[2])
|
||||
to_tag = float(line_split[3])
|
||||
f_tag = 0.0 if np.isnan(f_tag) else f_tag
|
||||
to_tag = 0.0 if np.isnan(to_tag) else to_tag
|
||||
|
||||
text_dict['caption'] = caption
|
||||
text_dict['tokens'] = t_tokens
|
||||
if f_tag == 0.0 and to_tag == 0.0:
|
||||
flag = True
|
||||
text_data.append(text_dict)
|
||||
else:
|
||||
m_token_list_new = [
|
||||
tokens[int(f_tag * fps / unit_length
|
||||
):int(to_tag * fps /
|
||||
unit_length)]
|
||||
for tokens in m_token_list
|
||||
if int(f_tag * fps / unit_length) <
|
||||
int(to_tag * fps / unit_length)
|
||||
]
|
||||
|
||||
if len(m_token_list_new) == 0:
|
||||
continue
|
||||
new_name = '%s_%f_%f' % (name, f_tag,
|
||||
to_tag)
|
||||
|
||||
data_dict[new_name] = {
|
||||
'm_token_list': m_token_list_new,
|
||||
'text': [text_dict]
|
||||
}
|
||||
new_name_list.append(new_name)
|
||||
except:
|
||||
pass
|
||||
|
||||
if flag:
|
||||
data_dict[name] = {
|
||||
'm_token_list': m_token_list,
|
||||
'text': text_data
|
||||
}
|
||||
new_name_list.append(name)
|
||||
except:
|
||||
pass
|
||||
|
||||
if tmpFile:
|
||||
os.makedirs(pjoin(data_root, 'tmp'), exist_ok=True)
|
||||
with open(
|
||||
pjoin(data_root,
|
||||
f'tmp/{split}{subset}_tokens_data.pkl'),
|
||||
'wb') as file:
|
||||
pickle.dump(data_dict, file)
|
||||
with open(
|
||||
pjoin(data_root,
|
||||
f'tmp/{split}{subset}_tokens_index.pkl'),
|
||||
'wb') as file:
|
||||
pickle.dump(new_name_list, file)
|
||||
|
||||
self.data_dict = data_dict
|
||||
self.name_list = new_name_list
|
||||
self.nlp = spacy.load('en_core_web_sm')
|
||||
self.std_text = std_text
|
||||
self.instructions = json.load(open(instructions, 'r'))
|
||||
self.tasks = []
|
||||
for task in self.instructions.keys():
|
||||
for subtask in self.instructions[task].keys():
|
||||
self.tasks.append(self.instructions[task][subtask])
|
||||
|
||||
def __len__(self):
|
||||
return len(self.name_list) * len(self.tasks)
|
||||
|
||||
def __getitem__(self, item):
|
||||
data_idx = item % len(self.name_list)
|
||||
task_idx = item // len(self.name_list)
|
||||
|
||||
data = self.data_dict[self.name_list[data_idx]]
|
||||
m_token_list, text_list = data['m_token_list'], data['text']
|
||||
|
||||
m_tokens = random.choice(m_token_list)
|
||||
text_data = random.choice(text_list)
|
||||
caption = text_data['caption']
|
||||
if self.std_text:
|
||||
doc = self.nlp(caption)
|
||||
word_list = []
|
||||
pos_list = []
|
||||
for token in doc:
|
||||
word = token.text
|
||||
if not word.isalpha():
|
||||
continue
|
||||
if (token.pos_ == 'NOUN'
|
||||
or token.pos_ == 'VERB') and (word != 'left'):
|
||||
word_list.append(token.lemma_)
|
||||
else:
|
||||
word_list.append(word)
|
||||
pos_list.append(token.pos_)
|
||||
|
||||
caption = ' '.join(word_list)
|
||||
|
||||
all_captions = [
|
||||
' '.join([token.split('/')[0] for token in text_dic['tokens']])
|
||||
for text_dic in text_list
|
||||
]
|
||||
|
||||
coin = np.random.choice([False, False, True])
|
||||
|
||||
if coin:
|
||||
# drop one token at the head or tail
|
||||
coin2 = np.random.choice([True, False])
|
||||
if coin2:
|
||||
m_tokens = m_tokens[:-1]
|
||||
else:
|
||||
m_tokens = m_tokens[1:]
|
||||
|
||||
m_tokens_len = m_tokens.shape[0]
|
||||
|
||||
tasks = self.tasks[task_idx]
|
||||
|
||||
return caption, m_tokens, m_tokens_len, None, None, None, None, all_captions, tasks
|
||||
@@ -1,92 +0,0 @@
|
||||
import random
|
||||
import numpy as np
|
||||
from .dataset_t2m import Text2MotionDataset
|
||||
|
||||
|
||||
class Text2MotionDatasetEval(Text2MotionDataset):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
data_root,
|
||||
split,
|
||||
mean,
|
||||
std,
|
||||
w_vectorizer,
|
||||
max_motion_length=196,
|
||||
min_motion_length=40,
|
||||
unit_length=4,
|
||||
fps=20,
|
||||
tmpFile=True,
|
||||
tiny=False,
|
||||
debug=False,
|
||||
**kwargs,
|
||||
):
|
||||
super().__init__(data_root, split, mean, std, max_motion_length,
|
||||
min_motion_length, unit_length, fps, tmpFile, tiny,
|
||||
debug, **kwargs)
|
||||
|
||||
self.w_vectorizer = w_vectorizer
|
||||
|
||||
|
||||
def __getitem__(self, item):
|
||||
# Get text data
|
||||
idx = self.pointer + item
|
||||
data = self.data_dict[self.name_list[idx]]
|
||||
motion, m_length, text_list = data["motion"], data["length"], data["text"]
|
||||
|
||||
all_captions = [
|
||||
' '.join([token.split('/')[0] for token in text_dic['tokens']])
|
||||
for text_dic in text_list
|
||||
]
|
||||
|
||||
if len(all_captions) > 3:
|
||||
all_captions = all_captions[:3]
|
||||
elif len(all_captions) == 2:
|
||||
all_captions = all_captions + all_captions[0:1]
|
||||
elif len(all_captions) == 1:
|
||||
all_captions = all_captions * 3
|
||||
|
||||
# Randomly select a caption
|
||||
text_data = random.choice(text_list)
|
||||
caption, tokens = text_data["caption"], text_data["tokens"]
|
||||
|
||||
# Text
|
||||
max_text_len = 20
|
||||
if len(tokens) < max_text_len:
|
||||
# pad with "unk"
|
||||
tokens = ["sos/OTHER"] + tokens + ["eos/OTHER"]
|
||||
sent_len = len(tokens)
|
||||
tokens = tokens + ["unk/OTHER"] * (max_text_len + 2 - sent_len)
|
||||
else:
|
||||
# crop
|
||||
tokens = tokens[:max_text_len]
|
||||
tokens = ["sos/OTHER"] + tokens + ["eos/OTHER"]
|
||||
sent_len = len(tokens)
|
||||
pos_one_hots = []
|
||||
word_embeddings = []
|
||||
for token in tokens:
|
||||
word_emb, pos_oh = self.w_vectorizer[token]
|
||||
pos_one_hots.append(pos_oh[None, :])
|
||||
word_embeddings.append(word_emb[None, :])
|
||||
pos_one_hots = np.concatenate(pos_one_hots, axis=0)
|
||||
word_embeddings = np.concatenate(word_embeddings, axis=0)
|
||||
|
||||
# Random crop
|
||||
if self.unit_length < 10:
|
||||
coin2 = np.random.choice(["single", "single", "double"])
|
||||
else:
|
||||
coin2 = "single"
|
||||
|
||||
if coin2 == "double":
|
||||
m_length = (m_length // self.unit_length - 1) * self.unit_length
|
||||
elif coin2 == "single":
|
||||
m_length = (m_length // self.unit_length) * self.unit_length
|
||||
|
||||
idx = random.randint(0, len(motion) - m_length)
|
||||
motion = motion[idx:idx + m_length]
|
||||
|
||||
# Z Normalization
|
||||
motion = (motion - self.mean) / self.std
|
||||
|
||||
return caption, motion, m_length, word_embeddings, pos_one_hots, sent_len, "_".join(
|
||||
tokens), all_captions
|
||||
@@ -1,119 +0,0 @@
|
||||
import random
|
||||
import numpy as np
|
||||
from torch.utils import data
|
||||
from .dataset_t2m import Text2MotionDataset
|
||||
import codecs as cs
|
||||
from os.path import join as pjoin
|
||||
|
||||
|
||||
class Text2MotionDatasetM2T(data.Dataset):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
data_root,
|
||||
split,
|
||||
mean,
|
||||
std,
|
||||
max_motion_length=196,
|
||||
min_motion_length=40,
|
||||
unit_length=4,
|
||||
fps=20,
|
||||
tmpFile=True,
|
||||
tiny=False,
|
||||
debug=False,
|
||||
**kwargs,
|
||||
):
|
||||
|
||||
self.max_motion_length = max_motion_length
|
||||
self.min_motion_length = min_motion_length
|
||||
self.unit_length = unit_length
|
||||
|
||||
# Data mean and std
|
||||
self.mean = mean
|
||||
self.std = std
|
||||
|
||||
# Data path
|
||||
split_file = pjoin(data_root, split + '.txt')
|
||||
motion_dir = pjoin(data_root, 'new_joint_vecs')
|
||||
text_dir = pjoin(data_root, 'texts')
|
||||
|
||||
# Data id list
|
||||
self.id_list = []
|
||||
with cs.open(split_file, "r") as f:
|
||||
for line in f.readlines():
|
||||
self.id_list.append(line.strip())
|
||||
|
||||
new_name_list = []
|
||||
length_list = []
|
||||
data_dict = {}
|
||||
for name in self.id_list:
|
||||
# try:
|
||||
motion = np.load(pjoin(motion_dir, name + '.npy'))
|
||||
if (len(motion)) < self.min_motion_length or (len(motion) >= 200):
|
||||
continue
|
||||
|
||||
|
||||
text_data = []
|
||||
flag = False
|
||||
|
||||
with cs.open(pjoin(text_dir, name + '.txt')) as f:
|
||||
for line in f.readlines():
|
||||
text_dict = {}
|
||||
line_split = line.strip().split('#')
|
||||
caption = line_split[0]
|
||||
tokens = line_split[1].split(' ')
|
||||
f_tag = float(line_split[2])
|
||||
to_tag = float(line_split[3])
|
||||
f_tag = 0.0 if np.isnan(f_tag) else f_tag
|
||||
to_tag = 0.0 if np.isnan(to_tag) else to_tag
|
||||
|
||||
text_dict['caption'] = caption
|
||||
text_dict['tokens'] = tokens
|
||||
if f_tag == 0.0 and to_tag == 0.0:
|
||||
flag = True
|
||||
text_data.append(text_dict)
|
||||
else:
|
||||
try:
|
||||
n_motion = motion[int(f_tag*20) : int(to_tag*20)]
|
||||
|
||||
if (len(n_motion)) < min_motion_length or (len(n_motion) >= 200):
|
||||
continue
|
||||
|
||||
new_name = "%s_%f_%f"%(name, f_tag, to_tag)
|
||||
data_dict[new_name] = {'motion': n_motion,
|
||||
'length': len(n_motion),
|
||||
'text':[text_dict]}
|
||||
new_name_list.append(new_name)
|
||||
except:
|
||||
print(line_split)
|
||||
print(line_split[2], line_split[3], f_tag, to_tag, name)
|
||||
if flag:
|
||||
data_dict[name] = {'motion': motion,
|
||||
'length': len(motion),
|
||||
'name': name,
|
||||
'text': text_data}
|
||||
|
||||
new_name_list.append(name)
|
||||
length_list.append(len(motion))
|
||||
# except:
|
||||
# # Some motion may not exist in KIT dataset
|
||||
# pass
|
||||
|
||||
self.length_arr = np.array(length_list)
|
||||
self.data_dict = data_dict
|
||||
self.name_list = new_name_list
|
||||
self.nfeats = motion.shape[-1]
|
||||
|
||||
|
||||
def __len__(self):
|
||||
return len(self.data_dict)
|
||||
|
||||
def __getitem__(self, item):
|
||||
name = self.name_list[item]
|
||||
data = self.data_dict[name]
|
||||
motion, m_length = data['motion'], data['length']
|
||||
|
||||
"Z Normalization"
|
||||
motion = (motion - self.mean) / self.std
|
||||
|
||||
return name, motion, m_length, True, True, True, True, True, True
|
||||
@@ -1,86 +0,0 @@
|
||||
import random
|
||||
import numpy as np
|
||||
from torch.utils import data
|
||||
from .dataset_t2m import Text2MotionDataset
|
||||
import codecs as cs
|
||||
from os.path import join as pjoin
|
||||
|
||||
|
||||
class Text2MotionDatasetToken(data.Dataset):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
data_root,
|
||||
split,
|
||||
mean,
|
||||
std,
|
||||
max_motion_length=196,
|
||||
min_motion_length=40,
|
||||
unit_length=4,
|
||||
fps=20,
|
||||
tmpFile=True,
|
||||
tiny=False,
|
||||
debug=False,
|
||||
**kwargs,
|
||||
):
|
||||
|
||||
self.max_motion_length = max_motion_length
|
||||
self.min_motion_length = min_motion_length
|
||||
self.unit_length = unit_length
|
||||
|
||||
# Data mean and std
|
||||
self.mean = mean
|
||||
self.std = std
|
||||
|
||||
# Data path
|
||||
split_file = pjoin(data_root, split + '.txt')
|
||||
motion_dir = pjoin(data_root, 'new_joint_vecs')
|
||||
text_dir = pjoin(data_root, 'texts')
|
||||
|
||||
# Data id list
|
||||
self.id_list = []
|
||||
with cs.open(split_file, "r") as f:
|
||||
for line in f.readlines():
|
||||
self.id_list.append(line.strip())
|
||||
|
||||
new_name_list = []
|
||||
length_list = []
|
||||
data_dict = {}
|
||||
for name in self.id_list:
|
||||
try:
|
||||
motion = np.load(pjoin(motion_dir, name + '.npy'))
|
||||
if (len(motion)) < self.min_motion_length or (len(motion) >= 200):
|
||||
continue
|
||||
|
||||
data_dict[name] = {'motion': motion,
|
||||
'length': len(motion),
|
||||
'name': name}
|
||||
new_name_list.append(name)
|
||||
length_list.append(len(motion))
|
||||
except:
|
||||
# Some motion may not exist in KIT dataset
|
||||
pass
|
||||
|
||||
self.length_arr = np.array(length_list)
|
||||
self.data_dict = data_dict
|
||||
self.name_list = new_name_list
|
||||
self.nfeats = motion.shape[-1]
|
||||
|
||||
|
||||
def __len__(self):
|
||||
return len(self.data_dict)
|
||||
|
||||
def __getitem__(self, item):
|
||||
name = self.name_list[item]
|
||||
data = self.data_dict[name]
|
||||
motion, m_length = data['motion'], data['length']
|
||||
|
||||
m_length = (m_length // self.unit_length) * self.unit_length
|
||||
|
||||
idx = random.randint(0, len(motion) - m_length)
|
||||
motion = motion[idx:idx+m_length]
|
||||
|
||||
"Z Normalization"
|
||||
motion = (motion - self.mean) / self.std
|
||||
|
||||
return name, motion, m_length, True, True, True, True, True, True
|
||||
@@ -1,529 +0,0 @@
|
||||
from os.path import join as pjoin
|
||||
|
||||
from ..common.skeleton import Skeleton
|
||||
import numpy as np
|
||||
import os
|
||||
from ..common.quaternion import *
|
||||
from ..utils.paramUtil import *
|
||||
|
||||
import torch
|
||||
from tqdm import tqdm
|
||||
|
||||
# positions (batch, joint_num, 3)
|
||||
def uniform_skeleton(positions, target_offset):
|
||||
src_skel = Skeleton(n_raw_offsets, kinematic_chain, 'cpu')
|
||||
src_offset = src_skel.get_offsets_joints(torch.from_numpy(positions[0]))
|
||||
src_offset = src_offset.numpy()
|
||||
tgt_offset = target_offset.numpy()
|
||||
# print(src_offset)
|
||||
# print(tgt_offset)
|
||||
'''Calculate Scale Ratio as the ratio of legs'''
|
||||
src_leg_len = np.abs(src_offset[l_idx1]).max() + np.abs(src_offset[l_idx2]).max()
|
||||
tgt_leg_len = np.abs(tgt_offset[l_idx1]).max() + np.abs(tgt_offset[l_idx2]).max()
|
||||
|
||||
scale_rt = tgt_leg_len / src_leg_len
|
||||
# print(scale_rt)
|
||||
src_root_pos = positions[:, 0]
|
||||
tgt_root_pos = src_root_pos * scale_rt
|
||||
|
||||
'''Inverse Kinematics'''
|
||||
quat_params = src_skel.inverse_kinematics_np(positions, face_joint_indx)
|
||||
# print(quat_params.shape)
|
||||
|
||||
'''Forward Kinematics'''
|
||||
src_skel.set_offset(target_offset)
|
||||
new_joints = src_skel.forward_kinematics_np(quat_params, tgt_root_pos)
|
||||
return new_joints
|
||||
|
||||
|
||||
def extract_features(positions, feet_thre, n_raw_offsets, kinematic_chain, face_joint_indx, fid_r, fid_l):
|
||||
global_positions = positions.copy()
|
||||
""" Get Foot Contacts """
|
||||
|
||||
def foot_detect(positions, thres):
|
||||
velfactor, heightfactor = np.array([thres, thres]), np.array([3.0, 2.0])
|
||||
|
||||
feet_l_x = (positions[1:, fid_l, 0] - positions[:-1, fid_l, 0]) ** 2
|
||||
feet_l_y = (positions[1:, fid_l, 1] - positions[:-1, fid_l, 1]) ** 2
|
||||
feet_l_z = (positions[1:, fid_l, 2] - positions[:-1, fid_l, 2]) ** 2
|
||||
# feet_l_h = positions[:-1,fid_l,1]
|
||||
# feet_l = (((feet_l_x + feet_l_y + feet_l_z) < velfactor) & (feet_l_h < heightfactor)).astype(np.float64)
|
||||
feet_l = ((feet_l_x + feet_l_y + feet_l_z) < velfactor).astype(np.float64)
|
||||
|
||||
feet_r_x = (positions[1:, fid_r, 0] - positions[:-1, fid_r, 0]) ** 2
|
||||
feet_r_y = (positions[1:, fid_r, 1] - positions[:-1, fid_r, 1]) ** 2
|
||||
feet_r_z = (positions[1:, fid_r, 2] - positions[:-1, fid_r, 2]) ** 2
|
||||
# feet_r_h = positions[:-1,fid_r,1]
|
||||
# feet_r = (((feet_r_x + feet_r_y + feet_r_z) < velfactor) & (feet_r_h < heightfactor)).astype(np.float64)
|
||||
feet_r = (((feet_r_x + feet_r_y + feet_r_z) < velfactor)).astype(np.float64)
|
||||
return feet_l, feet_r
|
||||
|
||||
#
|
||||
feet_l, feet_r = foot_detect(positions, feet_thre)
|
||||
# feet_l, feet_r = foot_detect(positions, 0.002)
|
||||
|
||||
'''Quaternion and Cartesian representation'''
|
||||
r_rot = None
|
||||
|
||||
def get_rifke(positions):
|
||||
'''Local pose'''
|
||||
positions[..., 0] -= positions[:, 0:1, 0]
|
||||
positions[..., 2] -= positions[:, 0:1, 2]
|
||||
'''All pose face Z+'''
|
||||
positions = qrot_np(np.repeat(r_rot[:, None], positions.shape[1], axis=1), positions)
|
||||
return positions
|
||||
|
||||
def get_quaternion(positions):
|
||||
skel = Skeleton(n_raw_offsets, kinematic_chain, "cpu")
|
||||
# (seq_len, joints_num, 4)
|
||||
quat_params = skel.inverse_kinematics_np(positions, face_joint_indx, smooth_forward=False)
|
||||
|
||||
'''Fix Quaternion Discontinuity'''
|
||||
quat_params = qfix(quat_params)
|
||||
# (seq_len, 4)
|
||||
r_rot = quat_params[:, 0].copy()
|
||||
# print(r_rot[0])
|
||||
'''Root Linear Velocity'''
|
||||
# (seq_len - 1, 3)
|
||||
velocity = (positions[1:, 0] - positions[:-1, 0]).copy()
|
||||
# print(r_rot.shape, velocity.shape)
|
||||
velocity = qrot_np(r_rot[1:], velocity)
|
||||
'''Root Angular Velocity'''
|
||||
# (seq_len - 1, 4)
|
||||
r_velocity = qmul_np(r_rot[1:], qinv_np(r_rot[:-1]))
|
||||
quat_params[1:, 0] = r_velocity
|
||||
# (seq_len, joints_num, 4)
|
||||
return quat_params, r_velocity, velocity, r_rot
|
||||
|
||||
def get_cont6d_params(positions):
|
||||
skel = Skeleton(n_raw_offsets, kinematic_chain, "cpu")
|
||||
# (seq_len, joints_num, 4)
|
||||
quat_params = skel.inverse_kinematics_np(positions, face_joint_indx, smooth_forward=True)
|
||||
|
||||
'''Quaternion to continuous 6D'''
|
||||
cont_6d_params = quaternion_to_cont6d_np(quat_params)
|
||||
# (seq_len, 4)
|
||||
r_rot = quat_params[:, 0].copy()
|
||||
# print(r_rot[0])
|
||||
'''Root Linear Velocity'''
|
||||
# (seq_len - 1, 3)
|
||||
velocity = (positions[1:, 0] - positions[:-1, 0]).copy()
|
||||
# print(r_rot.shape, velocity.shape)
|
||||
velocity = qrot_np(r_rot[1:], velocity)
|
||||
'''Root Angular Velocity'''
|
||||
# (seq_len - 1, 4)
|
||||
r_velocity = qmul_np(r_rot[1:], qinv_np(r_rot[:-1]))
|
||||
# (seq_len, joints_num, 4)
|
||||
return cont_6d_params, r_velocity, velocity, r_rot
|
||||
|
||||
cont_6d_params, r_velocity, velocity, r_rot = get_cont6d_params(positions)
|
||||
positions = get_rifke(positions)
|
||||
|
||||
# trejec = np.cumsum(np.concatenate([np.array([[0, 0, 0]]), velocity], axis=0), axis=0)
|
||||
# r_rotations, r_pos = recover_ric_glo_np(r_velocity, velocity[:, [0, 2]])
|
||||
|
||||
# plt.plot(positions_b[:, 0, 0], positions_b[:, 0, 2], marker='*')
|
||||
# plt.plot(ground_positions[:, 0, 0], ground_positions[:, 0, 2], marker='o', color='r')
|
||||
# plt.plot(trejec[:, 0], trejec[:, 2], marker='^', color='g')
|
||||
# plt.plot(r_pos[:, 0], r_pos[:, 2], marker='s', color='y')
|
||||
# plt.xlabel('x')
|
||||
# plt.ylabel('z')
|
||||
# plt.axis('equal')
|
||||
# plt.show()
|
||||
|
||||
'''Root height'''
|
||||
root_y = positions[:, 0, 1:2]
|
||||
|
||||
'''Root rotation and linear velocity'''
|
||||
# (seq_len-1, 1) rotation velocity along y-axis
|
||||
# (seq_len-1, 2) linear velovity on xz plane
|
||||
r_velocity = np.arcsin(r_velocity[:, 2:3])
|
||||
l_velocity = velocity[:, [0, 2]]
|
||||
# print(r_velocity.shape, l_velocity.shape, root_y.shape)
|
||||
root_data = np.concatenate([r_velocity, l_velocity, root_y[:-1]], axis=-1)
|
||||
|
||||
'''Get Joint Rotation Representation'''
|
||||
# (seq_len, (joints_num-1) *6) quaternion for skeleton joints
|
||||
rot_data = cont_6d_params[:, 1:].reshape(len(cont_6d_params), -1)
|
||||
|
||||
'''Get Joint Rotation Invariant Position Represention'''
|
||||
# (seq_len, (joints_num-1)*3) local joint position
|
||||
ric_data = positions[:, 1:].reshape(len(positions), -1)
|
||||
|
||||
'''Get Joint Velocity Representation'''
|
||||
# (seq_len-1, joints_num*3)
|
||||
local_vel = qrot_np(np.repeat(r_rot[:-1, None], global_positions.shape[1], axis=1),
|
||||
global_positions[1:] - global_positions[:-1])
|
||||
local_vel = local_vel.reshape(len(local_vel), -1)
|
||||
|
||||
data = root_data
|
||||
data = np.concatenate([data, ric_data[:-1]], axis=-1)
|
||||
data = np.concatenate([data, rot_data[:-1]], axis=-1)
|
||||
# print(dataset.shape, local_vel.shape)
|
||||
data = np.concatenate([data, local_vel], axis=-1)
|
||||
data = np.concatenate([data, feet_l, feet_r], axis=-1)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def process_file(positions, feet_thre):
|
||||
# (seq_len, joints_num, 3)
|
||||
# '''Down Sample'''
|
||||
# positions = positions[::ds_num]
|
||||
|
||||
'''Uniform Skeleton'''
|
||||
positions = uniform_skeleton(positions, tgt_offsets)
|
||||
|
||||
'''Put on Floor'''
|
||||
floor_height = positions.min(axis=0).min(axis=0)[1]
|
||||
positions[:, :, 1] -= floor_height
|
||||
# print(floor_height)
|
||||
|
||||
# plot_3d_motion("./positions_1.mp4", kinematic_chain, positions, 'title', fps=20)
|
||||
|
||||
'''XZ at origin'''
|
||||
root_pos_init = positions[0]
|
||||
root_pose_init_xz = root_pos_init[0] * np.array([1, 0, 1])
|
||||
positions = positions - root_pose_init_xz
|
||||
|
||||
# '''Move the first pose to origin '''
|
||||
# root_pos_init = positions[0]
|
||||
# positions = positions - root_pos_init[0]
|
||||
|
||||
'''All initially face Z+'''
|
||||
r_hip, l_hip, sdr_r, sdr_l = face_joint_indx
|
||||
across1 = root_pos_init[r_hip] - root_pos_init[l_hip]
|
||||
across2 = root_pos_init[sdr_r] - root_pos_init[sdr_l]
|
||||
across = across1 + across2
|
||||
across = across / np.sqrt((across ** 2).sum(axis=-1))[..., np.newaxis]
|
||||
|
||||
# forward (3,), rotate around y-axis
|
||||
forward_init = np.cross(np.array([[0, 1, 0]]), across, axis=-1)
|
||||
# forward (3,)
|
||||
forward_init = forward_init / np.sqrt((forward_init ** 2).sum(axis=-1))[..., np.newaxis]
|
||||
|
||||
# print(forward_init)
|
||||
|
||||
target = np.array([[0, 0, 1]])
|
||||
root_quat_init = qbetween_np(forward_init, target)
|
||||
root_quat_init = np.ones(positions.shape[:-1] + (4,)) * root_quat_init
|
||||
|
||||
positions_b = positions.copy()
|
||||
|
||||
positions = qrot_np(root_quat_init, positions)
|
||||
|
||||
# plot_3d_motion("./positions_2.mp4", kinematic_chain, positions, 'title', fps=20)
|
||||
|
||||
'''New ground truth positions'''
|
||||
global_positions = positions.copy()
|
||||
|
||||
# plt.plot(positions_b[:, 0, 0], positions_b[:, 0, 2], marker='*')
|
||||
# plt.plot(positions[:, 0, 0], positions[:, 0, 2], marker='o', color='r')
|
||||
# plt.xlabel('x')
|
||||
# plt.ylabel('z')
|
||||
# plt.axis('equal')
|
||||
# plt.show()
|
||||
|
||||
""" Get Foot Contacts """
|
||||
|
||||
def foot_detect(positions, thres):
|
||||
velfactor, heightfactor = np.array([thres, thres]), np.array([3.0, 2.0])
|
||||
|
||||
feet_l_x = (positions[1:, fid_l, 0] - positions[:-1, fid_l, 0]) ** 2
|
||||
feet_l_y = (positions[1:, fid_l, 1] - positions[:-1, fid_l, 1]) ** 2
|
||||
feet_l_z = (positions[1:, fid_l, 2] - positions[:-1, fid_l, 2]) ** 2
|
||||
# feet_l_h = positions[:-1,fid_l,1]
|
||||
# feet_l = (((feet_l_x + feet_l_y + feet_l_z) < velfactor) & (feet_l_h < heightfactor)).astype(np.float64)
|
||||
feet_l = ((feet_l_x + feet_l_y + feet_l_z) < velfactor).astype(np.float64)
|
||||
|
||||
feet_r_x = (positions[1:, fid_r, 0] - positions[:-1, fid_r, 0]) ** 2
|
||||
feet_r_y = (positions[1:, fid_r, 1] - positions[:-1, fid_r, 1]) ** 2
|
||||
feet_r_z = (positions[1:, fid_r, 2] - positions[:-1, fid_r, 2]) ** 2
|
||||
# feet_r_h = positions[:-1,fid_r,1]
|
||||
# feet_r = (((feet_r_x + feet_r_y + feet_r_z) < velfactor) & (feet_r_h < heightfactor)).astype(np.float64)
|
||||
feet_r = (((feet_r_x + feet_r_y + feet_r_z) < velfactor)).astype(np.float64)
|
||||
return feet_l, feet_r
|
||||
#
|
||||
feet_l, feet_r = foot_detect(positions, feet_thre)
|
||||
# feet_l, feet_r = foot_detect(positions, 0.002)
|
||||
|
||||
'''Quaternion and Cartesian representation'''
|
||||
r_rot = None
|
||||
|
||||
def get_rifke(positions):
|
||||
'''Local pose'''
|
||||
positions[..., 0] -= positions[:, 0:1, 0]
|
||||
positions[..., 2] -= positions[:, 0:1, 2]
|
||||
'''All pose face Z+'''
|
||||
positions = qrot_np(np.repeat(r_rot[:, None], positions.shape[1], axis=1), positions)
|
||||
return positions
|
||||
|
||||
def get_quaternion(positions):
|
||||
skel = Skeleton(n_raw_offsets, kinematic_chain, "cpu")
|
||||
# (seq_len, joints_num, 4)
|
||||
quat_params = skel.inverse_kinematics_np(positions, face_joint_indx, smooth_forward=False)
|
||||
|
||||
'''Fix Quaternion Discontinuity'''
|
||||
quat_params = qfix(quat_params)
|
||||
# (seq_len, 4)
|
||||
r_rot = quat_params[:, 0].copy()
|
||||
# print(r_rot[0])
|
||||
'''Root Linear Velocity'''
|
||||
# (seq_len - 1, 3)
|
||||
velocity = (positions[1:, 0] - positions[:-1, 0]).copy()
|
||||
# print(r_rot.shape, velocity.shape)
|
||||
velocity = qrot_np(r_rot[1:], velocity)
|
||||
'''Root Angular Velocity'''
|
||||
# (seq_len - 1, 4)
|
||||
r_velocity = qmul_np(r_rot[1:], qinv_np(r_rot[:-1]))
|
||||
quat_params[1:, 0] = r_velocity
|
||||
# (seq_len, joints_num, 4)
|
||||
return quat_params, r_velocity, velocity, r_rot
|
||||
|
||||
def get_cont6d_params(positions):
|
||||
skel = Skeleton(n_raw_offsets, kinematic_chain, "cpu")
|
||||
# (seq_len, joints_num, 4)
|
||||
quat_params = skel.inverse_kinematics_np(positions, face_joint_indx, smooth_forward=True)
|
||||
|
||||
'''Quaternion to continuous 6D'''
|
||||
cont_6d_params = quaternion_to_cont6d_np(quat_params)
|
||||
# (seq_len, 4)
|
||||
r_rot = quat_params[:, 0].copy()
|
||||
# print(r_rot[0])
|
||||
'''Root Linear Velocity'''
|
||||
# (seq_len - 1, 3)
|
||||
velocity = (positions[1:, 0] - positions[:-1, 0]).copy()
|
||||
# print(r_rot.shape, velocity.shape)
|
||||
velocity = qrot_np(r_rot[1:], velocity)
|
||||
'''Root Angular Velocity'''
|
||||
# (seq_len - 1, 4)
|
||||
r_velocity = qmul_np(r_rot[1:], qinv_np(r_rot[:-1]))
|
||||
# (seq_len, joints_num, 4)
|
||||
return cont_6d_params, r_velocity, velocity, r_rot
|
||||
|
||||
cont_6d_params, r_velocity, velocity, r_rot = get_cont6d_params(positions)
|
||||
positions = get_rifke(positions)
|
||||
|
||||
# trejec = np.cumsum(np.concatenate([np.array([[0, 0, 0]]), velocity], axis=0), axis=0)
|
||||
# r_rotations, r_pos = recover_ric_glo_np(r_velocity, velocity[:, [0, 2]])
|
||||
|
||||
# plt.plot(positions_b[:, 0, 0], positions_b[:, 0, 2], marker='*')
|
||||
# plt.plot(ground_positions[:, 0, 0], ground_positions[:, 0, 2], marker='o', color='r')
|
||||
# plt.plot(trejec[:, 0], trejec[:, 2], marker='^', color='g')
|
||||
# plt.plot(r_pos[:, 0], r_pos[:, 2], marker='s', color='y')
|
||||
# plt.xlabel('x')
|
||||
# plt.ylabel('z')
|
||||
# plt.axis('equal')
|
||||
# plt.show()
|
||||
|
||||
'''Root height'''
|
||||
root_y = positions[:, 0, 1:2]
|
||||
|
||||
'''Root rotation and linear velocity'''
|
||||
# (seq_len-1, 1) rotation velocity along y-axis
|
||||
# (seq_len-1, 2) linear velovity on xz plane
|
||||
r_velocity = np.arcsin(r_velocity[:, 2:3])
|
||||
l_velocity = velocity[:, [0, 2]]
|
||||
# print(r_velocity.shape, l_velocity.shape, root_y.shape)
|
||||
root_data = np.concatenate([r_velocity, l_velocity, root_y[:-1]], axis=-1)
|
||||
|
||||
'''Get Joint Rotation Representation'''
|
||||
# (seq_len, (joints_num-1) *6) quaternion for skeleton joints
|
||||
rot_data = cont_6d_params[:, 1:].reshape(len(cont_6d_params), -1)
|
||||
|
||||
'''Get Joint Rotation Invariant Position Represention'''
|
||||
# (seq_len, (joints_num-1)*3) local joint position
|
||||
ric_data = positions[:, 1:].reshape(len(positions), -1)
|
||||
|
||||
'''Get Joint Velocity Representation'''
|
||||
# (seq_len-1, joints_num*3)
|
||||
local_vel = qrot_np(np.repeat(r_rot[:-1, None], global_positions.shape[1], axis=1),
|
||||
global_positions[1:] - global_positions[:-1])
|
||||
local_vel = local_vel.reshape(len(local_vel), -1)
|
||||
|
||||
data = root_data
|
||||
data = np.concatenate([data, ric_data[:-1]], axis=-1)
|
||||
data = np.concatenate([data, rot_data[:-1]], axis=-1)
|
||||
# print(dataset.shape, local_vel.shape)
|
||||
data = np.concatenate([data, local_vel], axis=-1)
|
||||
data = np.concatenate([data, feet_l, feet_r], axis=-1)
|
||||
|
||||
return data, global_positions, positions, l_velocity
|
||||
|
||||
|
||||
# Recover global angle and positions for rotation dataset
|
||||
# root_rot_velocity (B, seq_len, 1)
|
||||
# root_linear_velocity (B, seq_len, 2)
|
||||
# root_y (B, seq_len, 1)
|
||||
# ric_data (B, seq_len, (joint_num - 1)*3)
|
||||
# rot_data (B, seq_len, (joint_num - 1)*6)
|
||||
# local_velocity (B, seq_len, joint_num*3)
|
||||
# foot contact (B, seq_len, 4)
|
||||
def recover_root_rot_pos(data):
|
||||
rot_vel = data[..., 0]
|
||||
r_rot_ang = torch.zeros_like(rot_vel).to(data.device)
|
||||
'''Get Y-axis rotation from rotation velocity'''
|
||||
r_rot_ang[..., 1:] = rot_vel[..., :-1]
|
||||
r_rot_ang = torch.cumsum(r_rot_ang, dim=-1)
|
||||
|
||||
r_rot_quat = torch.zeros(data.shape[:-1] + (4,)).to(data.device)
|
||||
r_rot_quat[..., 0] = torch.cos(r_rot_ang)
|
||||
r_rot_quat[..., 2] = torch.sin(r_rot_ang)
|
||||
|
||||
r_pos = torch.zeros(data.shape[:-1] + (3,)).to(data.device)
|
||||
r_pos[..., 1:, [0, 2]] = data[..., :-1, 1:3]
|
||||
'''Add Y-axis rotation to root position'''
|
||||
r_pos = qrot(qinv(r_rot_quat), r_pos)
|
||||
|
||||
r_pos = torch.cumsum(r_pos, dim=-2)
|
||||
|
||||
r_pos[..., 1] = data[..., 3]
|
||||
return r_rot_quat, r_pos
|
||||
|
||||
|
||||
def recover_from_rot(data, joints_num, skeleton):
|
||||
r_rot_quat, r_pos = recover_root_rot_pos(data)
|
||||
|
||||
r_rot_cont6d = quaternion_to_cont6d(r_rot_quat)
|
||||
|
||||
start_indx = 1 + 2 + 1 + (joints_num - 1) * 3
|
||||
end_indx = start_indx + (joints_num - 1) * 6
|
||||
cont6d_params = data[..., start_indx:end_indx]
|
||||
# print(r_rot_cont6d.shape, cont6d_params.shape, r_pos.shape)
|
||||
cont6d_params = torch.cat([r_rot_cont6d, cont6d_params], dim=-1)
|
||||
cont6d_params = cont6d_params.view(-1, joints_num, 6)
|
||||
|
||||
positions = skeleton.forward_kinematics_cont6d(cont6d_params, r_pos)
|
||||
|
||||
return positions
|
||||
|
||||
def recover_rot(data):
|
||||
# dataset [bs, seqlen, 263/251] HumanML/KIT
|
||||
joints_num = 22 if data.shape[-1] == 263 else 21
|
||||
r_rot_quat, r_pos = recover_root_rot_pos(data)
|
||||
r_pos_pad = torch.cat([r_pos, torch.zeros_like(r_pos)], dim=-1).unsqueeze(-2)
|
||||
r_rot_cont6d = quaternion_to_cont6d(r_rot_quat)
|
||||
start_indx = 1 + 2 + 1 + (joints_num - 1) * 3
|
||||
end_indx = start_indx + (joints_num - 1) * 6
|
||||
cont6d_params = data[..., start_indx:end_indx]
|
||||
cont6d_params = torch.cat([r_rot_cont6d, cont6d_params], dim=-1)
|
||||
cont6d_params = cont6d_params.view(-1, joints_num, 6)
|
||||
cont6d_params = torch.cat([cont6d_params, r_pos_pad], dim=-2)
|
||||
return cont6d_params
|
||||
|
||||
|
||||
def recover_from_ric(data, joints_num):
|
||||
r_rot_quat, r_pos = recover_root_rot_pos(data)
|
||||
positions = data[..., 4:(joints_num - 1) * 3 + 4]
|
||||
positions = positions.view(positions.shape[:-1] + (-1, 3))
|
||||
|
||||
'''Add Y-axis rotation to local joints'''
|
||||
positions = qrot(qinv(r_rot_quat[..., None, :]).expand(positions.shape[:-1] + (4,)), positions)
|
||||
|
||||
'''Add root XZ to joints'''
|
||||
positions[..., 0] += r_pos[..., 0:1]
|
||||
positions[..., 2] += r_pos[..., 2:3]
|
||||
|
||||
'''Concate root and joints'''
|
||||
positions = torch.cat([r_pos.unsqueeze(-2), positions], dim=-2)
|
||||
|
||||
return positions
|
||||
'''
|
||||
For Text2Motion Dataset
|
||||
'''
|
||||
'''
|
||||
if __name__ == "__main__":
|
||||
example_id = "000021"
|
||||
# Lower legs
|
||||
l_idx1, l_idx2 = 5, 8
|
||||
# Right/Left foot
|
||||
fid_r, fid_l = [8, 11], [7, 10]
|
||||
# Face direction, r_hip, l_hip, sdr_r, sdr_l
|
||||
face_joint_indx = [2, 1, 17, 16]
|
||||
# l_hip, r_hip
|
||||
r_hip, l_hip = 2, 1
|
||||
joints_num = 22
|
||||
# ds_num = 8
|
||||
data_dir = '../dataset/pose_data_raw/joints/'
|
||||
save_dir1 = '../dataset/pose_data_raw/new_joints/'
|
||||
save_dir2 = '../dataset/pose_data_raw/new_joint_vecs/'
|
||||
|
||||
n_raw_offsets = torch.from_numpy(t2m_raw_offsets)
|
||||
kinematic_chain = t2m_kinematic_chain
|
||||
|
||||
# Get offsets of target skeleton
|
||||
example_data = np.load(os.path.join(data_dir, example_id + '.npy'))
|
||||
example_data = example_data.reshape(len(example_data), -1, 3)
|
||||
example_data = torch.from_numpy(example_data)
|
||||
tgt_skel = Skeleton(n_raw_offsets, kinematic_chain, 'cpu')
|
||||
# (joints_num, 3)
|
||||
tgt_offsets = tgt_skel.get_offsets_joints(example_data[0])
|
||||
# print(tgt_offsets)
|
||||
|
||||
source_list = os.listdir(data_dir)
|
||||
frame_num = 0
|
||||
for source_file in tqdm(source_list):
|
||||
source_data = np.load(os.path.join(data_dir, source_file))[:, :joints_num]
|
||||
try:
|
||||
dataset, ground_positions, positions, l_velocity = process_file(source_data, 0.002)
|
||||
rec_ric_data = recover_from_ric(torch.from_numpy(dataset).unsqueeze(0).float(), joints_num)
|
||||
np.save(pjoin(save_dir1, source_file), rec_ric_data.squeeze().numpy())
|
||||
np.save(pjoin(save_dir2, source_file), dataset)
|
||||
frame_num += dataset.shape[0]
|
||||
except Exception as e:
|
||||
print(source_file)
|
||||
print(e)
|
||||
|
||||
print('Total clips: %d, Frames: %d, Duration: %fm' %
|
||||
(len(source_list), frame_num, frame_num / 20 / 60))
|
||||
'''
|
||||
|
||||
if __name__ == "__main__":
|
||||
example_id = "03950_gt"
|
||||
# Lower legs
|
||||
l_idx1, l_idx2 = 17, 18
|
||||
# Right/Left foot
|
||||
fid_r, fid_l = [14, 15], [19, 20]
|
||||
# Face direction, r_hip, l_hip, sdr_r, sdr_l
|
||||
face_joint_indx = [11, 16, 5, 8]
|
||||
# l_hip, r_hip
|
||||
r_hip, l_hip = 11, 16
|
||||
joints_num = 21
|
||||
# ds_num = 8
|
||||
data_dir = '../dataset/kit_mocap_dataset/joints/'
|
||||
save_dir1 = '../dataset/kit_mocap_dataset/new_joints/'
|
||||
save_dir2 = '../dataset/kit_mocap_dataset/new_joint_vecs/'
|
||||
|
||||
n_raw_offsets = torch.from_numpy(kit_raw_offsets)
|
||||
kinematic_chain = kit_kinematic_chain
|
||||
|
||||
'''Get offsets of target skeleton'''
|
||||
example_data = np.load(os.path.join(data_dir, example_id + '.npy'))
|
||||
example_data = example_data.reshape(len(example_data), -1, 3)
|
||||
example_data = torch.from_numpy(example_data)
|
||||
tgt_skel = Skeleton(n_raw_offsets, kinematic_chain, 'cpu')
|
||||
# (joints_num, 3)
|
||||
tgt_offsets = tgt_skel.get_offsets_joints(example_data[0])
|
||||
# print(tgt_offsets)
|
||||
|
||||
source_list = os.listdir(data_dir)
|
||||
frame_num = 0
|
||||
'''Read source dataset'''
|
||||
for source_file in tqdm(source_list):
|
||||
source_data = np.load(os.path.join(data_dir, source_file))[:, :joints_num]
|
||||
try:
|
||||
name = ''.join(source_file[:-7].split('_')) + '.npy'
|
||||
data, ground_positions, positions, l_velocity = process_file(source_data, 0.05)
|
||||
rec_ric_data = recover_from_ric(torch.from_numpy(data).unsqueeze(0).float(), joints_num)
|
||||
if np.isnan(rec_ric_data.numpy()).any():
|
||||
print(source_file)
|
||||
continue
|
||||
np.save(pjoin(save_dir1, name), rec_ric_data.squeeze().numpy())
|
||||
np.save(pjoin(save_dir2, name), data)
|
||||
frame_num += data.shape[0]
|
||||
except Exception as e:
|
||||
print(source_file)
|
||||
print(e)
|
||||
|
||||
print('Total clips: %d, Frames: %d, Duration: %fm' %
|
||||
(len(source_list), frame_num, frame_num / 12.5 / 60))
|
||||
@@ -1,63 +0,0 @@
|
||||
import numpy as np
|
||||
|
||||
# Define a kinematic tree for the skeletal struture
|
||||
kit_kinematic_chain = [[0, 11, 12, 13, 14, 15], [0, 16, 17, 18, 19, 20], [0, 1, 2, 3, 4], [3, 5, 6, 7], [3, 8, 9, 10]]
|
||||
|
||||
kit_raw_offsets = np.array(
|
||||
[
|
||||
[0, 0, 0],
|
||||
[0, 1, 0],
|
||||
[0, 1, 0],
|
||||
[0, 1, 0],
|
||||
[0, 1, 0],
|
||||
[1, 0, 0],
|
||||
[0, -1, 0],
|
||||
[0, -1, 0],
|
||||
[-1, 0, 0],
|
||||
[0, -1, 0],
|
||||
[0, -1, 0],
|
||||
[1, 0, 0],
|
||||
[0, -1, 0],
|
||||
[0, -1, 0],
|
||||
[0, 0, 1],
|
||||
[0, 0, 1],
|
||||
[-1, 0, 0],
|
||||
[0, -1, 0],
|
||||
[0, -1, 0],
|
||||
[0, 0, 1],
|
||||
[0, 0, 1]
|
||||
]
|
||||
)
|
||||
|
||||
t2m_raw_offsets = np.array([[0,0,0],
|
||||
[1,0,0],
|
||||
[-1,0,0],
|
||||
[0,1,0],
|
||||
[0,-1,0],
|
||||
[0,-1,0],
|
||||
[0,1,0],
|
||||
[0,-1,0],
|
||||
[0,-1,0],
|
||||
[0,1,0],
|
||||
[0,0,1],
|
||||
[0,0,1],
|
||||
[0,1,0],
|
||||
[1,0,0],
|
||||
[-1,0,0],
|
||||
[0,0,1],
|
||||
[0,-1,0],
|
||||
[0,-1,0],
|
||||
[0,-1,0],
|
||||
[0,-1,0],
|
||||
[0,-1,0],
|
||||
[0,-1,0]])
|
||||
|
||||
t2m_kinematic_chain = [[0, 2, 5, 8, 11], [0, 1, 4, 7, 10], [0, 3, 6, 9, 12, 15], [9, 14, 17, 19, 21], [9, 13, 16, 18, 20]]
|
||||
t2m_left_hand_chain = [[20, 22, 23, 24], [20, 34, 35, 36], [20, 25, 26, 27], [20, 31, 32, 33], [20, 28, 29, 30]]
|
||||
t2m_right_hand_chain = [[21, 43, 44, 45], [21, 46, 47, 48], [21, 40, 41, 42], [21, 37, 38, 39], [21, 49, 50, 51]]
|
||||
|
||||
|
||||
kit_tgt_skel_id = '03950'
|
||||
|
||||
t2m_tgt_skel_id = '000021'
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
import numpy as np
|
||||
import pickle
|
||||
from os.path import join as pjoin
|
||||
|
||||
POS_enumerator = {
|
||||
'VERB': 0,
|
||||
'NOUN': 1,
|
||||
'DET': 2,
|
||||
'ADP': 3,
|
||||
'NUM': 4,
|
||||
'AUX': 5,
|
||||
'PRON': 6,
|
||||
'ADJ': 7,
|
||||
'ADV': 8,
|
||||
'Loc_VIP': 9,
|
||||
'Body_VIP': 10,
|
||||
'Obj_VIP': 11,
|
||||
'Act_VIP': 12,
|
||||
'Desc_VIP': 13,
|
||||
'OTHER': 14,
|
||||
}
|
||||
|
||||
Loc_list = ('left', 'right', 'clockwise', 'counterclockwise', 'anticlockwise', 'forward', 'back', 'backward',
|
||||
'up', 'down', 'straight', 'curve')
|
||||
|
||||
Body_list = ('arm', 'chin', 'foot', 'feet', 'face', 'hand', 'mouth', 'leg', 'waist', 'eye', 'knee', 'shoulder', 'thigh')
|
||||
|
||||
Obj_List = ('stair', 'dumbbell', 'chair', 'window', 'floor', 'car', 'ball', 'handrail', 'baseball', 'basketball')
|
||||
|
||||
Act_list = ('walk', 'run', 'swing', 'pick', 'bring', 'kick', 'put', 'squat', 'throw', 'hop', 'dance', 'jump', 'turn',
|
||||
'stumble', 'dance', 'stop', 'sit', 'lift', 'lower', 'raise', 'wash', 'stand', 'kneel', 'stroll',
|
||||
'rub', 'bend', 'balance', 'flap', 'jog', 'shuffle', 'lean', 'rotate', 'spin', 'spread', 'climb')
|
||||
|
||||
Desc_list = ('slowly', 'carefully', 'fast', 'careful', 'slow', 'quickly', 'happy', 'angry', 'sad', 'happily', 'angrily', 'sadly')
|
||||
|
||||
VIP_dict = {
|
||||
'Loc_VIP': Loc_list,
|
||||
'Body_VIP': Body_list,
|
||||
'Obj_VIP': Obj_List,
|
||||
'Act_VIP': Act_list,
|
||||
'Desc_VIP': Desc_list,
|
||||
}
|
||||
|
||||
|
||||
class WordVectorizer(object):
|
||||
def __init__(self, meta_root, prefix):
|
||||
vectors = np.load(pjoin(meta_root, '%s_data.npy'%prefix))
|
||||
words = pickle.load(open(pjoin(meta_root, '%s_words.pkl'%prefix), 'rb'))
|
||||
word2idx = pickle.load(open(pjoin(meta_root, '%s_idx.pkl'%prefix), 'rb'))
|
||||
self.word2vec = {w: vectors[word2idx[w]] for w in words}
|
||||
|
||||
def _get_pos_ohot(self, pos):
|
||||
pos_vec = np.zeros(len(POS_enumerator))
|
||||
if pos in POS_enumerator:
|
||||
pos_vec[POS_enumerator[pos]] = 1
|
||||
else:
|
||||
pos_vec[POS_enumerator['OTHER']] = 1
|
||||
return pos_vec
|
||||
|
||||
def __len__(self):
|
||||
return len(self.word2vec)
|
||||
|
||||
def __getitem__(self, item):
|
||||
word, pos = item.split('/')
|
||||
if word in self.word2vec:
|
||||
word_vec = self.word2vec[word]
|
||||
vip_pos = None
|
||||
for key, values in VIP_dict.items():
|
||||
if word in values:
|
||||
vip_pos = key
|
||||
break
|
||||
if vip_pos is not None:
|
||||
pos_vec = self._get_pos_ohot(vip_pos)
|
||||
else:
|
||||
pos_vec = self._get_pos_ohot(pos)
|
||||
else:
|
||||
word_vec = self.word2vec['unk']
|
||||
pos_vec = self._get_pos_ohot('OTHER')
|
||||
return word_vec, pos_vec
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,2 +0,0 @@
|
||||
from .tensors import lengths_to_mask
|
||||
from .collate import collate_text_and_length, collate_pairs_and_text, collate_datastruct_and_text, collate_tensor_with_padding
|
||||
@@ -1,99 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Max-Planck-Gesellschaft zur Förderung der Wissenschaften e.V. (MPG) is
|
||||
# holder of all proprietary rights on this computer program.
|
||||
# You can only use this computer program if you have closed
|
||||
# a license agreement with MPG or you get the right to use the computer
|
||||
# program from someone who is authorized to grant you that right.
|
||||
# Any use of the computer program without a valid license is prohibited and
|
||||
# liable to prosecution.
|
||||
#
|
||||
# Copyright©2020 Max-Planck-Gesellschaft zur Förderung
|
||||
# der Wissenschaften e.V. (MPG). acting on behalf of its Max Planck Institute
|
||||
# for Intelligent Systems. All rights reserved.
|
||||
#
|
||||
# Contact: ps-license@tuebingen.mpg.de
|
||||
|
||||
from typing import List, Dict
|
||||
from torch import Tensor
|
||||
|
||||
|
||||
def collate_tensor_with_padding(batch: List[Tensor]) -> Tensor:
|
||||
dims = batch[0].dim()
|
||||
max_size = [max([b.size(i) for b in batch]) for i in range(dims)]
|
||||
size = (len(batch),) + tuple(max_size)
|
||||
canvas = batch[0].new_zeros(size=size)
|
||||
for i, b in enumerate(batch):
|
||||
sub_tensor = canvas[i]
|
||||
for d in range(dims):
|
||||
sub_tensor = sub_tensor.narrow(d, 0, b.size(d))
|
||||
sub_tensor.add_(b)
|
||||
return canvas
|
||||
|
||||
|
||||
def collate_datastruct_and_text(lst_elements: List) -> Dict:
|
||||
collate_datastruct = lst_elements[0]["datastruct"].transforms.collate
|
||||
|
||||
batch = {
|
||||
# Collate with padding for the datastruct
|
||||
"datastruct": collate_datastruct([x["datastruct"] for x in lst_elements]),
|
||||
# Collate normally for the length
|
||||
"length": [x["length"] for x in lst_elements],
|
||||
# Collate the text
|
||||
"text": [x["text"] for x in lst_elements]}
|
||||
|
||||
# add keyid for example
|
||||
otherkeys = [x for x in lst_elements[0].keys() if x not in batch]
|
||||
for key in otherkeys:
|
||||
batch[key] = [x[key] for x in lst_elements]
|
||||
|
||||
return batch
|
||||
|
||||
def collate_length_and_text(lst_elements: List) -> Dict:
|
||||
|
||||
batch = {
|
||||
"length_0": [x["length_0"] for x in lst_elements],
|
||||
"length_1": [x["length_1"] for x in lst_elements],
|
||||
"length_transition": [x["length_transition"] for x in lst_elements],
|
||||
"length_1_with_transition": [x["length_1_with_transition"] for x in lst_elements],
|
||||
"text_0": [x["text_0"] for x in lst_elements],
|
||||
"text_1": [x["text_1"] for x in lst_elements]
|
||||
}
|
||||
|
||||
return batch
|
||||
|
||||
def collate_pairs_and_text(lst_elements: List, ) -> Dict:
|
||||
if 'features_0' not in lst_elements[0]: # test set
|
||||
collate_datastruct = lst_elements[0]["datastruct"].transforms.collate
|
||||
batch = {"datastruct": collate_datastruct([x["datastruct"] for x in lst_elements]),
|
||||
"length_0": [x["length_0"] for x in lst_elements],
|
||||
"length_1": [x["length_1"] for x in lst_elements],
|
||||
"length_transition": [x["length_transition"] for x in lst_elements],
|
||||
"length_1_with_transition": [x["length_1_with_transition"] for x in lst_elements],
|
||||
"text_0": [x["text_0"] for x in lst_elements],
|
||||
"text_1": [x["text_1"] for x in lst_elements]
|
||||
}
|
||||
|
||||
else:
|
||||
batch = {"motion_feats_0": collate_tensor_with_padding([el["features_0"] for el in lst_elements]),
|
||||
"motion_feats_1": collate_tensor_with_padding([el["features_1"] for el in lst_elements]),
|
||||
"motion_feats_1_with_transition": collate_tensor_with_padding([el["features_1_with_transition"] for el in lst_elements]),
|
||||
"length_0": [x["length_0"] for x in lst_elements],
|
||||
"length_1": [x["length_1"] for x in lst_elements],
|
||||
"length_transition": [x["length_transition"] for x in lst_elements],
|
||||
"length_1_with_transition": [x["length_1_with_transition"] for x in lst_elements],
|
||||
"text_0": [x["text_0"] for x in lst_elements],
|
||||
"text_1": [x["text_1"] for x in lst_elements]
|
||||
}
|
||||
return batch
|
||||
|
||||
|
||||
def collate_text_and_length(lst_elements: Dict) -> Dict:
|
||||
batch = {"length": [x["length"] for x in lst_elements],
|
||||
"text": [x["text"] for x in lst_elements]}
|
||||
|
||||
# add keyid for example
|
||||
otherkeys = [x for x in lst_elements[0].keys() if x not in batch and x != "datastruct"]
|
||||
for key in otherkeys:
|
||||
batch[key] = [x[key] for x in lst_elements]
|
||||
return batch
|
||||
@@ -1,72 +0,0 @@
|
||||
from .geometry import *
|
||||
|
||||
def nfeats_of(rottype):
|
||||
if rottype in ["rotvec", "axisangle"]:
|
||||
return 3
|
||||
elif rottype in ["rotquat", "quaternion"]:
|
||||
return 4
|
||||
elif rottype in ["rot6d", "6drot", "rotation6d"]:
|
||||
return 6
|
||||
elif rottype in ["rotmat"]:
|
||||
return 9
|
||||
else:
|
||||
return TypeError("This rotation type doesn't have features.")
|
||||
|
||||
|
||||
def axis_angle_to(newtype, rotations):
|
||||
if newtype in ["matrix"]:
|
||||
rotations = axis_angle_to_matrix(rotations)
|
||||
return rotations
|
||||
elif newtype in ["rotmat"]:
|
||||
rotations = axis_angle_to_matrix(rotations)
|
||||
rotations = matrix_to("rotmat", rotations)
|
||||
return rotations
|
||||
elif newtype in ["rot6d", "6drot", "rotation6d"]:
|
||||
rotations = axis_angle_to_matrix(rotations)
|
||||
rotations = matrix_to("rot6d", rotations)
|
||||
return rotations
|
||||
elif newtype in ["rotquat", "quaternion"]:
|
||||
rotations = axis_angle_to_quaternion(rotations)
|
||||
return rotations
|
||||
elif newtype in ["rotvec", "axisangle"]:
|
||||
return rotations
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
def matrix_to(newtype, rotations):
|
||||
if newtype in ["matrix"]:
|
||||
return rotations
|
||||
if newtype in ["rotmat"]:
|
||||
rotations = rotations.reshape((*rotations.shape[:-2], 9))
|
||||
return rotations
|
||||
elif newtype in ["rot6d", "6drot", "rotation6d"]:
|
||||
rotations = matrix_to_rotation_6d(rotations)
|
||||
return rotations
|
||||
elif newtype in ["rotquat", "quaternion"]:
|
||||
rotations = matrix_to_quaternion(rotations)
|
||||
return rotations
|
||||
elif newtype in ["rotvec", "axisangle"]:
|
||||
rotations = matrix_to_axis_angle(rotations)
|
||||
return rotations
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
def to_matrix(oldtype, rotations):
|
||||
if oldtype in ["matrix"]:
|
||||
return rotations
|
||||
if oldtype in ["rotmat"]:
|
||||
rotations = rotations.reshape((*rotations.shape[:-2], 3, 3))
|
||||
return rotations
|
||||
elif oldtype in ["rot6d", "6drot", "rotation6d"]:
|
||||
rotations = rotation_6d_to_matrix(rotations)
|
||||
return rotations
|
||||
elif oldtype in ["rotquat", "quaternion"]:
|
||||
rotations = quaternion_to_matrix(rotations)
|
||||
return rotations
|
||||
elif oldtype in ["rotvec", "axisangle"]:
|
||||
rotations = axis_angle_to_matrix(rotations)
|
||||
return rotations
|
||||
else:
|
||||
raise NotImplementedError
|
||||
@@ -1,566 +0,0 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
# Check PYTORCH3D_LICENCE before use
|
||||
|
||||
import functools
|
||||
from typing import Optional
|
||||
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
|
||||
|
||||
"""
|
||||
The transformation matrices returned from the functions in this file assume
|
||||
the points on which the transformation will be applied are column vectors.
|
||||
i.e. the R matrix is structured as
|
||||
|
||||
R = [
|
||||
[Rxx, Rxy, Rxz],
|
||||
[Ryx, Ryy, Ryz],
|
||||
[Rzx, Rzy, Rzz],
|
||||
] # (3, 3)
|
||||
|
||||
This matrix can be applied to column vectors by post multiplication
|
||||
by the points e.g.
|
||||
|
||||
points = [[0], [1], [2]] # (3 x 1) xyz coordinates of a point
|
||||
transformed_points = R * points
|
||||
|
||||
To apply the same matrix to points which are row vectors, the R matrix
|
||||
can be transposed and pre multiplied by the points:
|
||||
|
||||
e.g.
|
||||
points = [[0, 1, 2]] # (1 x 3) xyz coordinates of a point
|
||||
transformed_points = points * R.transpose(1, 0)
|
||||
"""
|
||||
|
||||
|
||||
# Added
|
||||
def matrix_of_angles(cos, sin, inv=False, dim=2):
|
||||
assert dim in [2, 3]
|
||||
sin = -sin if inv else sin
|
||||
if dim == 2:
|
||||
row1 = torch.stack((cos, -sin), axis=-1)
|
||||
row2 = torch.stack((sin, cos), axis=-1)
|
||||
return torch.stack((row1, row2), axis=-2)
|
||||
elif dim == 3:
|
||||
row1 = torch.stack((cos, -sin, 0*cos), axis=-1)
|
||||
row2 = torch.stack((sin, cos, 0*cos), axis=-1)
|
||||
row3 = torch.stack((0*sin, 0*cos, 1+0*cos), axis=-1)
|
||||
return torch.stack((row1, row2, row3),axis=-2)
|
||||
|
||||
|
||||
def quaternion_to_matrix(quaternions):
|
||||
"""
|
||||
Convert rotations given as quaternions to rotation matrices.
|
||||
|
||||
Args:
|
||||
quaternions: quaternions with real part first,
|
||||
as tensor of shape (..., 4).
|
||||
|
||||
Returns:
|
||||
Rotation matrices as tensor of shape (..., 3, 3).
|
||||
"""
|
||||
r, i, j, k = torch.unbind(quaternions, -1)
|
||||
two_s = 2.0 / (quaternions * quaternions).sum(-1)
|
||||
|
||||
o = torch.stack(
|
||||
(
|
||||
1 - two_s * (j * j + k * k),
|
||||
two_s * (i * j - k * r),
|
||||
two_s * (i * k + j * r),
|
||||
two_s * (i * j + k * r),
|
||||
1 - two_s * (i * i + k * k),
|
||||
two_s * (j * k - i * r),
|
||||
two_s * (i * k - j * r),
|
||||
two_s * (j * k + i * r),
|
||||
1 - two_s * (i * i + j * j),
|
||||
),
|
||||
-1,
|
||||
)
|
||||
return o.reshape(quaternions.shape[:-1] + (3, 3))
|
||||
|
||||
|
||||
def _copysign(a, b):
|
||||
"""
|
||||
Return a tensor where each element has the absolute value taken from the,
|
||||
corresponding element of a, with sign taken from the corresponding
|
||||
element of b. This is like the standard copysign floating-point operation,
|
||||
but is not careful about negative 0 and NaN.
|
||||
|
||||
Args:
|
||||
a: source tensor.
|
||||
b: tensor whose signs will be used, of the same shape as a.
|
||||
|
||||
Returns:
|
||||
Tensor of the same shape as a with the signs of b.
|
||||
"""
|
||||
signs_differ = (a < 0) != (b < 0)
|
||||
return torch.where(signs_differ, -a, a)
|
||||
|
||||
|
||||
def _sqrt_positive_part(x):
|
||||
"""
|
||||
Returns torch.sqrt(torch.max(0, x))
|
||||
but with a zero subgradient where x is 0.
|
||||
"""
|
||||
ret = torch.zeros_like(x)
|
||||
positive_mask = x > 0
|
||||
ret[positive_mask] = torch.sqrt(x[positive_mask])
|
||||
return ret
|
||||
|
||||
|
||||
def matrix_to_quaternion(matrix):
|
||||
"""
|
||||
Convert rotations given as rotation matrices to quaternions.
|
||||
|
||||
Args:
|
||||
matrix: Rotation matrices as tensor of shape (..., 3, 3).
|
||||
|
||||
Returns:
|
||||
quaternions with real part first, as tensor of shape (..., 4).
|
||||
"""
|
||||
if matrix.size(-1) != 3 or matrix.size(-2) != 3:
|
||||
raise ValueError(f"Invalid rotation matrix shape f{matrix.shape}.")
|
||||
m00 = matrix[..., 0, 0]
|
||||
m11 = matrix[..., 1, 1]
|
||||
m22 = matrix[..., 2, 2]
|
||||
o0 = 0.5 * _sqrt_positive_part(1 + m00 + m11 + m22)
|
||||
x = 0.5 * _sqrt_positive_part(1 + m00 - m11 - m22)
|
||||
y = 0.5 * _sqrt_positive_part(1 - m00 + m11 - m22)
|
||||
z = 0.5 * _sqrt_positive_part(1 - m00 - m11 + m22)
|
||||
o1 = _copysign(x, matrix[..., 2, 1] - matrix[..., 1, 2])
|
||||
o2 = _copysign(y, matrix[..., 0, 2] - matrix[..., 2, 0])
|
||||
o3 = _copysign(z, matrix[..., 1, 0] - matrix[..., 0, 1])
|
||||
return torch.stack((o0, o1, o2, o3), -1)
|
||||
|
||||
|
||||
def _axis_angle_rotation(axis: str, angle):
|
||||
"""
|
||||
Return the rotation matrices for one of the rotations about an axis
|
||||
of which Euler angles describe, for each value of the angle given.
|
||||
|
||||
Args:
|
||||
axis: Axis label "X" or "Y or "Z".
|
||||
angle: any shape tensor of Euler angles in radians
|
||||
|
||||
Returns:
|
||||
Rotation matrices as tensor of shape (..., 3, 3).
|
||||
"""
|
||||
|
||||
cos = torch.cos(angle)
|
||||
sin = torch.sin(angle)
|
||||
one = torch.ones_like(angle)
|
||||
zero = torch.zeros_like(angle)
|
||||
|
||||
if axis == "X":
|
||||
R_flat = (one, zero, zero, zero, cos, -sin, zero, sin, cos)
|
||||
if axis == "Y":
|
||||
R_flat = (cos, zero, sin, zero, one, zero, -sin, zero, cos)
|
||||
if axis == "Z":
|
||||
R_flat = (cos, -sin, zero, sin, cos, zero, zero, zero, one)
|
||||
|
||||
return torch.stack(R_flat, -1).reshape(angle.shape + (3, 3))
|
||||
|
||||
|
||||
def euler_angles_to_matrix(euler_angles, convention: str):
|
||||
"""
|
||||
Convert rotations given as Euler angles in radians to rotation matrices.
|
||||
|
||||
Args:
|
||||
euler_angles: Euler angles in radians as tensor of shape (..., 3).
|
||||
convention: Convention string of three uppercase letters from
|
||||
{"X", "Y", and "Z"}.
|
||||
|
||||
Returns:
|
||||
Rotation matrices as tensor of shape (..., 3, 3).
|
||||
"""
|
||||
if euler_angles.dim() == 0 or euler_angles.shape[-1] != 3:
|
||||
raise ValueError("Invalid input euler angles.")
|
||||
if len(convention) != 3:
|
||||
raise ValueError("Convention must have 3 letters.")
|
||||
if convention[1] in (convention[0], convention[2]):
|
||||
raise ValueError(f"Invalid convention {convention}.")
|
||||
for letter in convention:
|
||||
if letter not in ("X", "Y", "Z"):
|
||||
raise ValueError(f"Invalid letter {letter} in convention string.")
|
||||
matrices = map(_axis_angle_rotation, convention, torch.unbind(euler_angles, -1))
|
||||
return functools.reduce(torch.matmul, matrices)
|
||||
|
||||
|
||||
def _angle_from_tan(
|
||||
axis: str, other_axis: str, data, horizontal: bool, tait_bryan: bool
|
||||
):
|
||||
"""
|
||||
Extract the first or third Euler angle from the two members of
|
||||
the matrix which are positive constant times its sine and cosine.
|
||||
|
||||
Args:
|
||||
axis: Axis label "X" or "Y or "Z" for the angle we are finding.
|
||||
other_axis: Axis label "X" or "Y or "Z" for the middle axis in the
|
||||
convention.
|
||||
data: Rotation matrices as tensor of shape (..., 3, 3).
|
||||
horizontal: Whether we are looking for the angle for the third axis,
|
||||
which means the relevant entries are in the same row of the
|
||||
rotation matrix. If not, they are in the same column.
|
||||
tait_bryan: Whether the first and third axes in the convention differ.
|
||||
|
||||
Returns:
|
||||
Euler Angles in radians for each matrix in data as a tensor
|
||||
of shape (...).
|
||||
"""
|
||||
|
||||
i1, i2 = {"X": (2, 1), "Y": (0, 2), "Z": (1, 0)}[axis]
|
||||
if horizontal:
|
||||
i2, i1 = i1, i2
|
||||
even = (axis + other_axis) in ["XY", "YZ", "ZX"]
|
||||
if horizontal == even:
|
||||
return torch.atan2(data[..., i1], data[..., i2])
|
||||
if tait_bryan:
|
||||
return torch.atan2(-data[..., i2], data[..., i1])
|
||||
return torch.atan2(data[..., i2], -data[..., i1])
|
||||
|
||||
|
||||
def _index_from_letter(letter: str):
|
||||
if letter == "X":
|
||||
return 0
|
||||
if letter == "Y":
|
||||
return 1
|
||||
if letter == "Z":
|
||||
return 2
|
||||
|
||||
|
||||
def matrix_to_euler_angles(matrix, convention: str):
|
||||
"""
|
||||
Convert rotations given as rotation matrices to Euler angles in radians.
|
||||
|
||||
Args:
|
||||
matrix: Rotation matrices as tensor of shape (..., 3, 3).
|
||||
convention: Convention string of three uppercase letters.
|
||||
|
||||
Returns:
|
||||
Euler angles in radians as tensor of shape (..., 3).
|
||||
"""
|
||||
if len(convention) != 3:
|
||||
raise ValueError("Convention must have 3 letters.")
|
||||
if convention[1] in (convention[0], convention[2]):
|
||||
raise ValueError(f"Invalid convention {convention}.")
|
||||
for letter in convention:
|
||||
if letter not in ("X", "Y", "Z"):
|
||||
raise ValueError(f"Invalid letter {letter} in convention string.")
|
||||
if matrix.size(-1) != 3 or matrix.size(-2) != 3:
|
||||
raise ValueError(f"Invalid rotation matrix shape f{matrix.shape}.")
|
||||
i0 = _index_from_letter(convention[0])
|
||||
i2 = _index_from_letter(convention[2])
|
||||
tait_bryan = i0 != i2
|
||||
if tait_bryan:
|
||||
central_angle = torch.asin(
|
||||
matrix[..., i0, i2] * (-1.0 if i0 - i2 in [-1, 2] else 1.0)
|
||||
)
|
||||
else:
|
||||
central_angle = torch.acos(matrix[..., i0, i0])
|
||||
|
||||
o = (
|
||||
_angle_from_tan(
|
||||
convention[0], convention[1], matrix[..., i2], False, tait_bryan
|
||||
),
|
||||
central_angle,
|
||||
_angle_from_tan(
|
||||
convention[2], convention[1], matrix[..., i0, :], True, tait_bryan
|
||||
),
|
||||
)
|
||||
return torch.stack(o, -1)
|
||||
|
||||
|
||||
def random_quaternions(
|
||||
n: int, dtype: Optional[torch.dtype] = None, device=None, requires_grad=False
|
||||
):
|
||||
"""
|
||||
Generate random quaternions representing rotations,
|
||||
i.e. versors with nonnegative real part.
|
||||
|
||||
Args:
|
||||
n: Number of quaternions in a batch to return.
|
||||
dtype: Type to return.
|
||||
device: Desired device of returned tensor. Default:
|
||||
uses the current device for the default tensor type.
|
||||
requires_grad: Whether the resulting tensor should have the gradient
|
||||
flag set.
|
||||
|
||||
Returns:
|
||||
Quaternions as tensor of shape (N, 4).
|
||||
"""
|
||||
o = torch.randn((n, 4), dtype=dtype, device=device, requires_grad=requires_grad)
|
||||
s = (o * o).sum(1)
|
||||
o = o / _copysign(torch.sqrt(s), o[:, 0])[:, None]
|
||||
return o
|
||||
|
||||
|
||||
def random_rotations(
|
||||
n: int, dtype: Optional[torch.dtype] = None, device=None, requires_grad=False
|
||||
):
|
||||
"""
|
||||
Generate random rotations as 3x3 rotation matrices.
|
||||
|
||||
Args:
|
||||
n: Number of rotation matrices in a batch to return.
|
||||
dtype: Type to return.
|
||||
device: Device of returned tensor. Default: if None,
|
||||
uses the current device for the default tensor type.
|
||||
requires_grad: Whether the resulting tensor should have the gradient
|
||||
flag set.
|
||||
|
||||
Returns:
|
||||
Rotation matrices as tensor of shape (n, 3, 3).
|
||||
"""
|
||||
quaternions = random_quaternions(
|
||||
n, dtype=dtype, device=device, requires_grad=requires_grad
|
||||
)
|
||||
return quaternion_to_matrix(quaternions)
|
||||
|
||||
|
||||
def random_rotation(
|
||||
dtype: Optional[torch.dtype] = None, device=None, requires_grad=False
|
||||
):
|
||||
"""
|
||||
Generate a single random 3x3 rotation matrix.
|
||||
|
||||
Args:
|
||||
dtype: Type to return
|
||||
device: Device of returned tensor. Default: if None,
|
||||
uses the current device for the default tensor type
|
||||
requires_grad: Whether the resulting tensor should have the gradient
|
||||
flag set
|
||||
|
||||
Returns:
|
||||
Rotation matrix as tensor of shape (3, 3).
|
||||
"""
|
||||
return random_rotations(1, dtype, device, requires_grad)[0]
|
||||
|
||||
|
||||
def standardize_quaternion(quaternions):
|
||||
"""
|
||||
Convert a unit quaternion to a standard form: one in which the real
|
||||
part is non negative.
|
||||
|
||||
Args:
|
||||
quaternions: Quaternions with real part first,
|
||||
as tensor of shape (..., 4).
|
||||
|
||||
Returns:
|
||||
Standardized quaternions as tensor of shape (..., 4).
|
||||
"""
|
||||
return torch.where(quaternions[..., 0:1] < 0, -quaternions, quaternions)
|
||||
|
||||
|
||||
def quaternion_raw_multiply(a, b):
|
||||
"""
|
||||
Multiply two quaternions.
|
||||
Usual torch rules for broadcasting apply.
|
||||
|
||||
Args:
|
||||
a: Quaternions as tensor of shape (..., 4), real part first.
|
||||
b: Quaternions as tensor of shape (..., 4), real part first.
|
||||
|
||||
Returns:
|
||||
The product of a and b, a tensor of quaternions shape (..., 4).
|
||||
"""
|
||||
aw, ax, ay, az = torch.unbind(a, -1)
|
||||
bw, bx, by, bz = torch.unbind(b, -1)
|
||||
ow = aw * bw - ax * bx - ay * by - az * bz
|
||||
ox = aw * bx + ax * bw + ay * bz - az * by
|
||||
oy = aw * by - ax * bz + ay * bw + az * bx
|
||||
oz = aw * bz + ax * by - ay * bx + az * bw
|
||||
return torch.stack((ow, ox, oy, oz), -1)
|
||||
|
||||
|
||||
def quaternion_multiply(a, b):
|
||||
"""
|
||||
Multiply two quaternions representing rotations, returning the quaternion
|
||||
representing their composition, i.e. the versor with nonnegative real part.
|
||||
Usual torch rules for broadcasting apply.
|
||||
|
||||
Args:
|
||||
a: Quaternions as tensor of shape (..., 4), real part first.
|
||||
b: Quaternions as tensor of shape (..., 4), real part first.
|
||||
|
||||
Returns:
|
||||
The product of a and b, a tensor of quaternions of shape (..., 4).
|
||||
"""
|
||||
ab = quaternion_raw_multiply(a, b)
|
||||
return standardize_quaternion(ab)
|
||||
|
||||
|
||||
def quaternion_invert(quaternion):
|
||||
"""
|
||||
Given a quaternion representing rotation, get the quaternion representing
|
||||
its inverse.
|
||||
|
||||
Args:
|
||||
quaternion: Quaternions as tensor of shape (..., 4), with real part
|
||||
first, which must be versors (unit quaternions).
|
||||
|
||||
Returns:
|
||||
The inverse, a tensor of quaternions of shape (..., 4).
|
||||
"""
|
||||
|
||||
return quaternion * quaternion.new_tensor([1, -1, -1, -1])
|
||||
|
||||
|
||||
def quaternion_apply(quaternion, point):
|
||||
"""
|
||||
Apply the rotation given by a quaternion to a 3D point.
|
||||
Usual torch rules for broadcasting apply.
|
||||
|
||||
Args:
|
||||
quaternion: Tensor of quaternions, real part first, of shape (..., 4).
|
||||
point: Tensor of 3D points of shape (..., 3).
|
||||
|
||||
Returns:
|
||||
Tensor of rotated points of shape (..., 3).
|
||||
"""
|
||||
if point.size(-1) != 3:
|
||||
raise ValueError(f"Points are not in 3D, f{point.shape}.")
|
||||
real_parts = point.new_zeros(point.shape[:-1] + (1,))
|
||||
point_as_quaternion = torch.cat((real_parts, point), -1)
|
||||
out = quaternion_raw_multiply(
|
||||
quaternion_raw_multiply(quaternion, point_as_quaternion),
|
||||
quaternion_invert(quaternion),
|
||||
)
|
||||
return out[..., 1:]
|
||||
|
||||
|
||||
def axis_angle_to_matrix(axis_angle):
|
||||
"""
|
||||
Convert rotations given as axis/angle to rotation matrices.
|
||||
|
||||
Args:
|
||||
axis_angle: Rotations given as a vector in axis angle form,
|
||||
as a tensor of shape (..., 3), where the magnitude is
|
||||
the angle turned anticlockwise in radians around the
|
||||
vector's direction.
|
||||
|
||||
Returns:
|
||||
Rotation matrices as tensor of shape (..., 3, 3).
|
||||
"""
|
||||
return quaternion_to_matrix(axis_angle_to_quaternion(axis_angle))
|
||||
|
||||
|
||||
def matrix_to_axis_angle(matrix):
|
||||
"""
|
||||
Convert rotations given as rotation matrices to axis/angle.
|
||||
|
||||
Args:
|
||||
matrix: Rotation matrices as tensor of shape (..., 3, 3).
|
||||
|
||||
Returns:
|
||||
Rotations given as a vector in axis angle form, as a tensor
|
||||
of shape (..., 3), where the magnitude is the angle
|
||||
turned anticlockwise in radians around the vector's
|
||||
direction.
|
||||
"""
|
||||
return quaternion_to_axis_angle(matrix_to_quaternion(matrix))
|
||||
|
||||
|
||||
def axis_angle_to_quaternion(axis_angle):
|
||||
"""
|
||||
Convert rotations given as axis/angle to quaternions.
|
||||
|
||||
Args:
|
||||
axis_angle: Rotations given as a vector in axis angle form,
|
||||
as a tensor of shape (..., 3), where the magnitude is
|
||||
the angle turned anticlockwise in radians around the
|
||||
vector's direction.
|
||||
|
||||
Returns:
|
||||
quaternions with real part first, as tensor of shape (..., 4).
|
||||
"""
|
||||
angles = torch.norm(axis_angle, p=2, dim=-1, keepdim=True)
|
||||
half_angles = 0.5 * angles
|
||||
eps = 1e-6
|
||||
small_angles = angles.abs() < eps
|
||||
sin_half_angles_over_angles = torch.empty_like(angles)
|
||||
sin_half_angles_over_angles[~small_angles] = (
|
||||
torch.sin(half_angles[~small_angles]) / angles[~small_angles]
|
||||
)
|
||||
# for x small, sin(x/2) is about x/2 - (x/2)^3/6
|
||||
# so sin(x/2)/x is about 1/2 - (x*x)/48
|
||||
sin_half_angles_over_angles[small_angles] = (
|
||||
0.5 - (angles[small_angles] * angles[small_angles]) / 48
|
||||
)
|
||||
quaternions = torch.cat(
|
||||
[torch.cos(half_angles), axis_angle * sin_half_angles_over_angles], dim=-1
|
||||
)
|
||||
return quaternions
|
||||
|
||||
|
||||
def quaternion_to_axis_angle(quaternions):
|
||||
"""
|
||||
Convert rotations given as quaternions to axis/angle.
|
||||
|
||||
Args:
|
||||
quaternions: quaternions with real part first,
|
||||
as tensor of shape (..., 4).
|
||||
|
||||
Returns:
|
||||
Rotations given as a vector in axis angle form, as a tensor
|
||||
of shape (..., 3), where the magnitude is the angle
|
||||
turned anticlockwise in radians around the vector's
|
||||
direction.
|
||||
"""
|
||||
norms = torch.norm(quaternions[..., 1:], p=2, dim=-1, keepdim=True)
|
||||
half_angles = torch.atan2(norms, quaternions[..., :1])
|
||||
angles = 2 * half_angles
|
||||
eps = 1e-6
|
||||
small_angles = angles.abs() < eps
|
||||
sin_half_angles_over_angles = torch.empty_like(angles)
|
||||
sin_half_angles_over_angles[~small_angles] = (
|
||||
torch.sin(half_angles[~small_angles]) / angles[~small_angles]
|
||||
)
|
||||
# for x small, sin(x/2) is about x/2 - (x/2)^3/6
|
||||
# so sin(x/2)/x is about 1/2 - (x*x)/48
|
||||
sin_half_angles_over_angles[small_angles] = (
|
||||
0.5 - (angles[small_angles] * angles[small_angles]) / 48
|
||||
)
|
||||
return quaternions[..., 1:] / sin_half_angles_over_angles
|
||||
|
||||
|
||||
def rotation_6d_to_matrix(d6: torch.Tensor) -> torch.Tensor:
|
||||
"""
|
||||
Converts 6D rotation representation by Zhou et al. [1] to rotation matrix
|
||||
using Gram--Schmidt orthogonalisation per Section B of [1].
|
||||
Args:
|
||||
d6: 6D rotation representation, of size (*, 6)
|
||||
|
||||
Returns:
|
||||
batch of rotation matrices of size (*, 3, 3)
|
||||
|
||||
[1] Zhou, Y., Barnes, C., Lu, J., Yang, J., & Li, H.
|
||||
On the Continuity of Rotation Representations in Neural Networks.
|
||||
IEEE Conference on Computer Vision and Pattern Recognition, 2019.
|
||||
Retrieved from http://arxiv.org/abs/1812.07035
|
||||
"""
|
||||
|
||||
a1, a2 = d6[..., :3], d6[..., 3:]
|
||||
b1 = F.normalize(a1, dim=-1)
|
||||
b2 = a2 - (b1 * a2).sum(-1, keepdim=True) * b1
|
||||
b2 = F.normalize(b2, dim=-1)
|
||||
b3 = torch.cross(b1, b2, dim=-1)
|
||||
return torch.stack((b1, b2, b3), dim=-2)
|
||||
|
||||
|
||||
def matrix_to_rotation_6d(matrix: torch.Tensor) -> torch.Tensor:
|
||||
"""
|
||||
Converts rotation matrices to 6D rotation representation by Zhou et al. [1]
|
||||
by dropping the last row. Note that 6D representation is not unique.
|
||||
Args:
|
||||
matrix: batch of rotation matrices of size (*, 3, 3)
|
||||
|
||||
Returns:
|
||||
6D rotation representation, of size (*, 6)
|
||||
|
||||
[1] Zhou, Y., Barnes, C., Lu, J., Yang, J., & Li, H.
|
||||
On the Continuity of Rotation Representations in Neural Networks.
|
||||
IEEE Conference on Computer Vision and Pattern Recognition, 2019.
|
||||
Retrieved from http://arxiv.org/abs/1812.07035
|
||||
"""
|
||||
return matrix[..., :2, :].clone().reshape(*matrix.size()[:-2], 6)
|
||||
@@ -1,26 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Max-Planck-Gesellschaft zur Förderung der Wissenschaften e.V. (MPG) is
|
||||
# holder of all proprietary rights on this computer program.
|
||||
# You can only use this computer program if you have closed
|
||||
# a license agreement with MPG or you get the right to use the computer
|
||||
# program from someone who is authorized to grant you that right.
|
||||
# Any use of the computer program without a valid license is prohibited and
|
||||
# liable to prosecution.
|
||||
#
|
||||
# Copyright©2020 Max-Planck-Gesellschaft zur Förderung
|
||||
# der Wissenschaften e.V. (MPG). acting on behalf of its Max Planck Institute
|
||||
# for Intelligent Systems. All rights reserved.
|
||||
#
|
||||
# Contact: ps-license@tuebingen.mpg.de
|
||||
|
||||
from typing import List, Dict
|
||||
import torch
|
||||
from torch import Tensor
|
||||
|
||||
|
||||
def lengths_to_mask(lengths: List[int], device: torch.device) -> Tensor:
|
||||
lengths = torch.tensor(lengths, device=device)
|
||||
max_len = max(lengths)
|
||||
mask = torch.arange(max_len, device=device).expand(len(lengths), max_len) < lengths.unsqueeze(1)
|
||||
return mask
|
||||
@@ -1,15 +0,0 @@
|
||||
from .base import Transform
|
||||
from .smpl import SMPLTransform
|
||||
from .xyz import XYZTransform
|
||||
|
||||
# rots2rfeats
|
||||
from .rots2rfeats import Rots2Rfeats
|
||||
from .rots2rfeats import Globalvelandy
|
||||
|
||||
# rots2joints
|
||||
from .rots2joints import Rots2Joints
|
||||
from .rots2joints import SMPLH, SMPLX
|
||||
|
||||
# joints2jfeats
|
||||
from .joints2jfeats import Joints2Jfeats
|
||||
from .joints2jfeats import Rifke
|
||||
@@ -1,84 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Max-Planck-Gesellschaft zur Förderung der Wissenschaften e.V. (MPG) is
|
||||
# holder of all proprietary rights on this computer program.
|
||||
# You can only use this computer program if you have closed
|
||||
# a license agreement with MPG or you get the right to use the computer
|
||||
# program from someone who is authorized to grant you that right.
|
||||
# Any use of the computer program without a valid license is prohibited and
|
||||
# liable to prosecution.
|
||||
#
|
||||
# Copyright©2020 Max-Planck-Gesellschaft zur Förderung
|
||||
# der Wissenschaften e.V. (MPG). acting on behalf of its Max Planck Institute
|
||||
# for Intelligent Systems. All rights reserved.
|
||||
#
|
||||
# Contact: ps-license@tuebingen.mpg.de
|
||||
|
||||
from dataclasses import dataclass, fields
|
||||
|
||||
|
||||
class Transform:
|
||||
|
||||
def collate(self, lst_datastruct):
|
||||
from ..tools import collate_tensor_with_padding
|
||||
example = lst_datastruct[0]
|
||||
|
||||
def collate_or_none(key):
|
||||
if example[key] is None:
|
||||
return None
|
||||
key_lst = [x[key] for x in lst_datastruct]
|
||||
return collate_tensor_with_padding(key_lst)
|
||||
|
||||
kwargs = {key: collate_or_none(key) for key in example.datakeys}
|
||||
|
||||
return self.Datastruct(**kwargs)
|
||||
|
||||
|
||||
# Inspired from SMPLX library
|
||||
# need to define "datakeys" and transforms
|
||||
@dataclass
|
||||
class Datastruct:
|
||||
|
||||
def __getitem__(self, key):
|
||||
return getattr(self, key)
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
self.__dict__[key] = value
|
||||
|
||||
def get(self, key, default=None):
|
||||
return getattr(self, key, default)
|
||||
|
||||
def __iter__(self):
|
||||
return self.keys()
|
||||
|
||||
def keys(self):
|
||||
keys = [t.name for t in fields(self)]
|
||||
return iter(keys)
|
||||
|
||||
def values(self):
|
||||
values = [getattr(self, t.name) for t in fields(self)]
|
||||
return iter(values)
|
||||
|
||||
def items(self):
|
||||
data = [(t.name, getattr(self, t.name)) for t in fields(self)]
|
||||
return iter(data)
|
||||
|
||||
def to(self, *args, **kwargs):
|
||||
for key in self.datakeys:
|
||||
if self[key] is not None:
|
||||
self[key] = self[key].to(*args, **kwargs)
|
||||
return self
|
||||
|
||||
@property
|
||||
def device(self):
|
||||
return self[self.datakeys[0]].device
|
||||
|
||||
def detach(self):
|
||||
|
||||
def detach_or_none(tensor):
|
||||
if tensor is not None:
|
||||
return tensor.detach()
|
||||
return None
|
||||
|
||||
kwargs = {key: detach_or_none(self[key]) for key in self.datakeys}
|
||||
return self.transforms.Datastruct(**kwargs)
|
||||
@@ -1,44 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Max-Planck-Gesellschaft zur Förderung der Wissenschaften e.V. (MPG) is
|
||||
# holder of all proprietary rights on this computer program.
|
||||
# You can only use this computer program if you have closed
|
||||
# a license agreement with MPG or you get the right to use the computer
|
||||
# program from someone who is authorized to grant you that right.
|
||||
# Any use of the computer program without a valid license is prohibited and
|
||||
# liable to prosecution.
|
||||
#
|
||||
# Copyright©2020 Max-Planck-Gesellschaft zur Förderung
|
||||
# der Wissenschaften e.V. (MPG). acting on behalf of its Max Planck Institute
|
||||
# for Intelligent Systems. All rights reserved.
|
||||
#
|
||||
# Contact: ps-license@tuebingen.mpg.de
|
||||
|
||||
from typing import Optional
|
||||
from torch import Tensor
|
||||
|
||||
from .base import Datastruct, dataclass, Transform
|
||||
|
||||
|
||||
class IdentityTransform(Transform):
|
||||
def __init__(self, **kwargs):
|
||||
return
|
||||
|
||||
def Datastruct(self, **kwargs):
|
||||
return IdentityDatastruct(**kwargs)
|
||||
|
||||
def __repr__(self):
|
||||
return "IdentityTransform()"
|
||||
|
||||
|
||||
@dataclass
|
||||
class IdentityDatastruct(Datastruct):
|
||||
transforms: IdentityTransform
|
||||
|
||||
features: Optional[Tensor] = None
|
||||
|
||||
def __post_init__(self):
|
||||
self.datakeys = ["features"]
|
||||
|
||||
def __len__(self):
|
||||
return len(self.rfeats)
|
||||
@@ -1,2 +0,0 @@
|
||||
from .base import Joints2Jfeats
|
||||
from .rifke import Rifke
|
||||
@@ -1,59 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Max-Planck-Gesellschaft zur Förderung der Wissenschaften e.V. (MPG) is
|
||||
# holder of all proprietary rights on this computer program.
|
||||
# You can only use this computer program if you have closed
|
||||
# a license agreement with MPG or you get the right to use the computer
|
||||
# program from someone who is authorized to grant you that right.
|
||||
# Any use of the computer program without a valid license is prohibited and
|
||||
# liable to prosecution.
|
||||
#
|
||||
# Copyright©2020 Max-Planck-Gesellschaft zur Förderung
|
||||
# der Wissenschaften e.V. (MPG). acting on behalf of its Max Planck Institute
|
||||
# for Intelligent Systems. All rights reserved.
|
||||
#
|
||||
# Contact: ps-license@tuebingen.mpg.de
|
||||
|
||||
from typing import Optional
|
||||
|
||||
import torch
|
||||
from torch import Tensor, nn
|
||||
from pathlib import Path
|
||||
import os
|
||||
|
||||
|
||||
class Joints2Jfeats(nn.Module):
|
||||
|
||||
def __init__(self,
|
||||
path: Optional[str] = None,
|
||||
normalization: bool = False,
|
||||
eps: float = 1e-12,
|
||||
**kwargs) -> None:
|
||||
if normalization and path is None:
|
||||
raise TypeError(
|
||||
"You should provide a path if normalization is on.")
|
||||
|
||||
super().__init__()
|
||||
self.normalization = normalization
|
||||
self.eps = eps
|
||||
# workaround for cluster local/sync
|
||||
if path is not None:
|
||||
# rel_p = path.split('/')
|
||||
# rel_p = rel_p[rel_p.index('deps'):]
|
||||
# rel_p = '/'.join(rel_p)
|
||||
pass
|
||||
if normalization:
|
||||
mean_path = Path(path) / "jfeats_mean.pt"
|
||||
std_path = Path(path) / "jfeats_std.pt"
|
||||
self.register_buffer('mean', torch.load(mean_path))
|
||||
self.register_buffer('std', torch.load(std_path))
|
||||
|
||||
def normalize(self, features: Tensor) -> Tensor:
|
||||
if self.normalization:
|
||||
features = (features - self.mean) / (self.std + self.eps)
|
||||
return features
|
||||
|
||||
def unnormalize(self, features: Tensor) -> Tensor:
|
||||
if self.normalization:
|
||||
features = features * self.std + self.mean
|
||||
return features
|
||||
@@ -1,159 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Max-Planck-Gesellschaft zur Förderung der Wissenschaften e.V. (MPG) is
|
||||
# holder of all proprietary rights on this computer program.
|
||||
# You can only use this computer program if you have closed
|
||||
# a license agreement with MPG or you get the right to use the computer
|
||||
# program from someone who is authorized to grant you that right.
|
||||
# Any use of the computer program without a valid license is prohibited and
|
||||
# liable to prosecution.
|
||||
#
|
||||
# Copyright©2020 Max-Planck-Gesellschaft zur Förderung
|
||||
# der Wissenschaften e.V. (MPG). acting on behalf of its Max Planck Institute
|
||||
# for Intelligent Systems. All rights reserved.
|
||||
#
|
||||
# Contact: ps-license@tuebingen.mpg.de
|
||||
|
||||
from typing import Optional
|
||||
|
||||
import torch
|
||||
from einops import rearrange
|
||||
from torch import Tensor
|
||||
from .tools import get_forward_direction, get_floor, gaussian_filter1d # noqa
|
||||
from mGPT.utils.geometry_tools import matrix_of_angles
|
||||
from .base import Joints2Jfeats
|
||||
|
||||
|
||||
class Rifke(Joints2Jfeats):
|
||||
|
||||
def __init__(self,
|
||||
jointstype: str = "mmm",
|
||||
path: Optional[str] = None,
|
||||
normalization: bool = False,
|
||||
forward_filter: bool = False,
|
||||
**kwargs) -> None:
|
||||
#
|
||||
# if jointstype != "mmm":
|
||||
# print("This function assume that the root is the first index")
|
||||
# raise NotImplementedError("This jointstype is not implemented.")
|
||||
|
||||
super().__init__(path=path, normalization=normalization)
|
||||
self.jointstype = jointstype
|
||||
self.forward_filter = forward_filter
|
||||
|
||||
def forward(self, joints: Tensor) -> Tensor:
|
||||
# Joints to rotation invariant poses (Holden et. al.)
|
||||
# Similar function than fke2rifke in Language2Pose repository
|
||||
# Adapted to pytorch
|
||||
# Put the origin center of the root joint instead of the ground projection
|
||||
poses = joints.clone()
|
||||
poses[..., 1] -= get_floor(poses, jointstype=self.jointstype)
|
||||
|
||||
translation = poses[..., 0, :].clone()
|
||||
# Let the root have the Y translation --> gravity axis
|
||||
root_y = translation[..., 1]
|
||||
|
||||
# Trajectory => Translation without gravity axis (Y)
|
||||
trajectory = translation[..., [0, 2]]
|
||||
|
||||
# Delete the root joints of the poses
|
||||
poses = poses[..., 1:, :]
|
||||
|
||||
# Remove the trajectory of the poses
|
||||
poses[..., [0, 2]] -= trajectory[..., None, :]
|
||||
|
||||
# Compute the trajectory
|
||||
vel_trajectory = torch.diff(trajectory, dim=-2)
|
||||
# 0 for the first one => keep the dimentionality
|
||||
vel_trajectory = torch.cat(
|
||||
(0 * vel_trajectory[..., [0], :], vel_trajectory), dim=-2)
|
||||
|
||||
# Compute the forward direction
|
||||
forward = get_forward_direction(poses, jointstype=self.jointstype)
|
||||
if self.forward_filter:
|
||||
# Smoothing to remove high frequencies
|
||||
forward = gaussian_filter1d(forward, 2)
|
||||
# normalize again to get real directions
|
||||
forward = torch.nn.functional.normalize(forward, dim=-1)
|
||||
# changed this also for New pytorch
|
||||
angles = torch.atan2(*(forward.transpose(0, -1))).transpose(0, -1)
|
||||
vel_angles = torch.diff(angles, dim=-1)
|
||||
# 0 for the first one => keep the dimentionality
|
||||
vel_angles = torch.cat((0 * vel_angles[..., [0]], vel_angles), dim=-1)
|
||||
|
||||
# Construct the inverse rotation matrix
|
||||
sin, cos = forward[..., 0], forward[..., 1]
|
||||
rotations_inv = matrix_of_angles(cos, sin, inv=True)
|
||||
|
||||
# Rotate the poses
|
||||
poses_local = torch.einsum("...lj,...jk->...lk", poses[..., [0, 2]],
|
||||
rotations_inv)
|
||||
poses_local = torch.stack(
|
||||
(poses_local[..., 0], poses[..., 1], poses_local[..., 1]), axis=-1)
|
||||
|
||||
# stack the xyz joints into feature vectors
|
||||
poses_features = rearrange(poses_local,
|
||||
"... joints xyz -> ... (joints xyz)")
|
||||
|
||||
# Rotate the vel_trajectory
|
||||
vel_trajectory_local = torch.einsum("...j,...jk->...k", vel_trajectory,
|
||||
rotations_inv)
|
||||
|
||||
# Stack things together
|
||||
features = torch.cat((root_y[..., None], poses_features,
|
||||
vel_angles[..., None], vel_trajectory_local), -1)
|
||||
|
||||
# Normalize if needed
|
||||
features = self.normalize(features)
|
||||
return features
|
||||
|
||||
def inverse(self, features: Tensor) -> Tensor:
|
||||
features = self.unnormalize(features)
|
||||
root_y, poses_features, vel_angles, vel_trajectory_local = self.extract(
|
||||
features)
|
||||
|
||||
# already have the good dimensionality
|
||||
angles = torch.cumsum(vel_angles, dim=-1)
|
||||
# First frame should be 0, but if infered it is better to ensure it
|
||||
angles = angles - angles[..., [0]]
|
||||
|
||||
cos, sin = torch.cos(angles), torch.sin(angles)
|
||||
rotations = matrix_of_angles(cos, sin, inv=False)
|
||||
|
||||
# Get back the poses
|
||||
poses_local = rearrange(poses_features,
|
||||
"... (joints xyz) -> ... joints xyz",
|
||||
xyz=3)
|
||||
|
||||
# Rotate the poses
|
||||
poses = torch.einsum("...lj,...jk->...lk", poses_local[..., [0, 2]],
|
||||
rotations)
|
||||
poses = torch.stack(
|
||||
(poses[..., 0], poses_local[..., 1], poses[..., 1]), axis=-1)
|
||||
|
||||
# Rotate the vel_trajectory
|
||||
vel_trajectory = torch.einsum("...j,...jk->...k", vel_trajectory_local,
|
||||
rotations)
|
||||
# Integrate the trajectory
|
||||
# Already have the good dimensionality
|
||||
trajectory = torch.cumsum(vel_trajectory, dim=-2)
|
||||
# First frame should be 0, but if infered it is better to ensure it
|
||||
trajectory = trajectory - trajectory[..., [0], :]
|
||||
|
||||
# Add the root joints (which is still zero)
|
||||
poses = torch.cat((0 * poses[..., [0], :], poses), -2)
|
||||
|
||||
# put back the root joint y
|
||||
poses[..., 0, 1] = root_y
|
||||
|
||||
# Add the trajectory globally
|
||||
poses[..., [0, 2]] += trajectory[..., None, :]
|
||||
return poses
|
||||
|
||||
def extract(self, features: Tensor):
|
||||
root_y = features[..., 0]
|
||||
poses_features = features[..., 1:-3]
|
||||
vel_angles = features[..., -3]
|
||||
vel_trajectory_local = features[..., -2:]
|
||||
|
||||
return root_y, poses_features, vel_angles, vel_trajectory_local
|
||||
@@ -1,97 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Max-Planck-Gesellschaft zur Förderung der Wissenschaften e.V. (MPG) is
|
||||
# holder of all proprietary rights on this computer program.
|
||||
# You can only use this computer program if you have closed
|
||||
# a license agreement with MPG or you get the right to use the computer
|
||||
# program from someone who is authorized to grant you that right.
|
||||
# Any use of the computer program without a valid license is prohibited and
|
||||
# liable to prosecution.
|
||||
#
|
||||
# Copyright©2020 Max-Planck-Gesellschaft zur Förderung
|
||||
# der Wissenschaften e.V. (MPG). acting on behalf of its Max Planck Institute
|
||||
# for Intelligent Systems. All rights reserved.
|
||||
#
|
||||
# Contact: ps-license@tuebingen.mpg.de
|
||||
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
|
||||
from mGPT.utils.joints import mmm_joints
|
||||
|
||||
# Get the indexes of particular body part SMPLH case
|
||||
# Feet
|
||||
# LM, RM = smplh_joints.index("left_ankle"), smplh_joints.index("right_ankle")
|
||||
# LF, RF = smplh_joints.index("left_foot"), smplh_joints.index("right_foot")
|
||||
# # Shoulders
|
||||
# LS, RS = smplh_joints.index("left_shoulder"), smplh_joints.index("right_shoulder")
|
||||
# # Hips
|
||||
# LH, RH = smplh_joints.index("left_hip"), smplh_joints.index("right_hip")
|
||||
|
||||
# Get the indexes of particular body part
|
||||
# Feet
|
||||
LM, RM = mmm_joints.index("LMrot"), mmm_joints.index("RMrot")
|
||||
LF, RF = mmm_joints.index("LF"), mmm_joints.index("RF")
|
||||
# Shoulders
|
||||
LS, RS = mmm_joints.index("LS"), mmm_joints.index("RS")
|
||||
# Hips
|
||||
LH, RH = mmm_joints.index("LH"), mmm_joints.index("RH")
|
||||
|
||||
|
||||
def get_forward_direction(poses, jointstype="mmm"):
|
||||
# assert jointstype == 'mmm'
|
||||
across = poses[..., RH, :] - poses[..., LH, :] + poses[..., RS, :] - poses[
|
||||
..., LS, :]
|
||||
forward = torch.stack((-across[..., 2], across[..., 0]), axis=-1)
|
||||
forward = torch.nn.functional.normalize(forward, dim=-1)
|
||||
return forward
|
||||
|
||||
|
||||
def get_floor(poses, jointstype="mmm"):
|
||||
# assert jointstype == 'mmm'
|
||||
ndim = len(poses.shape)
|
||||
foot_heights = poses[..., (LM, LF, RM, RF), 1].min(-1).values
|
||||
floor_height = softmin(foot_heights, softness=0.5, dim=-1)
|
||||
# changed this thing Mathis version 1.11 pytorch
|
||||
return floor_height[(ndim - 2) * [None]].transpose(0, -1)
|
||||
|
||||
|
||||
def softmax(x, softness=1.0, dim=None):
|
||||
maxi, mini = x.max(dim=dim).values, x.min(dim=dim).values
|
||||
return maxi + torch.log(softness + torch.exp(mini - maxi))
|
||||
|
||||
|
||||
def softmin(x, softness=1.0, dim=0):
|
||||
return -softmax(-x, softness=softness, dim=dim)
|
||||
|
||||
|
||||
def gaussian_filter1d(_inputs, sigma, truncate=4.0):
|
||||
# Code adapted/mixed from scipy library into pytorch
|
||||
# https://github.com/scipy/scipy/blob/47bb6febaa10658c72962b9615d5d5aa2513fa3a/scipy/ndimage/filters.py#L211
|
||||
# and gaussian kernel
|
||||
# https://github.com/scipy/scipy/blob/47bb6febaa10658c72962b9615d5d5aa2513fa3a/scipy/ndimage/filters.py#L179
|
||||
# Correspond to mode="nearest" and order = 0
|
||||
# But works batched
|
||||
if len(_inputs.shape) == 2:
|
||||
inputs = _inputs[None]
|
||||
else:
|
||||
inputs = _inputs
|
||||
|
||||
sd = float(sigma)
|
||||
radius = int(truncate * sd + 0.5)
|
||||
sigma2 = sigma * sigma
|
||||
x = torch.arange(-radius,
|
||||
radius + 1,
|
||||
device=inputs.device,
|
||||
dtype=inputs.dtype)
|
||||
phi_x = torch.exp(-0.5 / sigma2 * x**2)
|
||||
phi_x = phi_x / phi_x.sum()
|
||||
|
||||
# Conv1d weights
|
||||
groups = inputs.shape[-1]
|
||||
weights = torch.tile(phi_x, (groups, 1, 1))
|
||||
inputs = inputs.transpose(-1, -2)
|
||||
outputs = F.conv1d(inputs, weights, padding="same",
|
||||
groups=groups).transpose(-1, -2)
|
||||
|
||||
return outputs.reshape(_inputs.shape)
|
||||
@@ -1,119 +0,0 @@
|
||||
import numpy as np
|
||||
from mGPT.utils.joints import mmm_joints, smplh2mmm_indexes
|
||||
|
||||
# Map joints Name to SMPL joints idx
|
||||
JOINT_MAP = {
|
||||
'MidHip': 0,
|
||||
'LHip': 1,
|
||||
'LKnee': 4,
|
||||
'LAnkle': 7,
|
||||
'LFoot': 10,
|
||||
'RHip': 2,
|
||||
'RKnee': 5,
|
||||
'RAnkle': 8,
|
||||
'RFoot': 11,
|
||||
'LShoulder': 16,
|
||||
'LElbow': 18,
|
||||
'LWrist': 20,
|
||||
'LHand': 22,
|
||||
'RShoulder': 17,
|
||||
'RElbow': 19,
|
||||
'RWrist': 21,
|
||||
'RHand': 23,
|
||||
'spine1': 3,
|
||||
'spine2': 6,
|
||||
'spine3': 9,
|
||||
'Neck': 12,
|
||||
'Head': 15,
|
||||
'LCollar': 13,
|
||||
'Rcollar': 14,
|
||||
'Nose': 24,
|
||||
'REye': 26,
|
||||
'LEye': 26,
|
||||
'REar': 27,
|
||||
'LEar': 28,
|
||||
'LHeel': 31,
|
||||
'RHeel': 34,
|
||||
'OP RShoulder': 17,
|
||||
'OP LShoulder': 16,
|
||||
'OP RHip': 2,
|
||||
'OP LHip': 1,
|
||||
'OP Neck': 12,
|
||||
}
|
||||
|
||||
mmm2smpl_correspondence = {
|
||||
"root": "MidHip",
|
||||
"BP": "spine1",
|
||||
"BT": "spine3",
|
||||
"BLN": "Neck",
|
||||
"BUN": "Head",
|
||||
"LS": "LShoulder",
|
||||
"LE": "LElbow",
|
||||
"LW": "LWrist",
|
||||
"RS": "RShoulder",
|
||||
"RE": "RElbow",
|
||||
"RW": "RWrist",
|
||||
"LH": "LHip",
|
||||
"LK": "LKnee",
|
||||
"LA": "LAnkle",
|
||||
"LMrot": "LHeel",
|
||||
"LF": "LFoot",
|
||||
"RH": "RHip",
|
||||
"RK": "RKnee",
|
||||
"RA": "RAnkle",
|
||||
"RMrot": "RHeel",
|
||||
"RF": "RFoot"
|
||||
}
|
||||
|
||||
full_smpl_idx = range(24)
|
||||
key_smpl_idx = [0, 1, 4, 7, 2, 5, 8, 17, 19, 21, 16, 18, 20]
|
||||
|
||||
AMASS_JOINT_MAP = {
|
||||
'MidHip': 0,
|
||||
'LHip': 1,
|
||||
'LKnee': 4,
|
||||
'LAnkle': 7,
|
||||
'LFoot': 10,
|
||||
'RHip': 2,
|
||||
'RKnee': 5,
|
||||
'RAnkle': 8,
|
||||
'RFoot': 11,
|
||||
'LShoulder': 16,
|
||||
'LElbow': 18,
|
||||
'LWrist': 20,
|
||||
'RShoulder': 17,
|
||||
'RElbow': 19,
|
||||
'RWrist': 21,
|
||||
'spine1': 3,
|
||||
'spine2': 6,
|
||||
'spine3': 9,
|
||||
'Neck': 12,
|
||||
'Head': 15,
|
||||
'LCollar': 13,
|
||||
'Rcollar': 14,
|
||||
}
|
||||
amass_idx = range(22)
|
||||
amass_smpl_idx = range(22)
|
||||
|
||||
# cal mmm in smpl index
|
||||
smpl2mmm_correspondence = {
|
||||
val: key
|
||||
for key, val in mmm2smpl_correspondence.items()
|
||||
}
|
||||
smpl2mmm_indexes = [JOINT_MAP[mmm2smpl_correspondence[x]] for x in mmm_joints]
|
||||
|
||||
# cal mmm joints map
|
||||
MMM_JOINT_MAP = {
|
||||
val: JOINT_MAP[val]
|
||||
for key, val in mmm2smpl_correspondence.items()
|
||||
}
|
||||
|
||||
# mmm_idx = range(21)
|
||||
# mmm_smpl_dix = smpl2mmm_indexes
|
||||
# mmm_smpl_dix = smplh2mmm_indexes
|
||||
# todo - configable
|
||||
SMPL_MODEL_DIR = "/apdcephfs/share_1227775/shingxchen/AIMotion/TMOSTData/deps/smpl_models/"
|
||||
GMM_MODEL_DIR = "/apdcephfs/share_1227775/shingxchen/AIMotion/TMOSTData/deps/smpl_models/"
|
||||
SMPL_MEAN_FILE = "/apdcephfs/share_1227775/shingxchen/AIMotion/TMOSTData/deps/smpl_models/neutral_smpl_mean_params.h5"
|
||||
# for collsion
|
||||
Part_Seg_DIR = "/apdcephfs/share_1227775/shingxchen/AIMotion/TMOSTData/deps/smpl_models/smplx_parts_segm.pkl"
|
||||
@@ -1,217 +0,0 @@
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
import config
|
||||
|
||||
# Guassian
|
||||
def gmof(x, sigma):
|
||||
"""
|
||||
Geman-McClure error function
|
||||
"""
|
||||
x_squared = x ** 2
|
||||
sigma_squared = sigma ** 2
|
||||
return (sigma_squared * x_squared) / (sigma_squared + x_squared)
|
||||
|
||||
# angle prior
|
||||
def angle_prior(pose):
|
||||
"""
|
||||
Angle prior that penalizes unnatural bending of the knees and elbows
|
||||
"""
|
||||
# We subtract 3 because pose does not include the global rotation of the model
|
||||
return torch.exp(
|
||||
pose[:, [55 - 3, 58 - 3, 12 - 3, 15 - 3]] * torch.tensor([1., -1., -1, -1.], device=pose.device)) ** 2
|
||||
|
||||
|
||||
def perspective_projection(points, rotation, translation,
|
||||
focal_length, camera_center):
|
||||
"""
|
||||
This function computes the perspective projection of a set of points.
|
||||
Input:
|
||||
points (bs, N, 3): 3D points
|
||||
rotation (bs, 3, 3): Camera rotation
|
||||
translation (bs, 3): Camera translation
|
||||
focal_length (bs,) or scalar: Focal length
|
||||
camera_center (bs, 2): Camera center
|
||||
"""
|
||||
batch_size = points.shape[0]
|
||||
K = torch.zeros([batch_size, 3, 3], device=points.device)
|
||||
K[:, 0, 0] = focal_length
|
||||
K[:, 1, 1] = focal_length
|
||||
K[:, 2, 2] = 1.
|
||||
K[:, :-1, -1] = camera_center
|
||||
|
||||
# Transform points
|
||||
points = torch.einsum('bij,bkj->bki', rotation, points)
|
||||
points = points + translation.unsqueeze(1)
|
||||
|
||||
# Apply perspective distortion
|
||||
projected_points = points / points[:, :, -1].unsqueeze(-1)
|
||||
|
||||
# Apply camera intrinsics
|
||||
projected_points = torch.einsum('bij,bkj->bki', K, projected_points)
|
||||
|
||||
return projected_points[:, :, :-1]
|
||||
|
||||
|
||||
def body_fitting_loss(body_pose, betas, model_joints, camera_t, camera_center,
|
||||
joints_2d, joints_conf, pose_prior,
|
||||
focal_length=5000, sigma=100, pose_prior_weight=4.78,
|
||||
shape_prior_weight=5, angle_prior_weight=15.2,
|
||||
output='sum'):
|
||||
"""
|
||||
Loss function for body fitting
|
||||
"""
|
||||
batch_size = body_pose.shape[0]
|
||||
rotation = torch.eye(3, device=body_pose.device).unsqueeze(0).expand(batch_size, -1, -1)
|
||||
|
||||
projected_joints = perspective_projection(model_joints, rotation, camera_t,
|
||||
focal_length, camera_center)
|
||||
|
||||
# Weighted robust reprojection error
|
||||
reprojection_error = gmof(projected_joints - joints_2d, sigma)
|
||||
reprojection_loss = (joints_conf ** 2) * reprojection_error.sum(dim=-1)
|
||||
|
||||
# Pose prior loss
|
||||
pose_prior_loss = (pose_prior_weight ** 2) * pose_prior(body_pose, betas)
|
||||
|
||||
# Angle prior for knees and elbows
|
||||
angle_prior_loss = (angle_prior_weight ** 2) * angle_prior(body_pose).sum(dim=-1)
|
||||
|
||||
# Regularizer to prevent betas from taking large values
|
||||
shape_prior_loss = (shape_prior_weight ** 2) * (betas ** 2).sum(dim=-1)
|
||||
|
||||
total_loss = reprojection_loss.sum(dim=-1) + pose_prior_loss + angle_prior_loss + shape_prior_loss
|
||||
|
||||
if output == 'sum':
|
||||
return total_loss.sum()
|
||||
elif output == 'reprojection':
|
||||
return reprojection_loss
|
||||
|
||||
|
||||
# --- get camera fitting loss -----
|
||||
def camera_fitting_loss(model_joints, camera_t, camera_t_est, camera_center,
|
||||
joints_2d, joints_conf,
|
||||
focal_length=5000, depth_loss_weight=100):
|
||||
"""
|
||||
Loss function for camera optimization.
|
||||
"""
|
||||
# Project model joints
|
||||
batch_size = model_joints.shape[0]
|
||||
rotation = torch.eye(3, device=model_joints.device).unsqueeze(0).expand(batch_size, -1, -1)
|
||||
projected_joints = perspective_projection(model_joints, rotation, camera_t,
|
||||
focal_length, camera_center)
|
||||
|
||||
# get the indexed four
|
||||
op_joints = ['OP RHip', 'OP LHip', 'OP RShoulder', 'OP LShoulder']
|
||||
op_joints_ind = [config.JOINT_MAP[joint] for joint in op_joints]
|
||||
gt_joints = ['RHip', 'LHip', 'RShoulder', 'LShoulder']
|
||||
gt_joints_ind = [config.JOINT_MAP[joint] for joint in gt_joints]
|
||||
|
||||
reprojection_error_op = (joints_2d[:, op_joints_ind] -
|
||||
projected_joints[:, op_joints_ind]) ** 2
|
||||
reprojection_error_gt = (joints_2d[:, gt_joints_ind] -
|
||||
projected_joints[:, gt_joints_ind]) ** 2
|
||||
|
||||
# Check if for each example in the batch all 4 OpenPose detections are valid, otherwise use the GT detections
|
||||
# OpenPose joints are more reliable for this task, so we prefer to use them if possible
|
||||
is_valid = (joints_conf[:, op_joints_ind].min(dim=-1)[0][:, None, None] > 0).float()
|
||||
reprojection_loss = (is_valid * reprojection_error_op + (1 - is_valid) * reprojection_error_gt).sum(dim=(1, 2))
|
||||
|
||||
# Loss that penalizes deviation from depth estimate
|
||||
depth_loss = (depth_loss_weight ** 2) * (camera_t[:, 2] - camera_t_est[:, 2]) ** 2
|
||||
|
||||
total_loss = reprojection_loss + depth_loss
|
||||
return total_loss.sum()
|
||||
|
||||
|
||||
|
||||
# #####--- body fitiing loss -----
|
||||
def body_fitting_loss_3d(body_pose, preserve_pose,
|
||||
betas, model_joints, camera_translation,
|
||||
j3d, pose_prior,
|
||||
joints3d_conf,
|
||||
sigma=100, pose_prior_weight=4.78*1.5,
|
||||
shape_prior_weight=5.0, angle_prior_weight=15.2,
|
||||
joint_loss_weight=500.0,
|
||||
pose_preserve_weight=0.0,
|
||||
use_collision=False,
|
||||
model_vertices=None, model_faces=None,
|
||||
search_tree=None, pen_distance=None, filter_faces=None,
|
||||
collision_loss_weight=1000
|
||||
):
|
||||
"""
|
||||
Loss function for body fitting
|
||||
"""
|
||||
batch_size = body_pose.shape[0]
|
||||
|
||||
#joint3d_loss = (joint_loss_weight ** 2) * gmof((model_joints + camera_translation) - j3d, sigma).sum(dim=-1)
|
||||
|
||||
joint3d_error = gmof((model_joints + camera_translation) - j3d, sigma)
|
||||
|
||||
joint3d_loss_part = (joints3d_conf ** 2) * joint3d_error.sum(dim=-1)
|
||||
joint3d_loss = (joint_loss_weight ** 2) * joint3d_loss_part
|
||||
|
||||
# Pose prior loss
|
||||
pose_prior_loss = (pose_prior_weight ** 2) * pose_prior(body_pose, betas)
|
||||
# Angle prior for knees and elbows
|
||||
angle_prior_loss = (angle_prior_weight ** 2) * angle_prior(body_pose).sum(dim=-1)
|
||||
# Regularizer to prevent betas from taking large values
|
||||
shape_prior_loss = (shape_prior_weight ** 2) * (betas ** 2).sum(dim=-1)
|
||||
|
||||
collision_loss = 0.0
|
||||
# Calculate the loss due to interpenetration
|
||||
if use_collision:
|
||||
triangles = torch.index_select(
|
||||
model_vertices, 1,
|
||||
model_faces).view(batch_size, -1, 3, 3)
|
||||
|
||||
with torch.no_grad():
|
||||
collision_idxs = search_tree(triangles)
|
||||
|
||||
# Remove unwanted collisions
|
||||
if filter_faces is not None:
|
||||
collision_idxs = filter_faces(collision_idxs)
|
||||
|
||||
if collision_idxs.ge(0).sum().item() > 0:
|
||||
collision_loss = torch.sum(collision_loss_weight * pen_distance(triangles, collision_idxs))
|
||||
|
||||
pose_preserve_loss = (pose_preserve_weight ** 2) * ((body_pose - preserve_pose) ** 2).sum(dim=-1)
|
||||
|
||||
total_loss = joint3d_loss + pose_prior_loss + angle_prior_loss + shape_prior_loss + collision_loss + pose_preserve_loss
|
||||
|
||||
return total_loss.sum()
|
||||
|
||||
|
||||
# #####--- get camera fitting loss -----
|
||||
def camera_fitting_loss_3d(model_joints, camera_t, camera_t_est,
|
||||
j3d, joints_category="orig", depth_loss_weight=100.0):
|
||||
"""
|
||||
Loss function for camera optimization.
|
||||
"""
|
||||
model_joints = model_joints + camera_t
|
||||
# # get the indexed four
|
||||
# op_joints = ['OP RHip', 'OP LHip', 'OP RShoulder', 'OP LShoulder']
|
||||
# op_joints_ind = [config.JOINT_MAP[joint] for joint in op_joints]
|
||||
#
|
||||
# j3d_error_loss = (j3d[:, op_joints_ind] -
|
||||
# model_joints[:, op_joints_ind]) ** 2
|
||||
|
||||
gt_joints = ['RHip', 'LHip', 'RShoulder', 'LShoulder']
|
||||
gt_joints_ind = [config.JOINT_MAP[joint] for joint in gt_joints]
|
||||
|
||||
if joints_category=="orig":
|
||||
select_joints_ind = [config.JOINT_MAP[joint] for joint in gt_joints]
|
||||
elif joints_category=="AMASS":
|
||||
select_joints_ind = [config.AMASS_JOINT_MAP[joint] for joint in gt_joints]
|
||||
elif joints_category=="MMM":
|
||||
select_joints_ind = [config.MMM_JOINT_MAP[joint] for joint in gt_joints]
|
||||
else:
|
||||
print("NO SUCH JOINTS CATEGORY!")
|
||||
|
||||
j3d_error_loss = (j3d[:, select_joints_ind] -
|
||||
model_joints[:, gt_joints_ind]) ** 2
|
||||
|
||||
# Loss that penalizes deviation from depth estimate
|
||||
depth_loss = (depth_loss_weight**2) * (camera_t - camera_t_est)**2
|
||||
|
||||
total_loss = j3d_error_loss + depth_loss
|
||||
return total_loss.sum()
|
||||
@@ -1,229 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Max-Planck-Gesellschaft zur Förderung der Wissenschaften e.V. (MPG) is
|
||||
# holder of all proprietary rights on this computer program.
|
||||
# You can only use this computer program if you have closed
|
||||
# a license agreement with MPG or you get the right to use the computer
|
||||
# program from someone who is authorized to grant you that right.
|
||||
# Any use of the computer program without a valid license is prohibited and
|
||||
# liable to prosecution.
|
||||
#
|
||||
# Copyright©2019 Max-Planck-Gesellschaft zur Förderung
|
||||
# der Wissenschaften e.V. (MPG). acting on behalf of its Max Planck Institute
|
||||
# for Intelligent Systems. All rights reserved.
|
||||
#
|
||||
# Contact: ps-license@tuebingen.mpg.de
|
||||
|
||||
from __future__ import absolute_import
|
||||
from __future__ import print_function
|
||||
from __future__ import division
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
import time
|
||||
import pickle
|
||||
|
||||
import numpy as np
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
|
||||
DEFAULT_DTYPE = torch.float32
|
||||
|
||||
|
||||
def create_prior(prior_type, **kwargs):
|
||||
if prior_type == 'gmm':
|
||||
prior = MaxMixturePrior(**kwargs)
|
||||
elif prior_type == 'l2':
|
||||
return L2Prior(**kwargs)
|
||||
elif prior_type == 'angle':
|
||||
return SMPLifyAnglePrior(**kwargs)
|
||||
elif prior_type == 'none' or prior_type is None:
|
||||
# Don't use any pose prior
|
||||
def no_prior(*args, **kwargs):
|
||||
return 0.0
|
||||
prior = no_prior
|
||||
else:
|
||||
raise ValueError('Prior {}'.format(prior_type) + ' is not implemented')
|
||||
return prior
|
||||
|
||||
|
||||
class SMPLifyAnglePrior(nn.Module):
|
||||
def __init__(self, dtype=torch.float32, **kwargs):
|
||||
super(SMPLifyAnglePrior, self).__init__()
|
||||
|
||||
# Indices for the roration angle of
|
||||
# 55: left elbow, 90deg bend at -np.pi/2
|
||||
# 58: right elbow, 90deg bend at np.pi/2
|
||||
# 12: left knee, 90deg bend at np.pi/2
|
||||
# 15: right knee, 90deg bend at np.pi/2
|
||||
angle_prior_idxs = np.array([55, 58, 12, 15], dtype=np.int64)
|
||||
angle_prior_idxs = torch.tensor(angle_prior_idxs, dtype=torch.long)
|
||||
self.register_buffer('angle_prior_idxs', angle_prior_idxs)
|
||||
|
||||
angle_prior_signs = np.array([1, -1, -1, -1],
|
||||
dtype=np.float6432 if dtype == torch.float32
|
||||
else np.float6464)
|
||||
angle_prior_signs = torch.tensor(angle_prior_signs,
|
||||
dtype=dtype)
|
||||
self.register_buffer('angle_prior_signs', angle_prior_signs)
|
||||
|
||||
def forward(self, pose, with_global_pose=False):
|
||||
''' Returns the angle prior loss for the given pose
|
||||
Args:
|
||||
pose: (Bx[23 + 1] * 3) torch tensor with the axis-angle
|
||||
representation of the rotations of the joints of the SMPL model.
|
||||
Kwargs:
|
||||
with_global_pose: Whether the pose vector also contains the global
|
||||
orientation of the SMPL model. If not then the indices must be
|
||||
corrected.
|
||||
Returns:
|
||||
A sze (B) tensor containing the angle prior loss for each element
|
||||
in the batch.
|
||||
'''
|
||||
angle_prior_idxs = self.angle_prior_idxs - (not with_global_pose) * 3
|
||||
return torch.exp(pose[:, angle_prior_idxs] *
|
||||
self.angle_prior_signs).pow(2)
|
||||
|
||||
|
||||
class L2Prior(nn.Module):
|
||||
def __init__(self, dtype=DEFAULT_DTYPE, reduction='sum', **kwargs):
|
||||
super(L2Prior, self).__init__()
|
||||
|
||||
def forward(self, module_input, *args):
|
||||
return torch.sum(module_input.pow(2))
|
||||
|
||||
|
||||
class MaxMixturePrior(nn.Module):
|
||||
|
||||
def __init__(self, prior_folder='prior',
|
||||
num_gaussians=6, dtype=DEFAULT_DTYPE, epsilon=1e-16,
|
||||
use_merged=True,
|
||||
**kwargs):
|
||||
super(MaxMixturePrior, self).__init__()
|
||||
|
||||
if dtype == DEFAULT_DTYPE:
|
||||
np_dtype = np.float6432
|
||||
elif dtype == torch.float64:
|
||||
np_dtype = np.float6464
|
||||
else:
|
||||
print('Unknown float type {}, exiting!'.format(dtype))
|
||||
sys.exit(-1)
|
||||
|
||||
self.num_gaussians = num_gaussians
|
||||
self.epsilon = epsilon
|
||||
self.use_merged = use_merged
|
||||
gmm_fn = 'gmm_{:02d}.pkl'.format(num_gaussians)
|
||||
|
||||
full_gmm_fn = os.path.join(prior_folder, gmm_fn)
|
||||
if not os.path.exists(full_gmm_fn):
|
||||
print('The path to the mixture prior "{}"'.format(full_gmm_fn) +
|
||||
' does not exist, exiting!')
|
||||
sys.exit(-1)
|
||||
|
||||
with open(full_gmm_fn, 'rb') as f:
|
||||
gmm = pickle.load(f, encoding='latin1')
|
||||
|
||||
if type(gmm) == dict:
|
||||
means = gmm['means'].astype(np_dtype)
|
||||
covs = gmm['covars'].astype(np_dtype)
|
||||
weights = gmm['weights'].astype(np_dtype)
|
||||
elif 'sklearn.mixture.gmm.GMM' in str(type(gmm)):
|
||||
means = gmm.means_.astype(np_dtype)
|
||||
covs = gmm.covars_.astype(np_dtype)
|
||||
weights = gmm.weights_.astype(np_dtype)
|
||||
else:
|
||||
print('Unknown type for the prior: {}, exiting!'.format(type(gmm)))
|
||||
sys.exit(-1)
|
||||
|
||||
self.register_buffer('means', torch.tensor(means, dtype=dtype))
|
||||
|
||||
self.register_buffer('covs', torch.tensor(covs, dtype=dtype))
|
||||
|
||||
precisions = [np.linalg.inv(cov) for cov in covs]
|
||||
precisions = np.stack(precisions).astype(np_dtype)
|
||||
|
||||
self.register_buffer('precisions',
|
||||
torch.tensor(precisions, dtype=dtype))
|
||||
|
||||
# The constant term:
|
||||
sqrdets = np.array([(np.sqrt(np.linalg.det(c)))
|
||||
for c in gmm['covars']])
|
||||
const = (2 * np.pi)**(69 / 2.)
|
||||
|
||||
nll_weights = np.asarray(gmm['weights'] / (const *
|
||||
(sqrdets / sqrdets.min())))
|
||||
nll_weights = torch.tensor(nll_weights, dtype=dtype).unsqueeze(dim=0)
|
||||
self.register_buffer('nll_weights', nll_weights)
|
||||
|
||||
weights = torch.tensor(gmm['weights'], dtype=dtype).unsqueeze(dim=0)
|
||||
self.register_buffer('weights', weights)
|
||||
|
||||
self.register_buffer('pi_term',
|
||||
torch.log(torch.tensor(2 * np.pi, dtype=dtype)))
|
||||
|
||||
cov_dets = [np.log(np.linalg.det(cov.astype(np_dtype)) + epsilon)
|
||||
for cov in covs]
|
||||
self.register_buffer('cov_dets',
|
||||
torch.tensor(cov_dets, dtype=dtype))
|
||||
|
||||
# The dimensionality of the random variable
|
||||
self.random_var_dim = self.means.shape[1]
|
||||
|
||||
def get_mean(self):
|
||||
''' Returns the mean of the mixture '''
|
||||
mean_pose = torch.matmul(self.weights, self.means)
|
||||
return mean_pose
|
||||
|
||||
def merged_log_likelihood(self, pose, betas):
|
||||
diff_from_mean = pose.unsqueeze(dim=1) - self.means
|
||||
|
||||
prec_diff_prod = torch.einsum('mij,bmj->bmi',
|
||||
[self.precisions, diff_from_mean])
|
||||
diff_prec_quadratic = (prec_diff_prod * diff_from_mean).sum(dim=-1)
|
||||
|
||||
curr_loglikelihood = 0.5 * diff_prec_quadratic - \
|
||||
torch.log(self.nll_weights)
|
||||
# curr_loglikelihood = 0.5 * (self.cov_dets.unsqueeze(dim=0) +
|
||||
# self.random_var_dim * self.pi_term +
|
||||
# diff_prec_quadratic
|
||||
# ) - torch.log(self.weights)
|
||||
|
||||
min_likelihood, _ = torch.min(curr_loglikelihood, dim=1)
|
||||
return min_likelihood
|
||||
|
||||
def log_likelihood(self, pose, betas, *args, **kwargs):
|
||||
''' Create graph operation for negative log-likelihood calculation
|
||||
'''
|
||||
likelihoods = []
|
||||
|
||||
for idx in range(self.num_gaussians):
|
||||
mean = self.means[idx]
|
||||
prec = self.precisions[idx]
|
||||
cov = self.covs[idx]
|
||||
diff_from_mean = pose - mean
|
||||
|
||||
curr_loglikelihood = torch.einsum('bj,ji->bi',
|
||||
[diff_from_mean, prec])
|
||||
curr_loglikelihood = torch.einsum('bi,bi->b',
|
||||
[curr_loglikelihood,
|
||||
diff_from_mean])
|
||||
cov_term = torch.log(torch.det(cov) + self.epsilon)
|
||||
curr_loglikelihood += 0.5 * (cov_term +
|
||||
self.random_var_dim *
|
||||
self.pi_term)
|
||||
likelihoods.append(curr_loglikelihood)
|
||||
|
||||
log_likelihoods = torch.stack(likelihoods, dim=1)
|
||||
min_idx = torch.argmin(log_likelihoods, dim=1)
|
||||
weight_component = self.nll_weights[:, min_idx]
|
||||
weight_component = -torch.log(weight_component)
|
||||
|
||||
return weight_component + log_likelihoods[:, min_idx]
|
||||
|
||||
def forward(self, pose, betas):
|
||||
if self.use_merged:
|
||||
return self.merged_log_likelihood(pose, betas)
|
||||
else:
|
||||
return self.log_likelihood(pose, betas)
|
||||
@@ -1,284 +0,0 @@
|
||||
import torch
|
||||
import os, sys
|
||||
import pickle
|
||||
import smplx
|
||||
import numpy as np
|
||||
from tqdm import tqdm
|
||||
|
||||
sys.path.append(os.path.dirname(__file__))
|
||||
from customloss import (camera_fitting_loss,
|
||||
body_fitting_loss,
|
||||
camera_fitting_loss_3d,
|
||||
body_fitting_loss_3d,
|
||||
)
|
||||
from prior import MaxMixturePrior
|
||||
import config
|
||||
|
||||
|
||||
|
||||
@torch.no_grad()
|
||||
def guess_init_3d(model_joints,
|
||||
j3d,
|
||||
joints_category="orig"):
|
||||
"""Initialize the camera translation via triangle similarity, by using the torso joints .
|
||||
:param model_joints: SMPL model with pre joints
|
||||
:param j3d: 25x3 array of Kinect Joints
|
||||
:returns: 3D vector corresponding to the estimated camera translation
|
||||
"""
|
||||
# get the indexed four
|
||||
gt_joints = ['RHip', 'LHip', 'RShoulder', 'LShoulder']
|
||||
gt_joints_ind = [config.JOINT_MAP[joint] for joint in gt_joints]
|
||||
|
||||
if joints_category=="orig":
|
||||
joints_ind_category = [config.JOINT_MAP[joint] for joint in gt_joints]
|
||||
elif joints_category=="AMASS":
|
||||
joints_ind_category = [config.AMASS_JOINT_MAP[joint] for joint in gt_joints]
|
||||
elif joints_category=="MMM":
|
||||
joints_ind_category = [config.MMM_JOINT_MAP[joint] for joint in gt_joints]
|
||||
else:
|
||||
print("NO SUCH JOINTS CATEGORY!")
|
||||
|
||||
sum_init_t = (j3d[:, joints_ind_category] - model_joints[:, gt_joints_ind]).sum(dim=1)
|
||||
init_t = sum_init_t / 4.0
|
||||
return init_t
|
||||
|
||||
|
||||
# SMPLIfy 3D
|
||||
class SMPLify3D():
|
||||
"""Implementation of SMPLify, use 3D joints."""
|
||||
|
||||
def __init__(self,
|
||||
smplxmodel,
|
||||
step_size=1e-2,
|
||||
batch_size=1,
|
||||
num_iters=100,
|
||||
use_collision=False,
|
||||
use_lbfgs=True,
|
||||
joints_category="orig",
|
||||
device=torch.device('cuda:0'),
|
||||
):
|
||||
|
||||
# Store options
|
||||
self.batch_size = batch_size
|
||||
self.device = device
|
||||
self.step_size = step_size
|
||||
|
||||
self.num_iters = num_iters
|
||||
# --- choose optimizer
|
||||
self.use_lbfgs = use_lbfgs
|
||||
# GMM pose prior
|
||||
self.pose_prior = MaxMixturePrior(prior_folder=config.GMM_MODEL_DIR,
|
||||
num_gaussians=8,
|
||||
dtype=torch.float32).to(device)
|
||||
# collision part
|
||||
self.use_collision = use_collision
|
||||
if self.use_collision:
|
||||
self.part_segm_fn = config.Part_Seg_DIR
|
||||
|
||||
# reLoad SMPL-X model
|
||||
self.smpl = smplxmodel
|
||||
|
||||
self.model_faces = smplxmodel.faces_tensor.view(-1)
|
||||
|
||||
# select joint joint_category
|
||||
self.joints_category = joints_category
|
||||
|
||||
if joints_category=="orig":
|
||||
self.smpl_index = config.full_smpl_idx
|
||||
self.corr_index = config.full_smpl_idx
|
||||
elif joints_category=="AMASS":
|
||||
self.smpl_index = config.amass_smpl_idx
|
||||
self.corr_index = config.amass_idx
|
||||
# elif joints_category=="MMM":
|
||||
# self.smpl_index = config.mmm_smpl_dix
|
||||
# self.corr_index = config.mmm_idx
|
||||
else:
|
||||
self.smpl_index = None
|
||||
self.corr_index = None
|
||||
print("NO SUCH JOINTS CATEGORY!")
|
||||
|
||||
# ---- get the man function here ------
|
||||
def __call__(self, init_pose, init_betas, init_cam_t, j3d, conf_3d=1.0, seq_ind=0):
|
||||
"""Perform body fitting.
|
||||
Input:
|
||||
init_pose: SMPL pose estimate
|
||||
init_betas: SMPL betas estimate
|
||||
init_cam_t: Camera translation estimate
|
||||
j3d: joints 3d aka keypoints
|
||||
conf_3d: confidence for 3d joints
|
||||
seq_ind: index of the sequence
|
||||
Returns:
|
||||
vertices: Vertices of optimized shape
|
||||
joints: 3D joints of optimized shape
|
||||
pose: SMPL pose parameters of optimized shape
|
||||
betas: SMPL beta parameters of optimized shape
|
||||
camera_translation: Camera translation
|
||||
"""
|
||||
|
||||
# # # add the mesh inter-section to avoid
|
||||
search_tree = None
|
||||
pen_distance = None
|
||||
filter_faces = None
|
||||
|
||||
if self.use_collision:
|
||||
from mesh_intersection.bvh_search_tree import BVH
|
||||
import mesh_intersection.loss as collisions_loss
|
||||
from mesh_intersection.filter_faces import FilterFaces
|
||||
|
||||
search_tree = BVH(max_collisions=8)
|
||||
|
||||
pen_distance = collisions_loss.DistanceFieldPenetrationLoss(
|
||||
sigma=0.5, point2plane=False, vectorized=True, penalize_outside=True)
|
||||
|
||||
if self.part_segm_fn:
|
||||
# Read the part segmentation
|
||||
part_segm_fn = os.path.expandvars(self.part_segm_fn)
|
||||
with open(part_segm_fn, 'rb') as faces_parents_file:
|
||||
face_segm_data = pickle.load(faces_parents_file, encoding='latin1')
|
||||
faces_segm = face_segm_data['segm']
|
||||
faces_parents = face_segm_data['parents']
|
||||
# Create the module used to filter invalid collision pairs
|
||||
filter_faces = FilterFaces(
|
||||
faces_segm=faces_segm, faces_parents=faces_parents,
|
||||
ign_part_pairs=None).to(device=self.device)
|
||||
|
||||
|
||||
# Split SMPL pose to body pose and global orientation
|
||||
body_pose = init_pose[:, 3:].detach().clone()
|
||||
global_orient = init_pose[:, :3].detach().clone()
|
||||
betas = init_betas.detach().clone()
|
||||
|
||||
# use guess 3d to get the initial
|
||||
smpl_output = self.smpl(global_orient=global_orient,
|
||||
body_pose=body_pose,
|
||||
betas=betas)
|
||||
model_joints = smpl_output.joints
|
||||
|
||||
init_cam_t = guess_init_3d(model_joints, j3d, self.joints_category).detach()
|
||||
camera_translation = init_cam_t.clone()
|
||||
|
||||
preserve_pose = init_pose[:, 3:].detach().clone()
|
||||
# -------------Step 1: Optimize camera translation and body orientation--------
|
||||
# Optimize only camera translation and body orientation
|
||||
body_pose.requires_grad = False
|
||||
betas.requires_grad = False
|
||||
global_orient.requires_grad = True
|
||||
camera_translation.requires_grad = True
|
||||
|
||||
camera_opt_params = [global_orient, camera_translation]
|
||||
|
||||
if self.use_lbfgs:
|
||||
camera_optimizer = torch.optim.LBFGS(camera_opt_params, max_iter=self.num_iters,
|
||||
lr=self.step_size, line_search_fn='strong_wolfe')
|
||||
for i in range(10):
|
||||
def closure():
|
||||
camera_optimizer.zero_grad()
|
||||
smpl_output = self.smpl(global_orient=global_orient,
|
||||
body_pose=body_pose,
|
||||
betas=betas)
|
||||
model_joints = smpl_output.joints
|
||||
|
||||
loss = camera_fitting_loss_3d(model_joints, camera_translation,
|
||||
init_cam_t, j3d, self.joints_category)
|
||||
loss.backward()
|
||||
return loss
|
||||
|
||||
camera_optimizer.step(closure)
|
||||
else:
|
||||
camera_optimizer = torch.optim.Adam(camera_opt_params, lr=self.step_size, betas=(0.9, 0.999))
|
||||
|
||||
for i in range(20):
|
||||
smpl_output = self.smpl(global_orient=global_orient,
|
||||
body_pose=body_pose,
|
||||
betas=betas)
|
||||
model_joints = smpl_output.joints
|
||||
|
||||
loss = camera_fitting_loss_3d(model_joints[:, self.smpl_index], camera_translation,
|
||||
init_cam_t, j3d[:, self.corr_index], self.joints_category)
|
||||
camera_optimizer.zero_grad()
|
||||
loss.backward()
|
||||
camera_optimizer.step()
|
||||
|
||||
# Fix camera translation after optimizing camera
|
||||
# --------Step 2: Optimize body joints --------------------------
|
||||
# Optimize only the body pose and global orientation of the body
|
||||
body_pose.requires_grad = True
|
||||
global_orient.requires_grad = True
|
||||
camera_translation.requires_grad = True
|
||||
|
||||
# --- if we use the sequence, fix the shape
|
||||
if seq_ind == 0:
|
||||
betas.requires_grad = True
|
||||
body_opt_params = [body_pose, betas, global_orient, camera_translation]
|
||||
else:
|
||||
betas.requires_grad = False
|
||||
body_opt_params = [body_pose, global_orient, camera_translation]
|
||||
|
||||
if self.use_lbfgs:
|
||||
body_optimizer = torch.optim.LBFGS(body_opt_params, max_iter=self.num_iters,
|
||||
lr=self.step_size, line_search_fn='strong_wolfe')
|
||||
|
||||
for i in tqdm(range(self.num_iters), desc=f"LBFGS iter: "):
|
||||
# for i in range(self.num_iters):
|
||||
def closure():
|
||||
body_optimizer.zero_grad()
|
||||
smpl_output = self.smpl(global_orient=global_orient,
|
||||
body_pose=body_pose,
|
||||
betas=betas)
|
||||
model_joints = smpl_output.joints
|
||||
model_vertices = smpl_output.vertices
|
||||
|
||||
loss = body_fitting_loss_3d(body_pose, preserve_pose, betas, model_joints[:, self.smpl_index], camera_translation,
|
||||
j3d[:, self.corr_index], self.pose_prior,
|
||||
joints3d_conf=conf_3d,
|
||||
joint_loss_weight=600.0,
|
||||
pose_preserve_weight=5.0,
|
||||
use_collision=self.use_collision,
|
||||
model_vertices=model_vertices, model_faces=self.model_faces,
|
||||
search_tree=search_tree, pen_distance=pen_distance, filter_faces=filter_faces)
|
||||
loss.backward()
|
||||
return loss
|
||||
|
||||
body_optimizer.step(closure)
|
||||
else:
|
||||
body_optimizer = torch.optim.Adam(body_opt_params, lr=self.step_size, betas=(0.9, 0.999))
|
||||
|
||||
for i in range(self.num_iters):
|
||||
smpl_output = self.smpl(global_orient=global_orient,
|
||||
body_pose=body_pose,
|
||||
betas=betas)
|
||||
model_joints = smpl_output.joints
|
||||
model_vertices = smpl_output.vertices
|
||||
|
||||
loss = body_fitting_loss_3d(body_pose, preserve_pose, betas, model_joints[:, self.smpl_index], camera_translation,
|
||||
j3d[:, self.corr_index], self.pose_prior,
|
||||
joints3d_conf=conf_3d,
|
||||
joint_loss_weight=600.0,
|
||||
use_collision=self.use_collision,
|
||||
model_vertices=model_vertices, model_faces=self.model_faces,
|
||||
search_tree=search_tree, pen_distance=pen_distance, filter_faces=filter_faces)
|
||||
body_optimizer.zero_grad()
|
||||
loss.backward()
|
||||
body_optimizer.step()
|
||||
|
||||
# Get final loss value
|
||||
with torch.no_grad():
|
||||
smpl_output = self.smpl(global_orient=global_orient,
|
||||
body_pose=body_pose,
|
||||
betas=betas, return_full_pose=True)
|
||||
model_joints = smpl_output.joints
|
||||
model_vertices = smpl_output.vertices
|
||||
|
||||
final_loss = body_fitting_loss_3d(body_pose, preserve_pose, betas, model_joints[:, self.smpl_index], camera_translation,
|
||||
j3d[:, self.corr_index], self.pose_prior,
|
||||
joints3d_conf=conf_3d,
|
||||
joint_loss_weight=600.0,
|
||||
use_collision=self.use_collision, model_vertices=model_vertices, model_faces=self.model_faces,
|
||||
search_tree=search_tree, pen_distance=pen_distance, filter_faces=filter_faces)
|
||||
|
||||
vertices = smpl_output.vertices.detach()
|
||||
joints = smpl_output.joints.detach()
|
||||
pose = torch.cat([global_orient, body_pose], dim=-1).detach()
|
||||
betas = betas.detach()
|
||||
|
||||
return vertices, joints, pose, betas, camera_translation, final_loss
|
||||
@@ -1,3 +0,0 @@
|
||||
from .base import Rots2Joints
|
||||
from .smplh import SMPLH
|
||||
from .smplx import SMPLX
|
||||
@@ -1,56 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Max-Planck-Gesellschaft zur Förderung der Wissenschaften e.V. (MPG) is
|
||||
# holder of all proprietary rights on this computer program.
|
||||
# You can only use this computer program if you have closed
|
||||
# a license agreement with MPG or you get the right to use the computer
|
||||
# program from someone who is authorized to grant you that right.
|
||||
# Any use of the computer program without a valid license is prohibited and
|
||||
# liable to prosecution.
|
||||
#
|
||||
# Copyright©2020 Max-Planck-Gesellschaft zur Förderung
|
||||
# der Wissenschaften e.V. (MPG). acting on behalf of its Max Planck Institute
|
||||
# for Intelligent Systems. All rights reserved.
|
||||
#
|
||||
# Contact: ps-license@tuebingen.mpg.de
|
||||
|
||||
from typing import Optional
|
||||
|
||||
import torch
|
||||
from torch import Tensor, nn
|
||||
from pathlib import Path
|
||||
import os
|
||||
# import hydra
|
||||
|
||||
class Rots2Joints(nn.Module):
|
||||
def __init__(self, path: Optional[str] = None,
|
||||
normalization: bool = False,
|
||||
eps: float = 1e-12,
|
||||
**kwargs) -> None:
|
||||
if normalization and path is None:
|
||||
raise TypeError("You should provide a path if normalization is on.")
|
||||
|
||||
super().__init__()
|
||||
self.normalization = normalization
|
||||
self.eps = eps
|
||||
# workaround for cluster local/sync
|
||||
if path is not None:
|
||||
rel_p = path.split('/')
|
||||
rel_p = rel_p[rel_p.index('deps'):]
|
||||
rel_p = '/'.join(rel_p)
|
||||
# path = hydra.utils.get_original_cwd() + '/' + rel_p
|
||||
if normalization:
|
||||
mean_path = Path(path) / "mean.pt"
|
||||
std_path = Path(path) / "std.pt"
|
||||
self.register_buffer('mean', torch.load(mean_path))
|
||||
self.register_buffer('std', torch.load(std_path))
|
||||
|
||||
def normalize(self, features: Tensor) -> Tensor:
|
||||
if self.normalization:
|
||||
features = (features - self.mean)/(self.std + self.eps)
|
||||
return features
|
||||
|
||||
def unnormalize(self, features: Tensor) -> Tensor:
|
||||
if self.normalization:
|
||||
features = features * self.std + self.mean
|
||||
return features
|
||||
@@ -1,192 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Max-Planck-Gesellschaft zur Förderung der Wissenschaften e.V. (MPG) is
|
||||
# holder of all proprietary rights on this computer program.
|
||||
# You can only use this computer program if you have closed
|
||||
# a license agreement with MPG or you get the right to use the computer
|
||||
# program from someone who is authorized to grant you that right.
|
||||
# Any use of the computer program without a valid license is prohibited and
|
||||
# liable to prosecution.
|
||||
#
|
||||
# Copyright©2020 Max-Planck-Gesellschaft zur Förderung
|
||||
# der Wissenschaften e.V. (MPG). acting on behalf of its Max Planck Institute
|
||||
# for Intelligent Systems. All rights reserved.
|
||||
#
|
||||
# Contact: ps-license@tuebingen.mpg.de
|
||||
|
||||
import contextlib
|
||||
from typing import Optional
|
||||
|
||||
import torch
|
||||
from einops import rearrange
|
||||
from torch import Tensor
|
||||
from mGPT.utils.joints import smplh_to_mmm_scaling_factor
|
||||
from mGPT.utils.joints import smplh2mmm_indexes
|
||||
from .base import Rots2Joints
|
||||
|
||||
|
||||
def slice_or_none(data, cslice):
|
||||
if data is None:
|
||||
return data
|
||||
else:
|
||||
return data[cslice]
|
||||
|
||||
|
||||
class SMPLH(Rots2Joints):
|
||||
|
||||
def __init__(self,
|
||||
path: str,
|
||||
jointstype: str = "mmm",
|
||||
input_pose_rep: str = "matrix",
|
||||
batch_size: int = 512,
|
||||
gender="neutral",
|
||||
**kwargs) -> None:
|
||||
super().__init__(path=None, normalization=False)
|
||||
self.batch_size = batch_size
|
||||
self.input_pose_rep = input_pose_rep
|
||||
self.jointstype = jointstype
|
||||
self.training = False
|
||||
|
||||
from smplx.body_models import SMPLHLayer
|
||||
import os
|
||||
# rel_p = path.split('/')
|
||||
# rel_p = rel_p[rel_p.index('data'):]
|
||||
# rel_p = '/'.join(rel_p)
|
||||
|
||||
# Remove annoying print
|
||||
with contextlib.redirect_stdout(None):
|
||||
self.smplh = SMPLHLayer(path, ext="pkl", gender=gender).eval()
|
||||
|
||||
self.faces = self.smplh.faces
|
||||
for p in self.parameters():
|
||||
p.requires_grad = False
|
||||
|
||||
def train(self, *args, **kwargs):
|
||||
return self
|
||||
|
||||
def forward(self,
|
||||
smpl_data: dict,
|
||||
jointstype: Optional[str] = None,
|
||||
input_pose_rep: Optional[str] = None,
|
||||
batch_size: Optional[int] = None) -> Tensor:
|
||||
|
||||
# Take values from init if not specified there
|
||||
jointstype = self.jointstype if jointstype is None else jointstype
|
||||
batch_size = self.batch_size if batch_size is None else batch_size
|
||||
input_pose_rep = self.input_pose_rep if input_pose_rep is None else input_pose_rep
|
||||
|
||||
if input_pose_rep == "xyz":
|
||||
raise NotImplementedError(
|
||||
"You should use identity pose2joints instead")
|
||||
|
||||
poses = smpl_data.rots
|
||||
trans = smpl_data.trans
|
||||
|
||||
from functools import reduce
|
||||
import operator
|
||||
save_shape_bs_len = poses.shape[:-3]
|
||||
nposes = reduce(operator.mul, save_shape_bs_len, 1)
|
||||
|
||||
if poses.shape[-3] == 52:
|
||||
nohands = False
|
||||
elif poses.shape[-3] == 22:
|
||||
nohands = True
|
||||
else:
|
||||
raise NotImplementedError("Could not parse the poses.")
|
||||
|
||||
# Convert any rotations to matrix
|
||||
# from temos.tools.easyconvert import to_matrix
|
||||
# matrix_poses = to_matrix(input_pose_rep, poses)
|
||||
matrix_poses = poses
|
||||
|
||||
# Reshaping
|
||||
matrix_poses = matrix_poses.reshape((nposes, *matrix_poses.shape[-3:]))
|
||||
global_orient = matrix_poses[:, 0]
|
||||
|
||||
if trans is None:
|
||||
trans = torch.zeros((*save_shape_bs_len, 3),
|
||||
dtype=poses.dtype,
|
||||
device=poses.device)
|
||||
|
||||
trans_all = trans.reshape((nposes, *trans.shape[-1:]))
|
||||
|
||||
body_pose = matrix_poses[:, 1:22]
|
||||
if nohands:
|
||||
left_hand_pose = None
|
||||
right_hand_pose = None
|
||||
else:
|
||||
hand_pose = matrix_poses[:, 22:]
|
||||
left_hand_pose = hand_pose[:, :15]
|
||||
right_hand_pose = hand_pose[:, 15:]
|
||||
|
||||
n = len(body_pose)
|
||||
outputs = []
|
||||
for chunk in range(int((n - 1) / batch_size) + 1):
|
||||
chunk_slice = slice(chunk * batch_size, (chunk + 1) * batch_size)
|
||||
smpl_output = self.smplh(
|
||||
global_orient=slice_or_none(global_orient, chunk_slice),
|
||||
body_pose=slice_or_none(body_pose, chunk_slice),
|
||||
left_hand_pose=slice_or_none(left_hand_pose, chunk_slice),
|
||||
right_hand_pose=slice_or_none(right_hand_pose, chunk_slice),
|
||||
transl=slice_or_none(trans_all, chunk_slice))
|
||||
|
||||
if jointstype == "vertices":
|
||||
output_chunk = smpl_output.vertices
|
||||
else:
|
||||
joints = smpl_output.joints
|
||||
output_chunk = joints
|
||||
|
||||
outputs.append(output_chunk)
|
||||
|
||||
outputs = torch.cat(outputs)
|
||||
outputs = outputs.reshape((*save_shape_bs_len, *outputs.shape[1:]))
|
||||
|
||||
# Change topology if needed
|
||||
outputs = smplh_to(jointstype, outputs, trans)
|
||||
|
||||
return outputs
|
||||
|
||||
def inverse(self, joints: Tensor) -> Tensor:
|
||||
raise NotImplementedError("Cannot inverse SMPLH layer.")
|
||||
|
||||
|
||||
def smplh_to(jointstype, data, trans):
|
||||
from mGPT.utils.joints import get_root_idx
|
||||
|
||||
if "mmm" in jointstype:
|
||||
from mGPT.utils.joints import smplh2mmm_indexes
|
||||
indexes = smplh2mmm_indexes
|
||||
data = data[..., indexes, :]
|
||||
|
||||
# make it compatible with mmm
|
||||
if jointstype == "mmm":
|
||||
from mGPT.utils.joints import smplh_to_mmm_scaling_factor
|
||||
data *= smplh_to_mmm_scaling_factor
|
||||
|
||||
if jointstype == "smplmmm":
|
||||
pass
|
||||
elif jointstype in ["mmm", "mmmns"]:
|
||||
# swap axis
|
||||
data = data[..., [1, 2, 0]]
|
||||
# revert left and right
|
||||
data[..., 2] = -data[..., 2]
|
||||
|
||||
elif jointstype == "smplnh":
|
||||
from mGPT.utils.joints import smplh2smplnh_indexes
|
||||
indexes = smplh2smplnh_indexes
|
||||
data = data[..., indexes, :]
|
||||
elif jointstype == "smplh":
|
||||
pass
|
||||
elif jointstype == "vertices":
|
||||
pass
|
||||
else:
|
||||
raise NotImplementedError(f"SMPLH to {jointstype} is not implemented.")
|
||||
|
||||
if jointstype != "vertices":
|
||||
# shift the output in each batch
|
||||
# such that it is centered on the pelvis/root on the first frame
|
||||
root_joint_idx = get_root_idx(jointstype)
|
||||
shift = trans[..., 0, :] - data[..., 0, root_joint_idx, :]
|
||||
data += shift[..., None, None, :]
|
||||
|
||||
return data
|
||||
@@ -1,201 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Max-Planck-Gesellschaft zur Förderung der Wissenschaften e.V. (MPG) is
|
||||
# holder of all proprietary rights on this computer program.
|
||||
# You can only use this computer program if you have closed
|
||||
# a license agreement with MPG or you get the right to use the computer
|
||||
# program from someone who is authorized to grant you that right.
|
||||
# Any use of the computer program without a valid license is prohibited and
|
||||
# liable to prosecution.
|
||||
#
|
||||
# Copyright©2020 Max-Planck-Gesellschaft zur Förderung
|
||||
# der Wissenschaften e.V. (MPG). acting on behalf of its Max Planck Institute
|
||||
# for Intelligent Systems. All rights reserved.
|
||||
#
|
||||
# Contact: ps-license@tuebingen.mpg.de
|
||||
|
||||
import contextlib
|
||||
from typing import Optional
|
||||
import torch
|
||||
from torch import Tensor
|
||||
from mGPT.utils.joints import smplh_to_mmm_scaling_factor, smplh2mmm_indexes, get_root_idx
|
||||
from mGPT.utils.easyconvert import rep_to_rep
|
||||
from .base import Rots2Joints
|
||||
|
||||
|
||||
def slice_or_none(data, cslice):
|
||||
if data is None:
|
||||
return data
|
||||
else:
|
||||
return data[cslice]
|
||||
|
||||
|
||||
class SMPLX(Rots2Joints):
|
||||
def __init__(self,
|
||||
path: str,
|
||||
jointstype: str = "mmm",
|
||||
input_pose_rep: str = "matrix",
|
||||
batch_size: int = 512,
|
||||
gender="neutral",
|
||||
**kwargs) -> None:
|
||||
super().__init__(path=None, normalization=False)
|
||||
self.batch_size = batch_size
|
||||
self.input_pose_rep = input_pose_rep
|
||||
self.jointstype = jointstype
|
||||
self.training = False
|
||||
|
||||
from smplx.body_models import SMPLXLayer
|
||||
import os
|
||||
# rel_p = path.split('/')
|
||||
# rel_p = rel_p[rel_p.index('data'):]
|
||||
# rel_p = '/'.join(rel_p)
|
||||
|
||||
# Remove annoying print
|
||||
with contextlib.redirect_stdout(None):
|
||||
self.smplx = SMPLXLayer(path,
|
||||
ext="npz",
|
||||
gender=gender,
|
||||
batch_size=batch_size).eval()
|
||||
|
||||
self.faces = self.smplx.faces
|
||||
for p in self.parameters():
|
||||
p.requires_grad = False
|
||||
|
||||
def train(self, *args, **kwargs):
|
||||
return self
|
||||
|
||||
def forward(self,
|
||||
smpl_data: dict,
|
||||
jointstype: Optional[str] = None,
|
||||
input_pose_rep: Optional[str] = None,
|
||||
batch_size: Optional[int] = None) -> Tensor:
|
||||
|
||||
# Take values from init if not specified there
|
||||
jointstype = self.jointstype if jointstype is None else jointstype
|
||||
batch_size = self.batch_size if batch_size is None else batch_size
|
||||
input_pose_rep = self.input_pose_rep if input_pose_rep is None else input_pose_rep
|
||||
|
||||
poses = smpl_data.rots
|
||||
trans = smpl_data.trans
|
||||
|
||||
from functools import reduce
|
||||
import operator
|
||||
save_shape_bs_len = poses.shape[:-3]
|
||||
nposes = reduce(operator.mul, save_shape_bs_len, 1)
|
||||
|
||||
|
||||
matrix_poses = rep_to_rep(self.input_pose_rep, input_pose_rep, poses)
|
||||
|
||||
# Reshaping
|
||||
matrix_poses = matrix_poses.reshape((nposes, *matrix_poses.shape[-3:]))
|
||||
|
||||
global_orient = matrix_poses[:, 0]
|
||||
|
||||
if trans is None:
|
||||
trans = torch.zeros((*save_shape_bs_len, 3),
|
||||
dtype=poses.dtype,
|
||||
device=poses.device)
|
||||
|
||||
trans_all = trans.reshape((nposes, *trans.shape[-1:]))
|
||||
|
||||
body_pose = matrix_poses[:, 1:22]
|
||||
|
||||
if poses.shape[-3] == 55:
|
||||
nohands = False
|
||||
nofaces = False
|
||||
elif poses.shape[-3] == 52:
|
||||
nohands = False
|
||||
nofaces = True
|
||||
elif poses.shape[-3] == 22:
|
||||
nohands = True
|
||||
nofaces = True
|
||||
else:
|
||||
raise NotImplementedError("Could not parse the poses.")
|
||||
|
||||
if nohands:
|
||||
left_hand_pose = None
|
||||
right_hand_pose = None
|
||||
else:
|
||||
left_hand_pose = matrix_poses[:, 25:40]
|
||||
right_hand_pose = matrix_poses[:, 40:55]
|
||||
|
||||
if nofaces:
|
||||
jaw_pose = None
|
||||
leye_pose = None
|
||||
reye_pose = None
|
||||
else:
|
||||
jaw_pose = matrix_poses[:, 22:23]
|
||||
leye_pose = matrix_poses[:, 23:24]
|
||||
reye_pose = matrix_poses[:, 24:25]
|
||||
|
||||
n = len(body_pose)
|
||||
outputs = []
|
||||
for chunk in range(int((n - 1) / batch_size) + 1):
|
||||
chunk_slice = slice(chunk * batch_size, (chunk + 1) * batch_size)
|
||||
smpl_output = self.smplx(
|
||||
global_orient=slice_or_none(global_orient, chunk_slice),
|
||||
body_pose=slice_or_none(body_pose, chunk_slice),
|
||||
left_hand_pose=slice_or_none(left_hand_pose, chunk_slice),
|
||||
right_hand_pose=slice_or_none(right_hand_pose, chunk_slice),
|
||||
jaw_pose=slice_or_none(jaw_pose, chunk_slice),
|
||||
leye_pose=slice_or_none(leye_pose, chunk_slice),
|
||||
reye_pose=slice_or_none(reye_pose, chunk_slice),
|
||||
transl=slice_or_none(trans_all, chunk_slice))
|
||||
|
||||
if jointstype == "vertices":
|
||||
output_chunk = smpl_output.vertices
|
||||
else:
|
||||
joints = smpl_output.joints
|
||||
output_chunk = joints
|
||||
|
||||
outputs.append(output_chunk)
|
||||
|
||||
outputs = torch.cat(outputs)
|
||||
outputs = outputs.reshape((*save_shape_bs_len, *outputs.shape[1:]))
|
||||
|
||||
# Change topology if needed
|
||||
outputs = smplx_to(jointstype, outputs, trans)
|
||||
|
||||
return outputs
|
||||
|
||||
def inverse(self, joints: Tensor) -> Tensor:
|
||||
raise NotImplementedError("Cannot inverse SMPLX layer.")
|
||||
|
||||
|
||||
def smplx_to(jointstype, data, trans):
|
||||
|
||||
if "mmm" in jointstype:
|
||||
indexes = smplh2mmm_indexes
|
||||
data = data[..., indexes, :]
|
||||
|
||||
# make it compatible with mmm
|
||||
if jointstype == "mmm":
|
||||
data *= smplh_to_mmm_scaling_factor
|
||||
|
||||
if jointstype == "smplmmm":
|
||||
pass
|
||||
elif jointstype in ["mmm", "mmmns"]:
|
||||
# swap axis
|
||||
data = data[..., [1, 2, 0]]
|
||||
# revert left and right
|
||||
data[..., 2] = -data[..., 2]
|
||||
|
||||
elif jointstype == "smplnh":
|
||||
from mGPT.utils.joints import smplh2smplnh_indexes
|
||||
indexes = smplh2smplnh_indexes
|
||||
data = data[..., indexes, :]
|
||||
elif jointstype == "smplh":
|
||||
pass
|
||||
elif jointstype == "vertices":
|
||||
pass
|
||||
else:
|
||||
raise NotImplementedError(f"SMPLX to {jointstype} is not implemented.")
|
||||
|
||||
if jointstype != "vertices":
|
||||
# shift the output in each batch
|
||||
# such that it is centered on the pelvis/root on the first frame
|
||||
root_joint_idx = get_root_idx(jointstype)
|
||||
shift = trans[..., 0, :] - data[..., 0, root_joint_idx, :]
|
||||
data += shift[..., None, None, :]
|
||||
|
||||
return data
|
||||
@@ -1,5 +0,0 @@
|
||||
from .base import Rots2Rfeats
|
||||
# from .globvel import Globalvel
|
||||
|
||||
from .globvelandy import Globalvelandy
|
||||
# from .rifeats import Rifeats
|
||||
@@ -1,60 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Max-Planck-Gesellschaft zur Förderung der Wissenschaften e.V. (MPG) is
|
||||
# holder of all proprietary rights on this computer program.
|
||||
# You can only use this computer program if you have closed
|
||||
# a license agreement with MPG or you get the right to use the computer
|
||||
# program from someone who is authorized to grant you that right.
|
||||
# Any use of the computer program without a valid license is prohibited and
|
||||
# liable to prosecution.
|
||||
#
|
||||
# Copyright©2020 Max-Planck-Gesellschaft zur Förderung
|
||||
# der Wissenschaften e.V. (MPG). acting on behalf of its Max Planck Institute
|
||||
# for Intelligent Systems. All rights reserved.
|
||||
#
|
||||
# Contact: ps-license@tuebingen.mpg.de
|
||||
|
||||
from typing import Optional
|
||||
|
||||
import torch
|
||||
from torch import Tensor, nn
|
||||
from pathlib import Path
|
||||
import os
|
||||
|
||||
class Rots2Rfeats(nn.Module):
|
||||
def __init__(self, path: Optional[str] = None,
|
||||
normalization: bool = True,
|
||||
eps: float = 1e-12,
|
||||
**kwargs) -> None:
|
||||
if normalization and path is None:
|
||||
raise TypeError("You should provide a path if normalization is on.")
|
||||
|
||||
super().__init__()
|
||||
self.normalization = normalization
|
||||
self.eps = eps
|
||||
if normalization:
|
||||
# workaround for cluster local/sync
|
||||
rel_p = path.split('/')
|
||||
# superhacky it is for the datatype ugly stuff change it, copy the main stuff to seperate_pairs dict
|
||||
if rel_p[-1] == 'separate_pairs':
|
||||
rel_p.remove('separate_pairs')
|
||||
########################################################
|
||||
# rel_p = rel_p[rel_p.index('deps'):]
|
||||
rel_p = '/'.join(rel_p)
|
||||
# path = hydra.utils.get_original_cwd() + '/' + rel_p
|
||||
path = rel_p
|
||||
mean_path = Path(path) / "rfeats_mean.pt"
|
||||
std_path = Path(path) / "rfeats_std.pt"
|
||||
|
||||
self.register_buffer('mean', torch.load(mean_path))
|
||||
self.register_buffer('std', torch.load(std_path))
|
||||
|
||||
def normalize(self, features: Tensor) -> Tensor:
|
||||
if self.normalization:
|
||||
features = (features - self.mean)/(self.std + self.eps)
|
||||
return features
|
||||
|
||||
def unnormalize(self, features: Tensor) -> Tensor:
|
||||
if self.normalization:
|
||||
features = features * self.std + self.mean
|
||||
return features
|
||||
@@ -1,128 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Max-Planck-Gesellschaft zur Förderung der Wissenschaften e.V. (MPG) is
|
||||
# holder of all proprietary rights on this computer program.
|
||||
# You can only use this computer program if you have closed
|
||||
# a license agreement with MPG or you get the right to use the computer
|
||||
# program from someone who is authorized to grant you that right.
|
||||
# Any use of the computer program without a valid license is prohibited and
|
||||
# liable to prosecution.
|
||||
#
|
||||
# Copyright©2020 Max-Planck-Gesellschaft zur Förderung
|
||||
# der Wissenschaften e.V. (MPG). acting on behalf of its Max Planck Institute
|
||||
# for Intelligent Systems. All rights reserved.
|
||||
#
|
||||
# Contact: ps-license@tuebingen.mpg.de
|
||||
|
||||
from typing import Optional
|
||||
|
||||
import torch
|
||||
from torch import Tensor
|
||||
from einops import rearrange
|
||||
|
||||
from mGPT.utils.easyconvert import rep_to_rep, nfeats_of, to_matrix
|
||||
import mGPT.utils.geometry_tools as geometry_tools
|
||||
|
||||
from .base import Rots2Rfeats
|
||||
|
||||
|
||||
class Globalvelandy(Rots2Rfeats):
|
||||
def __init__(self,
|
||||
path: Optional[str] = None,
|
||||
normalization: bool = False,
|
||||
pose_rep: str = "rot6d",
|
||||
canonicalize: bool = False,
|
||||
offset: bool = True,
|
||||
**kwargs) -> None:
|
||||
super().__init__(path=path, normalization=normalization)
|
||||
|
||||
self.canonicalize = canonicalize
|
||||
self.pose_rep = pose_rep
|
||||
self.nfeats = nfeats_of(pose_rep)
|
||||
self.offset = offset
|
||||
|
||||
def forward(self, data, data_rep='matrix', first_frame=None) -> Tensor:
|
||||
|
||||
poses, trans = data.rots, data.trans
|
||||
|
||||
# extract the root gravity axis
|
||||
# for smpl it is the last coordinate
|
||||
root_y = trans[..., 2]
|
||||
trajectory = trans[..., [0, 1]]
|
||||
|
||||
# Compute the difference of trajectory
|
||||
vel_trajectory = torch.diff(trajectory, dim=-2)
|
||||
|
||||
# 0 for the first one => keep the dimentionality
|
||||
if first_frame is None:
|
||||
first_frame = 0 * vel_trajectory[..., [0], :]
|
||||
|
||||
vel_trajectory = torch.cat((first_frame, vel_trajectory), dim=-2)
|
||||
|
||||
# first normalize the data
|
||||
if self.canonicalize:
|
||||
|
||||
matrix_poses = rep_to_rep(data_rep, 'matrix', poses)
|
||||
global_orient = matrix_poses[..., 0, :, :]
|
||||
|
||||
# remove the rotation
|
||||
rot2d = rep_to_rep(data_rep, 'rotvec', poses[0, 0, ...])
|
||||
|
||||
# Remove the fist rotation along the vertical axis
|
||||
rot2d[..., :2] = 0
|
||||
|
||||
if self.offset:
|
||||
# add a bit more rotation
|
||||
rot2d[..., 2] += torch.pi / 2
|
||||
|
||||
rot2d = rep_to_rep('rotvec', 'matrix', rot2d)
|
||||
|
||||
# turn with the same amount all the rotations
|
||||
global_orient = torch.einsum("...kj,...kl->...jl", rot2d,
|
||||
global_orient)
|
||||
|
||||
matrix_poses = torch.cat(
|
||||
(global_orient[..., None, :, :], matrix_poses[..., 1:, :, :]),
|
||||
dim=-3)
|
||||
|
||||
poses = rep_to_rep('matrix', data_rep, matrix_poses)
|
||||
|
||||
# Turn the trajectory as well
|
||||
vel_trajectory = torch.einsum("...kj,...lk->...lj",
|
||||
rot2d[..., :2, :2], vel_trajectory)
|
||||
|
||||
poses = rep_to_rep(data_rep, self.pose_rep, poses)
|
||||
features = torch.cat(
|
||||
(root_y[..., None], vel_trajectory,
|
||||
rearrange(poses, "... joints rot -> ... (joints rot)")),
|
||||
dim=-1)
|
||||
features = self.normalize(features)
|
||||
|
||||
return features
|
||||
|
||||
def extract(self, features):
|
||||
root_y = features[..., 0]
|
||||
vel_trajectory = features[..., 1:3]
|
||||
poses_features = features[..., 3:]
|
||||
poses = rearrange(poses_features,
|
||||
"... (joints rot) -> ... joints rot",
|
||||
rot=self.nfeats)
|
||||
return root_y, vel_trajectory, poses
|
||||
|
||||
def inverse(self, features, last_frame=None):
|
||||
features = self.unnormalize(features)
|
||||
root_y, vel_trajectory, poses = self.extract(features)
|
||||
|
||||
# integrate the trajectory
|
||||
trajectory = torch.cumsum(vel_trajectory, dim=-2)
|
||||
if last_frame is None:
|
||||
pass
|
||||
# First frame should be 0, but if infered it is better to ensure it
|
||||
trajectory = trajectory - trajectory[..., [0], :]
|
||||
|
||||
# Get back the translation
|
||||
trans = torch.cat([trajectory, root_y[..., None]], dim=-1)
|
||||
matrix_poses = rep_to_rep(self.pose_rep, 'matrix', poses)
|
||||
|
||||
from ..smpl import RotTransDatastruct
|
||||
return RotTransDatastruct(rots=matrix_poses, trans=trans)
|
||||
@@ -1,191 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Max-Planck-Gesellschaft zur Förderung der Wissenschaften e.V. (MPG) is
|
||||
# holder of all proprietary rights on this computer program.
|
||||
# You can only use this computer program if you have closed
|
||||
# a license agreement with MPG or you get the right to use the computer
|
||||
# program from someone who is authorized to grant you that right.
|
||||
# Any use of the computer program without a valid license is prohibited and
|
||||
# liable to prosecution.
|
||||
#
|
||||
# Copyright©2020 Max-Planck-Gesellschaft zur Förderung
|
||||
# der Wissenschaften e.V. (MPG). acting on behalf of its Max Planck Institute
|
||||
# for Intelligent Systems. All rights reserved.
|
||||
#
|
||||
# Contact: ps-license@tuebingen.mpg.de
|
||||
|
||||
from typing import Optional
|
||||
from torch import Tensor
|
||||
import smplx
|
||||
|
||||
from .base import Datastruct, dataclass, Transform
|
||||
|
||||
from .rots2rfeats import Rots2Rfeats
|
||||
from .rots2joints import Rots2Joints
|
||||
from .joints2jfeats import Joints2Jfeats
|
||||
|
||||
|
||||
class SMPLTransform(Transform):
|
||||
def __init__(self, rots2rfeats: Rots2Rfeats,
|
||||
rots2joints: Rots2Joints,
|
||||
joints2jfeats: Joints2Jfeats,
|
||||
**kwargs):
|
||||
self.rots2rfeats = rots2rfeats
|
||||
self.rots2joints = rots2joints
|
||||
self.joints2jfeats = joints2jfeats
|
||||
|
||||
def Datastruct(self, **kwargs):
|
||||
return SMPLDatastruct(_rots2rfeats=self.rots2rfeats,
|
||||
_rots2joints=self.rots2joints,
|
||||
_joints2jfeats=self.joints2jfeats,
|
||||
transforms=self,
|
||||
**kwargs)
|
||||
|
||||
def __repr__(self):
|
||||
return "SMPLTransform()"
|
||||
|
||||
|
||||
class RotIdentityTransform(Transform):
|
||||
def __init__(self, **kwargs):
|
||||
return
|
||||
|
||||
def Datastruct(self, **kwargs):
|
||||
return RotTransDatastruct(**kwargs)
|
||||
|
||||
def __repr__(self):
|
||||
return "RotIdentityTransform()"
|
||||
|
||||
|
||||
@dataclass
|
||||
class RotTransDatastruct(Datastruct):
|
||||
rots: Tensor
|
||||
trans: Tensor
|
||||
|
||||
transforms: RotIdentityTransform = RotIdentityTransform()
|
||||
|
||||
def __post_init__(self):
|
||||
self.datakeys = ["rots", "trans"]
|
||||
|
||||
def __len__(self):
|
||||
return len(self.rots)
|
||||
|
||||
|
||||
@dataclass
|
||||
class SMPLDatastruct(Datastruct):
|
||||
transforms: SMPLTransform
|
||||
_rots2rfeats: Rots2Rfeats
|
||||
_rots2joints: Rots2Joints
|
||||
_joints2jfeats: Joints2Jfeats
|
||||
|
||||
features: Optional[Tensor] = None
|
||||
rots_: Optional[RotTransDatastruct] = None
|
||||
rfeats_: Optional[Tensor] = None
|
||||
joints_: Optional[Tensor] = None
|
||||
jfeats_: Optional[Tensor] = None
|
||||
vertices_: Optional[Tensor] = None
|
||||
|
||||
def __post_init__(self):
|
||||
self.datakeys = ['features', 'rots_', 'rfeats_',
|
||||
'joints_', 'jfeats_', 'vertices_']
|
||||
# starting point
|
||||
if self.features is not None and self.rfeats_ is None:
|
||||
self.rfeats_ = self.features
|
||||
|
||||
@property
|
||||
def rots(self):
|
||||
# Cached value
|
||||
if self.rots_ is not None:
|
||||
return self.rots_
|
||||
|
||||
# self.rfeats_ should be defined
|
||||
assert self.rfeats_ is not None
|
||||
|
||||
self._rots2rfeats.to(self.rfeats.device)
|
||||
self.rots_ = self._rots2rfeats.inverse(self.rfeats)
|
||||
return self.rots_
|
||||
|
||||
@property
|
||||
def rfeats(self):
|
||||
# Cached value
|
||||
if self.rfeats_ is not None:
|
||||
return self.rfeats_
|
||||
|
||||
# self.rots_ should be defined
|
||||
assert self.rots_ is not None
|
||||
|
||||
self._rots2rfeats.to(self.rots.device)
|
||||
self.rfeats_ = self._rots2rfeats(self.rots)
|
||||
return self.rfeats_
|
||||
|
||||
@property
|
||||
def joints(self):
|
||||
# Cached value
|
||||
if self.joints_ is not None:
|
||||
return self.joints_
|
||||
|
||||
self._rots2joints.to(self.rots.device)
|
||||
self.joints_ = self._rots2joints(self.rots)
|
||||
return self.joints_
|
||||
|
||||
@property
|
||||
def jfeats(self):
|
||||
# Cached value
|
||||
if self.jfeats_ is not None:
|
||||
return self.jfeats_
|
||||
|
||||
self._joints2jfeats.to(self.joints.device)
|
||||
self.jfeats_ = self._joints2jfeats(self.joints)
|
||||
return self.jfeats_
|
||||
|
||||
@property
|
||||
def vertices(self):
|
||||
# Cached value
|
||||
if self.vertices_ is not None:
|
||||
return self.vertices_
|
||||
|
||||
self._rots2joints.to(self.rots.device)
|
||||
self.vertices_ = self._rots2joints(self.rots, jointstype="vertices")
|
||||
return self.vertices_
|
||||
|
||||
def __len__(self):
|
||||
return len(self.rfeats)
|
||||
|
||||
|
||||
def get_body_model(model_type, gender, batch_size, device='cpu', ext='pkl'):
|
||||
'''
|
||||
type: smpl, smplx smplh and others. Refer to smplx tutorial
|
||||
gender: male, female, neutral
|
||||
batch_size: an positive integar
|
||||
'''
|
||||
mtype = model_type.upper()
|
||||
if gender != 'neutral':
|
||||
if not isinstance(gender, str):
|
||||
gender = str(gender.astype(str)).upper()
|
||||
else:
|
||||
gender = gender.upper()
|
||||
else:
|
||||
gender = gender.upper()
|
||||
ext = 'npz'
|
||||
body_model_path = f'data/smpl_models/{model_type}/{mtype}_{gender}.{ext}'
|
||||
|
||||
body_model = smplx.create(body_model_path, model_type=type,
|
||||
gender=gender, ext=ext,
|
||||
use_pca=False,
|
||||
num_pca_comps=12,
|
||||
create_global_orient=True,
|
||||
create_body_pose=True,
|
||||
create_betas=True,
|
||||
create_left_hand_pose=True,
|
||||
create_right_hand_pose=True,
|
||||
create_expression=True,
|
||||
create_jaw_pose=True,
|
||||
create_leye_pose=True,
|
||||
create_reye_pose=True,
|
||||
create_transl=True,
|
||||
batch_size=batch_size)
|
||||
|
||||
if device == 'cuda':
|
||||
return body_model.cuda()
|
||||
else:
|
||||
return body_model
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Max-Planck-Gesellschaft zur Förderung der Wissenschaften e.V. (MPG) is
|
||||
# holder of all proprietary rights on this computer program.
|
||||
# You can only use this computer program if you have closed
|
||||
# a license agreement with MPG or you get the right to use the computer
|
||||
# program from someone who is authorized to grant you that right.
|
||||
# Any use of the computer program without a valid license is prohibited and
|
||||
# liable to prosecution.
|
||||
#
|
||||
# Copyright©2020 Max-Planck-Gesellschaft zur Förderung
|
||||
# der Wissenschaften e.V. (MPG). acting on behalf of its Max Planck Institute
|
||||
# for Intelligent Systems. All rights reserved.
|
||||
#
|
||||
# Contact: ps-license@tuebingen.mpg.de
|
||||
|
||||
from typing import Optional
|
||||
from torch import Tensor
|
||||
|
||||
from .base import Datastruct, dataclass, Transform
|
||||
from ..tools import collate_tensor_with_padding
|
||||
|
||||
from .joints2jfeats import Joints2Jfeats
|
||||
|
||||
|
||||
class XYZTransform(Transform):
|
||||
def __init__(self, joints2jfeats: Joints2Jfeats, **kwargs):
|
||||
self.joints2jfeats = joints2jfeats
|
||||
|
||||
def Datastruct(self, **kwargs):
|
||||
return XYZDatastruct(_joints2jfeats=self.joints2jfeats,
|
||||
transforms=self,
|
||||
**kwargs)
|
||||
|
||||
def __repr__(self):
|
||||
return "XYZTransform()"
|
||||
|
||||
|
||||
@dataclass
|
||||
class XYZDatastruct(Datastruct):
|
||||
transforms: XYZTransform
|
||||
_joints2jfeats: Joints2Jfeats
|
||||
|
||||
features: Optional[Tensor] = None
|
||||
joints_: Optional[Tensor] = None
|
||||
jfeats_: Optional[Tensor] = None
|
||||
|
||||
def __post_init__(self):
|
||||
self.datakeys = ["features", "joints_", "jfeats_"]
|
||||
# starting point
|
||||
if self.features is not None and self.jfeats_ is None:
|
||||
self.jfeats_ = self.features
|
||||
|
||||
@property
|
||||
def joints(self):
|
||||
# Cached value
|
||||
if self.joints_ is not None:
|
||||
return self.joints_
|
||||
|
||||
# self.jfeats_ should be defined
|
||||
assert self.jfeats_ is not None
|
||||
|
||||
self._joints2jfeats.to(self.jfeats.device)
|
||||
self.joints_ = self._joints2jfeats.inverse(self.jfeats)
|
||||
return self.joints_
|
||||
|
||||
@property
|
||||
def jfeats(self):
|
||||
# Cached value
|
||||
if self.jfeats_ is not None:
|
||||
return self.jfeats_
|
||||
|
||||
# self.joints_ should be defined
|
||||
assert self.joints_ is not None
|
||||
|
||||
self._joints2jfeats.to(self.joints.device)
|
||||
self.jfeats_ = self._joints2jfeats(self.joints)
|
||||
return self.jfeats_
|
||||
|
||||
def __len__(self):
|
||||
return len(self.jfeats)
|
||||
@@ -1,81 +0,0 @@
|
||||
import torch
|
||||
import rich
|
||||
import pickle
|
||||
import numpy as np
|
||||
|
||||
|
||||
def lengths_to_mask(lengths):
|
||||
max_len = max(lengths)
|
||||
mask = torch.arange(max_len, device=lengths.device).expand(
|
||||
len(lengths), max_len) < lengths.unsqueeze(1)
|
||||
return mask
|
||||
|
||||
|
||||
# padding to max length in one batch
|
||||
def collate_tensors(batch):
|
||||
if isinstance(batch[0], np.ndarray):
|
||||
batch = [torch.tensor(b).float() for b in batch]
|
||||
|
||||
dims = batch[0].dim()
|
||||
max_size = [max([b.size(i) for b in batch]) for i in range(dims)]
|
||||
size = (len(batch), ) + tuple(max_size)
|
||||
canvas = batch[0].new_zeros(size=size)
|
||||
for i, b in enumerate(batch):
|
||||
sub_tensor = canvas[i]
|
||||
for d in range(dims):
|
||||
sub_tensor = sub_tensor.narrow(d, 0, b.size(d))
|
||||
sub_tensor.add_(b)
|
||||
return canvas
|
||||
|
||||
def humanml3d_collate(batch):
|
||||
notnone_batches = [b for b in batch if b is not None]
|
||||
EvalFlag = False if notnone_batches[0][5] is None else True
|
||||
|
||||
# Sort by text length
|
||||
if EvalFlag:
|
||||
notnone_batches.sort(key=lambda x: x[5], reverse=True)
|
||||
|
||||
# Motion only
|
||||
adapted_batch = {
|
||||
"motion":
|
||||
collate_tensors([torch.tensor(b[1]).float() for b in notnone_batches]),
|
||||
"length": [b[2] for b in notnone_batches],
|
||||
}
|
||||
|
||||
# Text and motion
|
||||
if notnone_batches[0][0] is not None:
|
||||
adapted_batch.update({
|
||||
"text": [b[0] for b in notnone_batches],
|
||||
"all_captions": [b[7] for b in notnone_batches],
|
||||
})
|
||||
|
||||
# Evaluation related
|
||||
if EvalFlag:
|
||||
adapted_batch.update({
|
||||
"text": [b[0] for b in notnone_batches],
|
||||
"word_embs":
|
||||
collate_tensors(
|
||||
[torch.tensor(b[3]).float() for b in notnone_batches]),
|
||||
"pos_ohot":
|
||||
collate_tensors(
|
||||
[torch.tensor(b[4]).float() for b in notnone_batches]),
|
||||
"text_len":
|
||||
collate_tensors([torch.tensor(b[5]) for b in notnone_batches]),
|
||||
"tokens": [b[6] for b in notnone_batches],
|
||||
})
|
||||
|
||||
# Tasks
|
||||
if len(notnone_batches[0]) == 9:
|
||||
adapted_batch.update({"tasks": [b[8] for b in notnone_batches]})
|
||||
|
||||
return adapted_batch
|
||||
|
||||
|
||||
def load_pkl(path, description=None, progressBar=False):
|
||||
if progressBar:
|
||||
with rich.progress.open(path, 'rb', description=description) as file:
|
||||
data = pickle.load(file)
|
||||
else:
|
||||
with open(path, 'rb') as file:
|
||||
data = pickle.load(file)
|
||||
return data
|
||||
@@ -1 +0,0 @@
|
||||
from .base import BaseLosses
|
||||
@@ -1,61 +0,0 @@
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
|
||||
class BaseLosses(nn.Module):
|
||||
def __init__(self, cfg, losses, params, losses_func, num_joints, **kwargs):
|
||||
super().__init__()
|
||||
|
||||
# Save parameters
|
||||
self.num_joints = num_joints
|
||||
self._params = params
|
||||
|
||||
# Add total indicator
|
||||
losses.append("total") if "total" not in losses else None
|
||||
|
||||
# Register losses
|
||||
for loss in losses:
|
||||
self.register_buffer(loss, torch.tensor(0.0))
|
||||
self.register_buffer("count", torch.tensor(0.0))
|
||||
self.losses = losses
|
||||
|
||||
# Instantiate loss functions
|
||||
self._losses_func = {}
|
||||
for loss in losses[:-1]:
|
||||
self._losses_func[loss] = losses_func[loss](reduction='mean')
|
||||
|
||||
def _update_loss(self, loss: str, outputs, inputs):
|
||||
'''Update the loss and return the weighted loss.'''
|
||||
# Update the loss
|
||||
val = self._losses_func[loss](outputs, inputs)
|
||||
# self.losses_values[loss] += val.detach()
|
||||
getattr(self, loss).add_(val.detach())
|
||||
# Return a weighted sum
|
||||
weighted_loss = self._params[loss] * val
|
||||
return weighted_loss
|
||||
|
||||
def reset(self):
|
||||
'''Reset the losses to 0.'''
|
||||
for loss in self.losses:
|
||||
setattr(self, loss, torch.tensor(0.0, device=getattr(self, loss).device))
|
||||
setattr(self, "count", torch.tensor(0.0, device=getattr(self, "count").device))
|
||||
|
||||
def compute(self, split):
|
||||
'''Compute the losses and return a dictionary with the losses.'''
|
||||
count = self.count
|
||||
# Loss dictionary
|
||||
loss_dict = {loss: getattr(self, loss)/count for loss in self.losses}
|
||||
# Format the losses for logging
|
||||
log_dict = { self.loss2logname(loss, split): value.item()
|
||||
for loss, value in loss_dict.items() if not torch.isnan(value)}
|
||||
# Reset the losses
|
||||
self.reset()
|
||||
return log_dict
|
||||
|
||||
def loss2logname(self, loss: str, split: str):
|
||||
'''Convert the loss name to a log name.'''
|
||||
if loss == "total":
|
||||
log_name = f"{loss}/{split}"
|
||||
else:
|
||||
loss_type, name = loss.split("_")
|
||||
log_name = f"{loss_type}/{name}/{split}"
|
||||
return log_name
|
||||
@@ -1,97 +0,0 @@
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
from .base import BaseLosses
|
||||
|
||||
|
||||
class CommitLoss(nn.Module):
|
||||
"""
|
||||
Useless Wrapper
|
||||
"""
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__()
|
||||
|
||||
def forward(self, commit, commit2, **kwargs):
|
||||
return commit
|
||||
|
||||
|
||||
class GPTLosses(BaseLosses):
|
||||
|
||||
def __init__(self, cfg, stage, num_joints, **kwargs):
|
||||
# Save parameters
|
||||
self.stage = stage
|
||||
recons_loss = cfg.LOSS.ABLATION.RECONS_LOSS
|
||||
|
||||
# Define losses
|
||||
losses = []
|
||||
params = {}
|
||||
if stage == "vae":
|
||||
losses.append("recons_feature")
|
||||
params['recons_feature'] = cfg.LOSS.LAMBDA_FEATURE
|
||||
|
||||
losses.append("recons_velocity")
|
||||
params['recons_velocity'] = cfg.LOSS.LAMBDA_VELOCITY
|
||||
|
||||
losses.append("vq_commit")
|
||||
params['vq_commit'] = cfg.LOSS.LAMBDA_COMMIT
|
||||
elif stage in ["lm_pretrain", "lm_instruct"]:
|
||||
losses.append("gpt_loss")
|
||||
params['gpt_loss'] = cfg.LOSS.LAMBDA_CLS
|
||||
|
||||
# Define loss functions & weights
|
||||
losses_func = {}
|
||||
for loss in losses:
|
||||
if loss.split('_')[0] == 'recons':
|
||||
if recons_loss == "l1":
|
||||
losses_func[loss] = nn.L1Loss
|
||||
elif recons_loss == "l2":
|
||||
losses_func[loss] = nn.MSELoss
|
||||
elif recons_loss == "l1_smooth":
|
||||
losses_func[loss] = nn.SmoothL1Loss
|
||||
elif loss.split('_')[1] in [
|
||||
'commit', 'loss', 'gpt', 'm2t2m', 't2m2t'
|
||||
]:
|
||||
losses_func[loss] = CommitLoss
|
||||
elif loss.split('_')[1] in ['cls', 'lm']:
|
||||
losses_func[loss] = nn.CrossEntropyLoss
|
||||
else:
|
||||
raise NotImplementedError(f"Loss {loss} not implemented.")
|
||||
|
||||
super().__init__(cfg, losses, params, losses_func, num_joints,
|
||||
**kwargs)
|
||||
|
||||
def update(self, rs_set):
|
||||
'''Update the losses'''
|
||||
total: float = 0.0
|
||||
|
||||
if self.stage in ["vae"]:
|
||||
total += self._update_loss("recons_feature", rs_set['m_rst'],
|
||||
rs_set['m_ref'])
|
||||
# total += self._update_loss("recons_joints", rs_set['joints_rst'], rs_set['joints_ref'])
|
||||
nfeats = rs_set['m_rst'].shape[-1]
|
||||
if nfeats in [263, 135 + 263]:
|
||||
if nfeats == 135 + 263:
|
||||
vel_start = 135 + 4
|
||||
elif nfeats == 263:
|
||||
vel_start = 4
|
||||
total += self._update_loss(
|
||||
"recons_velocity",
|
||||
rs_set['m_rst'][..., vel_start:(self.num_joints - 1) * 3 +
|
||||
vel_start],
|
||||
rs_set['m_ref'][..., vel_start:(self.num_joints - 1) * 3 +
|
||||
vel_start])
|
||||
else:
|
||||
if self._params['recons_velocity'] != 0.0:
|
||||
raise NotImplementedError(
|
||||
"Velocity not implemented for nfeats = {})".format(nfeats))
|
||||
total += self._update_loss("vq_commit", rs_set['loss_commit'],
|
||||
rs_set['loss_commit'])
|
||||
|
||||
if self.stage in ["lm_pretrain", "lm_instruct"]:
|
||||
total += self._update_loss("gpt_loss", rs_set['outputs'].loss,
|
||||
rs_set['outputs'].loss)
|
||||
|
||||
# Update the total loss
|
||||
self.total += total.detach()
|
||||
self.count += 1
|
||||
|
||||
return total
|
||||
@@ -1 +0,0 @@
|
||||
from .base import BaseMetrics
|
||||
@@ -1,46 +0,0 @@
|
||||
from torch import Tensor, nn
|
||||
from os.path import join as pjoin
|
||||
from .mr import MRMetrics
|
||||
from .t2m import TM2TMetrics
|
||||
from .mm import MMMetrics
|
||||
from .m2t import M2TMetrics
|
||||
from .m2m import PredMetrics
|
||||
|
||||
|
||||
class BaseMetrics(nn.Module):
|
||||
def __init__(self, cfg, datamodule, debug, **kwargs) -> None:
|
||||
super().__init__()
|
||||
|
||||
njoints = datamodule.njoints
|
||||
|
||||
data_name = datamodule.name
|
||||
if data_name in ["humanml3d", "kit"]:
|
||||
self.TM2TMetrics = TM2TMetrics(
|
||||
cfg=cfg,
|
||||
dataname=data_name,
|
||||
diversity_times=30 if debug else cfg.METRIC.DIVERSITY_TIMES,
|
||||
dist_sync_on_step=cfg.METRIC.DIST_SYNC_ON_STEP,
|
||||
)
|
||||
self.M2TMetrics = M2TMetrics(
|
||||
cfg=cfg,
|
||||
w_vectorizer=datamodule.hparams.w_vectorizer,
|
||||
diversity_times=30 if debug else cfg.METRIC.DIVERSITY_TIMES,
|
||||
dist_sync_on_step=cfg.METRIC.DIST_SYNC_ON_STEP)
|
||||
self.MMMetrics = MMMetrics(
|
||||
cfg=cfg,
|
||||
mm_num_times=cfg.METRIC.MM_NUM_TIMES,
|
||||
dist_sync_on_step=cfg.METRIC.DIST_SYNC_ON_STEP,
|
||||
)
|
||||
|
||||
self.MRMetrics = MRMetrics(
|
||||
njoints=njoints,
|
||||
jointstype=cfg.DATASET.JOINT_TYPE,
|
||||
dist_sync_on_step=cfg.METRIC.DIST_SYNC_ON_STEP,
|
||||
)
|
||||
self.PredMetrics = PredMetrics(
|
||||
cfg=cfg,
|
||||
njoints=njoints,
|
||||
jointstype=cfg.DATASET.JOINT_TYPE,
|
||||
dist_sync_on_step=cfg.METRIC.DIST_SYNC_ON_STEP,
|
||||
task=cfg.model.params.task,
|
||||
)
|
||||
@@ -1,95 +0,0 @@
|
||||
from typing import List
|
||||
|
||||
import torch
|
||||
from torch import Tensor
|
||||
from torchmetrics import Metric
|
||||
|
||||
from .utils import *
|
||||
|
||||
|
||||
# motion reconstruction metric
|
||||
class PredMetrics(Metric):
|
||||
|
||||
def __init__(self,
|
||||
cfg,
|
||||
njoints: int = 22,
|
||||
jointstype: str = "mmm",
|
||||
force_in_meter: bool = True,
|
||||
align_root: bool = True,
|
||||
dist_sync_on_step=True,
|
||||
task: str = "pred",
|
||||
**kwargs):
|
||||
super().__init__(dist_sync_on_step=dist_sync_on_step)
|
||||
|
||||
self.name = 'Motion Prdiction'
|
||||
self.cfg = cfg
|
||||
self.jointstype = jointstype
|
||||
self.align_root = align_root
|
||||
self.task = task
|
||||
self.force_in_meter = force_in_meter
|
||||
|
||||
self.add_state("count", default=torch.tensor(0), dist_reduce_fx="sum")
|
||||
self.add_state("count_seq",
|
||||
default=torch.tensor(0),
|
||||
dist_reduce_fx="sum")
|
||||
|
||||
self.add_state("APD",
|
||||
default=torch.tensor([0.0]),
|
||||
dist_reduce_fx="sum")
|
||||
self.add_state("ADE",
|
||||
default=torch.tensor([0.0]),
|
||||
dist_reduce_fx="sum")
|
||||
self.add_state("FDE",
|
||||
default=torch.tensor([0.0]),
|
||||
dist_reduce_fx="sum")
|
||||
|
||||
self.MR_metrics = ["APD", "ADE", "FDE"]
|
||||
|
||||
# All metric
|
||||
self.metrics = self.MR_metrics
|
||||
|
||||
def compute(self, sanity_flag):
|
||||
|
||||
count = self.count
|
||||
count_seq = self.count_seq
|
||||
mr_metrics = {}
|
||||
mr_metrics["APD"] = self.APD / count_seq
|
||||
mr_metrics["ADE"] = self.ADE / count_seq
|
||||
mr_metrics["FDE"] = self.FDE / count_seq
|
||||
|
||||
# Reset
|
||||
self.reset()
|
||||
|
||||
return mr_metrics
|
||||
|
||||
def update(self, joints_rst: Tensor, joints_ref: Tensor,
|
||||
lengths: List[int]):
|
||||
|
||||
assert joints_rst.shape == joints_ref.shape
|
||||
assert joints_rst.dim() == 4
|
||||
# (bs, seq, njoint=22, 3)
|
||||
|
||||
self.count += sum(lengths)
|
||||
self.count_seq += len(lengths)
|
||||
|
||||
rst = torch.flatten(joints_rst, start_dim=2)
|
||||
ref = torch.flatten(joints_ref, start_dim=2)
|
||||
|
||||
for i, l in enumerate(lengths):
|
||||
if self.task == "pred":
|
||||
pred_start = int(l*self.cfg.ABLATION.predict_ratio)
|
||||
diff = rst[i,pred_start:] - ref[i,pred_start:]
|
||||
elif self.task == "inbetween":
|
||||
inbetween_start = int(l*self.cfg.ABLATION.inbetween_ratio)
|
||||
inbetween_end = l - int(l*self.cfg.ABLATION.inbetween_ratio)
|
||||
diff = rst[i,inbetween_start:inbetween_end] - ref[i,inbetween_start:inbetween_end]
|
||||
else:
|
||||
print(f"Task {self.task} not implemented.")
|
||||
diff = rst - ref
|
||||
|
||||
dist = torch.linalg.norm(diff, dim=-1)[None]
|
||||
|
||||
ade = dist.mean(dim=1)
|
||||
fde = dist[:,-1]
|
||||
self.ADE = self.ADE + ade
|
||||
self.FDE = self.FDE + fde
|
||||
@@ -1,345 +0,0 @@
|
||||
from typing import List
|
||||
import os
|
||||
import torch
|
||||
from torch import Tensor
|
||||
from torchmetrics import Metric
|
||||
from .utils import *
|
||||
from bert_score import score as score_bert
|
||||
import spacy
|
||||
from ..config import instantiate_from_config
|
||||
|
||||
class M2TMetrics(Metric):
|
||||
|
||||
def __init__(self,
|
||||
cfg,
|
||||
w_vectorizer,
|
||||
dataname='humanml3d',
|
||||
top_k=3,
|
||||
bleu_k=4,
|
||||
R_size=32,
|
||||
max_text_len=40,
|
||||
diversity_times=300,
|
||||
dist_sync_on_step=True,
|
||||
unit_length=4,
|
||||
**kwargs):
|
||||
super().__init__(dist_sync_on_step=dist_sync_on_step)
|
||||
|
||||
self.cfg = cfg
|
||||
self.dataname = dataname
|
||||
self.w_vectorizer = w_vectorizer
|
||||
self.name = "matching, fid, and diversity scores"
|
||||
# self.text = True if cfg.TRAIN.STAGE in ["diffusion","t2m_gpt"] else False
|
||||
self.max_text_len = max_text_len
|
||||
self.top_k = top_k
|
||||
self.bleu_k = bleu_k
|
||||
self.R_size = R_size
|
||||
self.diversity_times = diversity_times
|
||||
self.unit_length = unit_length
|
||||
|
||||
self.add_state("count", default=torch.tensor(0), dist_reduce_fx="sum")
|
||||
self.add_state("count_seq",
|
||||
default=torch.tensor(0),
|
||||
dist_reduce_fx="sum")
|
||||
|
||||
self.metrics = []
|
||||
|
||||
# Matching scores
|
||||
self.add_state("Matching_score",
|
||||
default=torch.tensor(0.0),
|
||||
dist_reduce_fx="sum")
|
||||
self.add_state("gt_Matching_score",
|
||||
default=torch.tensor(0.0),
|
||||
dist_reduce_fx="sum")
|
||||
self.Matching_metrics = ["Matching_score", "gt_Matching_score"]
|
||||
for k in range(1, top_k + 1):
|
||||
self.add_state(
|
||||
f"R_precision_top_{str(k)}",
|
||||
default=torch.tensor(0.0),
|
||||
dist_reduce_fx="sum",
|
||||
)
|
||||
self.Matching_metrics.append(f"R_precision_top_{str(k)}")
|
||||
for k in range(1, top_k + 1):
|
||||
self.add_state(
|
||||
f"gt_R_precision_top_{str(k)}",
|
||||
default=torch.tensor(0.0),
|
||||
dist_reduce_fx="sum",
|
||||
)
|
||||
self.Matching_metrics.append(f"gt_R_precision_top_{str(k)}")
|
||||
|
||||
self.metrics.extend(self.Matching_metrics)
|
||||
|
||||
# NLG
|
||||
for k in range(1, top_k + 1):
|
||||
self.add_state(
|
||||
f"Bleu_{str(k)}",
|
||||
default=torch.tensor(0.0),
|
||||
dist_reduce_fx="sum",
|
||||
)
|
||||
self.metrics.append(f"Bleu_{str(k)}")
|
||||
|
||||
self.add_state("ROUGE_L",
|
||||
default=torch.tensor(0.0),
|
||||
dist_reduce_fx="sum")
|
||||
self.metrics.append("ROUGE_L")
|
||||
|
||||
self.add_state("CIDEr",
|
||||
default=torch.tensor(0.0),
|
||||
dist_reduce_fx="sum")
|
||||
self.metrics.append("CIDEr")
|
||||
|
||||
# Chached batches
|
||||
self.pred_texts = []
|
||||
self.gt_texts = []
|
||||
self.add_state("predtext_embeddings", default=[])
|
||||
self.add_state("gttext_embeddings", default=[])
|
||||
self.add_state("gtmotion_embeddings", default=[])
|
||||
|
||||
# T2M Evaluator
|
||||
self._get_t2m_evaluator(cfg)
|
||||
|
||||
self.nlp = spacy.load('en_core_web_sm')
|
||||
|
||||
if self.cfg.model.params.task == 'm2t':
|
||||
from nlgmetricverse import NLGMetricverse, load_metric
|
||||
metrics = [
|
||||
load_metric("bleu", resulting_name="bleu_1", compute_kwargs={"max_order": 1}),
|
||||
load_metric("bleu", resulting_name="bleu_4", compute_kwargs={"max_order": 4}),
|
||||
load_metric("rouge"),
|
||||
load_metric("cider"),
|
||||
]
|
||||
self.nlg_evaluator = NLGMetricverse(metrics)
|
||||
|
||||
def _get_t2m_evaluator(self, cfg):
|
||||
"""
|
||||
load T2M text encoder and motion encoder for evaluating
|
||||
"""
|
||||
# init module
|
||||
self.t2m_textencoder = instantiate_from_config(cfg.METRIC.TM2T.t2m_textencoder)
|
||||
self.t2m_moveencoder = instantiate_from_config(cfg.METRIC.TM2T.t2m_moveencoder)
|
||||
self.t2m_motionencoder = instantiate_from_config(cfg.METRIC.TM2T.t2m_motionencoder)
|
||||
|
||||
|
||||
# load pretrianed
|
||||
if self.dataname == "kit":
|
||||
dataname = "kit"
|
||||
else:
|
||||
dataname = "t2m"
|
||||
|
||||
t2m_checkpoint = torch.load(os.path.join(
|
||||
cfg.METRIC.TM2T.t2m_path, dataname, "text_mot_match/model/finest.tar"),
|
||||
map_location='cpu')
|
||||
self.t2m_textencoder.load_state_dict(t2m_checkpoint["text_encoder"])
|
||||
self.t2m_moveencoder.load_state_dict(
|
||||
t2m_checkpoint["movement_encoder"])
|
||||
self.t2m_motionencoder.load_state_dict(
|
||||
t2m_checkpoint["motion_encoder"])
|
||||
|
||||
# freeze params
|
||||
self.t2m_textencoder.eval()
|
||||
self.t2m_moveencoder.eval()
|
||||
self.t2m_motionencoder.eval()
|
||||
for p in self.t2m_textencoder.parameters():
|
||||
p.requires_grad = False
|
||||
for p in self.t2m_moveencoder.parameters():
|
||||
p.requires_grad = False
|
||||
for p in self.t2m_motionencoder.parameters():
|
||||
p.requires_grad = False
|
||||
|
||||
def _process_text(self, sentence):
|
||||
sentence = sentence.replace('-', '')
|
||||
doc = self.nlp(sentence)
|
||||
word_list = []
|
||||
pos_list = []
|
||||
for token in doc:
|
||||
word = token.text
|
||||
if not word.isalpha():
|
||||
continue
|
||||
if (token.pos_ == 'NOUN'
|
||||
or token.pos_ == 'VERB') and (word != 'left'):
|
||||
word_list.append(token.lemma_)
|
||||
else:
|
||||
word_list.append(word)
|
||||
pos_list.append(token.pos_)
|
||||
return word_list, pos_list
|
||||
|
||||
def _get_text_embeddings(self, texts):
|
||||
word_embs = []
|
||||
pos_ohot = []
|
||||
text_lengths = []
|
||||
for i, sentence in enumerate(texts):
|
||||
word_list, pos_list = self._process_text(sentence.strip())
|
||||
t_tokens = [
|
||||
'%s/%s' % (word_list[i], pos_list[i])
|
||||
for i in range(len(word_list))
|
||||
]
|
||||
|
||||
if len(t_tokens) < self.max_text_len:
|
||||
# pad with "unk"
|
||||
tokens = ['sos/OTHER'] + t_tokens + ['eos/OTHER']
|
||||
sent_len = len(tokens)
|
||||
tokens = tokens + ['unk/OTHER'
|
||||
] * (self.max_text_len + 2 - sent_len)
|
||||
else:
|
||||
# crop
|
||||
tokens = t_tokens[:self.max_text_len]
|
||||
tokens = ['sos/OTHER'] + tokens + ['eos/OTHER']
|
||||
sent_len = len(tokens)
|
||||
pos_one_hots = []
|
||||
word_embeddings = []
|
||||
for token in tokens:
|
||||
word_emb, pos_oh = self.w_vectorizer[token]
|
||||
pos_one_hots.append(torch.tensor(pos_oh).float()[None])
|
||||
word_embeddings.append(torch.tensor(word_emb).float()[None])
|
||||
text_lengths.append(sent_len)
|
||||
pos_ohot.append(torch.cat(pos_one_hots, dim=0)[None])
|
||||
word_embs.append(torch.cat(word_embeddings, dim=0)[None])
|
||||
|
||||
word_embs = torch.cat(word_embs, dim=0).to(self.Matching_score)
|
||||
pos_ohot = torch.cat(pos_ohot, dim=0).to(self.Matching_score)
|
||||
text_lengths = torch.tensor(text_lengths).to(self.Matching_score)
|
||||
|
||||
align_idx = np.argsort(text_lengths.data.tolist())[::-1].copy()
|
||||
|
||||
# get text embeddings
|
||||
text_embeddings = self.t2m_textencoder(word_embs[align_idx],
|
||||
pos_ohot[align_idx],
|
||||
text_lengths[align_idx])
|
||||
|
||||
original_text_embeddings = text_embeddings.clone()
|
||||
|
||||
for idx, sort in enumerate(align_idx):
|
||||
original_text_embeddings[sort] = text_embeddings[idx]
|
||||
|
||||
return original_text_embeddings
|
||||
|
||||
@torch.no_grad()
|
||||
def compute(self, sanity_flag):
|
||||
count = self.count.item()
|
||||
count_seq = self.count_seq.item()
|
||||
|
||||
# Init metrics dict
|
||||
metrics = {metric: getattr(self, metric) for metric in self.metrics}
|
||||
|
||||
# Jump in sanity check stage
|
||||
if sanity_flag:
|
||||
return metrics
|
||||
|
||||
# Cat cached batches and shuffle
|
||||
shuffle_idx = torch.randperm(count_seq)
|
||||
all_motions = torch.cat(self.gtmotion_embeddings,
|
||||
axis=0).cpu()[shuffle_idx, :]
|
||||
all_gttexts = torch.cat(self.gttext_embeddings,
|
||||
axis=0).cpu()[shuffle_idx, :]
|
||||
all_predtexts = torch.cat(self.predtext_embeddings,
|
||||
axis=0).cpu()[shuffle_idx, :]
|
||||
|
||||
print("Computing metrics...")
|
||||
|
||||
# Compute r-precision
|
||||
assert count_seq >= self.R_size
|
||||
top_k_mat = torch.zeros((self.top_k, ))
|
||||
for i in range(count_seq // self.R_size):
|
||||
# [bs=32, 1*256]
|
||||
group_texts = all_predtexts[i * self.R_size:(i + 1) * self.R_size]
|
||||
# [bs=32, 1*256]
|
||||
group_motions = all_motions[i * self.R_size:(i + 1) * self.R_size]
|
||||
# [bs=32, 32]
|
||||
dist_mat = euclidean_distance_matrix(group_texts,
|
||||
group_motions).nan_to_num()
|
||||
# print(dist_mat[:5])
|
||||
self.Matching_score += dist_mat.trace()
|
||||
argsmax = torch.argsort(dist_mat, dim=1)
|
||||
top_k_mat += calculate_top_k(argsmax, top_k=self.top_k).sum(axis=0)
|
||||
|
||||
R_count = count_seq // self.R_size * self.R_size
|
||||
metrics["Matching_score"] = self.Matching_score / R_count
|
||||
for k in range(self.top_k):
|
||||
metrics[f"R_precision_top_{str(k+1)}"] = top_k_mat[k] / R_count
|
||||
|
||||
# Compute r-precision with gt
|
||||
assert count_seq >= self.R_size
|
||||
top_k_mat = torch.zeros((self.top_k, ))
|
||||
for i in range(count_seq // self.R_size):
|
||||
# [bs=32, 1*256]
|
||||
group_texts = all_gttexts[i * self.R_size:(i + 1) * self.R_size]
|
||||
# [bs=32, 1*256]
|
||||
group_motions = all_motions[i * self.R_size:(i + 1) * self.R_size]
|
||||
# [bs=32, 32]
|
||||
dist_mat = euclidean_distance_matrix(group_texts,
|
||||
group_motions).nan_to_num()
|
||||
# match score
|
||||
self.gt_Matching_score += dist_mat.trace()
|
||||
argsmax = torch.argsort(dist_mat, dim=1)
|
||||
top_k_mat += calculate_top_k(argsmax, top_k=self.top_k).sum(axis=0)
|
||||
metrics["gt_Matching_score"] = self.gt_Matching_score / R_count
|
||||
for k in range(self.top_k):
|
||||
metrics[f"gt_R_precision_top_{str(k+1)}"] = top_k_mat[k] / R_count
|
||||
|
||||
# NLP metrics
|
||||
scores = self.nlg_evaluator(predictions=self.pred_texts,
|
||||
references=self.gt_texts)
|
||||
for k in range(1, self.bleu_k + 1):
|
||||
metrics[f"Bleu_{str(k)}"] = torch.tensor(scores[f'bleu_{str(k)}'],
|
||||
device=self.device)
|
||||
|
||||
metrics["ROUGE_L"] = torch.tensor(scores["rouge"]["rougeL"],
|
||||
device=self.device)
|
||||
metrics["CIDEr"] = torch.tensor(scores["cider"]['score'],device=self.device)
|
||||
|
||||
# Bert metrics
|
||||
P, R, F1 = score_bert(self.pred_texts,
|
||||
self.gt_texts,
|
||||
lang='en',
|
||||
rescale_with_baseline=True,
|
||||
idf=True,
|
||||
device=self.device,
|
||||
verbose=False)
|
||||
|
||||
metrics["Bert_F1"] = F1.mean()
|
||||
|
||||
# Reset
|
||||
self.reset()
|
||||
self.gt_texts = []
|
||||
self.pred_texts = []
|
||||
|
||||
return {**metrics}
|
||||
|
||||
@torch.no_grad()
|
||||
def update(self,
|
||||
feats_ref: Tensor,
|
||||
pred_texts: List[str],
|
||||
gt_texts: List[str],
|
||||
lengths: List[int],
|
||||
word_embs: Tensor = None,
|
||||
pos_ohot: Tensor = None,
|
||||
text_lengths: Tensor = None):
|
||||
|
||||
self.count += sum(lengths)
|
||||
self.count_seq += len(lengths)
|
||||
|
||||
# motion encoder
|
||||
m_lens = torch.tensor(lengths, device=feats_ref.device)
|
||||
align_idx = np.argsort(m_lens.data.tolist())[::-1].copy()
|
||||
feats_ref = feats_ref[align_idx]
|
||||
m_lens = m_lens[align_idx]
|
||||
m_lens = torch.div(m_lens,
|
||||
self.cfg.DATASET.HUMANML3D.UNIT_LEN,
|
||||
rounding_mode="floor")
|
||||
ref_mov = self.t2m_moveencoder(feats_ref[..., :-4]).detach()
|
||||
m_lens = m_lens // self.unit_length
|
||||
ref_emb = self.t2m_motionencoder(ref_mov, m_lens)
|
||||
gtmotion_embeddings = torch.flatten(ref_emb, start_dim=1).detach()
|
||||
self.gtmotion_embeddings.append(gtmotion_embeddings)
|
||||
|
||||
# text encoder
|
||||
gttext_emb = self.t2m_textencoder(word_embs, pos_ohot,
|
||||
text_lengths)[align_idx]
|
||||
gttext_embeddings = torch.flatten(gttext_emb, start_dim=1).detach()
|
||||
predtext_emb = self._get_text_embeddings(pred_texts)[align_idx]
|
||||
predtext_embeddings = torch.flatten(predtext_emb, start_dim=1).detach()
|
||||
|
||||
self.gttext_embeddings.append(gttext_embeddings)
|
||||
self.predtext_embeddings.append(predtext_embeddings)
|
||||
|
||||
self.pred_texts.extend(pred_texts)
|
||||
self.gt_texts.extend(gt_texts)
|
||||
@@ -1,129 +0,0 @@
|
||||
from typing import List
|
||||
|
||||
import torch
|
||||
from torch import Tensor
|
||||
from torchmetrics import Metric
|
||||
from torchmetrics.functional import pairwise_euclidean_distance
|
||||
from .utils import *
|
||||
import os
|
||||
from ..config import instantiate_from_config
|
||||
|
||||
class MMMetrics(Metric):
|
||||
full_state_update = True
|
||||
|
||||
def __init__(self, cfg, dataname='humanml3d', mm_num_times=10, dist_sync_on_step=True, **kwargs):
|
||||
super().__init__(dist_sync_on_step=dist_sync_on_step)
|
||||
|
||||
self.name = "MultiModality scores"
|
||||
self.cfg = cfg
|
||||
self.dataname = dataname
|
||||
self.mm_num_times = mm_num_times
|
||||
|
||||
self.add_state("count", default=torch.tensor(0), dist_reduce_fx="sum")
|
||||
self.add_state("count_seq",
|
||||
default=torch.tensor(0),
|
||||
dist_reduce_fx="sum")
|
||||
|
||||
self.metrics = ["MultiModality"]
|
||||
self.add_state("MultiModality",
|
||||
default=torch.tensor(0.),
|
||||
dist_reduce_fx="sum")
|
||||
|
||||
# chached batches
|
||||
self.add_state("mm_motion_embeddings", default=[], dist_reduce_fx=None)
|
||||
|
||||
# T2M Evaluator
|
||||
self._get_t2m_evaluator(cfg)
|
||||
|
||||
def _get_t2m_evaluator(self, cfg):
|
||||
"""
|
||||
load T2M text encoder and motion encoder for evaluating
|
||||
"""
|
||||
# init module
|
||||
self.t2m_textencoder = instantiate_from_config(cfg.METRIC.TM2T.t2m_textencoder)
|
||||
self.t2m_moveencoder = instantiate_from_config(cfg.METRIC.TM2T.t2m_moveencoder)
|
||||
self.t2m_motionencoder = instantiate_from_config(cfg.METRIC.TM2T.t2m_motionencoder)
|
||||
|
||||
# load pretrianed
|
||||
if self.dataname == "kit":
|
||||
dataname = "kit"
|
||||
else:
|
||||
dataname = "t2m"
|
||||
t2m_checkpoint = torch.load(os.path.join(
|
||||
cfg.METRIC.TM2T.t2m_path, dataname,
|
||||
"text_mot_match/model/finest.tar"),
|
||||
map_location="cpu")
|
||||
|
||||
self.t2m_textencoder.load_state_dict(t2m_checkpoint["text_encoder"])
|
||||
self.t2m_moveencoder.load_state_dict(
|
||||
t2m_checkpoint["movement_encoder"])
|
||||
self.t2m_motionencoder.load_state_dict(
|
||||
t2m_checkpoint["motion_encoder"])
|
||||
|
||||
# freeze params
|
||||
self.t2m_textencoder.eval()
|
||||
self.t2m_moveencoder.eval()
|
||||
self.t2m_motionencoder.eval()
|
||||
for p in self.t2m_textencoder.parameters():
|
||||
p.requires_grad = False
|
||||
for p in self.t2m_moveencoder.parameters():
|
||||
p.requires_grad = False
|
||||
for p in self.t2m_motionencoder.parameters():
|
||||
p.requires_grad = False
|
||||
|
||||
def compute(self, sanity_flag):
|
||||
count = self.count.item()
|
||||
count_seq = self.count_seq.item()
|
||||
|
||||
# init metrics
|
||||
metrics = {metric: getattr(self, metric) for metric in self.metrics}
|
||||
|
||||
# if in sanity check stage then jump
|
||||
if sanity_flag:
|
||||
return metrics
|
||||
|
||||
# cat all embeddings
|
||||
all_mm_motions = torch.cat(self.mm_motion_embeddings,
|
||||
axis=0).cpu().numpy()
|
||||
metrics['MultiModality'] = calculate_multimodality_np(
|
||||
all_mm_motions, self.mm_num_times)
|
||||
|
||||
# Reset
|
||||
self.reset()
|
||||
|
||||
return {**metrics}
|
||||
|
||||
def update(
|
||||
self,
|
||||
feats_rst: Tensor,
|
||||
lengths_rst: List[int],
|
||||
):
|
||||
self.count += sum(lengths_rst)
|
||||
self.count_seq += len(lengths_rst)
|
||||
|
||||
align_idx = np.argsort(lengths_rst)[::-1].copy()
|
||||
feats_rst = feats_rst[align_idx]
|
||||
lengths_rst = np.array(lengths_rst)[align_idx]
|
||||
recmotion_embeddings = self.get_motion_embeddings(
|
||||
feats_rst, lengths_rst)
|
||||
cache = [0] * len(lengths_rst)
|
||||
for i in range(len(lengths_rst)):
|
||||
cache[align_idx[i]] = recmotion_embeddings[i:i + 1]
|
||||
|
||||
mm_motion_embeddings = torch.cat(cache, axis=0).unsqueeze(0)
|
||||
# self.mm_motion_embeddings.extend(cache)
|
||||
# print(mm_motion_embeddings.shape)
|
||||
# # store all mm motion embeddings
|
||||
self.mm_motion_embeddings.append(mm_motion_embeddings)
|
||||
|
||||
def get_motion_embeddings(self, feats: Tensor, lengths: List[int]):
|
||||
m_lens = torch.tensor(lengths)
|
||||
m_lens = torch.div(m_lens,
|
||||
self.cfg.DATASET.HUMANML3D.UNIT_LEN,
|
||||
rounding_mode="floor")
|
||||
|
||||
mov = self.t2m_moveencoder(feats[..., :-4]).detach()
|
||||
emb = self.t2m_motionencoder(mov, m_lens)
|
||||
|
||||
# [bs, nlatent*ndim] <= [bs, nlatent, ndim]
|
||||
return torch.flatten(emb, start_dim=1).detach()
|
||||
@@ -1,97 +0,0 @@
|
||||
from typing import List
|
||||
|
||||
import torch
|
||||
from torch import Tensor
|
||||
from torchmetrics import Metric
|
||||
|
||||
from .utils import *
|
||||
|
||||
|
||||
# motion reconstruction metric
|
||||
class MRMetrics(Metric):
|
||||
|
||||
def __init__(self,
|
||||
njoints,
|
||||
jointstype: str = "mmm",
|
||||
force_in_meter: bool = True,
|
||||
align_root: bool = True,
|
||||
dist_sync_on_step=True,
|
||||
**kwargs):
|
||||
super().__init__(dist_sync_on_step=dist_sync_on_step)
|
||||
|
||||
self.name = 'Motion Reconstructions'
|
||||
self.jointstype = jointstype
|
||||
self.align_root = align_root
|
||||
self.force_in_meter = force_in_meter
|
||||
|
||||
self.add_state("count", default=torch.tensor(0), dist_reduce_fx="sum")
|
||||
self.add_state("count_seq",
|
||||
default=torch.tensor(0),
|
||||
dist_reduce_fx="sum")
|
||||
|
||||
self.add_state("MPJPE",
|
||||
default=torch.tensor([0.0]),
|
||||
dist_reduce_fx="sum")
|
||||
self.add_state("PAMPJPE",
|
||||
default=torch.tensor([0.0]),
|
||||
dist_reduce_fx="sum")
|
||||
self.add_state("ACCEL",
|
||||
default=torch.tensor([0.0]),
|
||||
dist_reduce_fx="sum")
|
||||
# todo
|
||||
# self.add_state("ROOT", default=torch.tensor([0.0]), dist_reduce_fx="sum")
|
||||
|
||||
self.MR_metrics = ["MPJPE", "PAMPJPE", "ACCEL"]
|
||||
|
||||
# All metric
|
||||
self.metrics = self.MR_metrics
|
||||
|
||||
def compute(self, sanity_flag):
|
||||
if self.force_in_meter:
|
||||
# different jointstypes have different scale factors
|
||||
# if self.jointstype == 'mmm':
|
||||
# factor = 1000.0
|
||||
# elif self.jointstype == 'humanml3d':
|
||||
# factor = 1000.0 * 0.75 / 480
|
||||
factor = 1000.0
|
||||
else:
|
||||
factor = 1.0
|
||||
|
||||
count = self.count
|
||||
count_seq = self.count_seq
|
||||
mr_metrics = {}
|
||||
mr_metrics["MPJPE"] = self.MPJPE / count * factor
|
||||
mr_metrics["PAMPJPE"] = self.PAMPJPE / count * factor
|
||||
# accel error: joints_gt[:-2] - 2 * joints_gt[1:-1] + joints_gt[2:]
|
||||
# n-2 for each sequences
|
||||
mr_metrics["ACCEL"] = self.ACCEL / (count - 2 * count_seq) * factor
|
||||
|
||||
# Reset
|
||||
self.reset()
|
||||
|
||||
return mr_metrics
|
||||
|
||||
def update(self, joints_rst: Tensor, joints_ref: Tensor,
|
||||
lengths: List[int]):
|
||||
assert joints_rst.shape == joints_ref.shape
|
||||
assert joints_rst.dim() == 4
|
||||
# (bs, seq, njoint=22, 3)
|
||||
|
||||
self.count += sum(lengths)
|
||||
self.count_seq += len(lengths)
|
||||
|
||||
# avoid cuda error of DDP in pampjpe
|
||||
rst = joints_rst.detach().cpu()
|
||||
ref = joints_ref.detach().cpu()
|
||||
|
||||
# align root joints index
|
||||
if self.align_root and self.jointstype in ['mmm', 'humanml3d']:
|
||||
align_inds = [0]
|
||||
else:
|
||||
align_inds = None
|
||||
|
||||
for i in range(len(lengths)):
|
||||
self.MPJPE += torch.sum(
|
||||
calc_mpjpe(rst[i], ref[i], align_inds=align_inds))
|
||||
self.PAMPJPE += torch.sum(calc_pampjpe(rst[i], ref[i]))
|
||||
self.ACCEL += torch.sum(calc_accel(rst[i], ref[i]))
|
||||
@@ -1,259 +0,0 @@
|
||||
from typing import List
|
||||
import os
|
||||
import torch
|
||||
from torch import Tensor
|
||||
from torchmetrics import Metric
|
||||
from torchmetrics.functional import pairwise_euclidean_distance
|
||||
from .utils import *
|
||||
from ..config import instantiate_from_config
|
||||
|
||||
class TM2TMetrics(Metric):
|
||||
def __init__(self,
|
||||
cfg,
|
||||
dataname='humanml3d',
|
||||
top_k=3,
|
||||
R_size=32,
|
||||
diversity_times=300,
|
||||
dist_sync_on_step=True,
|
||||
**kwargs):
|
||||
super().__init__(dist_sync_on_step=dist_sync_on_step)
|
||||
|
||||
self.cfg = cfg
|
||||
self.dataname = dataname
|
||||
self.name = "matching, fid, and diversity scores"
|
||||
self.top_k = top_k
|
||||
self.R_size = R_size
|
||||
self.text = 'lm' in cfg.TRAIN.STAGE and cfg.model.params.task == 't2m'
|
||||
self.diversity_times = diversity_times
|
||||
|
||||
self.add_state("count", default=torch.tensor(0), dist_reduce_fx="sum")
|
||||
self.add_state("count_seq",
|
||||
default=torch.tensor(0),
|
||||
dist_reduce_fx="sum")
|
||||
|
||||
self.metrics = []
|
||||
|
||||
# Matching scores
|
||||
if self.text:
|
||||
self.add_state("Matching_score",
|
||||
default=torch.tensor(0.0),
|
||||
dist_reduce_fx="sum")
|
||||
self.add_state("gt_Matching_score",
|
||||
default=torch.tensor(0.0),
|
||||
dist_reduce_fx="sum")
|
||||
self.Matching_metrics = ["Matching_score", "gt_Matching_score"]
|
||||
for k in range(1, top_k + 1):
|
||||
self.add_state(
|
||||
f"R_precision_top_{str(k)}",
|
||||
default=torch.tensor(0.0),
|
||||
dist_reduce_fx="sum",
|
||||
)
|
||||
self.Matching_metrics.append(f"R_precision_top_{str(k)}")
|
||||
for k in range(1, top_k + 1):
|
||||
self.add_state(
|
||||
f"gt_R_precision_top_{str(k)}",
|
||||
default=torch.tensor(0.0),
|
||||
dist_reduce_fx="sum",
|
||||
)
|
||||
self.Matching_metrics.append(f"gt_R_precision_top_{str(k)}")
|
||||
self.metrics.extend(self.Matching_metrics)
|
||||
|
||||
# Fid
|
||||
self.add_state("FID", default=torch.tensor(0.0), dist_reduce_fx="sum")
|
||||
self.metrics.append("FID")
|
||||
|
||||
# Diversity
|
||||
self.add_state("Diversity",
|
||||
default=torch.tensor(0.0),
|
||||
dist_reduce_fx="sum")
|
||||
self.add_state("gt_Diversity",
|
||||
default=torch.tensor(0.0),
|
||||
dist_reduce_fx="sum")
|
||||
self.metrics.extend(["Diversity", "gt_Diversity"])
|
||||
|
||||
# Chached batches
|
||||
self.add_state("text_embeddings", default=[], dist_reduce_fx=None)
|
||||
self.add_state("recmotion_embeddings", default=[], dist_reduce_fx=None)
|
||||
self.add_state("gtmotion_embeddings", default=[], dist_reduce_fx=None)
|
||||
|
||||
# T2M Evaluator
|
||||
self._get_t2m_evaluator(cfg)
|
||||
|
||||
def _get_t2m_evaluator(self, cfg):
|
||||
"""
|
||||
load T2M text encoder and motion encoder for evaluating
|
||||
"""
|
||||
# init module
|
||||
self.t2m_textencoder = instantiate_from_config(cfg.METRIC.TM2T.t2m_textencoder)
|
||||
self.t2m_moveencoder = instantiate_from_config(cfg.METRIC.TM2T.t2m_moveencoder)
|
||||
self.t2m_motionencoder = instantiate_from_config(cfg.METRIC.TM2T.t2m_motionencoder)
|
||||
|
||||
|
||||
# load pretrianed
|
||||
if self.dataname == "kit":
|
||||
dataname = "kit"
|
||||
else:
|
||||
dataname = "t2m"
|
||||
|
||||
t2m_checkpoint = torch.load(os.path.join(
|
||||
cfg.METRIC.TM2T.t2m_path, dataname, "text_mot_match/model/finest.tar"),
|
||||
map_location="cpu")
|
||||
|
||||
self.t2m_textencoder.load_state_dict(t2m_checkpoint["text_encoder"])
|
||||
self.t2m_moveencoder.load_state_dict(
|
||||
t2m_checkpoint["movement_encoder"])
|
||||
self.t2m_motionencoder.load_state_dict(
|
||||
t2m_checkpoint["motion_encoder"])
|
||||
|
||||
# freeze params
|
||||
self.t2m_textencoder.eval()
|
||||
self.t2m_moveencoder.eval()
|
||||
self.t2m_motionencoder.eval()
|
||||
for p in self.t2m_textencoder.parameters():
|
||||
p.requires_grad = False
|
||||
for p in self.t2m_moveencoder.parameters():
|
||||
p.requires_grad = False
|
||||
for p in self.t2m_motionencoder.parameters():
|
||||
p.requires_grad = False
|
||||
|
||||
@torch.no_grad()
|
||||
def compute(self, sanity_flag):
|
||||
count = self.count.item()
|
||||
count_seq = self.count_seq.item()
|
||||
|
||||
# Init metrics dict
|
||||
metrics = {metric: getattr(self, metric) for metric in self.metrics}
|
||||
|
||||
# Jump in sanity check stage
|
||||
if sanity_flag:
|
||||
return metrics
|
||||
|
||||
# Cat cached batches and shuffle
|
||||
shuffle_idx = torch.randperm(count_seq)
|
||||
|
||||
all_genmotions = torch.cat(self.recmotion_embeddings,
|
||||
axis=0).cpu()[shuffle_idx, :]
|
||||
all_gtmotions = torch.cat(self.gtmotion_embeddings,
|
||||
axis=0).cpu()[shuffle_idx, :]
|
||||
|
||||
# Compute text related metrics
|
||||
if self.text:
|
||||
all_texts = torch.cat(self.text_embeddings,
|
||||
axis=0).cpu()[shuffle_idx, :]
|
||||
# Compute r-precision
|
||||
assert count_seq > self.R_size
|
||||
top_k_mat = torch.zeros((self.top_k, ))
|
||||
for i in range(count_seq // self.R_size):
|
||||
# [bs=32, 1*256]
|
||||
group_texts = all_texts[i * self.R_size:(i + 1) * self.R_size]
|
||||
# [bs=32, 1*256]
|
||||
group_motions = all_genmotions[i * self.R_size:(i + 1) *
|
||||
self.R_size]
|
||||
# dist_mat = pairwise_euclidean_distance(group_texts, group_motions)
|
||||
# [bs=32, 32]
|
||||
dist_mat = euclidean_distance_matrix(
|
||||
group_texts, group_motions).nan_to_num()
|
||||
# print(dist_mat[:5])
|
||||
self.Matching_score += dist_mat.trace()
|
||||
argsmax = torch.argsort(dist_mat, dim=1)
|
||||
top_k_mat += calculate_top_k(argsmax,
|
||||
top_k=self.top_k).sum(axis=0)
|
||||
|
||||
R_count = count_seq // self.R_size * self.R_size
|
||||
metrics["Matching_score"] = self.Matching_score / R_count
|
||||
for k in range(self.top_k):
|
||||
metrics[f"R_precision_top_{str(k+1)}"] = top_k_mat[k] / R_count
|
||||
|
||||
# Compute r-precision with gt
|
||||
assert count_seq > self.R_size
|
||||
top_k_mat = torch.zeros((self.top_k, ))
|
||||
for i in range(count_seq // self.R_size):
|
||||
# [bs=32, 1*256]
|
||||
group_texts = all_texts[i * self.R_size:(i + 1) * self.R_size]
|
||||
# [bs=32, 1*256]
|
||||
group_motions = all_gtmotions[i * self.R_size:(i + 1) *
|
||||
self.R_size]
|
||||
# [bs=32, 32]
|
||||
dist_mat = euclidean_distance_matrix(
|
||||
group_texts, group_motions).nan_to_num()
|
||||
# match score
|
||||
self.gt_Matching_score += dist_mat.trace()
|
||||
argsmax = torch.argsort(dist_mat, dim=1)
|
||||
top_k_mat += calculate_top_k(argsmax,
|
||||
top_k=self.top_k).sum(axis=0)
|
||||
metrics["gt_Matching_score"] = self.gt_Matching_score / R_count
|
||||
for k in range(self.top_k):
|
||||
metrics[f"gt_R_precision_top_{str(k+1)}"] = top_k_mat[k] / R_count
|
||||
|
||||
# tensor -> numpy for FID
|
||||
all_genmotions = all_genmotions.numpy()
|
||||
all_gtmotions = all_gtmotions.numpy()
|
||||
|
||||
# Compute fid
|
||||
mu, cov = calculate_activation_statistics_np(all_genmotions)
|
||||
gt_mu, gt_cov = calculate_activation_statistics_np(all_gtmotions)
|
||||
metrics["FID"] = calculate_frechet_distance_np(gt_mu, gt_cov, mu, cov)
|
||||
|
||||
# Compute diversity
|
||||
assert count_seq > self.diversity_times
|
||||
metrics["Diversity"] = calculate_diversity_np(all_genmotions,
|
||||
self.diversity_times)
|
||||
metrics["gt_Diversity"] = calculate_diversity_np(
|
||||
all_gtmotions, self.diversity_times)
|
||||
|
||||
# Reset
|
||||
self.reset()
|
||||
|
||||
return {**metrics}
|
||||
|
||||
@torch.no_grad()
|
||||
def update(self,
|
||||
feats_ref: Tensor,
|
||||
feats_rst: Tensor,
|
||||
lengths_ref: List[int],
|
||||
lengths_rst: List[int],
|
||||
word_embs: Tensor = None,
|
||||
pos_ohot: Tensor = None,
|
||||
text_lengths: Tensor = None):
|
||||
|
||||
self.count += sum(lengths_ref)
|
||||
self.count_seq += len(lengths_ref)
|
||||
|
||||
# T2m motion encoder
|
||||
align_idx = np.argsort(lengths_ref)[::-1].copy()
|
||||
feats_ref = feats_ref[align_idx]
|
||||
lengths_ref = np.array(lengths_ref)[align_idx]
|
||||
gtmotion_embeddings = self.get_motion_embeddings(
|
||||
feats_ref, lengths_ref)
|
||||
cache = [0] * len(lengths_ref)
|
||||
for i in range(len(lengths_ref)):
|
||||
cache[align_idx[i]] = gtmotion_embeddings[i:i + 1]
|
||||
self.gtmotion_embeddings.extend(cache)
|
||||
|
||||
align_idx = np.argsort(lengths_rst)[::-1].copy()
|
||||
feats_rst = feats_rst[align_idx]
|
||||
lengths_rst = np.array(lengths_rst)[align_idx]
|
||||
recmotion_embeddings = self.get_motion_embeddings(
|
||||
feats_rst, lengths_rst)
|
||||
cache = [0] * len(lengths_rst)
|
||||
for i in range(len(lengths_rst)):
|
||||
cache[align_idx[i]] = recmotion_embeddings[i:i + 1]
|
||||
self.recmotion_embeddings.extend(cache)
|
||||
|
||||
# T2m text encoder
|
||||
if self.text:
|
||||
text_emb = self.t2m_textencoder(word_embs, pos_ohot, text_lengths)
|
||||
text_embeddings = torch.flatten(text_emb, start_dim=1).detach()
|
||||
self.text_embeddings.append(text_embeddings)
|
||||
|
||||
def get_motion_embeddings(self, feats: Tensor, lengths: List[int]):
|
||||
m_lens = torch.tensor(lengths)
|
||||
m_lens = torch.div(m_lens,
|
||||
self.cfg.DATASET.HUMANML3D.UNIT_LEN,
|
||||
rounding_mode="floor")
|
||||
m_lens = m_lens // self.cfg.DATASET.HUMANML3D.UNIT_LEN
|
||||
mov = self.t2m_moveencoder(feats[..., :-4]).detach()
|
||||
emb = self.t2m_motionencoder(mov, m_lens)
|
||||
|
||||
# [bs, nlatent*ndim] <= [bs, nlatent, ndim]
|
||||
return torch.flatten(emb, start_dim=1).detach()
|
||||
@@ -1,607 +0,0 @@
|
||||
import numpy as np
|
||||
import scipy.linalg
|
||||
import torch
|
||||
from torch import linalg
|
||||
import sys
|
||||
|
||||
|
||||
def l2_norm(x1, x2, dim):
|
||||
return torch.linalg.vector_norm(x1 - x2, ord=2, dim=dim)
|
||||
|
||||
|
||||
def variance(x, T, dim):
|
||||
mean = x.mean(dim)
|
||||
out = (x - mean)**2
|
||||
out = out.sum(dim)
|
||||
return out / (T - 1)
|
||||
|
||||
|
||||
def sqrtm(input):
|
||||
m = input.detach().cpu().numpy().astype(np.float64_)
|
||||
sqrtm = torch.from_numpy(scipy.linalg.sqrtm(m)).to(input)
|
||||
return sqrtm
|
||||
|
||||
|
||||
# (X - X_train)*(X - X_train) = -2X*X_train + X*X + X_train*X_train
|
||||
def euclidean_distance_matrix(matrix1, matrix2):
|
||||
"""
|
||||
Params:
|
||||
-- matrix1: N1 x D
|
||||
-- matrix2: N2 x D
|
||||
Returns:
|
||||
-- dist: N1 x N2
|
||||
dist[i, j] == distance(matrix1[i], matrix2[j])
|
||||
"""
|
||||
assert matrix1.shape[1] == matrix2.shape[1]
|
||||
d1 = -2 * torch.mm(matrix1, matrix2.T) # shape (num_test, num_train)
|
||||
d2 = torch.sum(torch.square(matrix1), axis=1,
|
||||
keepdims=True) # shape (num_test, 1)
|
||||
d3 = torch.sum(torch.square(matrix2), axis=1) # shape (num_train, )
|
||||
dists = torch.sqrt(d1 + d2 + d3) # broadcasting
|
||||
return dists
|
||||
|
||||
|
||||
def euclidean_distance_matrix_np(matrix1, matrix2):
|
||||
"""
|
||||
Params:
|
||||
-- matrix1: N1 x D
|
||||
-- matrix2: N2 x D
|
||||
Returns:
|
||||
-- dist: N1 x N2
|
||||
dist[i, j] == distance(matrix1[i], matrix2[j])
|
||||
"""
|
||||
assert matrix1.shape[1] == matrix2.shape[1]
|
||||
d1 = -2 * np.dot(matrix1, matrix2.T) # shape (num_test, num_train)
|
||||
d2 = np.sum(np.square(matrix1), axis=1,
|
||||
keepdims=True) # shape (num_test, 1)
|
||||
d3 = np.sum(np.square(matrix2), axis=1) # shape (num_train, )
|
||||
dists = np.sqrt(d1 + d2 + d3) # broadcasting
|
||||
return dists
|
||||
|
||||
|
||||
def calculate_top_k(mat, top_k):
|
||||
size = mat.shape[0]
|
||||
gt_mat = (torch.unsqueeze(torch.arange(size),
|
||||
1).to(mat.device).repeat_interleave(size, 1))
|
||||
bool_mat = mat == gt_mat
|
||||
correct_vec = False
|
||||
top_k_list = []
|
||||
for i in range(top_k):
|
||||
# print(correct_vec, bool_mat[:, i])
|
||||
correct_vec = correct_vec | bool_mat[:, i]
|
||||
# print(correct_vec)
|
||||
top_k_list.append(correct_vec[:, None])
|
||||
top_k_mat = torch.cat(top_k_list, dim=1)
|
||||
return top_k_mat
|
||||
|
||||
|
||||
def calculate_activation_statistics(activations):
|
||||
"""
|
||||
Params:
|
||||
-- activation: num_samples x dim_feat
|
||||
Returns:
|
||||
-- mu: dim_feat
|
||||
-- sigma: dim_feat x dim_feat
|
||||
"""
|
||||
activations = activations.cpu().numpy()
|
||||
mu = np.mean(activations, axis=0)
|
||||
sigma = np.cov(activations, rowvar=False)
|
||||
return mu, sigma
|
||||
|
||||
|
||||
def calculate_activation_statistics_np(activations):
|
||||
"""
|
||||
Params:
|
||||
-- activation: num_samples x dim_feat
|
||||
Returns:
|
||||
-- mu: dim_feat
|
||||
-- sigma: dim_feat x dim_feat
|
||||
"""
|
||||
mu = np.mean(activations, axis=0)
|
||||
cov = np.cov(activations, rowvar=False)
|
||||
return mu, cov
|
||||
|
||||
|
||||
# def calculate_frechet_distance(mu1, sigma1, mu2, sigma2, eps=1e-6):
|
||||
# """Numpy implementation of the Frechet Distance.
|
||||
# The Frechet distance between two multivariate Gaussians X_1 ~ N(mu_1, C_1)
|
||||
# and X_2 ~ N(mu_2, C_2) is
|
||||
# d^2 = ||mu_1 - mu_2||^2 + Tr(C_1 + C_2 - 2*sqrt(C_1*C_2)).
|
||||
# Stable version by Dougal J. Sutherland.
|
||||
# Params:
|
||||
# -- mu1 : Numpy array containing the activations of a layer of the
|
||||
# inception net (like returned by the function 'get_predictions')
|
||||
# for generated samples.
|
||||
# -- mu2 : The sample mean over activations, precalculated on an
|
||||
# representative data set.
|
||||
# -- sigma1: The covariance matrix over activations for generated samples.
|
||||
# -- sigma2: The covariance matrix over activations, precalculated on an
|
||||
# representative data set.
|
||||
# Returns:
|
||||
# -- : The Frechet Distance.
|
||||
# """
|
||||
|
||||
# mu1 = torch.atleast_1d(mu1)
|
||||
# mu2 = torch.atleast_1d(mu2)
|
||||
|
||||
# sigma1 = torch.atleast_2d(sigma1)
|
||||
# sigma2 = torch.atleast_2d(sigma2)
|
||||
|
||||
# assert mu1.shape == mu2.shape, \
|
||||
# 'Training and test mean vectors have different lengths'
|
||||
# assert sigma1.shape == sigma2.shape, \
|
||||
# 'Training and test covariances have different dimensions'
|
||||
|
||||
# diff = mu1 - mu2
|
||||
|
||||
# # Product might be almost singular
|
||||
# # covmean, _ = sqrtm(sigma1.dot(sigma2), disp=False)
|
||||
# covmean = sqrtm(torch.mm(sigma1,sigma2))
|
||||
# if not torch.isfinite(covmean).all():
|
||||
# msg = ('fid calculation produces singular product; '
|
||||
# 'adding %s to diagonal of cov estimates') % eps
|
||||
# print(msg)
|
||||
# offset = torch.eye(sigma1.shape[0]) * eps
|
||||
# # covmean = sqrtm((sigma1 + offset).dot(sigma2 + offset))
|
||||
# covmean = sqrtm(torch.mm(sigma1+ offset,sigma2+ offset))
|
||||
|
||||
# # Numerical error might give slight imaginary component
|
||||
# if torch.is_complex(covmean):
|
||||
# if not torch.allclose(torch.diagonal(covmean).imag, 0, atol=1e-3):
|
||||
# m = torch.max(torch.abs(covmean.imag))
|
||||
# raise ValueError('Imaginary component {}'.format(m))
|
||||
# covmean = covmean.real
|
||||
|
||||
# tr_covmean = torch.trace(covmean)
|
||||
|
||||
# return (diff.dot(diff) + torch.trace(sigma1) +
|
||||
# torch.trace(sigma2) - 2 * tr_covmean)
|
||||
|
||||
|
||||
def calculate_frechet_distance_np(mu1, sigma1, mu2, sigma2, eps=1e-6):
|
||||
"""Numpy implementation of the Frechet Distance.
|
||||
The Frechet distance between two multivariate Gaussians X_1 ~ N(mu_1, C_1)
|
||||
and X_2 ~ N(mu_2, C_2) is
|
||||
d^2 = ||mu_1 - mu_2||^2 + Tr(C_1 + C_2 - 2*sqrt(C_1*C_2)).
|
||||
Stable version by Dougal J. Sutherland.
|
||||
Params:
|
||||
-- mu1 : Numpy array containing the activations of a layer of the
|
||||
inception net (like returned by the function 'get_predictions')
|
||||
for generated samples.
|
||||
-- mu2 : The sample mean over activations, precalculated on an
|
||||
representative data set.
|
||||
-- sigma1: The covariance matrix over activations for generated samples.
|
||||
-- sigma2: The covariance matrix over activations, precalculated on an
|
||||
representative data set.
|
||||
Returns:
|
||||
-- : The Frechet Distance.
|
||||
"""
|
||||
|
||||
mu1 = np.atleast_1d(mu1)
|
||||
mu2 = np.atleast_1d(mu2)
|
||||
|
||||
sigma1 = np.atleast_2d(sigma1)
|
||||
sigma2 = np.atleast_2d(sigma2)
|
||||
|
||||
assert (mu1.shape == mu2.shape
|
||||
), "Training and test mean vectors have different lengths"
|
||||
assert (sigma1.shape == sigma2.shape
|
||||
), "Training and test covariances have different dimensions"
|
||||
|
||||
diff = mu1 - mu2
|
||||
# Product might be almost singular
|
||||
covmean, _ = scipy.linalg.sqrtm(sigma1.dot(sigma2), disp=False)
|
||||
if not np.isfinite(covmean).all():
|
||||
msg = ("fid calculation produces singular product; "
|
||||
"adding %s to diagonal of cov estimates") % eps
|
||||
print(msg)
|
||||
offset = np.eye(sigma1.shape[0]) * eps
|
||||
covmean = scipy.linalg.sqrtm((sigma1 + offset).dot(sigma2 + offset))
|
||||
|
||||
# Numerical error might give slight imaginary component
|
||||
if np.iscomplexobj(covmean):
|
||||
if not np.allclose(np.diagonal(covmean).imag, 0, atol=1e-3):
|
||||
m = np.max(np.abs(covmean.imag))
|
||||
raise ValueError("Imaginary component {}".format(m))
|
||||
# print("Imaginary component {}".format(m))
|
||||
covmean = covmean.real
|
||||
tr_covmean = np.trace(covmean)
|
||||
|
||||
return diff.dot(diff) + np.trace(sigma1) + np.trace(
|
||||
sigma2) - 2 * tr_covmean
|
||||
|
||||
|
||||
def calculate_diversity(activation, diversity_times):
|
||||
assert len(activation.shape) == 2
|
||||
assert activation.shape[0] > diversity_times
|
||||
num_samples = activation.shape[0]
|
||||
|
||||
first_indices = np.random.choice(num_samples,
|
||||
diversity_times,
|
||||
replace=False)
|
||||
second_indices = np.random.choice(num_samples,
|
||||
diversity_times,
|
||||
replace=False)
|
||||
dist = linalg.norm(activation[first_indices] - activation[second_indices],
|
||||
axis=1)
|
||||
return dist.mean()
|
||||
|
||||
|
||||
def calculate_diversity_np(activation, diversity_times):
|
||||
assert len(activation.shape) == 2
|
||||
assert activation.shape[0] > diversity_times
|
||||
num_samples = activation.shape[0]
|
||||
|
||||
first_indices = np.random.choice(num_samples,
|
||||
diversity_times,
|
||||
replace=False)
|
||||
second_indices = np.random.choice(num_samples,
|
||||
diversity_times,
|
||||
replace=False)
|
||||
dist = scipy.linalg.norm(activation[first_indices] -
|
||||
activation[second_indices],
|
||||
axis=1)
|
||||
return dist.mean()
|
||||
|
||||
|
||||
def calculate_multimodality_np(activation, multimodality_times):
|
||||
assert len(activation.shape) == 3
|
||||
assert activation.shape[1] > multimodality_times
|
||||
num_per_sent = activation.shape[1]
|
||||
|
||||
first_dices = np.random.choice(num_per_sent,
|
||||
multimodality_times,
|
||||
replace=False)
|
||||
second_dices = np.random.choice(num_per_sent,
|
||||
multimodality_times,
|
||||
replace=False)
|
||||
dist = scipy.linalg.norm(activation[:, first_dices] -
|
||||
activation[:, second_dices],
|
||||
axis=2)
|
||||
return dist.mean()
|
||||
|
||||
|
||||
# motion reconstructions metrics
|
||||
|
||||
|
||||
def batch_compute_similarity_transform_torch(S1, S2):
|
||||
"""
|
||||
Computes a similarity transform (sR, t) that takes
|
||||
a set of 3D points S1 (3 x N) closest to a set of 3D points S2,
|
||||
where R is an 3x3 rotation matrix, t 3x1 translation, s scale.
|
||||
i.e. solves the orthogonal Procrutes problem.
|
||||
"""
|
||||
transposed = False
|
||||
if S1.shape[0] != 3 and S1.shape[0] != 2:
|
||||
S1 = S1.permute(0, 2, 1)
|
||||
S2 = S2.permute(0, 2, 1)
|
||||
transposed = True
|
||||
assert S2.shape[1] == S1.shape[1]
|
||||
|
||||
# 1. Remove mean.
|
||||
mu1 = S1.mean(axis=-1, keepdims=True)
|
||||
mu2 = S2.mean(axis=-1, keepdims=True)
|
||||
|
||||
X1 = S1 - mu1
|
||||
X2 = S2 - mu2
|
||||
|
||||
# 2. Compute variance of X1 used for scale.
|
||||
var1 = torch.sum(X1**2, dim=1).sum(dim=1)
|
||||
|
||||
# 3. The outer product of X1 and X2.
|
||||
K = X1.bmm(X2.permute(0, 2, 1))
|
||||
|
||||
# 4. Solution that Maximizes trace(R'K) is R=U*V', where U, V are
|
||||
# singular vectors of K.
|
||||
U, s, V = torch.svd(K)
|
||||
|
||||
# Construct Z that fixes the orientation of R to get det(R)=1.
|
||||
Z = torch.eye(U.shape[1], device=S1.device).unsqueeze(0)
|
||||
Z = Z.repeat(U.shape[0], 1, 1)
|
||||
Z[:, -1, -1] *= torch.sign(torch.det(U.bmm(V.permute(0, 2, 1))))
|
||||
|
||||
# Construct R.
|
||||
R = V.bmm(Z.bmm(U.permute(0, 2, 1)))
|
||||
|
||||
# 5. Recover scale.
|
||||
scale = torch.cat([torch.trace(x).unsqueeze(0) for x in R.bmm(K)]) / var1
|
||||
|
||||
# 6. Recover translation.
|
||||
t = mu2 - (scale.unsqueeze(-1).unsqueeze(-1) * (R.bmm(mu1)))
|
||||
|
||||
# 7. Error:
|
||||
S1_hat = scale.unsqueeze(-1).unsqueeze(-1) * R.bmm(S1) + t
|
||||
|
||||
if transposed:
|
||||
S1_hat = S1_hat.permute(0, 2, 1)
|
||||
|
||||
return S1_hat, (scale, R, t)
|
||||
|
||||
|
||||
def compute_mpjpe(preds,
|
||||
target,
|
||||
valid_mask=None,
|
||||
pck_joints=None,
|
||||
sample_wise=True):
|
||||
"""
|
||||
Mean per-joint position error (i.e. mean Euclidean distance)
|
||||
often referred to as "Protocol #1" in many papers.
|
||||
"""
|
||||
assert preds.shape == target.shape, print(preds.shape,
|
||||
target.shape) # BxJx3
|
||||
mpjpe = torch.norm(preds - target, p=2, dim=-1) # BxJ
|
||||
|
||||
if pck_joints is None:
|
||||
if sample_wise:
|
||||
mpjpe_seq = ((mpjpe * valid_mask.float()).sum(-1) /
|
||||
valid_mask.float().sum(-1)
|
||||
if valid_mask is not None else mpjpe.mean(-1))
|
||||
else:
|
||||
mpjpe_seq = mpjpe[valid_mask] if valid_mask is not None else mpjpe
|
||||
return mpjpe_seq
|
||||
else:
|
||||
mpjpe_pck_seq = mpjpe[:, pck_joints]
|
||||
return mpjpe_pck_seq
|
||||
|
||||
|
||||
def align_by_parts(joints, align_inds=None):
|
||||
if align_inds is None:
|
||||
return joints
|
||||
pelvis = joints[:, align_inds].mean(1)
|
||||
return joints - torch.unsqueeze(pelvis, dim=1)
|
||||
|
||||
|
||||
def calc_mpjpe(preds, target, align_inds=[0], sample_wise=True, trans=None):
|
||||
# Expects BxJx3
|
||||
valid_mask = target[:, :, 0] != -2.0
|
||||
# valid_mask = torch.BoolTensor(target[:, :, 0].shape)
|
||||
if align_inds is not None:
|
||||
preds_aligned = align_by_parts(preds, align_inds=align_inds)
|
||||
if trans is not None:
|
||||
preds_aligned += trans
|
||||
target_aligned = align_by_parts(target, align_inds=align_inds)
|
||||
else:
|
||||
preds_aligned, target_aligned = preds, target
|
||||
mpjpe_each = compute_mpjpe(preds_aligned,
|
||||
target_aligned,
|
||||
valid_mask=valid_mask,
|
||||
sample_wise=sample_wise)
|
||||
return mpjpe_each
|
||||
|
||||
|
||||
def calc_accel(preds, target):
|
||||
"""
|
||||
Mean joint acceleration error
|
||||
often referred to as "Protocol #1" in many papers.
|
||||
"""
|
||||
assert preds.shape == target.shape, print(preds.shape,
|
||||
target.shape) # BxJx3
|
||||
assert preds.dim() == 3
|
||||
# Expects BxJx3
|
||||
# valid_mask = torch.BoolTensor(target[:, :, 0].shape)
|
||||
accel_gt = target[:-2] - 2 * target[1:-1] + target[2:]
|
||||
accel_pred = preds[:-2] - 2 * preds[1:-1] + preds[2:]
|
||||
normed = torch.linalg.norm(accel_pred - accel_gt, dim=-1)
|
||||
accel_seq = normed.mean(1)
|
||||
return accel_seq
|
||||
|
||||
|
||||
def calc_pampjpe(preds, target, sample_wise=True, return_transform_mat=False):
|
||||
# Expects BxJx3
|
||||
target, preds = target.float(), preds.float()
|
||||
# extracting the keypoints that all samples have valid annotations
|
||||
# valid_mask = (target[:, :, 0] != -2.).sum(0) == len(target)
|
||||
# preds_tranformed, PA_transform = batch_compute_similarity_transform_torch(preds[:, valid_mask], target[:, valid_mask])
|
||||
# pa_mpjpe_each = compute_mpjpe(preds_tranformed, target[:, valid_mask], sample_wise=sample_wise)
|
||||
|
||||
preds_tranformed, PA_transform = batch_compute_similarity_transform_torch(
|
||||
preds, target)
|
||||
pa_mpjpe_each = compute_mpjpe(preds_tranformed,
|
||||
target,
|
||||
sample_wise=sample_wise)
|
||||
|
||||
if return_transform_mat:
|
||||
return pa_mpjpe_each, PA_transform
|
||||
else:
|
||||
return pa_mpjpe_each
|
||||
|
||||
|
||||
# from action2motion
|
||||
def calculate_diversity_multimodality(activations,
|
||||
labels,
|
||||
num_labels,
|
||||
diversity_times=200,
|
||||
multimodality_times=20):
|
||||
labels = labels.long()
|
||||
num_motions = activations.shape[0] # len(labels)
|
||||
|
||||
diversity = 0
|
||||
|
||||
first_indices = np.random.randint(0, num_motions, diversity_times)
|
||||
second_indices = np.random.randint(0, num_motions, diversity_times)
|
||||
for first_idx, second_idx in zip(first_indices, second_indices):
|
||||
diversity += torch.dist(activations[first_idx, :],
|
||||
activations[second_idx, :])
|
||||
diversity /= diversity_times
|
||||
|
||||
multimodality = 0
|
||||
label_quotas = np.zeros(num_labels)
|
||||
label_quotas[labels.unique(
|
||||
)] = multimodality_times # if a label does not appear in batch, its quota remains zero
|
||||
while np.any(label_quotas > 0):
|
||||
# print(label_quotas)
|
||||
first_idx = np.random.randint(0, num_motions)
|
||||
first_label = labels[first_idx]
|
||||
if not label_quotas[first_label]:
|
||||
continue
|
||||
|
||||
second_idx = np.random.randint(0, num_motions)
|
||||
second_label = labels[second_idx]
|
||||
while first_label != second_label:
|
||||
second_idx = np.random.randint(0, num_motions)
|
||||
second_label = labels[second_idx]
|
||||
|
||||
label_quotas[first_label] -= 1
|
||||
|
||||
first_activation = activations[first_idx, :]
|
||||
second_activation = activations[second_idx, :]
|
||||
multimodality += torch.dist(first_activation, second_activation)
|
||||
|
||||
multimodality /= (multimodality_times * num_labels)
|
||||
|
||||
return diversity, multimodality
|
||||
|
||||
|
||||
def calculate_fid(statistics_1, statistics_2):
|
||||
return calculate_frechet_distance_np(statistics_1[0], statistics_1[1],
|
||||
statistics_2[0], statistics_2[1])
|
||||
|
||||
|
||||
# from: https://github.com/abdulfatir/gan-metrics-pytorch/blob/master/kid_score.py
|
||||
def polynomial_mmd_averages(codes_g,
|
||||
codes_r,
|
||||
n_subsets=50,
|
||||
subset_size=1000,
|
||||
ret_var=True,
|
||||
output=sys.stdout,
|
||||
**kernel_args):
|
||||
m = min(codes_g.shape[0], codes_r.shape[0])
|
||||
mmds = np.zeros(n_subsets)
|
||||
if ret_var:
|
||||
vars = np.zeros(n_subsets)
|
||||
choice = np.random.choice
|
||||
|
||||
replace = subset_size < len(codes_g)
|
||||
|
||||
for i in range(n_subsets):
|
||||
g = codes_g[choice(len(codes_g), subset_size, replace=replace)]
|
||||
r = codes_r[choice(len(codes_r), subset_size, replace=replace)]
|
||||
o = polynomial_mmd(g, r, **kernel_args, var_at_m=m, ret_var=ret_var)
|
||||
if ret_var:
|
||||
mmds[i], vars[i] = o
|
||||
else:
|
||||
mmds[i] = o
|
||||
|
||||
return (mmds, vars) if ret_var else mmds
|
||||
|
||||
|
||||
def polynomial_mmd(codes_g,
|
||||
codes_r,
|
||||
degree=3,
|
||||
gamma=None,
|
||||
coef0=1,
|
||||
var_at_m=None,
|
||||
ret_var=True):
|
||||
from sklearn.metrics.pairwise import polynomial_kernel
|
||||
|
||||
# use k(x, y) = (gamma <x, y> + coef0)^degree
|
||||
# default gamma is 1 / dim
|
||||
X = codes_g
|
||||
Y = codes_r
|
||||
|
||||
K_XX = polynomial_kernel(X, degree=degree, gamma=gamma, coef0=coef0)
|
||||
K_YY = polynomial_kernel(Y, degree=degree, gamma=gamma, coef0=coef0)
|
||||
K_XY = polynomial_kernel(X, Y, degree=degree, gamma=gamma, coef0=coef0)
|
||||
|
||||
return _mmd2_and_variance(K_XX,
|
||||
K_XY,
|
||||
K_YY,
|
||||
var_at_m=var_at_m,
|
||||
ret_var=ret_var)
|
||||
|
||||
|
||||
def _mmd2_and_variance(K_XX,
|
||||
K_XY,
|
||||
K_YY,
|
||||
unit_diagonal=False,
|
||||
mmd_est='unbiased',
|
||||
block_size=1024,
|
||||
var_at_m=None,
|
||||
ret_var=True):
|
||||
# based on
|
||||
# https://github.com/dougalsutherland/opt-mmd/blob/master/two_sample/mmd.py
|
||||
# but changed to not compute the full kernel matrix at once
|
||||
m = K_XX.shape[0]
|
||||
assert K_XX.shape == (m, m)
|
||||
assert K_XY.shape == (m, m)
|
||||
assert K_YY.shape == (m, m)
|
||||
if var_at_m is None:
|
||||
var_at_m = m
|
||||
|
||||
# Get the various sums of kernels that we'll use
|
||||
# Kts drop the diagonal, but we don't need to compute them explicitly
|
||||
if unit_diagonal:
|
||||
diag_X = diag_Y = 1
|
||||
sum_diag_X = sum_diag_Y = m
|
||||
sum_diag2_X = sum_diag2_Y = m
|
||||
else:
|
||||
diag_X = np.diagonal(K_XX)
|
||||
diag_Y = np.diagonal(K_YY)
|
||||
|
||||
sum_diag_X = diag_X.sum()
|
||||
sum_diag_Y = diag_Y.sum()
|
||||
|
||||
sum_diag2_X = _sqn(diag_X)
|
||||
sum_diag2_Y = _sqn(diag_Y)
|
||||
|
||||
Kt_XX_sums = K_XX.sum(axis=1) - diag_X
|
||||
Kt_YY_sums = K_YY.sum(axis=1) - diag_Y
|
||||
K_XY_sums_0 = K_XY.sum(axis=0)
|
||||
K_XY_sums_1 = K_XY.sum(axis=1)
|
||||
|
||||
Kt_XX_sum = Kt_XX_sums.sum()
|
||||
Kt_YY_sum = Kt_YY_sums.sum()
|
||||
K_XY_sum = K_XY_sums_0.sum()
|
||||
|
||||
if mmd_est == 'biased':
|
||||
mmd2 = ((Kt_XX_sum + sum_diag_X) / (m * m) + (Kt_YY_sum + sum_diag_Y) /
|
||||
(m * m) - 2 * K_XY_sum / (m * m))
|
||||
else:
|
||||
assert mmd_est in {'unbiased', 'u-statistic'}
|
||||
mmd2 = (Kt_XX_sum + Kt_YY_sum) / (m * (m - 1))
|
||||
if mmd_est == 'unbiased':
|
||||
mmd2 -= 2 * K_XY_sum / (m * m)
|
||||
else:
|
||||
mmd2 -= 2 * (K_XY_sum - np.trace(K_XY)) / (m * (m - 1))
|
||||
|
||||
if not ret_var:
|
||||
return mmd2
|
||||
|
||||
Kt_XX_2_sum = _sqn(K_XX) - sum_diag2_X
|
||||
Kt_YY_2_sum = _sqn(K_YY) - sum_diag2_Y
|
||||
K_XY_2_sum = _sqn(K_XY)
|
||||
|
||||
dot_XX_XY = Kt_XX_sums.dot(K_XY_sums_1)
|
||||
dot_YY_YX = Kt_YY_sums.dot(K_XY_sums_0)
|
||||
|
||||
m1 = m - 1
|
||||
m2 = m - 2
|
||||
zeta1_est = (
|
||||
1 / (m * m1 * m2) *
|
||||
(_sqn(Kt_XX_sums) - Kt_XX_2_sum + _sqn(Kt_YY_sums) - Kt_YY_2_sum) - 1 /
|
||||
(m * m1)**2 * (Kt_XX_sum**2 + Kt_YY_sum**2) + 1 / (m * m * m1) *
|
||||
(_sqn(K_XY_sums_1) + _sqn(K_XY_sums_0) - 2 * K_XY_2_sum) -
|
||||
2 / m**4 * K_XY_sum**2 - 2 / (m * m * m1) * (dot_XX_XY + dot_YY_YX) +
|
||||
2 / (m**3 * m1) * (Kt_XX_sum + Kt_YY_sum) * K_XY_sum)
|
||||
zeta2_est = (1 / (m * m1) * (Kt_XX_2_sum + Kt_YY_2_sum) - 1 / (m * m1)**2 *
|
||||
(Kt_XX_sum**2 + Kt_YY_sum**2) + 2 / (m * m) * K_XY_2_sum -
|
||||
2 / m**4 * K_XY_sum**2 - 4 / (m * m * m1) *
|
||||
(dot_XX_XY + dot_YY_YX) + 4 / (m**3 * m1) *
|
||||
(Kt_XX_sum + Kt_YY_sum) * K_XY_sum)
|
||||
var_est = (4 * (var_at_m - 2) / (var_at_m * (var_at_m - 1)) * zeta1_est +
|
||||
2 / (var_at_m * (var_at_m - 1)) * zeta2_est)
|
||||
|
||||
return mmd2, var_est
|
||||
|
||||
|
||||
def _sqn(arr):
|
||||
flat = np.ravel(arr)
|
||||
return flat.dot(flat)
|
||||
|
||||
|
||||
def calculate_kid(real_activations, generated_activations):
|
||||
kid_values = polynomial_mmd_averages(real_activations,
|
||||
generated_activations,
|
||||
n_subsets=100)
|
||||
results = (kid_values[0].mean(), kid_values[0].std())
|
||||
return results
|
||||
@@ -1,204 +0,0 @@
|
||||
import os
|
||||
import numpy as np
|
||||
import torch
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from pytorch_lightning import LightningModule
|
||||
from os.path import join as pjoin
|
||||
from collections import OrderedDict
|
||||
from ..metrics import BaseMetrics
|
||||
from ..config import get_obj_from_str
|
||||
|
||||
|
||||
class BaseModel(LightningModule):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
# self.configure_metrics()
|
||||
|
||||
# Ablation
|
||||
self.test_step_outputs = []
|
||||
self.times = []
|
||||
self.rep_i = 0
|
||||
|
||||
def training_step(self, batch, batch_idx):
|
||||
return self.allsplit_step("train", batch, batch_idx)
|
||||
|
||||
def validation_step(self, batch, batch_idx):
|
||||
return self.allsplit_step("val", batch, batch_idx)
|
||||
|
||||
def test_step(self, batch, batch_idx):
|
||||
outputs = self.allsplit_step("test", batch, batch_idx)
|
||||
self.test_step_outputs.append(outputs)
|
||||
return outputs
|
||||
|
||||
def predict_step(self, batch, batch_idx):
|
||||
return self.forward(batch)
|
||||
|
||||
def on_train_epoch_end(self):
|
||||
# Log steps and losses
|
||||
dico = self.step_log_dict()
|
||||
# Log losses
|
||||
dico.update(self.loss_log_dict('train'))
|
||||
# Write to log only if not sanity check
|
||||
if not self.trainer.sanity_checking:
|
||||
self.log_dict(dico, sync_dist=True, rank_zero_only=True)
|
||||
|
||||
def on_validation_epoch_end(self):
|
||||
# Log steps and losses
|
||||
dico = self.step_log_dict()
|
||||
# Log losses
|
||||
dico.update(self.loss_log_dict('train'))
|
||||
dico.update(self.loss_log_dict('val'))
|
||||
# Log metrics
|
||||
dico.update(self.metrics_log_dict())
|
||||
# Write to log only if not sanity check
|
||||
if not self.trainer.sanity_checking:
|
||||
self.log_dict(dico, sync_dist=True, rank_zero_only=True)
|
||||
|
||||
def on_test_epoch_end(self):
|
||||
# Log metrics
|
||||
dico = self.metrics_log_dict()
|
||||
# Write to log only if not sanity check
|
||||
if not self.trainer.sanity_checking:
|
||||
self.log_dict(dico, sync_dist=True, rank_zero_only=True)
|
||||
self.save_npy(self.test_step_outputs)
|
||||
self.rep_i = self.rep_i + 1
|
||||
# Free up the memory
|
||||
self.test_step_outputs.clear()
|
||||
|
||||
def preprocess_state_dict(self, state_dict):
|
||||
new_state_dict = OrderedDict()
|
||||
|
||||
# metric_state_dict = self.metrics.state_dict()
|
||||
loss_state_dict = self._losses.state_dict()
|
||||
|
||||
# for k, v in metric_state_dict.items():
|
||||
# new_state_dict['metrics.' + k] = v
|
||||
|
||||
for k, v in loss_state_dict.items():
|
||||
new_state_dict['_losses.' + k] = v
|
||||
|
||||
for k, v in state_dict.items():
|
||||
if '_losses' not in k and 'Metrics' not in k:
|
||||
new_state_dict[k] = v
|
||||
|
||||
return new_state_dict
|
||||
|
||||
def load_state_dict(self, state_dict, strict=True):
|
||||
new_state_dict = self.preprocess_state_dict(state_dict)
|
||||
super().load_state_dict(new_state_dict, strict)
|
||||
|
||||
def step_log_dict(self):
|
||||
return {
|
||||
"epoch": float(self.trainer.current_epoch),
|
||||
"step": float(self.trainer.current_epoch)
|
||||
}
|
||||
|
||||
def loss_log_dict(self, split: str):
|
||||
losses = self._losses['losses_' + split]
|
||||
loss_dict = losses.compute(split)
|
||||
return loss_dict
|
||||
|
||||
def metrics_log_dict(self):
|
||||
|
||||
# For TM2TMetrics MM
|
||||
if self.trainer.datamodule.is_mm and "TM2TMetrics" in self.hparams.metrics_dict:
|
||||
metrics_dicts = ['MMMetrics']
|
||||
else:
|
||||
metrics_dicts = self.hparams.metrics_dict
|
||||
|
||||
# Compute all metrics
|
||||
metrics_log_dict = {}
|
||||
for metric in metrics_dicts:
|
||||
metrics_dict = getattr(
|
||||
self.metrics,
|
||||
metric).compute(sanity_flag=self.trainer.sanity_checking)
|
||||
metrics_log_dict.update({
|
||||
f"Metrics/{metric}": value.item()
|
||||
for metric, value in metrics_dict.items()
|
||||
})
|
||||
|
||||
return metrics_log_dict
|
||||
|
||||
def configure_optimizers(self):
|
||||
# Optimizer
|
||||
optim_target = self.hparams.cfg.TRAIN.OPTIM.target
|
||||
if len(optim_target.split('.')) == 1:
|
||||
optim_target = 'torch.optim.' + optim_target
|
||||
optimizer = get_obj_from_str(optim_target)(
|
||||
params=self.parameters(), **self.hparams.cfg.TRAIN.OPTIM.params)
|
||||
|
||||
# Scheduler
|
||||
scheduler_target = self.hparams.cfg.TRAIN.LR_SCHEDULER.target
|
||||
if len(scheduler_target.split('.')) == 1:
|
||||
scheduler_target = 'torch.optim.lr_scheduler.' + scheduler_target
|
||||
lr_scheduler = get_obj_from_str(scheduler_target)(
|
||||
optimizer=optimizer, **self.hparams.cfg.TRAIN.LR_SCHEDULER.params)
|
||||
|
||||
return {'optimizer': optimizer, 'lr_scheduler': lr_scheduler}
|
||||
|
||||
def configure_metrics(self):
|
||||
self.metrics = BaseMetrics(datamodule=self.datamodule, **self.hparams)
|
||||
|
||||
def save_npy(self, outputs):
|
||||
cfg = self.hparams.cfg
|
||||
output_dir = Path(
|
||||
os.path.join(
|
||||
cfg.FOLDER,
|
||||
str(cfg.model.target.split('.')[-2].lower()),
|
||||
str(cfg.NAME),
|
||||
"samples_" + cfg.TIME,
|
||||
))
|
||||
if cfg.TEST.SAVE_PREDICTIONS:
|
||||
lengths = [i[1] for i in outputs]
|
||||
outputs = [i[0] for i in outputs]
|
||||
|
||||
if cfg.TEST.DATASETS[0].lower() in ["humanml3d", "kit"]:
|
||||
keyids = self.trainer.datamodule.test_dataset.name_list
|
||||
for i in range(len(outputs)):
|
||||
for bid in range(
|
||||
min(cfg.TEST.BATCH_SIZE, outputs[i].shape[0])):
|
||||
keyid = keyids[i * cfg.TEST.BATCH_SIZE + bid]
|
||||
data = self.trainer.datamodule.test_dataset.data_dict[
|
||||
keyid]
|
||||
|
||||
motion = torch.tensor(data['motion'],
|
||||
device=outputs[i].device)
|
||||
motion = self.datamodule.normalize(motion)
|
||||
length = data['length']
|
||||
text_list = data['text']
|
||||
gen_joints = outputs[i][bid][:lengths[i][bid]].cpu(
|
||||
).numpy()
|
||||
if cfg.TEST.REPLICATION_TIMES > 1:
|
||||
name = f"{keyid}.npy"
|
||||
else:
|
||||
name = f"{keyid}.npy"
|
||||
# save predictions results
|
||||
npypath = output_dir / name
|
||||
np.save(npypath, gen_joints)
|
||||
npypath = output_dir / f"{keyid}_gt.npy"
|
||||
joints = self.feats2joints(motion).cpu().numpy()
|
||||
np.save(npypath, joints)
|
||||
|
||||
with open(output_dir / f"{keyid}.txt", "a") as f:
|
||||
for text in text_list:
|
||||
f.write(f"{text['caption']}\n")
|
||||
|
||||
elif cfg.TEST.DATASETS[0].lower() in ["humanact12", "uestc"]:
|
||||
keyids = range(len(self.trainer.datamodule.test_dataset))
|
||||
for i in range(len(outputs)):
|
||||
for bid in range(
|
||||
min(cfg.TEST.BATCH_SIZE, outputs[i].shape[0])):
|
||||
keyid = keyids[i * cfg.TEST.BATCH_SIZE + bid]
|
||||
gen_joints = outputs[i][bid].cpu()
|
||||
gen_joints = gen_joints.permute(2, 0,
|
||||
1)[:lengths[i][bid],
|
||||
...].numpy()
|
||||
if cfg.TEST.REPLICATION_TIMES > 1:
|
||||
name = f"{keyid}_{self.rep_i}"
|
||||
else:
|
||||
name = f"{keyid}.npy"
|
||||
# save predictions results
|
||||
npypath = output_dir / name
|
||||
np.save(npypath, gen_joints)
|
||||
@@ -1,8 +0,0 @@
|
||||
from omegaconf import OmegaConf
|
||||
from ..config import instantiate_from_config
|
||||
|
||||
def build_model(cfg, datamodule):
|
||||
model_config = OmegaConf.to_container(cfg.model, resolve=True)
|
||||
model_config['params']['cfg'] = cfg
|
||||
model_config['params']['datamodule'] = datamodule
|
||||
return instantiate_from_config(model_config)
|
||||
@@ -1,492 +0,0 @@
|
||||
import numpy as np
|
||||
import os
|
||||
import random
|
||||
import torch
|
||||
import time
|
||||
from ..config import instantiate_from_config
|
||||
from os.path import join as pjoin
|
||||
from ..losses.mgpt import GPTLosses
|
||||
from .base import BaseModel
|
||||
import json
|
||||
#import mGPT.render.matplot.plot_3d_global as plot_3d
|
||||
|
||||
class MotionGPT(BaseModel):
|
||||
"""
|
||||
Stage 1 Motion Tokenizer
|
||||
Stage 2 Motion-language pretrian
|
||||
Stage 3 Motion-language instruction tuning
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
cfg,
|
||||
datamodule,
|
||||
lm,
|
||||
motion_vae,
|
||||
codebook_size=512,
|
||||
stage='vae',
|
||||
debug=True,
|
||||
condition='text',
|
||||
task='t2m',
|
||||
metrics_dict=['TM2TMetrics'],
|
||||
**kwargs):
|
||||
|
||||
self.save_hyperparameters(ignore='datamodule', logger=False)
|
||||
self.datamodule = datamodule
|
||||
super().__init__()
|
||||
|
||||
# Instantiate motion tokenizer
|
||||
if motion_vae != None:
|
||||
self.vae = instantiate_from_config(motion_vae)
|
||||
|
||||
# Instantiate motion-language model
|
||||
self.lm = instantiate_from_config(lm)
|
||||
|
||||
# Freeze the motion tokenizer for lm training
|
||||
if 'lm' in self.hparams.stage:
|
||||
self.vae.training = False
|
||||
for p in self.vae.parameters():
|
||||
p.requires_grad = False
|
||||
|
||||
# Instantiate the losses
|
||||
self._losses = torch.nn.ModuleDict({
|
||||
split: GPTLosses(cfg, self.hparams.stage, self.datamodule.njoints)
|
||||
for split in ["losses_train", "losses_test", "losses_val"]
|
||||
})
|
||||
|
||||
# Data transform
|
||||
self.feats2joints = datamodule.feats2joints
|
||||
|
||||
# Count codebook frequency
|
||||
self.codePred = []
|
||||
self.codeFrequency = torch.zeros((self.hparams.codebook_size, ))
|
||||
|
||||
def forward(self, batch, task="t2m"):
|
||||
texts = batch["text"]
|
||||
lengths_ref = batch["length"]
|
||||
|
||||
# Forward
|
||||
# texts = ['Generate motion: ' + text for text in texts]
|
||||
outputs, output_texts = self.lm.generate_direct(texts, do_sample=True)
|
||||
|
||||
# Motion Decode
|
||||
feats_rst_lst = []
|
||||
lengths = []
|
||||
max_len = 0
|
||||
|
||||
for i in range(len(texts)):
|
||||
if task == "pred":
|
||||
motion = self.vae.decode(
|
||||
torch.cat((batch["motion"][i], outputs[i])))
|
||||
elif task in ["t2m", "m2t", "inbetween"]:
|
||||
motion = self.vae.decode(outputs[i])
|
||||
# motion = self.datamodule.denormalize(motion)
|
||||
lengths.append(motion.shape[1])
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
if motion.shape[1] > max_len:
|
||||
max_len = motion.shape[1]
|
||||
|
||||
if task in ["t2m", "m2t", "pred"]:
|
||||
feats_rst_lst.append(motion)
|
||||
|
||||
elif task == "inbetween":
|
||||
motion = torch.cat(
|
||||
(batch["motion_heading"][i][None],
|
||||
motion[:, lengths_ref[i] // 4:lengths_ref[i] // 4 * 3,
|
||||
...], batch["motion_tailing"][i][None]),
|
||||
dim=1)
|
||||
feats_rst_lst.append(motion)
|
||||
|
||||
feats_rst = torch.zeros(
|
||||
(len(feats_rst_lst), max_len, motion.shape[-1])).to(self.device)
|
||||
|
||||
# padding and concat
|
||||
for i in range(len(feats_rst_lst)):
|
||||
feats_rst[i, :feats_rst_lst[i].shape[1], ...] = feats_rst_lst[i]
|
||||
|
||||
# Recover joints for evaluation
|
||||
joints_rst = self.feats2joints(feats_rst)
|
||||
|
||||
# return set
|
||||
outputs = {
|
||||
"texts": output_texts,
|
||||
"feats": feats_rst,
|
||||
"joints": joints_rst,
|
||||
"length": lengths
|
||||
}
|
||||
|
||||
return outputs
|
||||
|
||||
def train_lm_forward(self, batch):
|
||||
tokens_ref = batch["motion"]
|
||||
texts = batch["text"]
|
||||
lengths = batch["length"]
|
||||
tasks = batch["tasks"]
|
||||
all_captions = batch['all_captions']
|
||||
if self.hparams.condition == 'caption':
|
||||
texts = [random.choice(all_captions[i]) for i in range(len(texts))]
|
||||
|
||||
# LLM Forward
|
||||
outputs = self.lm(texts, tokens_ref, lengths, tasks)
|
||||
# outputs = self.t2m_gpt.generate(texts)
|
||||
return {'outputs': outputs}
|
||||
|
||||
@torch.no_grad()
|
||||
def val_t2m_forward(self, batch):
|
||||
feats_ref = batch["motion"]
|
||||
texts = batch["text"]
|
||||
lengths = batch["length"]
|
||||
tasks = None
|
||||
if self.trainer.datamodule.is_mm:
|
||||
texts = texts * self.hparams.cfg.METRIC.MM_NUM_REPEATS
|
||||
feats_ref = feats_ref.repeat_interleave(
|
||||
self.hparams.cfg.METRIC.MM_NUM_REPEATS, dim=0)
|
||||
lengths = lengths * self.hparams.cfg.METRIC.MM_NUM_REPEATS
|
||||
instructions = pjoin(self.datamodule.hparams.data_root,
|
||||
'template_instructions.json')
|
||||
instructions = json.load(open(instructions, 'r'))
|
||||
tasks = [instructions["Text-to-Motion"]["caption"]] * len(texts)
|
||||
|
||||
if self.hparams.condition == 'caption':
|
||||
tasks = [{
|
||||
'input': ['<Caption_Placeholder>'],
|
||||
'output': ['']
|
||||
}] * len(texts)
|
||||
|
||||
if self.hparams.cfg.DATASET.TASK_PATH:
|
||||
instructions = pjoin(self.hparams.cfg.DATASET.TASK_PATH)
|
||||
instructions = json.load(open(instructions, 'r'))
|
||||
tasks = [instructions["Text-to-Motion"]["t2m"]] * len(texts)
|
||||
|
||||
min_len = lengths.copy()
|
||||
# Forward
|
||||
outputs = self.lm.generate_conditional(texts,
|
||||
lengths=lengths,
|
||||
stage='test',
|
||||
tasks=tasks)
|
||||
|
||||
# Motion Decode
|
||||
feats_rst = torch.zeros_like(feats_ref)
|
||||
|
||||
for i in range(len(texts)):
|
||||
outputs[i] = torch.clamp(outputs[i],
|
||||
0,
|
||||
self.hparams.codebook_size - 1,
|
||||
out=None)
|
||||
|
||||
if len(outputs[i]) > 1:
|
||||
motion = self.vae.decode(outputs[i])
|
||||
else:
|
||||
motion = torch.zeros_like(feats_ref[i:i + 1, ...])
|
||||
|
||||
min_len[i] = min(motion.shape[1], lengths[i])
|
||||
|
||||
# Cut Motion
|
||||
feats_rst[i:i + 1, :min_len[i], ...] = motion[:, :lengths[i]]
|
||||
|
||||
# Recover joints for evaluation
|
||||
joints_ref = self.feats2joints(feats_ref)
|
||||
joints_rst = self.feats2joints(feats_rst)
|
||||
|
||||
# Renorm for evaluation
|
||||
feats_ref = self.datamodule.renorm4t2m(feats_ref)
|
||||
feats_rst = self.datamodule.renorm4t2m(feats_rst)
|
||||
|
||||
# return set
|
||||
rs_set = {
|
||||
"m_ref": feats_ref,
|
||||
"m_rst": feats_rst,
|
||||
"joints_ref": joints_ref,
|
||||
"joints_rst": joints_rst,
|
||||
"length": min_len
|
||||
# "length": lengths
|
||||
}
|
||||
|
||||
return rs_set
|
||||
|
||||
@torch.no_grad()
|
||||
def val_m2t_forward(self, batch):
|
||||
self.hparams.metrics_dict = []
|
||||
|
||||
feats_ref = batch["motion"]
|
||||
texts = batch["text"]
|
||||
lengths = batch["length"]
|
||||
all_captions = batch['all_captions']
|
||||
|
||||
# Motion Encode
|
||||
motion_tokens = []
|
||||
lengths_tokens = []
|
||||
for i in range(len(feats_ref)):
|
||||
motion_token, _ = self.vae.encode(feats_ref[i:i + 1])
|
||||
motion_tokens.append(motion_token[0])
|
||||
lengths_tokens.append(motion_token.shape[1])
|
||||
|
||||
# Forward
|
||||
outputs = self.lm.generate_conditional(motion_tokens=motion_tokens,
|
||||
lengths=lengths_tokens,
|
||||
task="m2t",
|
||||
stage='test')
|
||||
|
||||
# return set
|
||||
rs_set = {
|
||||
"m_ref": feats_ref,
|
||||
"t_ref": all_captions,
|
||||
# "t_ref": texts,
|
||||
"t_pred": outputs,
|
||||
"length": lengths
|
||||
}
|
||||
|
||||
return rs_set
|
||||
|
||||
@torch.no_grad()
|
||||
def val_m2m_forward(self, batch, task="pred"):
|
||||
feats_ref = batch["motion"]
|
||||
lengths = batch["length"]
|
||||
|
||||
# Motion Encode
|
||||
motion_tokens = []
|
||||
lengths_tokens = []
|
||||
for i in range(len(feats_ref)):
|
||||
motion_token, _ = self.vae.encode(feats_ref[i:i + 1])
|
||||
motion_tokens.append(motion_token[0])
|
||||
|
||||
# Forward
|
||||
outputs = self.lm.generate_conditional(motion_tokens=motion_tokens,
|
||||
lengths=lengths,
|
||||
task=task,
|
||||
stage='test')
|
||||
|
||||
# Motion Decode
|
||||
feats_rst = torch.zeros_like(feats_ref)
|
||||
min_len = lengths.copy()
|
||||
|
||||
for i in range(len(lengths)):
|
||||
outputs[i] = torch.clamp(outputs[i],
|
||||
0,
|
||||
self.hparams.codebook_size - 1,
|
||||
out=None)
|
||||
|
||||
if len(outputs[i]) > 1:
|
||||
motion = self.vae.decode(outputs[i])
|
||||
else:
|
||||
motion = torch.zeros_like(feats_ref[i:i + 1, ...])
|
||||
|
||||
min_len[i] = min(motion.shape[1], lengths[i])
|
||||
|
||||
# Cut Motion
|
||||
feats_rst[i:i + 1, :min_len[i], ...] = motion[:, :lengths[i]]
|
||||
|
||||
# Recover joints for evaluation
|
||||
joints_ref = self.feats2joints(feats_ref)
|
||||
joints_rst = self.feats2joints(feats_rst)
|
||||
|
||||
# Renorm for evaluation
|
||||
feats_ref = self.datamodule.renorm4t2m(feats_ref)
|
||||
feats_rst = self.datamodule.renorm4t2m(feats_rst)
|
||||
|
||||
# return set
|
||||
rs_set = {
|
||||
"m_ref": feats_ref,
|
||||
"m_rst": feats_rst,
|
||||
"joints_ref": joints_ref,
|
||||
"joints_rst": joints_rst,
|
||||
"length": min_len
|
||||
# "length": lengths
|
||||
}
|
||||
|
||||
return rs_set
|
||||
|
||||
def train_vae_forward(self, batch):
|
||||
# batch detach
|
||||
feats_ref = batch["motion"]
|
||||
joints_ref = self.feats2joints(feats_ref)
|
||||
# motion encode & decode
|
||||
feats_rst, loss_commit, perplexity = self.vae(feats_ref)
|
||||
joints_rst = self.feats2joints(feats_rst)
|
||||
# return set
|
||||
rs_set = {
|
||||
"m_ref": feats_ref,
|
||||
"joints_ref": joints_ref,
|
||||
"m_rst": feats_rst,
|
||||
"joints_rst": joints_rst,
|
||||
"loss_commit": loss_commit,
|
||||
"perplexity": perplexity,
|
||||
}
|
||||
return rs_set
|
||||
|
||||
@torch.no_grad()
|
||||
def val_vae_forward(self, batch, split="train"):
|
||||
# Detach batch
|
||||
feats_ref = batch["motion"]
|
||||
lengths = batch["length"]
|
||||
|
||||
# Repeat for multimodal evaluation
|
||||
if self.trainer.datamodule.is_mm:
|
||||
feats_ref = feats_ref.repeat_interleave(
|
||||
self.hparams.cfg.METRIC.MM_NUM_REPEATS, dim=0)
|
||||
lengths = lengths * self.hparams.cfg.METRIC.MM_NUM_REPEATS
|
||||
|
||||
# Motion encode & decode
|
||||
feats_rst = torch.zeros_like(feats_ref)
|
||||
|
||||
for i in range(len(feats_ref)):
|
||||
if lengths[i] == 0:
|
||||
continue
|
||||
feats_pred, _, _ = self.vae(feats_ref[i:i + 1, :lengths[i]])
|
||||
feats_rst[i:i + 1, :feats_pred.shape[1], :] = feats_pred
|
||||
|
||||
code_pred, _ = self.vae.encode(feats_ref[i:i + 1, :lengths[i]])
|
||||
|
||||
# codeFre_pred = torch.bincount(code_pred[0],
|
||||
# minlength=self.hparams.codebook_size).to(
|
||||
# self.codeFrequency.device)
|
||||
# self.codePred.append(code_pred[0])
|
||||
# self.codeFrequency += codeFre_pred
|
||||
|
||||
# np.save('../memData/results/codeFrequency.npy',
|
||||
# self.codeFrequency.cpu().numpy())
|
||||
|
||||
# Recover joints for evaluation
|
||||
joints_ref = self.feats2joints(feats_ref)
|
||||
joints_rst = self.feats2joints(feats_rst)
|
||||
|
||||
# Renorm for evaluation
|
||||
feats_ref = self.datamodule.renorm4t2m(feats_ref)
|
||||
feats_rst = self.datamodule.renorm4t2m(feats_rst)
|
||||
|
||||
# Return set
|
||||
rs_set = {
|
||||
"m_ref": feats_ref,
|
||||
"joints_ref": joints_ref,
|
||||
"m_rst": feats_rst,
|
||||
"joints_rst": joints_rst,
|
||||
"length": lengths,
|
||||
}
|
||||
|
||||
return rs_set
|
||||
|
||||
|
||||
def allsplit_step(self, split: str, batch, batch_idx):
|
||||
# Compute the losses
|
||||
loss = None
|
||||
|
||||
if self.hparams.stage == "vae" and split in ["train", "val"]:
|
||||
rs_set = self.train_vae_forward(batch)
|
||||
loss = self._losses['losses_' + split].update(rs_set)
|
||||
elif self.hparams.stage in ["lm_instruct", "lm_pretrain"
|
||||
] and split in ["train"]:
|
||||
rs_set = self.train_lm_forward(batch)
|
||||
loss = self._losses['losses_' + split].update(rs_set)
|
||||
elif self.hparams.stage == 'lm_rl' and split in ['train']:
|
||||
rs_set = self.train_rl_forward(batch)
|
||||
loss = None
|
||||
|
||||
# Compute the metrics
|
||||
if split in ["val", "test"]:
|
||||
if self.hparams.stage == "vae":
|
||||
rs_set = self.val_vae_forward(batch, split)
|
||||
elif self.hparams.stage in ["lm_instruct", "lm_pretrain", "lm_rl"]:
|
||||
if self.hparams.task == "t2m":
|
||||
rs_set = self.val_t2m_forward(batch)
|
||||
elif self.hparams.task == "m2t":
|
||||
rs_set = self.val_m2t_forward(batch)
|
||||
elif self.hparams.task in ["m2m", "pred", "inbetween"]:
|
||||
rs_set = self.val_m2m_forward(batch, self.hparams.task)
|
||||
|
||||
if self.hparams.task not in ["m2t"]:
|
||||
# MultiModality evaluation sperately
|
||||
if self.trainer.datamodule.is_mm:
|
||||
metrics_dicts = ['MMMetrics']
|
||||
else:
|
||||
metrics_dicts = self.hparams.metrics_dict
|
||||
|
||||
if self.hparams.task not in ['pred', 'inbetween']:
|
||||
metrics_dicts.remove('PredMetrics')
|
||||
|
||||
for metric in metrics_dicts:
|
||||
lengths = batch['length']
|
||||
if metric == "TemosMetric":
|
||||
getattr(self.metrics,
|
||||
metric).update(rs_set["joints_rst"],
|
||||
rs_set["joints_ref"], lengths)
|
||||
elif metric == "TM2TMetrics":
|
||||
if self.hparams.stage in [
|
||||
"lm_instruct", "lm_pretrain", "lm_rl"
|
||||
]:
|
||||
word_embs = batch['word_embs']
|
||||
pos_ohot = batch['pos_ohot']
|
||||
text_lengths = batch['text_len']
|
||||
if self.trainer.datamodule.is_mm:
|
||||
word_embs = word_embs.repeat_interleave(
|
||||
self.hparams.cfg.METRIC.MM_NUM_REPEATS,
|
||||
dim=0)
|
||||
pos_ohot = pos_ohot.repeat_interleave(
|
||||
self.hparams.cfg.METRIC.MM_NUM_REPEATS,
|
||||
dim=0)
|
||||
text_lengths = text_lengths.repeat_interleave(
|
||||
self.hparams.cfg.METRIC.MM_NUM_REPEATS,
|
||||
dim=0)
|
||||
else:
|
||||
word_embs = None
|
||||
pos_ohot = None
|
||||
text_lengths = None
|
||||
|
||||
getattr(self.metrics, metric).update(
|
||||
feats_ref=rs_set["m_ref"],
|
||||
feats_rst=rs_set["m_rst"],
|
||||
lengths_ref=lengths,
|
||||
lengths_rst=rs_set['length'],
|
||||
word_embs=word_embs,
|
||||
pos_ohot=pos_ohot,
|
||||
text_lengths=text_lengths,
|
||||
)
|
||||
elif metric == "UncondMetrics":
|
||||
getattr(self.metrics, metric).update(
|
||||
recmotion_embeddings=rs_set["lat_rm"],
|
||||
gtmotion_embeddings=rs_set["lat_m"],
|
||||
lengths=lengths,
|
||||
)
|
||||
elif metric == "MRMetrics":
|
||||
getattr(self.metrics,
|
||||
metric).update(rs_set["joints_rst"],
|
||||
rs_set["joints_ref"], lengths)
|
||||
elif metric == "PredMetrics":
|
||||
getattr(self.metrics,
|
||||
metric).update(rs_set["joints_rst"],
|
||||
rs_set["joints_ref"], lengths)
|
||||
elif metric == "MMMetrics":
|
||||
# pass
|
||||
getattr(self.metrics,
|
||||
metric).update(rs_set["m_rst"],
|
||||
rs_set['length'])
|
||||
else:
|
||||
raise TypeError(f"Not support this metric {metric}")
|
||||
|
||||
elif self.hparams.task == "m2t" and self.hparams.stage in [
|
||||
"lm_instruct", "lm_pretrain", "lm_rl"
|
||||
]:
|
||||
self.hparams.metrics_dict = metrics_dicts = ['M2TMetrics']
|
||||
for metric in metrics_dicts:
|
||||
if metric == "M2TMetrics":
|
||||
getattr(self.metrics, metric).update(
|
||||
feats_ref=rs_set["m_ref"],
|
||||
pred_texts=rs_set["t_pred"],
|
||||
gt_texts=batch["all_captions"],
|
||||
lengths=rs_set['length'],
|
||||
word_embs=batch["word_embs"],
|
||||
pos_ohot=batch["pos_ohot"],
|
||||
text_lengths=batch["text_len"],
|
||||
)
|
||||
|
||||
# return forward output rather than loss during test
|
||||
if split in ["test"]:
|
||||
if self.hparams.task == "t2m":
|
||||
return rs_set["joints_rst"], rs_set["length"], rs_set[
|
||||
"joints_ref"]
|
||||
# pass
|
||||
elif self.hparams.task == "m2t":
|
||||
return rs_set["t_pred"], batch["length"]
|
||||
# return batch["length"]
|
||||
|
||||
return loss
|
||||
@@ -1,66 +0,0 @@
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
|
||||
class AdaptiveInstanceNorm1d(nn.Module):
|
||||
def __init__(self, num_features, eps=1e-5, momentum=0.1):
|
||||
super(AdaptiveInstanceNorm1d, self).__init__()
|
||||
self.num_features = num_features
|
||||
self.eps = eps
|
||||
self.momentum = momentum
|
||||
self.weight = None
|
||||
self.bias = None
|
||||
self.register_buffer('running_mean', torch.zeros(num_features))
|
||||
self.register_buffer('running_var', torch.ones(num_features))
|
||||
|
||||
def forward(self, x, direct_weighting=False, no_std=False):
|
||||
assert self.weight is not None and \
|
||||
self.bias is not None, "Please assign AdaIN weight first"
|
||||
# (bs, nfeats, nframe) <= (nframe, bs, nfeats)
|
||||
x = x.permute(1,2,0)
|
||||
|
||||
b, c = x.size(0), x.size(1) # batch size & channels
|
||||
running_mean = self.running_mean.repeat(b)
|
||||
running_var = self.running_var.repeat(b)
|
||||
# self.weight = torch.ones_like(self.weight)
|
||||
|
||||
if direct_weighting:
|
||||
x_reshaped = x.contiguous().view(b * c)
|
||||
if no_std:
|
||||
out = x_reshaped + self.bias
|
||||
else:
|
||||
out = x_reshaped.mul(self.weight) + self.bias
|
||||
out = out.view(b, c, *x.size()[2:])
|
||||
else:
|
||||
x_reshaped = x.contiguous().view(1, b * c, *x.size()[2:])
|
||||
out = F.batch_norm(
|
||||
x_reshaped, running_mean, running_var, self.weight, self.bias,
|
||||
True, self.momentum, self.eps)
|
||||
out = out.view(b, c, *x.size()[2:])
|
||||
|
||||
# (nframe, bs, nfeats) <= (bs, nfeats, nframe)
|
||||
out = out.permute(2,0,1)
|
||||
return out
|
||||
|
||||
def __repr__(self):
|
||||
return self.__class__.__name__ + '(' + str(self.num_features) + ')'
|
||||
|
||||
def assign_adain_params(adain_params, model):
|
||||
# assign the adain_params to the AdaIN layers in model
|
||||
for m in model.modules():
|
||||
if m.__class__.__name__ == "AdaptiveInstanceNorm1d":
|
||||
mean = adain_params[: , : m.num_features]
|
||||
std = adain_params[: , m.num_features: 2 * m.num_features]
|
||||
m.bias = mean.contiguous().view(-1)
|
||||
m.weight = std.contiguous().view(-1)
|
||||
if adain_params.size(1) > 2 * m.num_features:
|
||||
adain_params = adain_params[: , 2 * m.num_features:]
|
||||
|
||||
|
||||
def get_num_adain_params(model):
|
||||
# return the number of AdaIN parameters needed by the model
|
||||
num_adain_params = 0
|
||||
for m in model.modules():
|
||||
if m.__class__.__name__ == "AdaptiveInstanceNorm1d":
|
||||
num_adain_params += 2 * m.num_features
|
||||
return num_adain_params
|
||||
@@ -1,146 +0,0 @@
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
from mGPT.models.notused import AdaptiveInstanceNorm1d
|
||||
|
||||
|
||||
class MLP(nn.Module):
|
||||
|
||||
def __init__(self, cfg, out_dim, is_init):
|
||||
super(MLP, self).__init__()
|
||||
dims = cfg.MODEL.MOTION_DECODER.MLP_DIM
|
||||
n_blk = len(dims)
|
||||
norm = 'none'
|
||||
acti = 'lrelu'
|
||||
|
||||
layers = []
|
||||
for i in range(n_blk - 1):
|
||||
layers += LinearBlock(dims[i], dims[i + 1], norm=norm, acti=acti)
|
||||
layers += LinearBlock(dims[-1], out_dim, norm='none', acti='none')
|
||||
self.model = nn.Sequential(*layers)
|
||||
|
||||
if is_init:
|
||||
for m in self.modules():
|
||||
if isinstance(m, nn.Linear):
|
||||
#nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
|
||||
nn.init.constant_(m.weight, 1)
|
||||
elif isinstance(m, (nn.BatchNorm2d, nn.GroupNorm)):
|
||||
nn.init.constant_(m.weight, 1)
|
||||
nn.init.constant_(m.bias, 0)
|
||||
|
||||
def forward(self, x):
|
||||
return self.model(x.view(x.size(0), -1))
|
||||
|
||||
|
||||
def ZeroPad1d(sizes):
|
||||
return nn.ConstantPad1d(sizes, 0)
|
||||
|
||||
|
||||
def get_acti_layer(acti='relu', inplace=True):
|
||||
|
||||
if acti == 'relu':
|
||||
return [nn.ReLU(inplace=inplace)]
|
||||
elif acti == 'lrelu':
|
||||
return [nn.LeakyReLU(0.2, inplace=inplace)]
|
||||
elif acti == 'tanh':
|
||||
return [nn.Tanh()]
|
||||
elif acti == 'none':
|
||||
return []
|
||||
else:
|
||||
assert 0, "Unsupported activation: {}".format(acti)
|
||||
|
||||
|
||||
def get_norm_layer(norm='none', norm_dim=None):
|
||||
|
||||
if norm == 'bn':
|
||||
return [nn.BatchNorm1d(norm_dim)]
|
||||
elif norm == 'in':
|
||||
# return [nn.InstanceNorm1d(norm_dim, affine=False)] # for rt42!
|
||||
return [nn.InstanceNorm1d(norm_dim, affine=True)]
|
||||
elif norm == 'adain':
|
||||
return [AdaptiveInstanceNorm1d(norm_dim)]
|
||||
elif norm == 'none':
|
||||
return []
|
||||
else:
|
||||
assert 0, "Unsupported normalization: {}".format(norm)
|
||||
|
||||
|
||||
def get_dropout_layer(dropout=None):
|
||||
if dropout is not None:
|
||||
return [nn.Dropout(p=dropout)]
|
||||
else:
|
||||
return []
|
||||
|
||||
|
||||
def ConvLayers(kernel_size,
|
||||
in_channels,
|
||||
out_channels,
|
||||
stride=1,
|
||||
pad_type='reflect',
|
||||
use_bias=True):
|
||||
"""
|
||||
returns a list of [pad, conv] => should be += to some list, then apply sequential
|
||||
"""
|
||||
|
||||
if pad_type == 'reflect':
|
||||
pad = nn.ReflectionPad1d
|
||||
elif pad_type == 'replicate':
|
||||
pad = nn.ReplicationPad1d
|
||||
elif pad_type == 'zero':
|
||||
pad = ZeroPad1d
|
||||
else:
|
||||
assert 0, "Unsupported padding type: {}".format(pad_type)
|
||||
|
||||
pad_l = (kernel_size - 1) // 2
|
||||
pad_r = kernel_size - 1 - pad_l
|
||||
return [
|
||||
pad((pad_l, pad_r)),
|
||||
nn.Conv1d(in_channels,
|
||||
out_channels,
|
||||
kernel_size=kernel_size,
|
||||
stride=stride,
|
||||
bias=use_bias)
|
||||
]
|
||||
|
||||
|
||||
def ConvBlock(kernel_size,
|
||||
in_channels,
|
||||
out_channels,
|
||||
stride=1,
|
||||
pad_type='reflect',
|
||||
dropout=None,
|
||||
norm='none',
|
||||
acti='lrelu',
|
||||
acti_first=False,
|
||||
use_bias=True,
|
||||
inplace=True):
|
||||
"""
|
||||
returns a list of [pad, conv, norm, acti] or [acti, pad, conv, norm]
|
||||
"""
|
||||
|
||||
layers = ConvLayers(kernel_size,
|
||||
in_channels,
|
||||
out_channels,
|
||||
stride=stride,
|
||||
pad_type=pad_type,
|
||||
use_bias=use_bias)
|
||||
layers += get_dropout_layer(dropout)
|
||||
layers += get_norm_layer(norm, norm_dim=out_channels)
|
||||
acti_layers = get_acti_layer(acti, inplace=inplace)
|
||||
|
||||
if acti_first:
|
||||
return acti_layers + layers
|
||||
else:
|
||||
return layers + acti_layers
|
||||
|
||||
|
||||
def LinearBlock(in_dim, out_dim, dropout=None, norm='none', acti='relu'):
|
||||
|
||||
use_bias = True
|
||||
layers = []
|
||||
layers.append(nn.Linear(in_dim, out_dim, bias=use_bias))
|
||||
layers += get_dropout_layer(dropout)
|
||||
layers += get_norm_layer(norm, norm_dim=out_dim)
|
||||
layers += get_acti_layer(acti)
|
||||
|
||||
return layers
|
||||
@@ -1,412 +0,0 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
|
||||
"""
|
||||
DETR Transformer class.
|
||||
Copy-paste from torch.nn.Transformer with modifications:
|
||||
* positional encodings are passed in MHattention
|
||||
* extra LN at the end of encoder is removed
|
||||
* decoder returns a stack of activations from all decoding layers
|
||||
"""
|
||||
import copy
|
||||
from typing import List, Optional
|
||||
from numpy import block
|
||||
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
from torch import Tensor, nn
|
||||
|
||||
|
||||
class SkipTransformerEncoder(nn.Module):
|
||||
def __init__(self, encoder_layer, num_layers, norm=None):
|
||||
super().__init__()
|
||||
self.d_model = encoder_layer.d_model
|
||||
|
||||
self.num_layers = num_layers
|
||||
self.norm = norm
|
||||
|
||||
assert num_layers % 2 == 1
|
||||
|
||||
num_block = (num_layers-1)//2
|
||||
self.input_blocks = _get_clones(encoder_layer, num_block)
|
||||
self.middle_block = _get_clone(encoder_layer)
|
||||
self.output_blocks = _get_clones(encoder_layer, num_block)
|
||||
self.linear_blocks = _get_clones(nn.Linear(2*self.d_model, self.d_model), num_block)
|
||||
|
||||
self._reset_parameters()
|
||||
|
||||
def _reset_parameters(self):
|
||||
for p in self.parameters():
|
||||
if p.dim() > 1:
|
||||
nn.init.xavier_uniform_(p)
|
||||
|
||||
def forward(self, src,
|
||||
mask: Optional[Tensor] = None,
|
||||
src_key_padding_mask: Optional[Tensor] = None,
|
||||
pos: Optional[Tensor] = None):
|
||||
x = src
|
||||
|
||||
xs = []
|
||||
for module in self.input_blocks:
|
||||
x = module(x, src_mask=mask,
|
||||
src_key_padding_mask=src_key_padding_mask, pos=pos)
|
||||
xs.append(x)
|
||||
|
||||
x = self.middle_block(x, src_mask=mask,
|
||||
src_key_padding_mask=src_key_padding_mask, pos=pos)
|
||||
|
||||
for (module, linear) in zip(self.output_blocks, self.linear_blocks):
|
||||
x = torch.cat([x, xs.pop()], dim=-1)
|
||||
x = linear(x)
|
||||
x = module(x, src_mask=mask,
|
||||
src_key_padding_mask=src_key_padding_mask, pos=pos)
|
||||
|
||||
if self.norm is not None:
|
||||
x = self.norm(x)
|
||||
return x
|
||||
|
||||
class SkipTransformerDecoder(nn.Module):
|
||||
def __init__(self, decoder_layer, num_layers, norm=None):
|
||||
super().__init__()
|
||||
self.d_model = decoder_layer.d_model
|
||||
|
||||
self.num_layers = num_layers
|
||||
self.norm = norm
|
||||
|
||||
assert num_layers % 2 == 1
|
||||
|
||||
num_block = (num_layers-1)//2
|
||||
self.input_blocks = _get_clones(decoder_layer, num_block)
|
||||
self.middle_block = _get_clone(decoder_layer)
|
||||
self.output_blocks = _get_clones(decoder_layer, num_block)
|
||||
self.linear_blocks = _get_clones(nn.Linear(2*self.d_model, self.d_model), num_block)
|
||||
|
||||
self._reset_parameters()
|
||||
|
||||
def _reset_parameters(self):
|
||||
for p in self.parameters():
|
||||
if p.dim() > 1:
|
||||
nn.init.xavier_uniform_(p)
|
||||
|
||||
def forward(self, tgt, memory,
|
||||
tgt_mask: Optional[Tensor] = None,
|
||||
memory_mask: Optional[Tensor] = None,
|
||||
tgt_key_padding_mask: Optional[Tensor] = None,
|
||||
memory_key_padding_mask: Optional[Tensor] = None,
|
||||
pos: Optional[Tensor] = None,
|
||||
query_pos: Optional[Tensor] = None):
|
||||
x = tgt
|
||||
|
||||
xs = []
|
||||
for module in self.input_blocks:
|
||||
x = module(x, memory, tgt_mask=tgt_mask,
|
||||
memory_mask=memory_mask,
|
||||
tgt_key_padding_mask=tgt_key_padding_mask,
|
||||
memory_key_padding_mask=memory_key_padding_mask,
|
||||
pos=pos, query_pos=query_pos)
|
||||
xs.append(x)
|
||||
|
||||
x = self.middle_block(x, memory, tgt_mask=tgt_mask,
|
||||
memory_mask=memory_mask,
|
||||
tgt_key_padding_mask=tgt_key_padding_mask,
|
||||
memory_key_padding_mask=memory_key_padding_mask,
|
||||
pos=pos, query_pos=query_pos)
|
||||
|
||||
for (module, linear) in zip(self.output_blocks, self.linear_blocks):
|
||||
x = torch.cat([x, xs.pop()], dim=-1)
|
||||
x = linear(x)
|
||||
x = module(x, memory, tgt_mask=tgt_mask,
|
||||
memory_mask=memory_mask,
|
||||
tgt_key_padding_mask=tgt_key_padding_mask,
|
||||
memory_key_padding_mask=memory_key_padding_mask,
|
||||
pos=pos, query_pos=query_pos)
|
||||
|
||||
if self.norm is not None:
|
||||
x = self.norm(x)
|
||||
|
||||
return x
|
||||
|
||||
class Transformer(nn.Module):
|
||||
|
||||
def __init__(self, d_model=512, nhead=8, num_encoder_layers=6,
|
||||
num_decoder_layers=6, dim_feedforward=2048, dropout=0.1,
|
||||
activation="relu", normalize_before=False,
|
||||
return_intermediate_dec=False):
|
||||
super().__init__()
|
||||
|
||||
encoder_layer = TransformerEncoderLayer(d_model, nhead, dim_feedforward,
|
||||
dropout, activation, normalize_before)
|
||||
encoder_norm = nn.LayerNorm(d_model) if normalize_before else None
|
||||
self.encoder = TransformerEncoder(encoder_layer, num_encoder_layers, encoder_norm)
|
||||
|
||||
decoder_layer = TransformerDecoderLayer(d_model, nhead, dim_feedforward,
|
||||
dropout, activation, normalize_before)
|
||||
decoder_norm = nn.LayerNorm(d_model)
|
||||
self.decoder = TransformerDecoder(decoder_layer, num_decoder_layers, decoder_norm,
|
||||
return_intermediate=return_intermediate_dec)
|
||||
|
||||
self._reset_parameters()
|
||||
|
||||
self.d_model = d_model
|
||||
self.nhead = nhead
|
||||
|
||||
def _reset_parameters(self):
|
||||
for p in self.parameters():
|
||||
if p.dim() > 1:
|
||||
nn.init.xavier_uniform_(p)
|
||||
|
||||
def forward(self, src, mask, query_embed, pos_embed):
|
||||
# flatten NxCxHxW to HWxNxC
|
||||
bs, c, h, w = src.shape
|
||||
src = src.flatten(2).permute(2, 0, 1)
|
||||
pos_embed = pos_embed.flatten(2).permute(2, 0, 1)
|
||||
query_embed = query_embed.unsqueeze(1).repeat(1, bs, 1)
|
||||
mask = mask.flatten(1)
|
||||
|
||||
tgt = torch.zeros_like(query_embed)
|
||||
memory = self.encoder(src, src_key_padding_mask=mask, pos=pos_embed)
|
||||
hs = self.decoder(tgt, memory, memory_key_padding_mask=mask,
|
||||
pos=pos_embed, query_pos=query_embed)
|
||||
return hs.transpose(1, 2), memory.permute(1, 2, 0).view(bs, c, h, w)
|
||||
|
||||
|
||||
class TransformerEncoder(nn.Module):
|
||||
|
||||
def __init__(self, encoder_layer, num_layers, norm=None):
|
||||
super().__init__()
|
||||
self.layers = _get_clones(encoder_layer, num_layers)
|
||||
self.num_layers = num_layers
|
||||
self.norm = norm
|
||||
|
||||
def forward(self, src,
|
||||
mask: Optional[Tensor] = None,
|
||||
src_key_padding_mask: Optional[Tensor] = None,
|
||||
pos: Optional[Tensor] = None):
|
||||
output = src
|
||||
|
||||
for layer in self.layers:
|
||||
output = layer(output, src_mask=mask,
|
||||
src_key_padding_mask=src_key_padding_mask, pos=pos)
|
||||
|
||||
if self.norm is not None:
|
||||
output = self.norm(output)
|
||||
|
||||
return output
|
||||
|
||||
|
||||
class TransformerDecoder(nn.Module):
|
||||
|
||||
def __init__(self, decoder_layer, num_layers, norm=None, return_intermediate=False):
|
||||
super().__init__()
|
||||
self.layers = _get_clones(decoder_layer, num_layers)
|
||||
self.num_layers = num_layers
|
||||
self.norm = norm
|
||||
self.return_intermediate = return_intermediate
|
||||
|
||||
def forward(self, tgt, memory,
|
||||
tgt_mask: Optional[Tensor] = None,
|
||||
memory_mask: Optional[Tensor] = None,
|
||||
tgt_key_padding_mask: Optional[Tensor] = None,
|
||||
memory_key_padding_mask: Optional[Tensor] = None,
|
||||
pos: Optional[Tensor] = None,
|
||||
query_pos: Optional[Tensor] = None):
|
||||
output = tgt
|
||||
|
||||
intermediate = []
|
||||
|
||||
for layer in self.layers:
|
||||
output = layer(output, memory, tgt_mask=tgt_mask,
|
||||
memory_mask=memory_mask,
|
||||
tgt_key_padding_mask=tgt_key_padding_mask,
|
||||
memory_key_padding_mask=memory_key_padding_mask,
|
||||
pos=pos, query_pos=query_pos)
|
||||
if self.return_intermediate:
|
||||
intermediate.append(self.norm(output))
|
||||
|
||||
if self.norm is not None:
|
||||
output = self.norm(output)
|
||||
if self.return_intermediate:
|
||||
intermediate.pop()
|
||||
intermediate.append(output)
|
||||
|
||||
if self.return_intermediate:
|
||||
return torch.stack(intermediate)
|
||||
|
||||
return output.unsqueeze(0)
|
||||
|
||||
|
||||
class TransformerEncoderLayer(nn.Module):
|
||||
|
||||
def __init__(self, d_model, nhead, dim_feedforward=2048, dropout=0.1,
|
||||
activation="relu", normalize_before=False):
|
||||
super().__init__()
|
||||
self.d_model = d_model
|
||||
self.self_attn = nn.MultiheadAttention(d_model, nhead, dropout=dropout)
|
||||
# Implementation of Feedforward model
|
||||
self.linear1 = nn.Linear(d_model, dim_feedforward)
|
||||
self.dropout = nn.Dropout(dropout)
|
||||
self.linear2 = nn.Linear(dim_feedforward, d_model)
|
||||
|
||||
self.norm1 = nn.LayerNorm(d_model)
|
||||
self.norm2 = nn.LayerNorm(d_model)
|
||||
self.dropout1 = nn.Dropout(dropout)
|
||||
self.dropout2 = nn.Dropout(dropout)
|
||||
|
||||
self.activation = _get_activation_fn(activation)
|
||||
self.normalize_before = normalize_before
|
||||
|
||||
def with_pos_embed(self, tensor, pos: Optional[Tensor]):
|
||||
return tensor if pos is None else tensor + pos
|
||||
|
||||
def forward_post(self,
|
||||
src,
|
||||
src_mask: Optional[Tensor] = None,
|
||||
src_key_padding_mask: Optional[Tensor] = None,
|
||||
pos: Optional[Tensor] = None):
|
||||
q = k = self.with_pos_embed(src, pos)
|
||||
src2 = self.self_attn(q, k, value=src, attn_mask=src_mask,
|
||||
key_padding_mask=src_key_padding_mask)[0]
|
||||
src = src + self.dropout1(src2)
|
||||
src = self.norm1(src)
|
||||
src2 = self.linear2(self.dropout(self.activation(self.linear1(src))))
|
||||
src = src + self.dropout2(src2)
|
||||
src = self.norm2(src)
|
||||
return src
|
||||
|
||||
def forward_pre(self, src,
|
||||
src_mask: Optional[Tensor] = None,
|
||||
src_key_padding_mask: Optional[Tensor] = None,
|
||||
pos: Optional[Tensor] = None):
|
||||
src2 = self.norm1(src)
|
||||
q = k = self.with_pos_embed(src2, pos)
|
||||
src2 = self.self_attn(q, k, value=src2, attn_mask=src_mask,
|
||||
key_padding_mask=src_key_padding_mask)[0]
|
||||
src = src + self.dropout1(src2)
|
||||
src2 = self.norm2(src)
|
||||
src2 = self.linear2(self.dropout(self.activation(self.linear1(src2))))
|
||||
src = src + self.dropout2(src2)
|
||||
return src
|
||||
|
||||
def forward(self, src,
|
||||
src_mask: Optional[Tensor] = None,
|
||||
src_key_padding_mask: Optional[Tensor] = None,
|
||||
pos: Optional[Tensor] = None):
|
||||
if self.normalize_before:
|
||||
return self.forward_pre(src, src_mask, src_key_padding_mask, pos)
|
||||
return self.forward_post(src, src_mask, src_key_padding_mask, pos)
|
||||
|
||||
|
||||
class TransformerDecoderLayer(nn.Module):
|
||||
|
||||
def __init__(self, d_model, nhead, dim_feedforward=2048, dropout=0.1,
|
||||
activation="relu", normalize_before=False):
|
||||
super().__init__()
|
||||
self.self_attn = nn.MultiheadAttention(d_model, nhead, dropout=dropout)
|
||||
self.multihead_attn = nn.MultiheadAttention(d_model, nhead, dropout=dropout)
|
||||
# Implementation of Feedforward model
|
||||
self.d_model = d_model
|
||||
self.linear1 = nn.Linear(d_model, dim_feedforward)
|
||||
self.dropout = nn.Dropout(dropout)
|
||||
self.linear2 = nn.Linear(dim_feedforward, d_model)
|
||||
|
||||
self.norm1 = nn.LayerNorm(d_model)
|
||||
self.norm2 = nn.LayerNorm(d_model)
|
||||
self.norm3 = nn.LayerNorm(d_model)
|
||||
self.dropout1 = nn.Dropout(dropout)
|
||||
self.dropout2 = nn.Dropout(dropout)
|
||||
self.dropout3 = nn.Dropout(dropout)
|
||||
|
||||
self.activation = _get_activation_fn(activation)
|
||||
self.normalize_before = normalize_before
|
||||
|
||||
def with_pos_embed(self, tensor, pos: Optional[Tensor]):
|
||||
return tensor if pos is None else tensor + pos
|
||||
|
||||
def forward_post(self, tgt, memory,
|
||||
tgt_mask: Optional[Tensor] = None,
|
||||
memory_mask: Optional[Tensor] = None,
|
||||
tgt_key_padding_mask: Optional[Tensor] = None,
|
||||
memory_key_padding_mask: Optional[Tensor] = None,
|
||||
pos: Optional[Tensor] = None,
|
||||
query_pos: Optional[Tensor] = None):
|
||||
|
||||
q = k = self.with_pos_embed(tgt, query_pos)
|
||||
tgt2 = self.self_attn(q, k, value=tgt, attn_mask=tgt_mask,
|
||||
key_padding_mask=tgt_key_padding_mask)[0]
|
||||
tgt = tgt + self.dropout1(tgt2)
|
||||
tgt = self.norm1(tgt)
|
||||
tgt2 = self.multihead_attn(query=self.with_pos_embed(tgt, query_pos),
|
||||
key=self.with_pos_embed(memory, pos),
|
||||
value=memory, attn_mask=memory_mask,
|
||||
key_padding_mask=memory_key_padding_mask)[0]
|
||||
tgt = tgt + self.dropout2(tgt2)
|
||||
tgt = self.norm2(tgt)
|
||||
tgt2 = self.linear2(self.dropout(self.activation(self.linear1(tgt))))
|
||||
tgt = tgt + self.dropout3(tgt2)
|
||||
tgt = self.norm3(tgt)
|
||||
return tgt
|
||||
|
||||
def forward_pre(self, tgt, memory,
|
||||
tgt_mask: Optional[Tensor] = None,
|
||||
memory_mask: Optional[Tensor] = None,
|
||||
tgt_key_padding_mask: Optional[Tensor] = None,
|
||||
memory_key_padding_mask: Optional[Tensor] = None,
|
||||
pos: Optional[Tensor] = None,
|
||||
query_pos: Optional[Tensor] = None):
|
||||
tgt2 = self.norm1(tgt)
|
||||
q = k = self.with_pos_embed(tgt2, query_pos)
|
||||
tgt2 = self.self_attn(q, k, value=tgt2, attn_mask=tgt_mask,
|
||||
key_padding_mask=tgt_key_padding_mask)[0]
|
||||
tgt = tgt + self.dropout1(tgt2)
|
||||
tgt2 = self.norm2(tgt)
|
||||
tgt2 = self.multihead_attn(query=self.with_pos_embed(tgt2, query_pos),
|
||||
key=self.with_pos_embed(memory, pos),
|
||||
value=memory, attn_mask=memory_mask,
|
||||
key_padding_mask=memory_key_padding_mask)[0]
|
||||
tgt = tgt + self.dropout2(tgt2)
|
||||
tgt2 = self.norm3(tgt)
|
||||
tgt2 = self.linear2(self.dropout(self.activation(self.linear1(tgt2))))
|
||||
tgt = tgt + self.dropout3(tgt2)
|
||||
return tgt
|
||||
|
||||
def forward(self, tgt, memory,
|
||||
tgt_mask: Optional[Tensor] = None,
|
||||
memory_mask: Optional[Tensor] = None,
|
||||
tgt_key_padding_mask: Optional[Tensor] = None,
|
||||
memory_key_padding_mask: Optional[Tensor] = None,
|
||||
pos: Optional[Tensor] = None,
|
||||
query_pos: Optional[Tensor] = None):
|
||||
if self.normalize_before:
|
||||
return self.forward_pre(tgt, memory, tgt_mask, memory_mask,
|
||||
tgt_key_padding_mask, memory_key_padding_mask, pos, query_pos)
|
||||
return self.forward_post(tgt, memory, tgt_mask, memory_mask,
|
||||
tgt_key_padding_mask, memory_key_padding_mask, pos, query_pos)
|
||||
|
||||
|
||||
def _get_clone(module):
|
||||
return copy.deepcopy(module)
|
||||
|
||||
def _get_clones(module, N):
|
||||
return nn.ModuleList([copy.deepcopy(module) for i in range(N)])
|
||||
|
||||
|
||||
def build_transformer(args):
|
||||
return Transformer(
|
||||
d_model=args.hidden_dim,
|
||||
dropout=args.dropout,
|
||||
nhead=args.nheads,
|
||||
dim_feedforward=args.dim_feedforward,
|
||||
num_encoder_layers=args.enc_layers,
|
||||
num_decoder_layers=args.dec_layers,
|
||||
normalize_before=args.pre_norm,
|
||||
return_intermediate_dec=True,
|
||||
)
|
||||
|
||||
|
||||
def _get_activation_fn(activation):
|
||||
"""Return an activation function given a string"""
|
||||
if activation == "relu":
|
||||
return F.relu
|
||||
if activation == "gelu":
|
||||
return F.gelu
|
||||
if activation == "glu":
|
||||
return F.glu
|
||||
raise RuntimeError(F"activation should be relu/gelu, not {activation}.")
|
||||
@@ -1,192 +0,0 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
|
||||
"""
|
||||
Various positional encodings for the transformer.
|
||||
"""
|
||||
import math
|
||||
from typing import List, Optional
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
from torch import Tensor, nn
|
||||
|
||||
# from util.misc import NestedTensor
|
||||
|
||||
|
||||
class NestedTensor(object):
|
||||
|
||||
def __init__(self, tensors, mask: Optional[Tensor]):
|
||||
self.tensors = tensors
|
||||
self.mask = mask
|
||||
|
||||
def to(self, device):
|
||||
# type: (Device) -> NestedTensor # noqa
|
||||
cast_tensor = self.tensors.to(device)
|
||||
mask = self.mask
|
||||
if mask is not None:
|
||||
assert mask is not None
|
||||
cast_mask = mask.to(device)
|
||||
else:
|
||||
cast_mask = None
|
||||
return NestedTensor(cast_tensor, cast_mask)
|
||||
|
||||
def decompose(self):
|
||||
return self.tensors, self.mask
|
||||
|
||||
def __repr__(self):
|
||||
return str(self.tensors)
|
||||
|
||||
|
||||
class PositionEmbeddingSine(nn.Module):
|
||||
"""
|
||||
This is a more standard version of the position embedding, very similar to the one
|
||||
used by the Attention is all you need paper, generalized to work on images.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
num_pos_feats=64,
|
||||
temperature=10000,
|
||||
normalize=False,
|
||||
scale=None):
|
||||
super().__init__()
|
||||
self.num_pos_feats = num_pos_feats
|
||||
self.temperature = temperature
|
||||
self.normalize = normalize
|
||||
if scale is not None and normalize is False:
|
||||
raise ValueError("normalize should be True if scale is passed")
|
||||
if scale is None:
|
||||
scale = 2 * math.pi
|
||||
self.scale = scale
|
||||
|
||||
def forward(self, tensor_list: NestedTensor):
|
||||
x = tensor_list.tensors
|
||||
mask = tensor_list.mask
|
||||
assert mask is not None
|
||||
not_mask = ~mask
|
||||
y_embed = not_mask.cumsum(1, dtype=torch.float32)
|
||||
x_embed = not_mask.cumsum(2, dtype=torch.float32)
|
||||
if self.normalize:
|
||||
eps = 1e-6
|
||||
y_embed = y_embed / (y_embed[:, -1:, :] + eps) * self.scale
|
||||
x_embed = x_embed / (x_embed[:, :, -1:] + eps) * self.scale
|
||||
|
||||
dim_t = torch.arange(self.num_pos_feats,
|
||||
dtype=torch.float32,
|
||||
device=x.device)
|
||||
dim_t = self.temperature**(2 * (dim_t // 2) / self.num_pos_feats)
|
||||
|
||||
pos_x = x_embed[:, :, :, None] / dim_t
|
||||
pos_y = y_embed[:, :, :, None] / dim_t
|
||||
pos_x = torch.stack(
|
||||
(pos_x[:, :, :, 0::2].sin(), pos_x[:, :, :, 1::2].cos()),
|
||||
dim=4).flatten(3)
|
||||
pos_y = torch.stack(
|
||||
(pos_y[:, :, :, 0::2].sin(), pos_y[:, :, :, 1::2].cos()),
|
||||
dim=4).flatten(3)
|
||||
pos = torch.cat((pos_y, pos_x), dim=3).permute(0, 3, 1, 2)
|
||||
return pos
|
||||
|
||||
|
||||
class PositionEmbeddingLearned(nn.Module):
|
||||
"""
|
||||
Absolute pos embedding, learned.
|
||||
"""
|
||||
|
||||
def __init__(self, num_pos_feats=256):
|
||||
super().__init__()
|
||||
self.row_embed = nn.Embedding(50, num_pos_feats)
|
||||
self.col_embed = nn.Embedding(50, num_pos_feats)
|
||||
self.reset_parameters()
|
||||
|
||||
def reset_parameters(self):
|
||||
nn.init.uniform_(self.row_embed.weight)
|
||||
nn.init.uniform_(self.col_embed.weight)
|
||||
|
||||
def forward(self, tensor_list: NestedTensor):
|
||||
x = tensor_list.tensors
|
||||
h, w = x.shape[-2:]
|
||||
i = torch.arange(w, device=x.device)
|
||||
j = torch.arange(h, device=x.device)
|
||||
x_emb = self.col_embed(i)
|
||||
y_emb = self.row_embed(j)
|
||||
pos = torch.cat([
|
||||
x_emb.unsqueeze(0).repeat(h, 1, 1),
|
||||
y_emb.unsqueeze(1).repeat(1, w, 1),
|
||||
],
|
||||
dim=-1).permute(2, 0, 1).unsqueeze(0).repeat(
|
||||
x.shape[0], 1, 1, 1)
|
||||
return pos
|
||||
|
||||
|
||||
class PositionEmbeddingSine1D(nn.Module):
|
||||
|
||||
def __init__(self, d_model, max_len=500, batch_first=False):
|
||||
super().__init__()
|
||||
self.batch_first = batch_first
|
||||
|
||||
pe = torch.zeros(max_len, d_model)
|
||||
position = torch.arange(0, max_len, dtype=torch.float).unsqueeze(1)
|
||||
div_term = torch.exp(
|
||||
torch.arange(0, d_model, 2).float() * (-np.log(10000.0) / d_model))
|
||||
pe[:, 0::2] = torch.sin(position * div_term)
|
||||
pe[:, 1::2] = torch.cos(position * div_term)
|
||||
pe = pe.unsqueeze(0).transpose(0, 1)
|
||||
|
||||
self.register_buffer('pe', pe)
|
||||
|
||||
def forward(self, x):
|
||||
# not used in the final model
|
||||
if self.batch_first:
|
||||
pos = self.pe.permute(1, 0, 2)[:, :x.shape[1], :]
|
||||
else:
|
||||
pos = self.pe[:x.shape[0], :]
|
||||
return pos
|
||||
|
||||
|
||||
class PositionEmbeddingLearned1D(nn.Module):
|
||||
|
||||
def __init__(self, d_model, max_len=500, batch_first=False):
|
||||
super().__init__()
|
||||
self.batch_first = batch_first
|
||||
# self.dropout = nn.Dropout(p=dropout)
|
||||
|
||||
self.pe = nn.Parameter(torch.zeros(max_len, 1, d_model))
|
||||
# self.pe = pe.unsqueeze(0).transpose(0, 1)
|
||||
|
||||
self.reset_parameters()
|
||||
|
||||
def reset_parameters(self):
|
||||
nn.init.uniform_(self.pe)
|
||||
|
||||
def forward(self, x):
|
||||
# not used in the final model
|
||||
if self.batch_first:
|
||||
pos = self.pe.permute(1, 0, 2)[:, :x.shape[1], :]
|
||||
else:
|
||||
x = x + self.pe[:x.shape[0], :]
|
||||
return x
|
||||
# return self.dropout(x)
|
||||
|
||||
|
||||
def build_position_encoding(N_steps,
|
||||
position_embedding="sine",
|
||||
embedding_dim="1D"):
|
||||
# N_steps = hidden_dim // 2
|
||||
if embedding_dim == "1D":
|
||||
if position_embedding in ('v2', 'sine'):
|
||||
position_embedding = PositionEmbeddingSine1D(N_steps)
|
||||
elif position_embedding in ('v3', 'learned'):
|
||||
position_embedding = PositionEmbeddingLearned1D(N_steps)
|
||||
else:
|
||||
raise ValueError(f"not supported {position_embedding}")
|
||||
elif embedding_dim == "2D":
|
||||
if position_embedding in ('v2', 'sine'):
|
||||
# TODO find a better way of exposing other arguments
|
||||
position_embedding = PositionEmbeddingSine(N_steps, normalize=True)
|
||||
elif position_embedding in ('v3', 'learned'):
|
||||
position_embedding = PositionEmbeddingLearned(N_steps)
|
||||
else:
|
||||
raise ValueError(f"not supported {position_embedding}")
|
||||
else:
|
||||
raise ValueError(f"not supported {embedding_dim}")
|
||||
|
||||
return position_embedding
|
||||
@@ -1,30 +0,0 @@
|
||||
import numpy as np
|
||||
import torch
|
||||
from torch import nn
|
||||
|
||||
|
||||
class PositionalEncoding(nn.Module):
|
||||
|
||||
def __init__(self, d_model, dropout=0.1, max_len=5000, batch_first=False):
|
||||
super().__init__()
|
||||
self.batch_first = batch_first
|
||||
|
||||
self.dropout = nn.Dropout(p=dropout)
|
||||
|
||||
pe = torch.zeros(max_len, d_model)
|
||||
position = torch.arange(0, max_len, dtype=torch.float).unsqueeze(1)
|
||||
div_term = torch.exp(torch.arange(
|
||||
0, d_model, 2).float() * (-np.log(10000.0) / d_model))
|
||||
pe[:, 0::2] = torch.sin(position * div_term)
|
||||
pe[:, 1::2] = torch.cos(position * div_term)
|
||||
pe = pe.unsqueeze(0).transpose(0, 1)
|
||||
|
||||
self.register_buffer("pe", pe)
|
||||
|
||||
def forward(self, x):
|
||||
# not used in the final model
|
||||
if self.batch_first:
|
||||
x = x + self.pe.permute(1, 0, 2)[:, : x.shape[1], :]
|
||||
else:
|
||||
x = x + self.pe[: x.shape[0], :]
|
||||
return self.dropout(x)
|
||||
@@ -1,37 +0,0 @@
|
||||
import torch.nn as nn
|
||||
|
||||
def remove_padding(tensors, lengths):
|
||||
return [tensor[:tensor_length] for tensor, tensor_length in zip(tensors, lengths)]
|
||||
|
||||
class AutoParams(nn.Module):
|
||||
def __init__(self, **kargs):
|
||||
try:
|
||||
for param in self.needed_params:
|
||||
if param in kargs:
|
||||
setattr(self, param, kargs[param])
|
||||
else:
|
||||
raise ValueError(f"{param} is needed.")
|
||||
except :
|
||||
pass
|
||||
|
||||
try:
|
||||
for param, default in self.optional_params.items():
|
||||
if param in kargs and kargs[param] is not None:
|
||||
setattr(self, param, kargs[param])
|
||||
else:
|
||||
setattr(self, param, default)
|
||||
except :
|
||||
pass
|
||||
super().__init__()
|
||||
|
||||
|
||||
# taken from joeynmt repo
|
||||
def freeze_params(module: nn.Module) -> None:
|
||||
"""
|
||||
Freeze the parameters of this module,
|
||||
i.e. do not update them during training
|
||||
|
||||
:param module: freeze parameters of this module
|
||||
"""
|
||||
for _, p in module.named_parameters():
|
||||
p.requires_grad = False
|
||||
@@ -1,79 +0,0 @@
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
# load example data
|
||||
def load_example_input(txt_path):
|
||||
file = open(txt_path, "r")
|
||||
Lines = file.readlines()
|
||||
count = 0
|
||||
texts, lens = [], []
|
||||
# Strips the newline character
|
||||
for line in Lines:
|
||||
count += 1
|
||||
s = line.strip()
|
||||
s_l = s.split(" ")[0]
|
||||
s_t = s[(len(s_l) + 1):]
|
||||
lens.append(int(s_l))
|
||||
texts.append(s_t)
|
||||
print("Length-{}: {}".format(s_l, s_t))
|
||||
return texts, lens
|
||||
|
||||
|
||||
# render batch
|
||||
def render_batch(npy_dir, execute_python="./scripts/visualize_motion.sh", mode="sequence"):
|
||||
os.system(f"{execute_python} {npy_dir} {mode}")
|
||||
|
||||
|
||||
# render
|
||||
def render(execute_python, npy_path, jointtype, cfg_path):
|
||||
# execute_python = "/apdcephfs/share_1227775/shingxchen/libs/blender_bpy/blender-2.93.2-linux-x64/blender"
|
||||
# execute_python = "/apdcephfs/share_1227775/mingzhenzhu/jiangbiao/libs/blender-2.93.2-linux-x64/blender"
|
||||
export_scripts = "render.py"
|
||||
|
||||
os.system(
|
||||
f"{execute_python} --background --python {export_scripts} -- --cfg={cfg_path} --npy={npy_path} --joint_type={jointtype}"
|
||||
)
|
||||
|
||||
fig_path = Path(str(npy_path).replace(".npy", ".png"))
|
||||
return fig_path
|
||||
|
||||
|
||||
# origin render
|
||||
# def render(npy_path, jointtype):
|
||||
# execute_python = '/apdcephfs/share_1227775/shingxchen/libs/blender_bpy/blender-2.93.2-linux-x64/blender'
|
||||
# export_scripts = 'render.py'
|
||||
|
||||
# os.system(f"{execute_python} --background --python {export_scripts} -- npy={npy_path} jointstype={jointtype}")
|
||||
|
||||
# fig_path = Path(str(npy_path).replace(".npy",".png"))
|
||||
# return fig_path
|
||||
|
||||
# export fbx with hand params from pkl files
|
||||
# refer to /apdcephfs/share_1227775/shingxchen/AIMotion/TMOST/scripts/fbx_output_smplx.py
|
||||
def export_fbx_hand(pkl_path):
|
||||
input = pkl_path
|
||||
output = pkl_path.replace(".pkl", ".fbx")
|
||||
|
||||
execute_python = "/apdcephfs/share_1227775/shingxchen/libs/blender_bpy/blender-2.93.2-linux-x64/blender"
|
||||
export_scripts = "./scripts/fbx_output_smplx.py"
|
||||
os.system(
|
||||
f"{execute_python} -noaudio --background --python {export_scripts}\
|
||||
--input {input} \
|
||||
--output {output}"
|
||||
)
|
||||
|
||||
|
||||
# export fbx without hand params from pkl files
|
||||
# refer to /apdcephfs/share_1227775/shingxchen/AIMotion/TMOST/scripts/fbx_output.py
|
||||
def export_fbx(pkl_path):
|
||||
input = pkl_path
|
||||
output = pkl_path.replace(".pkl", ".fbx")
|
||||
|
||||
execute_python = "/apdcephfs/share_1227775/shingxchen/libs/blender_bpy/blender-2.93.2-linux-x64/blender"
|
||||
export_scripts = "./scripts/fbx_output.py"
|
||||
os.system(
|
||||
f"{execute_python} -noaudio --background --python {export_scripts}\
|
||||
--input {input} \
|
||||
--output {output}"
|
||||
)
|
||||
@@ -1,84 +0,0 @@
|
||||
from .geometry_tools import *
|
||||
|
||||
|
||||
def rep_to_rep(oldtype, newtype, rotations):
|
||||
if newtype in ["matrix"]:
|
||||
return to_matrix(oldtype, rotations)
|
||||
|
||||
if oldtype in ["rotvec", "axisangle"]:
|
||||
return axis_angle_to(newtype, rotations)
|
||||
elif oldtype in ["matrix"]:
|
||||
return matrix_to(newtype, rotations)
|
||||
else:
|
||||
raise NotImplementedError("Only rotvec and matrix are supported.")
|
||||
|
||||
def nfeats_of(rottype):
|
||||
if rottype in ["rotvec", "axisangle"]:
|
||||
return 3
|
||||
elif rottype in ["rotquat", "quaternion"]:
|
||||
return 4
|
||||
elif rottype in ["rot6d", "6drot", "rotation6d"]:
|
||||
return 6
|
||||
elif rottype in ["rotmat"]:
|
||||
return 9
|
||||
else:
|
||||
return TypeError("This rotation type doesn't have features.")
|
||||
|
||||
|
||||
def axis_angle_to(newtype, rotations):
|
||||
if newtype in ["matrix"]:
|
||||
rotations = axis_angle_to_matrix(rotations)
|
||||
return rotations
|
||||
elif newtype in ["rotmat"]:
|
||||
rotations = axis_angle_to_matrix(rotations)
|
||||
rotations = matrix_to("rotmat", rotations)
|
||||
return rotations
|
||||
elif newtype in ["rot6d", "6drot", "rotation6d"]:
|
||||
rotations = axis_angle_to_matrix(rotations)
|
||||
rotations = matrix_to("rot6d", rotations)
|
||||
return rotations
|
||||
elif newtype in ["rotquat", "quaternion"]:
|
||||
rotations = axis_angle_to_quaternion(rotations)
|
||||
return rotations
|
||||
elif newtype in ["rotvec", "axisangle"]:
|
||||
return rotations
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
def matrix_to(newtype, rotations):
|
||||
if newtype in ["matrix"]:
|
||||
return rotations
|
||||
if newtype in ["rotmat"]:
|
||||
rotations = rotations.reshape((*rotations.shape[:-2], 9))
|
||||
return rotations
|
||||
elif newtype in ["rot6d", "6drot", "rotation6d"]:
|
||||
rotations = matrix_to_rotation_6d(rotations)
|
||||
return rotations
|
||||
elif newtype in ["rotquat", "quaternion"]:
|
||||
rotations = matrix_to_quaternion(rotations)
|
||||
return rotations
|
||||
elif newtype in ["rotvec", "axisangle"]:
|
||||
rotations = matrix_to_axis_angle(rotations)
|
||||
return rotations
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
def to_matrix(oldtype, rotations):
|
||||
if oldtype in ["matrix"]:
|
||||
return rotations
|
||||
if oldtype in ["rotmat"]:
|
||||
rotations = rotations.reshape((*rotations.shape[:-2], 3, 3))
|
||||
return rotations
|
||||
elif oldtype in ["rot6d", "6drot", "rotation6d"]:
|
||||
rotations = rotation_6d_to_matrix(rotations)
|
||||
return rotations
|
||||
elif oldtype in ["rotquat", "quaternion"]:
|
||||
rotations = quaternion_to_matrix(rotations)
|
||||
return rotations
|
||||
elif oldtype in ["rotvec", "axisangle"]:
|
||||
rotations = axis_angle_to_matrix(rotations)
|
||||
return rotations
|
||||
else:
|
||||
raise NotImplementedError
|
||||
@@ -1,18 +0,0 @@
|
||||
import numpy as np
|
||||
import torch
|
||||
import random
|
||||
|
||||
|
||||
def fixseed(seed):
|
||||
random.seed(seed)
|
||||
np.random.seed(seed)
|
||||
torch.manual_seed(seed)
|
||||
|
||||
|
||||
SEED = 10
|
||||
EVALSEED = 0
|
||||
# Provoc warning: not fully functionnal yet
|
||||
# torch.set_deterministic(True)
|
||||
torch.backends.cudnn.benchmark = False
|
||||
|
||||
fixseed(SEED)
|
||||
@@ -1,550 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Max-Planck-Gesellschaft zur Förderung der Wissenschaften e.V. (MPG) is
|
||||
# holder of all proprietary rights on this computer program.
|
||||
# You can only use this computer program if you have closed
|
||||
# a license agreement with MPG or you get the right to use the computer
|
||||
# program from someone who is authorized to grant you that right.
|
||||
# Any use of the computer program without a valid license is prohibited and
|
||||
# liable to prosecution.
|
||||
#
|
||||
# Copyright©2019 Max-Planck-Gesellschaft zur Förderung
|
||||
# der Wissenschaften e.V. (MPG). acting on behalf of its Max Planck Institute
|
||||
# for Intelligent Systems. All rights reserved.
|
||||
#
|
||||
# Contact: ps-license@tuebingen.mpg.de
|
||||
|
||||
import torch
|
||||
import numpy as np
|
||||
from torch.nn import functional as F
|
||||
|
||||
|
||||
def axis_angle_to_quaternion(axis_angle):
|
||||
"""
|
||||
Convert rotations given as axis/angle to quaternions.
|
||||
|
||||
Args:
|
||||
axis_angle: Rotations given as a vector in axis angle form,
|
||||
as a tensor of shape (..., 3), where the magnitude is
|
||||
the angle turned anticlockwise in radians around the
|
||||
vector's direction.
|
||||
|
||||
Returns:
|
||||
quaternions with real part first, as tensor of shape (..., 4).
|
||||
"""
|
||||
angles = torch.norm(axis_angle, p=2, dim=-1, keepdim=True)
|
||||
half_angles = 0.5 * angles
|
||||
eps = 1e-6
|
||||
small_angles = angles.abs() < eps
|
||||
sin_half_angles_over_angles = torch.empty_like(angles)
|
||||
sin_half_angles_over_angles[~small_angles] = (
|
||||
torch.sin(half_angles[~small_angles]) / angles[~small_angles])
|
||||
# for x small, sin(x/2) is about x/2 - (x/2)^3/6
|
||||
# so sin(x/2)/x is about 1/2 - (x*x)/48
|
||||
sin_half_angles_over_angles[small_angles] = (
|
||||
0.5 - (angles[small_angles] * angles[small_angles]) / 48)
|
||||
quaternions = torch.cat(
|
||||
[torch.cos(half_angles), axis_angle * sin_half_angles_over_angles],
|
||||
dim=-1)
|
||||
return quaternions
|
||||
|
||||
|
||||
def quaternion_to_matrix(quaternions):
|
||||
"""
|
||||
Convert rotations given as quaternions to rotation matrices.
|
||||
|
||||
Args:
|
||||
quaternions: quaternions with real part first,
|
||||
as tensor of shape (..., 4).
|
||||
|
||||
Returns:
|
||||
Rotation matrices as tensor of shape (..., 3, 3).
|
||||
"""
|
||||
r, i, j, k = torch.unbind(quaternions, -1)
|
||||
two_s = 2.0 / (quaternions * quaternions).sum(-1)
|
||||
|
||||
o = torch.stack(
|
||||
(
|
||||
1 - two_s * (j * j + k * k),
|
||||
two_s * (i * j - k * r),
|
||||
two_s * (i * k + j * r),
|
||||
two_s * (i * j + k * r),
|
||||
1 - two_s * (i * i + k * k),
|
||||
two_s * (j * k - i * r),
|
||||
two_s * (i * k - j * r),
|
||||
two_s * (j * k + i * r),
|
||||
1 - two_s * (i * i + j * j),
|
||||
),
|
||||
-1,
|
||||
)
|
||||
return o.reshape(quaternions.shape[:-1] + (3, 3))
|
||||
|
||||
|
||||
def axis_angle_to_matrix(axis_angle):
|
||||
"""
|
||||
Convert rotations given as axis/angle to rotation matrices.
|
||||
|
||||
Args:
|
||||
axis_angle: Rotations given as a vector in axis angle form,
|
||||
as a tensor of shape (..., 3), where the magnitude is
|
||||
the angle turned anticlockwise in radians around the
|
||||
vector's direction.
|
||||
|
||||
Returns:
|
||||
Rotation matrices as tensor of shape (..., 3, 3).
|
||||
"""
|
||||
return quaternion_to_matrix(axis_angle_to_quaternion(axis_angle))
|
||||
|
||||
|
||||
def matrix_of_angles(cos, sin, inv=False, dim=2):
|
||||
assert dim in [2, 3]
|
||||
sin = -sin if inv else sin
|
||||
if dim == 2:
|
||||
row1 = torch.stack((cos, -sin), axis=-1)
|
||||
row2 = torch.stack((sin, cos), axis=-1)
|
||||
return torch.stack((row1, row2), axis=-2)
|
||||
elif dim == 3:
|
||||
row1 = torch.stack((cos, -sin, 0 * cos), axis=-1)
|
||||
row2 = torch.stack((sin, cos, 0 * cos), axis=-1)
|
||||
row3 = torch.stack((0 * sin, 0 * cos, 1 + 0 * cos), axis=-1)
|
||||
return torch.stack((row1, row2, row3), axis=-2)
|
||||
|
||||
|
||||
def matrot2axisangle(matrots):
|
||||
# This function is borrowed from https://github.com/davrempe/humor/utils/transforms.py
|
||||
# axisang N x 3
|
||||
'''
|
||||
:param matrots: N*num_joints*9
|
||||
:return: N*num_joints*3
|
||||
'''
|
||||
import cv2
|
||||
batch_size = matrots.shape[0]
|
||||
matrots = matrots.reshape([batch_size, -1, 9])
|
||||
out_axisangle = []
|
||||
for mIdx in range(matrots.shape[0]):
|
||||
cur_axisangle = []
|
||||
for jIdx in range(matrots.shape[1]):
|
||||
a = cv2.Rodrigues(matrots[mIdx,
|
||||
jIdx:jIdx + 1, :].reshape(3,
|
||||
3))[0].reshape(
|
||||
(1, 3))
|
||||
cur_axisangle.append(a)
|
||||
|
||||
out_axisangle.append(np.array(cur_axisangle).reshape([1, -1, 3]))
|
||||
return np.vstack(out_axisangle)
|
||||
|
||||
|
||||
def axisangle2matrots(axisangle):
|
||||
# This function is borrowed from https://github.com/davrempe/humor/utils/transforms.py
|
||||
# axisang N x 3
|
||||
'''
|
||||
:param axisangle: N*num_joints*3
|
||||
:return: N*num_joints*9
|
||||
'''
|
||||
import cv2
|
||||
batch_size = axisangle.shape[0]
|
||||
axisangle = axisangle.reshape([batch_size, -1, 3])
|
||||
out_matrot = []
|
||||
for mIdx in range(axisangle.shape[0]):
|
||||
cur_axisangle = []
|
||||
for jIdx in range(axisangle.shape[1]):
|
||||
a = cv2.Rodrigues(axisangle[mIdx, jIdx:jIdx + 1, :].reshape(1,
|
||||
3))[0]
|
||||
cur_axisangle.append(a)
|
||||
|
||||
out_matrot.append(np.array(cur_axisangle).reshape([1, -1, 9]))
|
||||
return np.vstack(out_matrot)
|
||||
|
||||
|
||||
def batch_rodrigues(axisang):
|
||||
# This function is borrowed from https://github.com/MandyMo/pytorch_HMR/blob/master/src/util.py#L37
|
||||
# axisang N x 3
|
||||
axisang_norm = torch.norm(axisang + 1e-8, p=2, dim=1)
|
||||
angle = torch.unsqueeze(axisang_norm, -1)
|
||||
axisang_normalized = torch.div(axisang, angle)
|
||||
angle = angle * 0.5
|
||||
v_cos = torch.cos(angle)
|
||||
v_sin = torch.sin(angle)
|
||||
|
||||
quat = torch.cat([v_cos, v_sin * axisang_normalized], dim=1)
|
||||
rot_mat = quat2mat(quat)
|
||||
rot_mat = rot_mat.view(rot_mat.shape[0], 9)
|
||||
return rot_mat
|
||||
|
||||
|
||||
def quat2mat(quat):
|
||||
"""
|
||||
This function is borrowed from https://github.com/MandyMo/pytorch_HMR/blob/master/src/util.py#L50
|
||||
|
||||
Convert quaternion coefficients to rotation matrix.
|
||||
Args:
|
||||
quat: size = [batch_size, 4] 4 <===>(w, x, y, z)
|
||||
Returns:
|
||||
Rotation matrix corresponding to the quaternion -- size = [batch_size, 3, 3]
|
||||
"""
|
||||
norm_quat = quat
|
||||
norm_quat = norm_quat / norm_quat.norm(p=2, dim=1, keepdim=True)
|
||||
w, x, y, z = norm_quat[:, 0], norm_quat[:, 1], norm_quat[:,
|
||||
2], norm_quat[:,
|
||||
3]
|
||||
|
||||
batch_size = quat.size(0)
|
||||
|
||||
w2, x2, y2, z2 = w.pow(2), x.pow(2), y.pow(2), z.pow(2)
|
||||
wx, wy, wz = w * x, w * y, w * z
|
||||
xy, xz, yz = x * y, x * z, y * z
|
||||
|
||||
rotMat = torch.stack([
|
||||
w2 + x2 - y2 - z2, 2 * xy - 2 * wz, 2 * wy + 2 * xz, 2 * wz + 2 * xy,
|
||||
w2 - x2 + y2 - z2, 2 * yz - 2 * wx, 2 * xz - 2 * wy, 2 * wx + 2 * yz,
|
||||
w2 - x2 - y2 + z2
|
||||
],
|
||||
dim=1).view(batch_size, 3, 3)
|
||||
return rotMat
|
||||
|
||||
|
||||
def rotation_matrix_to_angle_axis(rotation_matrix):
|
||||
"""
|
||||
This function is borrowed from https://github.com/kornia/kornia
|
||||
|
||||
Convert 3x4 rotation matrix to Rodrigues vector
|
||||
|
||||
Args:
|
||||
rotation_matrix (Tensor): rotation matrix.
|
||||
|
||||
Returns:
|
||||
Tensor: Rodrigues vector transformation.
|
||||
|
||||
Shape:
|
||||
- Input: :math:`(N, 3, 4)`
|
||||
- Output: :math:`(N, 3)`
|
||||
|
||||
Example:
|
||||
>>> input = torch.rand(2, 3, 4) # Nx4x4
|
||||
>>> output = tgm.rotation_matrix_to_angle_axis(input) # Nx3
|
||||
"""
|
||||
if rotation_matrix.shape[1:] == (3, 3):
|
||||
rot_mat = rotation_matrix.reshape(-1, 3, 3)
|
||||
hom = torch.tensor([0, 0, 1],
|
||||
dtype=torch.float32,
|
||||
device=rotation_matrix.device).reshape(
|
||||
1, 3, 1).expand(rot_mat.shape[0], -1, -1)
|
||||
rotation_matrix = torch.cat([rot_mat, hom], dim=-1)
|
||||
|
||||
quaternion = rotation_matrix_to_quaternion(rotation_matrix)
|
||||
aa = quaternion_to_angle_axis(quaternion)
|
||||
aa[torch.isnan(aa)] = 0.0
|
||||
return aa
|
||||
|
||||
|
||||
def quaternion_to_angle_axis(quaternion: torch.Tensor) -> torch.Tensor:
|
||||
"""
|
||||
This function is borrowed from https://github.com/kornia/kornia
|
||||
|
||||
Convert quaternion vector to angle axis of rotation.
|
||||
|
||||
Adapted from ceres C++ library: ceres-solver/include/ceres/rotation.h
|
||||
|
||||
Args:
|
||||
quaternion (torch.Tensor): tensor with quaternions.
|
||||
|
||||
Return:
|
||||
torch.Tensor: tensor with angle axis of rotation.
|
||||
|
||||
Shape:
|
||||
- Input: :math:`(*, 4)` where `*` means, any number of dimensions
|
||||
- Output: :math:`(*, 3)`
|
||||
|
||||
Example:
|
||||
>>> quaternion = torch.rand(2, 4) # Nx4
|
||||
>>> angle_axis = tgm.quaternion_to_angle_axis(quaternion) # Nx3
|
||||
"""
|
||||
if not torch.is_tensor(quaternion):
|
||||
raise TypeError("Input type is not a torch.Tensor. Got {}".format(
|
||||
type(quaternion)))
|
||||
|
||||
if not quaternion.shape[-1] == 4:
|
||||
raise ValueError(
|
||||
"Input must be a tensor of shape Nx4 or 4. Got {}".format(
|
||||
quaternion.shape))
|
||||
# unpack input and compute conversion
|
||||
q1: torch.Tensor = quaternion[..., 1]
|
||||
q2: torch.Tensor = quaternion[..., 2]
|
||||
q3: torch.Tensor = quaternion[..., 3]
|
||||
sin_squared_theta: torch.Tensor = q1 * q1 + q2 * q2 + q3 * q3
|
||||
|
||||
sin_theta: torch.Tensor = torch.sqrt(sin_squared_theta)
|
||||
cos_theta: torch.Tensor = quaternion[..., 0]
|
||||
two_theta: torch.Tensor = 2.0 * torch.where(
|
||||
cos_theta < 0.0, torch.atan2(-sin_theta, -cos_theta),
|
||||
torch.atan2(sin_theta, cos_theta))
|
||||
|
||||
k_pos: torch.Tensor = two_theta / sin_theta
|
||||
k_neg: torch.Tensor = 2.0 * torch.ones_like(sin_theta)
|
||||
k: torch.Tensor = torch.where(sin_squared_theta > 0.0, k_pos, k_neg)
|
||||
|
||||
angle_axis: torch.Tensor = torch.zeros_like(quaternion)[..., :3]
|
||||
angle_axis[..., 0] += q1 * k
|
||||
angle_axis[..., 1] += q2 * k
|
||||
angle_axis[..., 2] += q3 * k
|
||||
return angle_axis
|
||||
|
||||
|
||||
def rotation_matrix_to_quaternion(rotation_matrix, eps=1e-6):
|
||||
"""
|
||||
This function is borrowed from https://github.com/kornia/kornia
|
||||
|
||||
Convert 3x4 rotation matrix to 4d quaternion vector
|
||||
|
||||
This algorithm is based on algorithm described in
|
||||
https://github.com/KieranWynn/pyquaternion/blob/master/pyquaternion/quaternion.py#L201
|
||||
|
||||
Args:
|
||||
rotation_matrix (Tensor): the rotation matrix to convert.
|
||||
|
||||
Return:
|
||||
Tensor: the rotation in quaternion
|
||||
|
||||
Shape:
|
||||
- Input: :math:`(N, 3, 4)`
|
||||
- Output: :math:`(N, 4)`
|
||||
|
||||
Example:
|
||||
>>> input = torch.rand(4, 3, 4) # Nx3x4
|
||||
>>> output = tgm.rotation_matrix_to_quaternion(input) # Nx4
|
||||
"""
|
||||
if not torch.is_tensor(rotation_matrix):
|
||||
raise TypeError("Input type is not a torch.Tensor. Got {}".format(
|
||||
type(rotation_matrix)))
|
||||
|
||||
if len(rotation_matrix.shape) > 3:
|
||||
raise ValueError(
|
||||
"Input size must be a three dimensional tensor. Got {}".format(
|
||||
rotation_matrix.shape))
|
||||
if not rotation_matrix.shape[-2:] == (3, 4):
|
||||
raise ValueError(
|
||||
"Input size must be a N x 3 x 4 tensor. Got {}".format(
|
||||
rotation_matrix.shape))
|
||||
|
||||
rmat_t = torch.transpose(rotation_matrix, 1, 2)
|
||||
|
||||
mask_d2 = rmat_t[:, 2, 2] < eps
|
||||
|
||||
mask_d0_d1 = rmat_t[:, 0, 0] > rmat_t[:, 1, 1]
|
||||
mask_d0_nd1 = rmat_t[:, 0, 0] < -rmat_t[:, 1, 1]
|
||||
|
||||
t0 = 1 + rmat_t[:, 0, 0] - rmat_t[:, 1, 1] - rmat_t[:, 2, 2]
|
||||
q0 = torch.stack([
|
||||
rmat_t[:, 1, 2] - rmat_t[:, 2, 1], t0,
|
||||
rmat_t[:, 0, 1] + rmat_t[:, 1, 0], rmat_t[:, 2, 0] + rmat_t[:, 0, 2]
|
||||
], -1)
|
||||
t0_rep = t0.repeat(4, 1).t()
|
||||
|
||||
t1 = 1 - rmat_t[:, 0, 0] + rmat_t[:, 1, 1] - rmat_t[:, 2, 2]
|
||||
q1 = torch.stack([
|
||||
rmat_t[:, 2, 0] - rmat_t[:, 0, 2], rmat_t[:, 0, 1] + rmat_t[:, 1, 0],
|
||||
t1, rmat_t[:, 1, 2] + rmat_t[:, 2, 1]
|
||||
], -1)
|
||||
t1_rep = t1.repeat(4, 1).t()
|
||||
|
||||
t2 = 1 - rmat_t[:, 0, 0] - rmat_t[:, 1, 1] + rmat_t[:, 2, 2]
|
||||
q2 = torch.stack([
|
||||
rmat_t[:, 0, 1] - rmat_t[:, 1, 0], rmat_t[:, 2, 0] + rmat_t[:, 0, 2],
|
||||
rmat_t[:, 1, 2] + rmat_t[:, 2, 1], t2
|
||||
], -1)
|
||||
t2_rep = t2.repeat(4, 1).t()
|
||||
|
||||
t3 = 1 + rmat_t[:, 0, 0] + rmat_t[:, 1, 1] + rmat_t[:, 2, 2]
|
||||
q3 = torch.stack([
|
||||
t3, rmat_t[:, 1, 2] - rmat_t[:, 2, 1],
|
||||
rmat_t[:, 2, 0] - rmat_t[:, 0, 2], rmat_t[:, 0, 1] - rmat_t[:, 1, 0]
|
||||
], -1)
|
||||
t3_rep = t3.repeat(4, 1).t()
|
||||
|
||||
mask_c0 = mask_d2 * mask_d0_d1
|
||||
mask_c1 = mask_d2 * ~mask_d0_d1
|
||||
mask_c2 = ~mask_d2 * mask_d0_nd1
|
||||
mask_c3 = ~mask_d2 * ~mask_d0_nd1
|
||||
mask_c0 = mask_c0.view(-1, 1).type_as(q0)
|
||||
mask_c1 = mask_c1.view(-1, 1).type_as(q1)
|
||||
mask_c2 = mask_c2.view(-1, 1).type_as(q2)
|
||||
mask_c3 = mask_c3.view(-1, 1).type_as(q3)
|
||||
|
||||
q = q0 * mask_c0 + q1 * mask_c1 + q2 * mask_c2 + q3 * mask_c3
|
||||
q /= torch.sqrt(t0_rep * mask_c0 + t1_rep * mask_c1 + # noqa
|
||||
t2_rep * mask_c2 + t3_rep * mask_c3) # noqa
|
||||
q *= 0.5
|
||||
return q
|
||||
|
||||
|
||||
def estimate_translation_np(S,
|
||||
joints_2d,
|
||||
joints_conf,
|
||||
focal_length=5000.,
|
||||
img_size=224.):
|
||||
"""
|
||||
This function is borrowed from https://github.com/nkolot/SPIN/utils/geometry.py
|
||||
|
||||
Find camera translation that brings 3D joints S closest to 2D the corresponding joints_2d.
|
||||
Input:
|
||||
S: (25, 3) 3D joint locations
|
||||
joints: (25, 3) 2D joint locations and confidence
|
||||
Returns:
|
||||
(3,) camera translation vector
|
||||
"""
|
||||
|
||||
num_joints = S.shape[0]
|
||||
# focal length
|
||||
f = np.array([focal_length, focal_length])
|
||||
# optical center
|
||||
center = np.array([img_size / 2., img_size / 2.])
|
||||
|
||||
# transformations
|
||||
Z = np.reshape(np.tile(S[:, 2], (2, 1)).T, -1)
|
||||
XY = np.reshape(S[:, 0:2], -1)
|
||||
O = np.tile(center, num_joints)
|
||||
F = np.tile(f, num_joints)
|
||||
weight2 = np.reshape(np.tile(np.sqrt(joints_conf), (2, 1)).T, -1)
|
||||
|
||||
# least squares
|
||||
Q = np.array([
|
||||
F * np.tile(np.array([1, 0]), num_joints),
|
||||
F * np.tile(np.array([0, 1]), num_joints),
|
||||
O - np.reshape(joints_2d, -1)
|
||||
]).T
|
||||
c = (np.reshape(joints_2d, -1) - O) * Z - F * XY
|
||||
|
||||
# weighted least squares
|
||||
W = np.diagflat(weight2)
|
||||
Q = np.dot(W, Q)
|
||||
c = np.dot(W, c)
|
||||
|
||||
# square matrix
|
||||
A = np.dot(Q.T, Q)
|
||||
b = np.dot(Q.T, c)
|
||||
|
||||
# solution
|
||||
trans = np.linalg.solve(A, b)
|
||||
|
||||
return trans
|
||||
|
||||
|
||||
def estimate_translation(S, joints_2d, focal_length=5000., img_size=224.):
|
||||
"""
|
||||
This function is borrowed from https://github.com/nkolot/SPIN/utils/geometry.py
|
||||
|
||||
Find camera translation that brings 3D joints S closest to 2D the corresponding joints_2d.
|
||||
Input:
|
||||
S: (B, 49, 3) 3D joint locations
|
||||
joints: (B, 49, 3) 2D joint locations and confidence
|
||||
Returns:
|
||||
(B, 3) camera translation vectors
|
||||
"""
|
||||
|
||||
device = S.device
|
||||
# Use only joints 25:49 (GT joints)
|
||||
S = S[:, 25:, :].cpu().numpy()
|
||||
joints_2d = joints_2d[:, 25:, :].cpu().numpy()
|
||||
joints_conf = joints_2d[:, :, -1]
|
||||
joints_2d = joints_2d[:, :, :-1]
|
||||
trans = np.zeros((S.shape[0], 3), dtype=np.float6432)
|
||||
# Find the translation for each example in the batch
|
||||
for i in range(S.shape[0]):
|
||||
S_i = S[i]
|
||||
joints_i = joints_2d[i]
|
||||
conf_i = joints_conf[i]
|
||||
trans[i] = estimate_translation_np(S_i,
|
||||
joints_i,
|
||||
conf_i,
|
||||
focal_length=focal_length,
|
||||
img_size=img_size)
|
||||
return torch.from_numpy(trans).to(device)
|
||||
|
||||
|
||||
def rot6d_to_rotmat_spin(x):
|
||||
"""Convert 6D rotation representation to 3x3 rotation matrix.
|
||||
Based on Zhou et al., "On the Continuity of Rotation Representations in Neural Networks", CVPR 2019
|
||||
Input:
|
||||
(B,6) Batch of 6-D rotation representations
|
||||
Output:
|
||||
(B,3,3) Batch of corresponding rotation matrices
|
||||
"""
|
||||
x = x.view(-1, 3, 2)
|
||||
a1 = x[:, :, 0]
|
||||
a2 = x[:, :, 1]
|
||||
b1 = F.normalize(a1)
|
||||
b2 = F.normalize(a2 - torch.einsum('bi,bi->b', b1, a2).unsqueeze(-1) * b1)
|
||||
|
||||
# inp = a2 - torch.einsum('bi,bi->b', b1, a2).unsqueeze(-1) * b1
|
||||
# denom = inp.pow(2).sum(dim=1).sqrt().unsqueeze(-1) + 1e-8
|
||||
# b2 = inp / denom
|
||||
|
||||
b3 = torch.cross(b1, b2)
|
||||
return torch.stack((b1, b2, b3), dim=-1)
|
||||
|
||||
|
||||
def rot6d_to_rotmat(x):
|
||||
x = x.view(-1, 3, 2)
|
||||
|
||||
# Normalize the first vector
|
||||
b1 = F.normalize(x[:, :, 0], dim=1, eps=1e-6)
|
||||
|
||||
dot_prod = torch.sum(b1 * x[:, :, 1], dim=1, keepdim=True)
|
||||
# Compute the second vector by finding the orthogonal complement to it
|
||||
b2 = F.normalize(x[:, :, 1] - dot_prod * b1, dim=-1, eps=1e-6)
|
||||
|
||||
# Finish building the basis by taking the cross product
|
||||
b3 = torch.cross(b1, b2, dim=1)
|
||||
rot_mats = torch.stack([b1, b2, b3], dim=-1)
|
||||
|
||||
return rot_mats
|
||||
|
||||
|
||||
import mGPT.utils.rotation_conversions as rotation_conversions
|
||||
|
||||
|
||||
def rot6d(x_rotations, pose_rep):
|
||||
time, njoints, feats = x_rotations.shape
|
||||
|
||||
# Compute rotations (convert only masked sequences output)
|
||||
if pose_rep == "rotvec":
|
||||
rotations = rotation_conversions.axis_angle_to_matrix(x_rotations)
|
||||
elif pose_rep == "rotmat":
|
||||
rotations = x_rotations.view(njoints, 3, 3)
|
||||
elif pose_rep == "rotquat":
|
||||
rotations = rotation_conversions.quaternion_to_matrix(x_rotations)
|
||||
elif pose_rep == "rot6d":
|
||||
rotations = rotation_conversions.rotation_6d_to_matrix(x_rotations)
|
||||
else:
|
||||
raise NotImplementedError("No geometry for this one.")
|
||||
|
||||
rotations_6d = rotation_conversions.matrix_to_rotation_6d(rotations)
|
||||
return rotations_6d
|
||||
|
||||
|
||||
def rot6d_batch(x_rotations, pose_rep):
|
||||
nsamples, time, njoints, feats = x_rotations.shape
|
||||
|
||||
# Compute rotations (convert only masked sequences output)
|
||||
if pose_rep == "rotvec":
|
||||
rotations = rotation_conversions.axis_angle_to_matrix(x_rotations)
|
||||
elif pose_rep == "rotmat":
|
||||
rotations = x_rotations.view(-1, njoints, 3, 3)
|
||||
elif pose_rep == "rotquat":
|
||||
rotations = rotation_conversions.quaternion_to_matrix(x_rotations)
|
||||
elif pose_rep == "rot6d":
|
||||
rotations = rotation_conversions.rotation_6d_to_matrix(x_rotations)
|
||||
else:
|
||||
raise NotImplementedError("No geometry for this one.")
|
||||
|
||||
rotations_6d = rotation_conversions.matrix_to_rotation_6d(rotations)
|
||||
return rotations_6d
|
||||
|
||||
|
||||
def rot6d_to_rotvec_batch(pose):
|
||||
# nsamples, time, njoints, feats = rot6d.shape
|
||||
bs, nfeats = pose.shape
|
||||
rot6d = pose.reshape(bs, 24, 6)
|
||||
rotations = rotation_conversions.rotation_6d_to_matrix(rot6d)
|
||||
rotvec = rotation_conversions.matrix_to_axis_angle(rotations)
|
||||
return rotvec.reshape(bs, 24 * 3)
|
||||
@@ -1,566 +0,0 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
# Check PYTORCH3D_LICENCE before use
|
||||
|
||||
import functools
|
||||
from typing import Optional
|
||||
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
|
||||
|
||||
"""
|
||||
The transformation matrices returned from the functions in this file assume
|
||||
the points on which the transformation will be applied are column vectors.
|
||||
i.e. the R matrix is structured as
|
||||
|
||||
R = [
|
||||
[Rxx, Rxy, Rxz],
|
||||
[Ryx, Ryy, Ryz],
|
||||
[Rzx, Rzy, Rzz],
|
||||
] # (3, 3)
|
||||
|
||||
This matrix can be applied to column vectors by post multiplication
|
||||
by the points e.g.
|
||||
|
||||
points = [[0], [1], [2]] # (3 x 1) xyz coordinates of a point
|
||||
transformed_points = R * points
|
||||
|
||||
To apply the same matrix to points which are row vectors, the R matrix
|
||||
can be transposed and pre multiplied by the points:
|
||||
|
||||
e.g.
|
||||
points = [[0, 1, 2]] # (1 x 3) xyz coordinates of a point
|
||||
transformed_points = points * R.transpose(1, 0)
|
||||
"""
|
||||
|
||||
|
||||
# Added
|
||||
def matrix_of_angles(cos, sin, inv=False, dim=2):
|
||||
assert dim in [2, 3]
|
||||
sin = -sin if inv else sin
|
||||
if dim == 2:
|
||||
row1 = torch.stack((cos, -sin), axis=-1)
|
||||
row2 = torch.stack((sin, cos), axis=-1)
|
||||
return torch.stack((row1, row2), axis=-2)
|
||||
elif dim == 3:
|
||||
row1 = torch.stack((cos, -sin, 0*cos), axis=-1)
|
||||
row2 = torch.stack((sin, cos, 0*cos), axis=-1)
|
||||
row3 = torch.stack((0*sin, 0*cos, 1+0*cos), axis=-1)
|
||||
return torch.stack((row1, row2, row3),axis=-2)
|
||||
|
||||
|
||||
def quaternion_to_matrix(quaternions):
|
||||
"""
|
||||
Convert rotations given as quaternions to rotation matrices.
|
||||
|
||||
Args:
|
||||
quaternions: quaternions with real part first,
|
||||
as tensor of shape (..., 4).
|
||||
|
||||
Returns:
|
||||
Rotation matrices as tensor of shape (..., 3, 3).
|
||||
"""
|
||||
r, i, j, k = torch.unbind(quaternions, -1)
|
||||
two_s = 2.0 / (quaternions * quaternions).sum(-1)
|
||||
|
||||
o = torch.stack(
|
||||
(
|
||||
1 - two_s * (j * j + k * k),
|
||||
two_s * (i * j - k * r),
|
||||
two_s * (i * k + j * r),
|
||||
two_s * (i * j + k * r),
|
||||
1 - two_s * (i * i + k * k),
|
||||
two_s * (j * k - i * r),
|
||||
two_s * (i * k - j * r),
|
||||
two_s * (j * k + i * r),
|
||||
1 - two_s * (i * i + j * j),
|
||||
),
|
||||
-1,
|
||||
)
|
||||
return o.reshape(quaternions.shape[:-1] + (3, 3))
|
||||
|
||||
|
||||
def _copysign(a, b):
|
||||
"""
|
||||
Return a tensor where each element has the absolute value taken from the,
|
||||
corresponding element of a, with sign taken from the corresponding
|
||||
element of b. This is like the standard copysign floating-point operation,
|
||||
but is not careful about negative 0 and NaN.
|
||||
|
||||
Args:
|
||||
a: source tensor.
|
||||
b: tensor whose signs will be used, of the same shape as a.
|
||||
|
||||
Returns:
|
||||
Tensor of the same shape as a with the signs of b.
|
||||
"""
|
||||
signs_differ = (a < 0) != (b < 0)
|
||||
return torch.where(signs_differ, -a, a)
|
||||
|
||||
|
||||
def _sqrt_positive_part(x):
|
||||
"""
|
||||
Returns torch.sqrt(torch.max(0, x))
|
||||
but with a zero subgradient where x is 0.
|
||||
"""
|
||||
ret = torch.zeros_like(x)
|
||||
positive_mask = x > 0
|
||||
ret[positive_mask] = torch.sqrt(x[positive_mask])
|
||||
return ret
|
||||
|
||||
|
||||
def matrix_to_quaternion(matrix):
|
||||
"""
|
||||
Convert rotations given as rotation matrices to quaternions.
|
||||
|
||||
Args:
|
||||
matrix: Rotation matrices as tensor of shape (..., 3, 3).
|
||||
|
||||
Returns:
|
||||
quaternions with real part first, as tensor of shape (..., 4).
|
||||
"""
|
||||
if matrix.size(-1) != 3 or matrix.size(-2) != 3:
|
||||
raise ValueError(f"Invalid rotation matrix shape f{matrix.shape}.")
|
||||
m00 = matrix[..., 0, 0]
|
||||
m11 = matrix[..., 1, 1]
|
||||
m22 = matrix[..., 2, 2]
|
||||
o0 = 0.5 * _sqrt_positive_part(1 + m00 + m11 + m22)
|
||||
x = 0.5 * _sqrt_positive_part(1 + m00 - m11 - m22)
|
||||
y = 0.5 * _sqrt_positive_part(1 - m00 + m11 - m22)
|
||||
z = 0.5 * _sqrt_positive_part(1 - m00 - m11 + m22)
|
||||
o1 = _copysign(x, matrix[..., 2, 1] - matrix[..., 1, 2])
|
||||
o2 = _copysign(y, matrix[..., 0, 2] - matrix[..., 2, 0])
|
||||
o3 = _copysign(z, matrix[..., 1, 0] - matrix[..., 0, 1])
|
||||
return torch.stack((o0, o1, o2, o3), -1)
|
||||
|
||||
|
||||
def _axis_angle_rotation(axis: str, angle):
|
||||
"""
|
||||
Return the rotation matrices for one of the rotations about an axis
|
||||
of which Euler angles describe, for each value of the angle given.
|
||||
|
||||
Args:
|
||||
axis: Axis label "X" or "Y or "Z".
|
||||
angle: any shape tensor of Euler angles in radians
|
||||
|
||||
Returns:
|
||||
Rotation matrices as tensor of shape (..., 3, 3).
|
||||
"""
|
||||
|
||||
cos = torch.cos(angle)
|
||||
sin = torch.sin(angle)
|
||||
one = torch.ones_like(angle)
|
||||
zero = torch.zeros_like(angle)
|
||||
|
||||
if axis == "X":
|
||||
R_flat = (one, zero, zero, zero, cos, -sin, zero, sin, cos)
|
||||
if axis == "Y":
|
||||
R_flat = (cos, zero, sin, zero, one, zero, -sin, zero, cos)
|
||||
if axis == "Z":
|
||||
R_flat = (cos, -sin, zero, sin, cos, zero, zero, zero, one)
|
||||
|
||||
return torch.stack(R_flat, -1).reshape(angle.shape + (3, 3))
|
||||
|
||||
|
||||
def euler_angles_to_matrix(euler_angles, convention: str):
|
||||
"""
|
||||
Convert rotations given as Euler angles in radians to rotation matrices.
|
||||
|
||||
Args:
|
||||
euler_angles: Euler angles in radians as tensor of shape (..., 3).
|
||||
convention: Convention string of three uppercase letters from
|
||||
{"X", "Y", and "Z"}.
|
||||
|
||||
Returns:
|
||||
Rotation matrices as tensor of shape (..., 3, 3).
|
||||
"""
|
||||
if euler_angles.dim() == 0 or euler_angles.shape[-1] != 3:
|
||||
raise ValueError("Invalid input euler angles.")
|
||||
if len(convention) != 3:
|
||||
raise ValueError("Convention must have 3 letters.")
|
||||
if convention[1] in (convention[0], convention[2]):
|
||||
raise ValueError(f"Invalid convention {convention}.")
|
||||
for letter in convention:
|
||||
if letter not in ("X", "Y", "Z"):
|
||||
raise ValueError(f"Invalid letter {letter} in convention string.")
|
||||
matrices = map(_axis_angle_rotation, convention, torch.unbind(euler_angles, -1))
|
||||
return functools.reduce(torch.matmul, matrices)
|
||||
|
||||
|
||||
def _angle_from_tan(
|
||||
axis: str, other_axis: str, data, horizontal: bool, tait_bryan: bool
|
||||
):
|
||||
"""
|
||||
Extract the first or third Euler angle from the two members of
|
||||
the matrix which are positive constant times its sine and cosine.
|
||||
|
||||
Args:
|
||||
axis: Axis label "X" or "Y or "Z" for the angle we are finding.
|
||||
other_axis: Axis label "X" or "Y or "Z" for the middle axis in the
|
||||
convention.
|
||||
data: Rotation matrices as tensor of shape (..., 3, 3).
|
||||
horizontal: Whether we are looking for the angle for the third axis,
|
||||
which means the relevant entries are in the same row of the
|
||||
rotation matrix. If not, they are in the same column.
|
||||
tait_bryan: Whether the first and third axes in the convention differ.
|
||||
|
||||
Returns:
|
||||
Euler Angles in radians for each matrix in data as a tensor
|
||||
of shape (...).
|
||||
"""
|
||||
|
||||
i1, i2 = {"X": (2, 1), "Y": (0, 2), "Z": (1, 0)}[axis]
|
||||
if horizontal:
|
||||
i2, i1 = i1, i2
|
||||
even = (axis + other_axis) in ["XY", "YZ", "ZX"]
|
||||
if horizontal == even:
|
||||
return torch.atan2(data[..., i1], data[..., i2])
|
||||
if tait_bryan:
|
||||
return torch.atan2(-data[..., i2], data[..., i1])
|
||||
return torch.atan2(data[..., i2], -data[..., i1])
|
||||
|
||||
|
||||
def _index_from_letter(letter: str):
|
||||
if letter == "X":
|
||||
return 0
|
||||
if letter == "Y":
|
||||
return 1
|
||||
if letter == "Z":
|
||||
return 2
|
||||
|
||||
|
||||
def matrix_to_euler_angles(matrix, convention: str):
|
||||
"""
|
||||
Convert rotations given as rotation matrices to Euler angles in radians.
|
||||
|
||||
Args:
|
||||
matrix: Rotation matrices as tensor of shape (..., 3, 3).
|
||||
convention: Convention string of three uppercase letters.
|
||||
|
||||
Returns:
|
||||
Euler angles in radians as tensor of shape (..., 3).
|
||||
"""
|
||||
if len(convention) != 3:
|
||||
raise ValueError("Convention must have 3 letters.")
|
||||
if convention[1] in (convention[0], convention[2]):
|
||||
raise ValueError(f"Invalid convention {convention}.")
|
||||
for letter in convention:
|
||||
if letter not in ("X", "Y", "Z"):
|
||||
raise ValueError(f"Invalid letter {letter} in convention string.")
|
||||
if matrix.size(-1) != 3 or matrix.size(-2) != 3:
|
||||
raise ValueError(f"Invalid rotation matrix shape f{matrix.shape}.")
|
||||
i0 = _index_from_letter(convention[0])
|
||||
i2 = _index_from_letter(convention[2])
|
||||
tait_bryan = i0 != i2
|
||||
if tait_bryan:
|
||||
central_angle = torch.asin(
|
||||
matrix[..., i0, i2] * (-1.0 if i0 - i2 in [-1, 2] else 1.0)
|
||||
)
|
||||
else:
|
||||
central_angle = torch.acos(matrix[..., i0, i0])
|
||||
|
||||
o = (
|
||||
_angle_from_tan(
|
||||
convention[0], convention[1], matrix[..., i2], False, tait_bryan
|
||||
),
|
||||
central_angle,
|
||||
_angle_from_tan(
|
||||
convention[2], convention[1], matrix[..., i0, :], True, tait_bryan
|
||||
),
|
||||
)
|
||||
return torch.stack(o, -1)
|
||||
|
||||
|
||||
def random_quaternions(
|
||||
n: int, dtype: Optional[torch.dtype] = None, device=None, requires_grad=False
|
||||
):
|
||||
"""
|
||||
Generate random quaternions representing rotations,
|
||||
i.e. versors with nonnegative real part.
|
||||
|
||||
Args:
|
||||
n: Number of quaternions in a batch to return.
|
||||
dtype: Type to return.
|
||||
device: Desired device of returned tensor. Default:
|
||||
uses the current device for the default tensor type.
|
||||
requires_grad: Whether the resulting tensor should have the gradient
|
||||
flag set.
|
||||
|
||||
Returns:
|
||||
Quaternions as tensor of shape (N, 4).
|
||||
"""
|
||||
o = torch.randn((n, 4), dtype=dtype, device=device, requires_grad=requires_grad)
|
||||
s = (o * o).sum(1)
|
||||
o = o / _copysign(torch.sqrt(s), o[:, 0])[:, None]
|
||||
return o
|
||||
|
||||
|
||||
def random_rotations(
|
||||
n: int, dtype: Optional[torch.dtype] = None, device=None, requires_grad=False
|
||||
):
|
||||
"""
|
||||
Generate random rotations as 3x3 rotation matrices.
|
||||
|
||||
Args:
|
||||
n: Number of rotation matrices in a batch to return.
|
||||
dtype: Type to return.
|
||||
device: Device of returned tensor. Default: if None,
|
||||
uses the current device for the default tensor type.
|
||||
requires_grad: Whether the resulting tensor should have the gradient
|
||||
flag set.
|
||||
|
||||
Returns:
|
||||
Rotation matrices as tensor of shape (n, 3, 3).
|
||||
"""
|
||||
quaternions = random_quaternions(
|
||||
n, dtype=dtype, device=device, requires_grad=requires_grad
|
||||
)
|
||||
return quaternion_to_matrix(quaternions)
|
||||
|
||||
|
||||
def random_rotation(
|
||||
dtype: Optional[torch.dtype] = None, device=None, requires_grad=False
|
||||
):
|
||||
"""
|
||||
Generate a single random 3x3 rotation matrix.
|
||||
|
||||
Args:
|
||||
dtype: Type to return
|
||||
device: Device of returned tensor. Default: if None,
|
||||
uses the current device for the default tensor type
|
||||
requires_grad: Whether the resulting tensor should have the gradient
|
||||
flag set
|
||||
|
||||
Returns:
|
||||
Rotation matrix as tensor of shape (3, 3).
|
||||
"""
|
||||
return random_rotations(1, dtype, device, requires_grad)[0]
|
||||
|
||||
|
||||
def standardize_quaternion(quaternions):
|
||||
"""
|
||||
Convert a unit quaternion to a standard form: one in which the real
|
||||
part is non negative.
|
||||
|
||||
Args:
|
||||
quaternions: Quaternions with real part first,
|
||||
as tensor of shape (..., 4).
|
||||
|
||||
Returns:
|
||||
Standardized quaternions as tensor of shape (..., 4).
|
||||
"""
|
||||
return torch.where(quaternions[..., 0:1] < 0, -quaternions, quaternions)
|
||||
|
||||
|
||||
def quaternion_raw_multiply(a, b):
|
||||
"""
|
||||
Multiply two quaternions.
|
||||
Usual torch rules for broadcasting apply.
|
||||
|
||||
Args:
|
||||
a: Quaternions as tensor of shape (..., 4), real part first.
|
||||
b: Quaternions as tensor of shape (..., 4), real part first.
|
||||
|
||||
Returns:
|
||||
The product of a and b, a tensor of quaternions shape (..., 4).
|
||||
"""
|
||||
aw, ax, ay, az = torch.unbind(a, -1)
|
||||
bw, bx, by, bz = torch.unbind(b, -1)
|
||||
ow = aw * bw - ax * bx - ay * by - az * bz
|
||||
ox = aw * bx + ax * bw + ay * bz - az * by
|
||||
oy = aw * by - ax * bz + ay * bw + az * bx
|
||||
oz = aw * bz + ax * by - ay * bx + az * bw
|
||||
return torch.stack((ow, ox, oy, oz), -1)
|
||||
|
||||
|
||||
def quaternion_multiply(a, b):
|
||||
"""
|
||||
Multiply two quaternions representing rotations, returning the quaternion
|
||||
representing their composition, i.e. the versor with nonnegative real part.
|
||||
Usual torch rules for broadcasting apply.
|
||||
|
||||
Args:
|
||||
a: Quaternions as tensor of shape (..., 4), real part first.
|
||||
b: Quaternions as tensor of shape (..., 4), real part first.
|
||||
|
||||
Returns:
|
||||
The product of a and b, a tensor of quaternions of shape (..., 4).
|
||||
"""
|
||||
ab = quaternion_raw_multiply(a, b)
|
||||
return standardize_quaternion(ab)
|
||||
|
||||
|
||||
def quaternion_invert(quaternion):
|
||||
"""
|
||||
Given a quaternion representing rotation, get the quaternion representing
|
||||
its inverse.
|
||||
|
||||
Args:
|
||||
quaternion: Quaternions as tensor of shape (..., 4), with real part
|
||||
first, which must be versors (unit quaternions).
|
||||
|
||||
Returns:
|
||||
The inverse, a tensor of quaternions of shape (..., 4).
|
||||
"""
|
||||
|
||||
return quaternion * quaternion.new_tensor([1, -1, -1, -1])
|
||||
|
||||
|
||||
def quaternion_apply(quaternion, point):
|
||||
"""
|
||||
Apply the rotation given by a quaternion to a 3D point.
|
||||
Usual torch rules for broadcasting apply.
|
||||
|
||||
Args:
|
||||
quaternion: Tensor of quaternions, real part first, of shape (..., 4).
|
||||
point: Tensor of 3D points of shape (..., 3).
|
||||
|
||||
Returns:
|
||||
Tensor of rotated points of shape (..., 3).
|
||||
"""
|
||||
if point.size(-1) != 3:
|
||||
raise ValueError(f"Points are not in 3D, f{point.shape}.")
|
||||
real_parts = point.new_zeros(point.shape[:-1] + (1,))
|
||||
point_as_quaternion = torch.cat((real_parts, point), -1)
|
||||
out = quaternion_raw_multiply(
|
||||
quaternion_raw_multiply(quaternion, point_as_quaternion),
|
||||
quaternion_invert(quaternion),
|
||||
)
|
||||
return out[..., 1:]
|
||||
|
||||
|
||||
def axis_angle_to_matrix(axis_angle):
|
||||
"""
|
||||
Convert rotations given as axis/angle to rotation matrices.
|
||||
|
||||
Args:
|
||||
axis_angle: Rotations given as a vector in axis angle form,
|
||||
as a tensor of shape (..., 3), where the magnitude is
|
||||
the angle turned anticlockwise in radians around the
|
||||
vector's direction.
|
||||
|
||||
Returns:
|
||||
Rotation matrices as tensor of shape (..., 3, 3).
|
||||
"""
|
||||
return quaternion_to_matrix(axis_angle_to_quaternion(axis_angle))
|
||||
|
||||
|
||||
def matrix_to_axis_angle(matrix):
|
||||
"""
|
||||
Convert rotations given as rotation matrices to axis/angle.
|
||||
|
||||
Args:
|
||||
matrix: Rotation matrices as tensor of shape (..., 3, 3).
|
||||
|
||||
Returns:
|
||||
Rotations given as a vector in axis angle form, as a tensor
|
||||
of shape (..., 3), where the magnitude is the angle
|
||||
turned anticlockwise in radians around the vector's
|
||||
direction.
|
||||
"""
|
||||
return quaternion_to_axis_angle(matrix_to_quaternion(matrix))
|
||||
|
||||
|
||||
def axis_angle_to_quaternion(axis_angle):
|
||||
"""
|
||||
Convert rotations given as axis/angle to quaternions.
|
||||
|
||||
Args:
|
||||
axis_angle: Rotations given as a vector in axis angle form,
|
||||
as a tensor of shape (..., 3), where the magnitude is
|
||||
the angle turned anticlockwise in radians around the
|
||||
vector's direction.
|
||||
|
||||
Returns:
|
||||
quaternions with real part first, as tensor of shape (..., 4).
|
||||
"""
|
||||
angles = torch.norm(axis_angle, p=2, dim=-1, keepdim=True)
|
||||
half_angles = 0.5 * angles
|
||||
eps = 1e-6
|
||||
small_angles = angles.abs() < eps
|
||||
sin_half_angles_over_angles = torch.empty_like(angles)
|
||||
sin_half_angles_over_angles[~small_angles] = (
|
||||
torch.sin(half_angles[~small_angles]) / angles[~small_angles]
|
||||
)
|
||||
# for x small, sin(x/2) is about x/2 - (x/2)^3/6
|
||||
# so sin(x/2)/x is about 1/2 - (x*x)/48
|
||||
sin_half_angles_over_angles[small_angles] = (
|
||||
0.5 - (angles[small_angles] * angles[small_angles]) / 48
|
||||
)
|
||||
quaternions = torch.cat(
|
||||
[torch.cos(half_angles), axis_angle * sin_half_angles_over_angles], dim=-1
|
||||
)
|
||||
return quaternions
|
||||
|
||||
|
||||
def quaternion_to_axis_angle(quaternions):
|
||||
"""
|
||||
Convert rotations given as quaternions to axis/angle.
|
||||
|
||||
Args:
|
||||
quaternions: quaternions with real part first,
|
||||
as tensor of shape (..., 4).
|
||||
|
||||
Returns:
|
||||
Rotations given as a vector in axis angle form, as a tensor
|
||||
of shape (..., 3), where the magnitude is the angle
|
||||
turned anticlockwise in radians around the vector's
|
||||
direction.
|
||||
"""
|
||||
norms = torch.norm(quaternions[..., 1:], p=2, dim=-1, keepdim=True)
|
||||
half_angles = torch.atan2(norms, quaternions[..., :1])
|
||||
angles = 2 * half_angles
|
||||
eps = 1e-6
|
||||
small_angles = angles.abs() < eps
|
||||
sin_half_angles_over_angles = torch.empty_like(angles)
|
||||
sin_half_angles_over_angles[~small_angles] = (
|
||||
torch.sin(half_angles[~small_angles]) / angles[~small_angles]
|
||||
)
|
||||
# for x small, sin(x/2) is about x/2 - (x/2)^3/6
|
||||
# so sin(x/2)/x is about 1/2 - (x*x)/48
|
||||
sin_half_angles_over_angles[small_angles] = (
|
||||
0.5 - (angles[small_angles] * angles[small_angles]) / 48
|
||||
)
|
||||
return quaternions[..., 1:] / sin_half_angles_over_angles
|
||||
|
||||
|
||||
def rotation_6d_to_matrix(d6: torch.Tensor) -> torch.Tensor:
|
||||
"""
|
||||
Converts 6D rotation representation by Zhou et al. [1] to rotation matrix
|
||||
using Gram--Schmidt orthogonalisation per Section B of [1].
|
||||
Args:
|
||||
d6: 6D rotation representation, of size (*, 6)
|
||||
|
||||
Returns:
|
||||
batch of rotation matrices of size (*, 3, 3)
|
||||
|
||||
[1] Zhou, Y., Barnes, C., Lu, J., Yang, J., & Li, H.
|
||||
On the Continuity of Rotation Representations in Neural Networks.
|
||||
IEEE Conference on Computer Vision and Pattern Recognition, 2019.
|
||||
Retrieved from http://arxiv.org/abs/1812.07035
|
||||
"""
|
||||
|
||||
a1, a2 = d6[..., :3], d6[..., 3:]
|
||||
b1 = F.normalize(a1, dim=-1)
|
||||
b2 = a2 - (b1 * a2).sum(-1, keepdim=True) * b1
|
||||
b2 = F.normalize(b2, dim=-1)
|
||||
b3 = torch.cross(b1, b2, dim=-1)
|
||||
return torch.stack((b1, b2, b3), dim=-2)
|
||||
|
||||
|
||||
def matrix_to_rotation_6d(matrix: torch.Tensor) -> torch.Tensor:
|
||||
"""
|
||||
Converts rotation matrices to 6D rotation representation by Zhou et al. [1]
|
||||
by dropping the last row. Note that 6D representation is not unique.
|
||||
Args:
|
||||
matrix: batch of rotation matrices of size (*, 3, 3)
|
||||
|
||||
Returns:
|
||||
6D rotation representation, of size (*, 6)
|
||||
|
||||
[1] Zhou, Y., Barnes, C., Lu, J., Yang, J., & Li, H.
|
||||
On the Continuity of Rotation Representations in Neural Networks.
|
||||
IEEE Conference on Computer Vision and Pattern Recognition, 2019.
|
||||
Retrieved from http://arxiv.org/abs/1812.07035
|
||||
"""
|
||||
return matrix[..., :2, :].clone().reshape(*matrix.size()[:-2], 6)
|
||||
@@ -1,444 +0,0 @@
|
||||
mmm_joints = [
|
||||
"root",
|
||||
"BP",
|
||||
"BT",
|
||||
"BLN",
|
||||
"BUN",
|
||||
"LS",
|
||||
"LE",
|
||||
"LW",
|
||||
"RS",
|
||||
"RE",
|
||||
"RW",
|
||||
"LH",
|
||||
"LK",
|
||||
"LA",
|
||||
"LMrot",
|
||||
"LF",
|
||||
"RH",
|
||||
"RK",
|
||||
"RA",
|
||||
"RMrot",
|
||||
"RF",
|
||||
]
|
||||
|
||||
humanml3d_joints = [
|
||||
"root",
|
||||
"RH",
|
||||
"LH",
|
||||
"BP",
|
||||
"RK",
|
||||
"LK",
|
||||
"BT",
|
||||
"RMrot",
|
||||
"LMrot",
|
||||
"BLN",
|
||||
"RF",
|
||||
"LF",
|
||||
"BMN",
|
||||
"RSI",
|
||||
"LSI",
|
||||
"BUN",
|
||||
"RS",
|
||||
"LS",
|
||||
"RE",
|
||||
"LE",
|
||||
"RW",
|
||||
"LW",
|
||||
]
|
||||
|
||||
smplx_joints = [
|
||||
"pelvis",
|
||||
"left_hip",
|
||||
"right_hip",
|
||||
"spine1",
|
||||
"left_knee",
|
||||
"right_knee",
|
||||
"spine2",
|
||||
"left_ankle",
|
||||
"right_ankle",
|
||||
"spine3",
|
||||
"left_foot",
|
||||
"right_foot",
|
||||
"neck",
|
||||
"left_collar",
|
||||
"right_collar",
|
||||
"head",
|
||||
"left_shoulder",
|
||||
"right_shoulder",
|
||||
"left_elbow",
|
||||
"right_elbow",
|
||||
"left_wrist",
|
||||
"right_wrist",
|
||||
"jaw",
|
||||
"left_eye_smplhf",
|
||||
"right_eye_smplhf",
|
||||
"left_index1",
|
||||
"left_index2",
|
||||
"left_index3",
|
||||
"left_middle1",
|
||||
"left_middle2",
|
||||
"left_middle3",
|
||||
"left_pinky1",
|
||||
"left_pinky2",
|
||||
"left_pinky3",
|
||||
"left_ring1",
|
||||
"left_ring2",
|
||||
"left_ring3",
|
||||
"left_thumb1",
|
||||
"left_thumb2",
|
||||
"left_thumb3",
|
||||
"right_index1",
|
||||
"right_index2",
|
||||
"right_index3",
|
||||
"right_middle1",
|
||||
"right_middle2",
|
||||
"right_middle3",
|
||||
"right_pinky1",
|
||||
"right_pinky2",
|
||||
"right_pinky3",
|
||||
"right_ring1",
|
||||
"right_ring2",
|
||||
"right_ring3",
|
||||
"right_thumb1",
|
||||
"right_thumb2",
|
||||
"right_thumb3",
|
||||
"nose",
|
||||
"right_eye",
|
||||
"left_eye",
|
||||
"right_ear",
|
||||
"left_ear",
|
||||
"left_big_toe",
|
||||
"left_small_toe",
|
||||
"left_heel",
|
||||
"right_big_toe",
|
||||
"right_small_toe",
|
||||
"right_heel",
|
||||
"left_thumb",
|
||||
"left_index",
|
||||
"left_middle",
|
||||
"left_ring",
|
||||
"left_pinky",
|
||||
"right_thumb",
|
||||
"right_index",
|
||||
"right_middle",
|
||||
"right_ring",
|
||||
"right_pinky",
|
||||
"right_eye_brow1",
|
||||
"right_eye_brow2",
|
||||
"right_eye_brow3",
|
||||
"right_eye_brow4",
|
||||
"right_eye_brow5",
|
||||
"left_eye_brow5",
|
||||
"left_eye_brow4",
|
||||
"left_eye_brow3",
|
||||
"left_eye_brow2",
|
||||
"left_eye_brow1",
|
||||
"nose1",
|
||||
"nose2",
|
||||
"nose3",
|
||||
"nose4",
|
||||
"right_nose_2",
|
||||
"right_nose_1",
|
||||
"nose_middle",
|
||||
"left_nose_1",
|
||||
"left_nose_2",
|
||||
"right_eye1",
|
||||
"right_eye2",
|
||||
"right_eye3",
|
||||
"right_eye4",
|
||||
"right_eye5",
|
||||
"right_eye6",
|
||||
"left_eye4",
|
||||
"left_eye3",
|
||||
"left_eye2",
|
||||
"left_eye1",
|
||||
"left_eye6",
|
||||
"left_eye5",
|
||||
"right_mouth_1",
|
||||
"right_mouth_2",
|
||||
"right_mouth_3",
|
||||
"mouth_top",
|
||||
"left_mouth_3",
|
||||
"left_mouth_2",
|
||||
"left_mouth_1",
|
||||
"left_mouth_5", # 59 in OpenPose output
|
||||
"left_mouth_4", # 58 in OpenPose output
|
||||
"mouth_bottom",
|
||||
"right_mouth_4",
|
||||
"right_mouth_5",
|
||||
"right_lip_1",
|
||||
"right_lip_2",
|
||||
"lip_top",
|
||||
"left_lip_2",
|
||||
"left_lip_1",
|
||||
"left_lip_3",
|
||||
"lip_bottom",
|
||||
"right_lip_3",
|
||||
# Face contour
|
||||
"right_contour_1",
|
||||
"right_contour_2",
|
||||
"right_contour_3",
|
||||
"right_contour_4",
|
||||
"right_contour_5",
|
||||
"right_contour_6",
|
||||
"right_contour_7",
|
||||
"right_contour_8",
|
||||
"contour_middle",
|
||||
"left_contour_8",
|
||||
"left_contour_7",
|
||||
"left_contour_6",
|
||||
"left_contour_5",
|
||||
"left_contour_4",
|
||||
"left_contour_3",
|
||||
"left_contour_2",
|
||||
"left_contour_1",
|
||||
]
|
||||
|
||||
smplxnh_joints = [
|
||||
"pelvis",
|
||||
"left_hip",
|
||||
"right_hip",
|
||||
"spine1",
|
||||
"left_knee",
|
||||
"right_knee",
|
||||
"spine2",
|
||||
"left_ankle",
|
||||
"right_ankle",
|
||||
"spine3",
|
||||
"left_foot",
|
||||
"right_foot",
|
||||
"neck",
|
||||
"left_collar",
|
||||
"right_collar",
|
||||
"head",
|
||||
"left_shoulder",
|
||||
"right_shoulder",
|
||||
"left_elbow",
|
||||
"right_elbow",
|
||||
"left_wrist",
|
||||
"right_wrist",
|
||||
]
|
||||
|
||||
smplh_joints = [
|
||||
"pelvis",
|
||||
"left_hip",
|
||||
"right_hip",
|
||||
"spine1",
|
||||
"left_knee",
|
||||
"right_knee",
|
||||
"spine2",
|
||||
"left_ankle",
|
||||
"right_ankle",
|
||||
"spine3",
|
||||
"left_foot",
|
||||
"right_foot",
|
||||
"neck",
|
||||
"left_collar",
|
||||
"right_collar",
|
||||
"head",
|
||||
"left_shoulder",
|
||||
"right_shoulder",
|
||||
"left_elbow",
|
||||
"right_elbow",
|
||||
"left_wrist",
|
||||
"right_wrist",
|
||||
"left_index1",
|
||||
"left_index2",
|
||||
"left_index3",
|
||||
"left_middle1",
|
||||
"left_middle2",
|
||||
"left_middle3",
|
||||
"left_pinky1",
|
||||
"left_pinky2",
|
||||
"left_pinky3",
|
||||
"left_ring1",
|
||||
"left_ring2",
|
||||
"left_ring3",
|
||||
"left_thumb1",
|
||||
"left_thumb2",
|
||||
"left_thumb3",
|
||||
"right_index1",
|
||||
"right_index2",
|
||||
"right_index3",
|
||||
"right_middle1",
|
||||
"right_middle2",
|
||||
"right_middle3",
|
||||
"right_pinky1",
|
||||
"right_pinky2",
|
||||
"right_pinky3",
|
||||
"right_ring1",
|
||||
"right_ring2",
|
||||
"right_ring3",
|
||||
"right_thumb1",
|
||||
"right_thumb2",
|
||||
"right_thumb3",
|
||||
"nose",
|
||||
"right_eye",
|
||||
"left_eye",
|
||||
"right_ear",
|
||||
"left_ear",
|
||||
"left_big_toe",
|
||||
"left_small_toe",
|
||||
"left_heel",
|
||||
"right_big_toe",
|
||||
"right_small_toe",
|
||||
"right_heel",
|
||||
"left_thumb",
|
||||
"left_index",
|
||||
"left_middle",
|
||||
"left_ring",
|
||||
"left_pinky",
|
||||
"right_thumb",
|
||||
"right_index",
|
||||
"right_middle",
|
||||
"right_ring",
|
||||
"right_pinky",
|
||||
]
|
||||
|
||||
smplnh_joints = [
|
||||
"pelvis",
|
||||
"left_hip",
|
||||
"right_hip",
|
||||
"spine1",
|
||||
"left_knee",
|
||||
"right_knee",
|
||||
"spine2",
|
||||
"left_ankle",
|
||||
"right_ankle",
|
||||
"spine3",
|
||||
"left_foot",
|
||||
"right_foot",
|
||||
"neck",
|
||||
"left_collar",
|
||||
"right_collar",
|
||||
"head",
|
||||
"left_shoulder",
|
||||
"right_shoulder",
|
||||
"left_elbow",
|
||||
"right_elbow",
|
||||
"left_wrist",
|
||||
"right_wrist",
|
||||
]
|
||||
|
||||
mmm2smplh_correspondence = {
|
||||
"root": "pelvis",
|
||||
"BP": "spine1",
|
||||
"BT": "spine3",
|
||||
"BLN": "neck",
|
||||
"BUN": "head",
|
||||
"LS": "left_shoulder",
|
||||
"LE": "left_elbow",
|
||||
"LW": "left_wrist",
|
||||
"RS": "right_shoulder",
|
||||
"RE": "right_elbow",
|
||||
"RW": "right_wrist",
|
||||
"LH": "left_hip",
|
||||
"LK": "left_knee",
|
||||
"LA": "left_ankle",
|
||||
"LMrot": "left_heel",
|
||||
"LF": "left_foot",
|
||||
"RH": "right_hip",
|
||||
"RK": "right_knee",
|
||||
"RA": "right_ankle",
|
||||
"RMrot": "right_heel",
|
||||
"RF": "right_foot",
|
||||
}
|
||||
|
||||
smplh2mmm_correspondence = {
|
||||
val: key
|
||||
for key, val in mmm2smplh_correspondence.items()
|
||||
}
|
||||
smplh2mmm_indexes = [
|
||||
smplh_joints.index(mmm2smplh_correspondence[x]) for x in mmm_joints
|
||||
]
|
||||
|
||||
smplnh2smplh_correspondence = {key: key for key in smplnh_joints}
|
||||
smplh2smplnh_correspondence = {
|
||||
val: key
|
||||
for key, val in smplnh2smplh_correspondence.items()
|
||||
}
|
||||
|
||||
smplh2smplnh_indexes = [
|
||||
smplh_joints.index(smplnh2smplh_correspondence[x]) for x in smplnh_joints
|
||||
]
|
||||
|
||||
mmm_kinematic_tree = [
|
||||
[0, 1, 2, 3, 4], # body
|
||||
[3, 5, 6, 7], # right arm
|
||||
[3, 8, 9, 10], # left arm
|
||||
[0, 11, 12, 13, 14, 15], # right leg
|
||||
[0, 16, 17, 18, 19, 20],
|
||||
] # left leg
|
||||
|
||||
humanml3d_kinematic_tree = [
|
||||
[0, 3, 6, 9, 12, 15], # body
|
||||
[9, 14, 17, 19, 21], # right arm
|
||||
[9, 13, 16, 18, 20], # left arm
|
||||
[0, 2, 5, 8, 11], # right leg
|
||||
[0, 1, 4, 7, 10],
|
||||
] # left leg
|
||||
|
||||
smplh_to_mmm_scaling_factor = 480 / 0.75
|
||||
mmm_to_smplh_scaling_factor = 0.75 / 480
|
||||
|
||||
mmm_joints_info = {
|
||||
"root":
|
||||
mmm_joints.index("root"),
|
||||
"feet": [
|
||||
mmm_joints.index("LMrot"),
|
||||
mmm_joints.index("RMrot"),
|
||||
mmm_joints.index("LF"),
|
||||
mmm_joints.index("RF"),
|
||||
],
|
||||
"shoulders": [mmm_joints.index("LS"),
|
||||
mmm_joints.index("RS")],
|
||||
"hips": [mmm_joints.index("LH"),
|
||||
mmm_joints.index("RH")],
|
||||
}
|
||||
|
||||
smplnh_joints_info = {
|
||||
"root":
|
||||
smplnh_joints.index("pelvis"),
|
||||
"feet": [
|
||||
smplnh_joints.index("left_ankle"),
|
||||
smplnh_joints.index("right_ankle"),
|
||||
smplnh_joints.index("left_foot"),
|
||||
smplnh_joints.index("right_foot"),
|
||||
],
|
||||
"shoulders": [
|
||||
smplnh_joints.index("left_shoulder"),
|
||||
smplnh_joints.index("right_shoulder"),
|
||||
],
|
||||
"hips":
|
||||
[smplnh_joints.index("left_hip"),
|
||||
smplnh_joints.index("right_hip")],
|
||||
}
|
||||
|
||||
infos = {"mmm": mmm_joints_info, "smplnh": smplnh_joints_info}
|
||||
|
||||
smplh_indexes = {"mmm": smplh2mmm_indexes, "smplnh": smplh2smplnh_indexes}
|
||||
|
||||
root_joints = {
|
||||
"mmm": mmm_joints_info["root"],
|
||||
"mmmns": mmm_joints_info["root"],
|
||||
"smplmmm": mmm_joints_info["root"],
|
||||
"smplnh": smplnh_joints_info["root"],
|
||||
"smplh": smplh_joints.index("pelvis"),
|
||||
}
|
||||
|
||||
|
||||
def get_root_idx(joinstype):
|
||||
return root_joints[joinstype]
|
||||
|
||||
|
||||
# def mmm2smpl(joints_mmm):
|
||||
# mmm2smplnh_indexes = []
|
||||
# for x in smplnh_joints:
|
||||
# if x in smplh2mmm_correspondence:
|
||||
# mmm2smplnh_indexes.append(mmm_joints.index(smplh2mmm_correspondence[x]))
|
||||
|
||||
# spine2 = 0.5*(joints[mmm_joints.index("spine1")] + joints[mmm_joints.index("spine3")])
|
||||
|
||||
# joints = joints_mmm[indexes]
|
||||
# return joints
|
||||
@@ -1,34 +0,0 @@
|
||||
import torch
|
||||
|
||||
def load_pretrained(cfg, model, logger, phase="train"):
|
||||
logger.info(f"Loading pretrain model from {cfg.TRAIN.PRETRAINED}")
|
||||
if phase == "train":
|
||||
ckpt_path = cfg.TRAIN.PRETRAINED
|
||||
elif phase == "test":
|
||||
ckpt_path = cfg.TEST.CHECKPOINTS
|
||||
|
||||
state_dict = torch.load(ckpt_path, map_location="cpu")["state_dict"]
|
||||
model.load_state_dict(state_dict, strict=True)
|
||||
return model
|
||||
|
||||
|
||||
def load_pretrained_vae(cfg, model, logger):
|
||||
state_dict = torch.load(cfg.TRAIN.PRETRAINED_VAE,
|
||||
map_location="cpu")['state_dict']
|
||||
logger.info(f"Loading pretrain vae from {cfg.TRAIN.PRETRAINED_VAE}")
|
||||
# Extract encoder/decoder
|
||||
from collections import OrderedDict
|
||||
vae_dict = OrderedDict()
|
||||
for k, v in state_dict.items():
|
||||
if "motion_vae" in k:
|
||||
name = k.replace("motion_vae.", "")
|
||||
vae_dict[name] = v
|
||||
elif "vae" in k:
|
||||
name = k.replace("vae.", "")
|
||||
vae_dict[name] = v
|
||||
if hasattr(model, 'vae'):
|
||||
model.vae.load_state_dict(vae_dict, strict=True)
|
||||
else:
|
||||
model.motion_vae.load_state_dict(vae_dict, strict=True)
|
||||
|
||||
return model
|
||||
@@ -1,68 +0,0 @@
|
||||
from pathlib import Path
|
||||
import os
|
||||
import time
|
||||
import logging
|
||||
from omegaconf import OmegaConf
|
||||
from pytorch_lightning.utilities.rank_zero import rank_zero_only
|
||||
|
||||
def create_logger(cfg, phase='train'):
|
||||
# root dir set by cfg
|
||||
root_output_dir = Path(cfg.FOLDER)
|
||||
# set up logger
|
||||
if not root_output_dir.exists():
|
||||
print('=> creating {}'.format(root_output_dir))
|
||||
root_output_dir.mkdir()
|
||||
|
||||
cfg_name = cfg.NAME
|
||||
model = cfg.model.target.split('.')[-2]
|
||||
cfg_name = os.path.basename(cfg_name).split('.')[0]
|
||||
|
||||
final_output_dir = root_output_dir / model / cfg_name
|
||||
cfg.FOLDER_EXP = str(final_output_dir)
|
||||
|
||||
time_str = time.strftime('%Y-%m-%d-%H-%M-%S')
|
||||
|
||||
new_dir(cfg, phase, time_str, final_output_dir)
|
||||
|
||||
head = '%(asctime)-15s %(message)s'
|
||||
logger = config_logger(final_output_dir, time_str, phase, head)
|
||||
if logger is None:
|
||||
logger = logging.getLogger()
|
||||
logger.setLevel(logging.CRITICAL)
|
||||
logging.basicConfig(format=head)
|
||||
return logger
|
||||
|
||||
|
||||
@rank_zero_only
|
||||
def config_logger(final_output_dir, time_str, phase, head):
|
||||
log_file = '{}_{}_{}.log'.format('log', time_str, phase)
|
||||
final_log_file = final_output_dir / log_file
|
||||
logging.basicConfig(filename=str(final_log_file))
|
||||
logger = logging.getLogger()
|
||||
logger.setLevel(logging.INFO)
|
||||
console = logging.StreamHandler()
|
||||
formatter = logging.Formatter(head)
|
||||
console.setFormatter(formatter)
|
||||
logging.getLogger('').addHandler(console)
|
||||
file_handler = logging.FileHandler(final_log_file, 'w')
|
||||
file_handler.setFormatter(logging.Formatter(head))
|
||||
file_handler.setLevel(logging.INFO)
|
||||
logging.getLogger('').addHandler(file_handler)
|
||||
return logger
|
||||
|
||||
|
||||
@rank_zero_only
|
||||
def new_dir(cfg, phase, time_str, final_output_dir):
|
||||
# new experiment folder
|
||||
cfg.TIME = str(time_str)
|
||||
if os.path.exists(final_output_dir) and not os.path.exists(cfg.TRAIN.RESUME) and not cfg.DEBUG and phase not in ['test', 'demo']:
|
||||
file_list = sorted(os.listdir(final_output_dir), reverse=True)
|
||||
for item in file_list:
|
||||
if item.endswith('.log'):
|
||||
os.rename(str(final_output_dir), str(final_output_dir) + '_' + cfg.TIME)
|
||||
break
|
||||
final_output_dir.mkdir(parents=True, exist_ok=True)
|
||||
# write config yaml
|
||||
config_file = '{}_{}_{}.yaml'.format('config', time_str, phase)
|
||||
final_config_file = final_output_dir / config_file
|
||||
OmegaConf.save(config=cfg, f=final_config_file)
|
||||
@@ -1,29 +0,0 @@
|
||||
import torch
|
||||
|
||||
|
||||
def to_numpy(tensor):
|
||||
if torch.is_tensor(tensor):
|
||||
return tensor.cpu().numpy()
|
||||
elif type(tensor).__module__ != 'numpy':
|
||||
raise ValueError("Cannot convert {} to numpy array".format(
|
||||
type(tensor)))
|
||||
return tensor
|
||||
|
||||
|
||||
def to_torch(ndarray):
|
||||
if type(ndarray).__module__ == 'numpy':
|
||||
return torch.from_numpy(ndarray)
|
||||
elif not torch.is_tensor(ndarray):
|
||||
raise ValueError("Cannot convert {} to torch tensor".format(
|
||||
type(ndarray)))
|
||||
return ndarray
|
||||
|
||||
|
||||
def cleanexit():
|
||||
import sys
|
||||
import os
|
||||
try:
|
||||
sys.exit(0)
|
||||
except SystemExit:
|
||||
os._exit(0)
|
||||
|
||||
@@ -1,551 +0,0 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
# Check PYTORCH3D_LICENCE before use
|
||||
|
||||
import functools
|
||||
from typing import Optional
|
||||
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
|
||||
|
||||
"""
|
||||
The transformation matrices returned from the functions in this file assume
|
||||
the points on which the transformation will be applied are column vectors.
|
||||
i.e. the R matrix is structured as
|
||||
|
||||
R = [
|
||||
[Rxx, Rxy, Rxz],
|
||||
[Ryx, Ryy, Ryz],
|
||||
[Rzx, Rzy, Rzz],
|
||||
] # (3, 3)
|
||||
|
||||
This matrix can be applied to column vectors by post multiplication
|
||||
by the points e.g.
|
||||
|
||||
points = [[0], [1], [2]] # (3 x 1) xyz coordinates of a point
|
||||
transformed_points = R * points
|
||||
|
||||
To apply the same matrix to points which are row vectors, the R matrix
|
||||
can be transposed and pre multiplied by the points:
|
||||
|
||||
e.g.
|
||||
points = [[0, 1, 2]] # (1 x 3) xyz coordinates of a point
|
||||
transformed_points = points * R.transpose(1, 0)
|
||||
"""
|
||||
|
||||
|
||||
def quaternion_to_matrix(quaternions):
|
||||
"""
|
||||
Convert rotations given as quaternions to rotation matrices.
|
||||
|
||||
Args:
|
||||
quaternions: quaternions with real part first,
|
||||
as tensor of shape (..., 4).
|
||||
|
||||
Returns:
|
||||
Rotation matrices as tensor of shape (..., 3, 3).
|
||||
"""
|
||||
r, i, j, k = torch.unbind(quaternions, -1)
|
||||
two_s = 2.0 / (quaternions * quaternions).sum(-1)
|
||||
|
||||
o = torch.stack(
|
||||
(
|
||||
1 - two_s * (j * j + k * k),
|
||||
two_s * (i * j - k * r),
|
||||
two_s * (i * k + j * r),
|
||||
two_s * (i * j + k * r),
|
||||
1 - two_s * (i * i + k * k),
|
||||
two_s * (j * k - i * r),
|
||||
two_s * (i * k - j * r),
|
||||
two_s * (j * k + i * r),
|
||||
1 - two_s * (i * i + j * j),
|
||||
),
|
||||
-1,
|
||||
)
|
||||
return o.reshape(quaternions.shape[:-1] + (3, 3))
|
||||
|
||||
|
||||
def _copysign(a, b):
|
||||
"""
|
||||
Return a tensor where each element has the absolute value taken from the,
|
||||
corresponding element of a, with sign taken from the corresponding
|
||||
element of b. This is like the standard copysign floating-point operation,
|
||||
but is not careful about negative 0 and NaN.
|
||||
|
||||
Args:
|
||||
a: source tensor.
|
||||
b: tensor whose signs will be used, of the same shape as a.
|
||||
|
||||
Returns:
|
||||
Tensor of the same shape as a with the signs of b.
|
||||
"""
|
||||
signs_differ = (a < 0) != (b < 0)
|
||||
return torch.where(signs_differ, -a, a)
|
||||
|
||||
|
||||
def _sqrt_positive_part(x):
|
||||
"""
|
||||
Returns torch.sqrt(torch.max(0, x))
|
||||
but with a zero subgradient where x is 0.
|
||||
"""
|
||||
ret = torch.zeros_like(x)
|
||||
positive_mask = x > 0
|
||||
ret[positive_mask] = torch.sqrt(x[positive_mask])
|
||||
return ret
|
||||
|
||||
|
||||
def matrix_to_quaternion(matrix):
|
||||
"""
|
||||
Convert rotations given as rotation matrices to quaternions.
|
||||
|
||||
Args:
|
||||
matrix: Rotation matrices as tensor of shape (..., 3, 3).
|
||||
|
||||
Returns:
|
||||
quaternions with real part first, as tensor of shape (..., 4).
|
||||
"""
|
||||
if matrix.size(-1) != 3 or matrix.size(-2) != 3:
|
||||
raise ValueError(f"Invalid rotation matrix shape f{matrix.shape}.")
|
||||
m00 = matrix[..., 0, 0]
|
||||
m11 = matrix[..., 1, 1]
|
||||
m22 = matrix[..., 2, 2]
|
||||
o0 = 0.5 * _sqrt_positive_part(1 + m00 + m11 + m22)
|
||||
x = 0.5 * _sqrt_positive_part(1 + m00 - m11 - m22)
|
||||
y = 0.5 * _sqrt_positive_part(1 - m00 + m11 - m22)
|
||||
z = 0.5 * _sqrt_positive_part(1 - m00 - m11 + m22)
|
||||
o1 = _copysign(x, matrix[..., 2, 1] - matrix[..., 1, 2])
|
||||
o2 = _copysign(y, matrix[..., 0, 2] - matrix[..., 2, 0])
|
||||
o3 = _copysign(z, matrix[..., 1, 0] - matrix[..., 0, 1])
|
||||
return torch.stack((o0, o1, o2, o3), -1)
|
||||
|
||||
|
||||
def _axis_angle_rotation(axis: str, angle):
|
||||
"""
|
||||
Return the rotation matrices for one of the rotations about an axis
|
||||
of which Euler angles describe, for each value of the angle given.
|
||||
|
||||
Args:
|
||||
axis: Axis label "X" or "Y or "Z".
|
||||
angle: any shape tensor of Euler angles in radians
|
||||
|
||||
Returns:
|
||||
Rotation matrices as tensor of shape (..., 3, 3).
|
||||
"""
|
||||
|
||||
cos = torch.cos(angle)
|
||||
sin = torch.sin(angle)
|
||||
one = torch.ones_like(angle)
|
||||
zero = torch.zeros_like(angle)
|
||||
|
||||
if axis == "X":
|
||||
R_flat = (one, zero, zero, zero, cos, -sin, zero, sin, cos)
|
||||
if axis == "Y":
|
||||
R_flat = (cos, zero, sin, zero, one, zero, -sin, zero, cos)
|
||||
if axis == "Z":
|
||||
R_flat = (cos, -sin, zero, sin, cos, zero, zero, zero, one)
|
||||
|
||||
return torch.stack(R_flat, -1).reshape(angle.shape + (3, 3))
|
||||
|
||||
|
||||
def euler_angles_to_matrix(euler_angles, convention: str):
|
||||
"""
|
||||
Convert rotations given as Euler angles in radians to rotation matrices.
|
||||
|
||||
Args:
|
||||
euler_angles: Euler angles in radians as tensor of shape (..., 3).
|
||||
convention: Convention string of three uppercase letters from
|
||||
{"X", "Y", and "Z"}.
|
||||
|
||||
Returns:
|
||||
Rotation matrices as tensor of shape (..., 3, 3).
|
||||
"""
|
||||
if euler_angles.dim() == 0 or euler_angles.shape[-1] != 3:
|
||||
raise ValueError("Invalid input euler angles.")
|
||||
if len(convention) != 3:
|
||||
raise ValueError("Convention must have 3 letters.")
|
||||
if convention[1] in (convention[0], convention[2]):
|
||||
raise ValueError(f"Invalid convention {convention}.")
|
||||
for letter in convention:
|
||||
if letter not in ("X", "Y", "Z"):
|
||||
raise ValueError(f"Invalid letter {letter} in convention string.")
|
||||
matrices = map(_axis_angle_rotation, convention, torch.unbind(euler_angles, -1))
|
||||
return functools.reduce(torch.matmul, matrices)
|
||||
|
||||
|
||||
def _angle_from_tan(
|
||||
axis: str, other_axis: str, data, horizontal: bool, tait_bryan: bool
|
||||
):
|
||||
"""
|
||||
Extract the first or third Euler angle from the two members of
|
||||
the matrix which are positive constant times its sine and cosine.
|
||||
|
||||
Args:
|
||||
axis: Axis label "X" or "Y or "Z" for the angle we are finding.
|
||||
other_axis: Axis label "X" or "Y or "Z" for the middle axis in the
|
||||
convention.
|
||||
data: Rotation matrices as tensor of shape (..., 3, 3).
|
||||
horizontal: Whether we are looking for the angle for the third axis,
|
||||
which means the relevant entries are in the same row of the
|
||||
rotation matrix. If not, they are in the same column.
|
||||
tait_bryan: Whether the first and third axes in the convention differ.
|
||||
|
||||
Returns:
|
||||
Euler Angles in radians for each matrix in data as a tensor
|
||||
of shape (...).
|
||||
"""
|
||||
|
||||
i1, i2 = {"X": (2, 1), "Y": (0, 2), "Z": (1, 0)}[axis]
|
||||
if horizontal:
|
||||
i2, i1 = i1, i2
|
||||
even = (axis + other_axis) in ["XY", "YZ", "ZX"]
|
||||
if horizontal == even:
|
||||
return torch.atan2(data[..., i1], data[..., i2])
|
||||
if tait_bryan:
|
||||
return torch.atan2(-data[..., i2], data[..., i1])
|
||||
return torch.atan2(data[..., i2], -data[..., i1])
|
||||
|
||||
|
||||
def _index_from_letter(letter: str):
|
||||
if letter == "X":
|
||||
return 0
|
||||
if letter == "Y":
|
||||
return 1
|
||||
if letter == "Z":
|
||||
return 2
|
||||
|
||||
|
||||
def matrix_to_euler_angles(matrix, convention: str):
|
||||
"""
|
||||
Convert rotations given as rotation matrices to Euler angles in radians.
|
||||
|
||||
Args:
|
||||
matrix: Rotation matrices as tensor of shape (..., 3, 3).
|
||||
convention: Convention string of three uppercase letters.
|
||||
|
||||
Returns:
|
||||
Euler angles in radians as tensor of shape (..., 3).
|
||||
"""
|
||||
if len(convention) != 3:
|
||||
raise ValueError("Convention must have 3 letters.")
|
||||
if convention[1] in (convention[0], convention[2]):
|
||||
raise ValueError(f"Invalid convention {convention}.")
|
||||
for letter in convention:
|
||||
if letter not in ("X", "Y", "Z"):
|
||||
raise ValueError(f"Invalid letter {letter} in convention string.")
|
||||
if matrix.size(-1) != 3 or matrix.size(-2) != 3:
|
||||
raise ValueError(f"Invalid rotation matrix shape f{matrix.shape}.")
|
||||
i0 = _index_from_letter(convention[0])
|
||||
i2 = _index_from_letter(convention[2])
|
||||
tait_bryan = i0 != i2
|
||||
if tait_bryan:
|
||||
central_angle = torch.asin(
|
||||
matrix[..., i0, i2] * (-1.0 if i0 - i2 in [-1, 2] else 1.0)
|
||||
)
|
||||
else:
|
||||
central_angle = torch.acos(matrix[..., i0, i0])
|
||||
|
||||
o = (
|
||||
_angle_from_tan(
|
||||
convention[0], convention[1], matrix[..., i2], False, tait_bryan
|
||||
),
|
||||
central_angle,
|
||||
_angle_from_tan(
|
||||
convention[2], convention[1], matrix[..., i0, :], True, tait_bryan
|
||||
),
|
||||
)
|
||||
return torch.stack(o, -1)
|
||||
|
||||
|
||||
def random_quaternions(
|
||||
n: int, dtype: Optional[torch.dtype] = None, device=None, requires_grad=False
|
||||
):
|
||||
"""
|
||||
Generate random quaternions representing rotations,
|
||||
i.e. versors with nonnegative real part.
|
||||
|
||||
Args:
|
||||
n: Number of quaternions in a batch to return.
|
||||
dtype: Type to return.
|
||||
device: Desired device of returned tensor. Default:
|
||||
uses the current device for the default tensor type.
|
||||
requires_grad: Whether the resulting tensor should have the gradient
|
||||
flag set.
|
||||
|
||||
Returns:
|
||||
Quaternions as tensor of shape (N, 4).
|
||||
"""
|
||||
o = torch.randn((n, 4), dtype=dtype, device=device, requires_grad=requires_grad)
|
||||
s = (o * o).sum(1)
|
||||
o = o / _copysign(torch.sqrt(s), o[:, 0])[:, None]
|
||||
return o
|
||||
|
||||
|
||||
def random_rotations(
|
||||
n: int, dtype: Optional[torch.dtype] = None, device=None, requires_grad=False
|
||||
):
|
||||
"""
|
||||
Generate random rotations as 3x3 rotation matrices.
|
||||
|
||||
Args:
|
||||
n: Number of rotation matrices in a batch to return.
|
||||
dtype: Type to return.
|
||||
device: Device of returned tensor. Default: if None,
|
||||
uses the current device for the default tensor type.
|
||||
requires_grad: Whether the resulting tensor should have the gradient
|
||||
flag set.
|
||||
|
||||
Returns:
|
||||
Rotation matrices as tensor of shape (n, 3, 3).
|
||||
"""
|
||||
quaternions = random_quaternions(
|
||||
n, dtype=dtype, device=device, requires_grad=requires_grad
|
||||
)
|
||||
return quaternion_to_matrix(quaternions)
|
||||
|
||||
|
||||
def random_rotation(
|
||||
dtype: Optional[torch.dtype] = None, device=None, requires_grad=False
|
||||
):
|
||||
"""
|
||||
Generate a single random 3x3 rotation matrix.
|
||||
|
||||
Args:
|
||||
dtype: Type to return
|
||||
device: Device of returned tensor. Default: if None,
|
||||
uses the current device for the default tensor type
|
||||
requires_grad: Whether the resulting tensor should have the gradient
|
||||
flag set
|
||||
|
||||
Returns:
|
||||
Rotation matrix as tensor of shape (3, 3).
|
||||
"""
|
||||
return random_rotations(1, dtype, device, requires_grad)[0]
|
||||
|
||||
|
||||
def standardize_quaternion(quaternions):
|
||||
"""
|
||||
Convert a unit quaternion to a standard form: one in which the real
|
||||
part is non negative.
|
||||
|
||||
Args:
|
||||
quaternions: Quaternions with real part first,
|
||||
as tensor of shape (..., 4).
|
||||
|
||||
Returns:
|
||||
Standardized quaternions as tensor of shape (..., 4).
|
||||
"""
|
||||
return torch.where(quaternions[..., 0:1] < 0, -quaternions, quaternions)
|
||||
|
||||
|
||||
def quaternion_raw_multiply(a, b):
|
||||
"""
|
||||
Multiply two quaternions.
|
||||
Usual torch rules for broadcasting apply.
|
||||
|
||||
Args:
|
||||
a: Quaternions as tensor of shape (..., 4), real part first.
|
||||
b: Quaternions as tensor of shape (..., 4), real part first.
|
||||
|
||||
Returns:
|
||||
The product of a and b, a tensor of quaternions shape (..., 4).
|
||||
"""
|
||||
aw, ax, ay, az = torch.unbind(a, -1)
|
||||
bw, bx, by, bz = torch.unbind(b, -1)
|
||||
ow = aw * bw - ax * bx - ay * by - az * bz
|
||||
ox = aw * bx + ax * bw + ay * bz - az * by
|
||||
oy = aw * by - ax * bz + ay * bw + az * bx
|
||||
oz = aw * bz + ax * by - ay * bx + az * bw
|
||||
return torch.stack((ow, ox, oy, oz), -1)
|
||||
|
||||
|
||||
def quaternion_multiply(a, b):
|
||||
"""
|
||||
Multiply two quaternions representing rotations, returning the quaternion
|
||||
representing their composition, i.e. the versor with nonnegative real part.
|
||||
Usual torch rules for broadcasting apply.
|
||||
|
||||
Args:
|
||||
a: Quaternions as tensor of shape (..., 4), real part first.
|
||||
b: Quaternions as tensor of shape (..., 4), real part first.
|
||||
|
||||
Returns:
|
||||
The product of a and b, a tensor of quaternions of shape (..., 4).
|
||||
"""
|
||||
ab = quaternion_raw_multiply(a, b)
|
||||
return standardize_quaternion(ab)
|
||||
|
||||
|
||||
def quaternion_invert(quaternion):
|
||||
"""
|
||||
Given a quaternion representing rotation, get the quaternion representing
|
||||
its inverse.
|
||||
|
||||
Args:
|
||||
quaternion: Quaternions as tensor of shape (..., 4), with real part
|
||||
first, which must be versors (unit quaternions).
|
||||
|
||||
Returns:
|
||||
The inverse, a tensor of quaternions of shape (..., 4).
|
||||
"""
|
||||
|
||||
return quaternion * quaternion.new_tensor([1, -1, -1, -1])
|
||||
|
||||
|
||||
def quaternion_apply(quaternion, point):
|
||||
"""
|
||||
Apply the rotation given by a quaternion to a 3D point.
|
||||
Usual torch rules for broadcasting apply.
|
||||
|
||||
Args:
|
||||
quaternion: Tensor of quaternions, real part first, of shape (..., 4).
|
||||
point: Tensor of 3D points of shape (..., 3).
|
||||
|
||||
Returns:
|
||||
Tensor of rotated points of shape (..., 3).
|
||||
"""
|
||||
if point.size(-1) != 3:
|
||||
raise ValueError(f"Points are not in 3D, f{point.shape}.")
|
||||
real_parts = point.new_zeros(point.shape[:-1] + (1,))
|
||||
point_as_quaternion = torch.cat((real_parts, point), -1)
|
||||
out = quaternion_raw_multiply(
|
||||
quaternion_raw_multiply(quaternion, point_as_quaternion),
|
||||
quaternion_invert(quaternion),
|
||||
)
|
||||
return out[..., 1:]
|
||||
|
||||
|
||||
def axis_angle_to_matrix(axis_angle):
|
||||
"""
|
||||
Convert rotations given as axis/angle to rotation matrices.
|
||||
|
||||
Args:
|
||||
axis_angle: Rotations given as a vector in axis angle form,
|
||||
as a tensor of shape (..., 3), where the magnitude is
|
||||
the angle turned anticlockwise in radians around the
|
||||
vector's direction.
|
||||
|
||||
Returns:
|
||||
Rotation matrices as tensor of shape (..., 3, 3).
|
||||
"""
|
||||
return quaternion_to_matrix(axis_angle_to_quaternion(axis_angle))
|
||||
|
||||
|
||||
def matrix_to_axis_angle(matrix):
|
||||
"""
|
||||
Convert rotations given as rotation matrices to axis/angle.
|
||||
|
||||
Args:
|
||||
matrix: Rotation matrices as tensor of shape (..., 3, 3).
|
||||
|
||||
Returns:
|
||||
Rotations given as a vector in axis angle form, as a tensor
|
||||
of shape (..., 3), where the magnitude is the angle
|
||||
turned anticlockwise in radians around the vector's
|
||||
direction.
|
||||
"""
|
||||
return quaternion_to_axis_angle(matrix_to_quaternion(matrix))
|
||||
|
||||
|
||||
def axis_angle_to_quaternion(axis_angle):
|
||||
"""
|
||||
Convert rotations given as axis/angle to quaternions.
|
||||
|
||||
Args:
|
||||
axis_angle: Rotations given as a vector in axis angle form,
|
||||
as a tensor of shape (..., 3), where the magnitude is
|
||||
the angle turned anticlockwise in radians around the
|
||||
vector's direction.
|
||||
|
||||
Returns:
|
||||
quaternions with real part first, as tensor of shape (..., 4).
|
||||
"""
|
||||
angles = torch.norm(axis_angle, p=2, dim=-1, keepdim=True)
|
||||
half_angles = 0.5 * angles
|
||||
eps = 1e-6
|
||||
small_angles = angles.abs() < eps
|
||||
sin_half_angles_over_angles = torch.empty_like(angles)
|
||||
sin_half_angles_over_angles[~small_angles] = (
|
||||
torch.sin(half_angles[~small_angles]) / angles[~small_angles]
|
||||
)
|
||||
# for x small, sin(x/2) is about x/2 - (x/2)^3/6
|
||||
# so sin(x/2)/x is about 1/2 - (x*x)/48
|
||||
sin_half_angles_over_angles[small_angles] = (
|
||||
0.5 - (angles[small_angles] * angles[small_angles]) / 48
|
||||
)
|
||||
quaternions = torch.cat(
|
||||
[torch.cos(half_angles), axis_angle * sin_half_angles_over_angles], dim=-1
|
||||
)
|
||||
return quaternions
|
||||
|
||||
|
||||
def quaternion_to_axis_angle(quaternions):
|
||||
"""
|
||||
Convert rotations given as quaternions to axis/angle.
|
||||
|
||||
Args:
|
||||
quaternions: quaternions with real part first,
|
||||
as tensor of shape (..., 4).
|
||||
|
||||
Returns:
|
||||
Rotations given as a vector in axis angle form, as a tensor
|
||||
of shape (..., 3), where the magnitude is the angle
|
||||
turned anticlockwise in radians around the vector's
|
||||
direction.
|
||||
"""
|
||||
norms = torch.norm(quaternions[..., 1:], p=2, dim=-1, keepdim=True)
|
||||
half_angles = torch.atan2(norms, quaternions[..., :1])
|
||||
angles = 2 * half_angles
|
||||
eps = 1e-6
|
||||
small_angles = angles.abs() < eps
|
||||
sin_half_angles_over_angles = torch.empty_like(angles)
|
||||
sin_half_angles_over_angles[~small_angles] = (
|
||||
torch.sin(half_angles[~small_angles]) / angles[~small_angles]
|
||||
)
|
||||
# for x small, sin(x/2) is about x/2 - (x/2)^3/6
|
||||
# so sin(x/2)/x is about 1/2 - (x*x)/48
|
||||
sin_half_angles_over_angles[small_angles] = (
|
||||
0.5 - (angles[small_angles] * angles[small_angles]) / 48
|
||||
)
|
||||
return quaternions[..., 1:] / sin_half_angles_over_angles
|
||||
|
||||
|
||||
def rotation_6d_to_matrix(d6: torch.Tensor) -> torch.Tensor:
|
||||
"""
|
||||
Converts 6D rotation representation by Zhou et al. [1] to rotation matrix
|
||||
using Gram--Schmidt orthogonalisation per Section B of [1].
|
||||
Args:
|
||||
d6: 6D rotation representation, of size (*, 6)
|
||||
|
||||
Returns:
|
||||
batch of rotation matrices of size (*, 3, 3)
|
||||
|
||||
[1] Zhou, Y., Barnes, C., Lu, J., Yang, J., & Li, H.
|
||||
On the Continuity of Rotation Representations in Neural Networks.
|
||||
IEEE Conference on Computer Vision and Pattern Recognition, 2019.
|
||||
Retrieved from http://arxiv.org/abs/1812.07035
|
||||
"""
|
||||
|
||||
a1, a2 = d6[..., :3], d6[..., 3:]
|
||||
b1 = F.normalize(a1, dim=-1)
|
||||
b2 = a2 - (b1 * a2).sum(-1, keepdim=True) * b1
|
||||
b2 = F.normalize(b2, dim=-1)
|
||||
b3 = torch.cross(b1, b2, dim=-1)
|
||||
return torch.stack((b1, b2, b3), dim=-2)
|
||||
|
||||
|
||||
def matrix_to_rotation_6d(matrix: torch.Tensor) -> torch.Tensor:
|
||||
"""
|
||||
Converts rotation matrices to 6D rotation representation by Zhou et al. [1]
|
||||
by dropping the last row. Note that 6D representation is not unique.
|
||||
Args:
|
||||
matrix: batch of rotation matrices of size (*, 3, 3)
|
||||
|
||||
Returns:
|
||||
6D rotation representation, of size (*, 6)
|
||||
|
||||
[1] Zhou, Y., Barnes, C., Lu, J., Yang, J., & Li, H.
|
||||
On the Continuity of Rotation Representations in Neural Networks.
|
||||
IEEE Conference on Computer Vision and Pattern Recognition, 2019.
|
||||
Retrieved from http://arxiv.org/abs/1812.07035
|
||||
"""
|
||||
return matrix[..., :2, :].clone().reshape(*matrix.size()[:-2], 6)
|
||||
@@ -1,18 +0,0 @@
|
||||
import logging
|
||||
from pathlib import Path
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def cfg_mean_nsamples_resolution(cfg):
|
||||
if cfg.mean and cfg.number_of_samples > 1:
|
||||
logger.error("All the samples will be the mean.. cfg.number_of_samples=1 will be forced.")
|
||||
cfg.number_of_samples = 1
|
||||
|
||||
return cfg.number_of_samples == 1
|
||||
|
||||
|
||||
def get_path(sample_path: Path, is_amass: bool, gender: str, split: str, onesample: bool, mean: bool, fact: float):
|
||||
extra_str = ("_mean" if mean else "") if onesample else "_multi"
|
||||
fact_str = "" if fact == 1 else f"{fact}_"
|
||||
gender_str = gender + "_" if is_amass else ""
|
||||
path = sample_path / f"{fact_str}{gender_str}{split}{extra_str}"
|
||||
return path
|
||||
@@ -1,133 +0,0 @@
|
||||
from typing import Dict, List
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
from torch import Tensor
|
||||
|
||||
import mGPT.utils.geometry_conver as geometry_conver
|
||||
|
||||
|
||||
def lengths_to_mask(lengths: List[int],
|
||||
device: torch.device,
|
||||
max_len: int = None) -> Tensor:
|
||||
lengths = torch.tensor(lengths, device=device)
|
||||
max_len = max_len if max_len else max(lengths)
|
||||
mask = torch.arange(max_len, device=device).expand(
|
||||
len(lengths), max_len) < lengths.unsqueeze(1)
|
||||
return mask
|
||||
|
||||
|
||||
def detach_to_numpy(tensor):
|
||||
return tensor.detach().cpu().numpy()
|
||||
|
||||
|
||||
def remove_padding(tensors, lengths):
|
||||
return [
|
||||
tensor[:tensor_length]
|
||||
for tensor, tensor_length in zip(tensors, lengths)
|
||||
]
|
||||
|
||||
|
||||
def nfeats_of(rottype):
|
||||
if rottype in ["rotvec", "axisangle"]:
|
||||
return 3
|
||||
elif rottype in ["rotquat", "quaternion"]:
|
||||
return 4
|
||||
elif rottype in ["rot6d", "6drot", "rotation6d"]:
|
||||
return 6
|
||||
elif rottype in ["rotmat"]:
|
||||
return 9
|
||||
else:
|
||||
return TypeError("This rotation type doesn't have features.")
|
||||
|
||||
|
||||
def axis_angle_to(newtype, rotations):
|
||||
if newtype in ["matrix"]:
|
||||
rotations = geometry_conver.axis_angle_to_matrix(rotations)
|
||||
return rotations
|
||||
elif newtype in ["rotmat"]:
|
||||
rotations = geometry_conver.axis_angle_to_matrix(rotations)
|
||||
rotations = matrix_to("rotmat", rotations)
|
||||
return rotations
|
||||
elif newtype in ["rot6d", "6drot", "rotation6d"]:
|
||||
rotations = geometry_conver.axis_angle_to_matrix(rotations)
|
||||
rotations = matrix_to("rot6d", rotations)
|
||||
return rotations
|
||||
elif newtype in ["rotquat", "quaternion"]:
|
||||
rotations = geometry_conver.axis_angle_to_quaternion(rotations)
|
||||
return rotations
|
||||
elif newtype in ["rotvec", "axisangle"]:
|
||||
return rotations
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
def matrix_to(newtype, rotations):
|
||||
if newtype in ["matrix"]:
|
||||
return rotations
|
||||
if newtype in ["rotmat"]:
|
||||
rotations = rotations.reshape((*rotations.shape[:-2], 9))
|
||||
return rotations
|
||||
elif newtype in ["rot6d", "6drot", "rotation6d"]:
|
||||
rotations = geometry_conver.matrix_to_rotation_6d(rotations)
|
||||
return rotations
|
||||
elif newtype in ["rotquat", "quaternion"]:
|
||||
rotations = geometry_conver.matrix_to_quaternion(rotations)
|
||||
return rotations
|
||||
elif newtype in ["rotvec", "axisangle"]:
|
||||
rotations = geometry_conver.matrix_to_axis_angle(rotations)
|
||||
return rotations
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
def to_matrix(oldtype, rotations):
|
||||
if oldtype in ["matrix"]:
|
||||
return rotations
|
||||
if oldtype in ["rotmat"]:
|
||||
rotations = rotations.reshape((*rotations.shape[:-2], 3, 3))
|
||||
return rotations
|
||||
elif oldtype in ["rot6d", "6drot", "rotation6d"]:
|
||||
rotations = geometry_conver.rotation_6d_to_matrix(rotations)
|
||||
return rotations
|
||||
elif oldtype in ["rotquat", "quaternion"]:
|
||||
rotations = geometry_conver.quaternion_to_matrix(rotations)
|
||||
return rotations
|
||||
elif oldtype in ["rotvec", "axisangle"]:
|
||||
rotations = geometry_conver.axis_angle_to_matrix(rotations)
|
||||
return rotations
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
# TODO: use a real subsampler..
|
||||
def subsample(num_frames, last_framerate, new_framerate):
|
||||
step = int(last_framerate / new_framerate)
|
||||
assert step >= 1
|
||||
frames = np.arange(0, num_frames, step)
|
||||
return frames
|
||||
|
||||
|
||||
# TODO: use a real upsampler..
|
||||
def upsample(motion, last_framerate, new_framerate):
|
||||
step = int(new_framerate / last_framerate)
|
||||
assert step >= 1
|
||||
|
||||
# Alpha blending => interpolation
|
||||
alpha = np.linspace(0, 1, step + 1)
|
||||
last = np.einsum("l,...->l...", 1 - alpha, motion[:-1])
|
||||
new = np.einsum("l,...->l...", alpha, motion[1:])
|
||||
|
||||
chuncks = (last + new)[:-1]
|
||||
output = np.concatenate(chuncks.swapaxes(1, 0))
|
||||
# Don't forget the last one
|
||||
output = np.concatenate((output, motion[[-1]]))
|
||||
return output
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
motion = np.arange(105)
|
||||
submotion = motion[subsample(len(motion), 100.0, 12.5)]
|
||||
newmotion = upsample(submotion, 12.5, 100)
|
||||
|
||||
print(newmotion)
|
||||
@@ -1,74 +0,0 @@
|
||||
import torch
|
||||
|
||||
|
||||
def lengths_to_mask(lengths):
|
||||
max_len = max(lengths)
|
||||
mask = torch.arange(max_len, device=lengths.device).expand(
|
||||
len(lengths), max_len) < lengths.unsqueeze(1)
|
||||
return mask
|
||||
|
||||
|
||||
def collate_tensors(batch):
|
||||
dims = batch[0].dim()
|
||||
max_size = [max([b.size(i) for b in batch]) for i in range(dims)]
|
||||
size = (len(batch),) + tuple(max_size)
|
||||
canvas = batch[0].new_zeros(size=size)
|
||||
for i, b in enumerate(batch):
|
||||
sub_tensor = canvas[i]
|
||||
for d in range(dims):
|
||||
sub_tensor = sub_tensor.narrow(d, 0, b.size(d))
|
||||
sub_tensor.add_(b)
|
||||
return canvas
|
||||
|
||||
|
||||
def collate(batch):
|
||||
databatch = [b[0] for b in batch]
|
||||
labelbatch = [b[1] for b in batch]
|
||||
lenbatch = [len(b[0][0][0]) for b in batch]
|
||||
|
||||
databatchTensor = collate_tensors(databatch)
|
||||
labelbatchTensor = torch.as_tensor(labelbatch)
|
||||
lenbatchTensor = torch.as_tensor(lenbatch)
|
||||
|
||||
maskbatchTensor = lengths_to_mask(lenbatchTensor)
|
||||
# x - [bs, njoints, nfeats, lengths]
|
||||
# - nfeats, the representation of a joint
|
||||
# y - [bs]
|
||||
# mask - [bs, lengths]
|
||||
# lengths - [bs]
|
||||
batch = {"x": databatchTensor, "y": labelbatchTensor,
|
||||
"mask": maskbatchTensor, 'lengths': lenbatchTensor}
|
||||
return batch
|
||||
|
||||
|
||||
# slow version with padding
|
||||
def collate_data3d_slow(batch):
|
||||
batchTensor = {}
|
||||
for key in batch[0].keys():
|
||||
databatch = [b[key] for b in batch]
|
||||
batchTensor[key] = collate_tensors(databatch)
|
||||
batch = batchTensor
|
||||
# theta - [bs, lengths, 85], theta shape (85,)
|
||||
# - (np.array([1., 0., 0.]), pose(72), shape(10)), axis=0)
|
||||
# kp_2d - [bs, lengths, njoints, nfeats], nfeats (x,y,weight)
|
||||
# kp_3d - [bs, lengths, njoints, nfeats], nfeats (x,y,z)
|
||||
# w_smpl - [bs, lengths] zeros
|
||||
# w_3d - [bs, lengths] zeros
|
||||
return batch
|
||||
|
||||
def collate_data3d(batch):
|
||||
batchTensor = {}
|
||||
for key in batch[0].keys():
|
||||
databatch = [b[key] for b in batch]
|
||||
if key == "paths":
|
||||
batchTensor[key] = databatch
|
||||
else:
|
||||
batchTensor[key] = torch.stack(databatch,axis=0)
|
||||
batch = batchTensor
|
||||
# theta - [bs, lengths, 85], theta shape (85,)
|
||||
# - (np.array([1., 0., 0.]), pose(72), shape(10)), axis=0)
|
||||
# kp_2d - [bs, lengths, njoints, nfeats], nfeats (x,y,weight)
|
||||
# kp_3d - [bs, lengths, njoints, nfeats], nfeats (x,y,z)
|
||||
# w_smpl - [bs, lengths] zeros
|
||||
# w_3d - [bs, lengths] zeros
|
||||
return batch
|
||||
@@ -1,56 +0,0 @@
|
||||
import warnings
|
||||
|
||||
import custom_mmpkg.custom_mmcv as mmcv
|
||||
from packaging.version import parse
|
||||
|
||||
from .version import __version__
|
||||
|
||||
|
||||
def digit_version(version_str: str, length: int = 4):
|
||||
"""Convert a version string into a tuple of integers.
|
||||
This method is usually used for comparing two versions. For pre-release
|
||||
versions: alpha < beta < rc.
|
||||
Args:
|
||||
version_str (str): The version string.
|
||||
length (int): The maximum number of version levels. Default: 4.
|
||||
Returns:
|
||||
tuple[int]: The version info in digits (integers).
|
||||
"""
|
||||
version = parse(version_str)
|
||||
assert version.release, f'failed to parse version {version_str}'
|
||||
release = list(version.release)
|
||||
release = release[:length]
|
||||
if len(release) < length:
|
||||
release = release + [0] * (length - len(release))
|
||||
if version.is_prerelease:
|
||||
mapping = {'a': -3, 'b': -2, 'rc': -1}
|
||||
val = -4
|
||||
# version.pre can be None
|
||||
if version.pre:
|
||||
if version.pre[0] not in mapping:
|
||||
warnings.warn(f'unknown prerelease version {version.pre[0]}, '
|
||||
'version checking may go wrong')
|
||||
else:
|
||||
val = mapping[version.pre[0]]
|
||||
release.extend([val, version.pre[-1]])
|
||||
else:
|
||||
release.extend([val, 0])
|
||||
|
||||
elif version.is_postrelease:
|
||||
release.extend([1, version.post])
|
||||
else:
|
||||
release.extend([0, 0])
|
||||
return tuple(release)
|
||||
|
||||
|
||||
mmcv_minimum_version = '1.3.17' #Dirty hacking for custom_mmcv
|
||||
mmcv_maximum_version = '1.9.0'
|
||||
mmcv_version = digit_version(mmcv.__version__)
|
||||
|
||||
|
||||
assert (mmcv_version >= digit_version(mmcv_minimum_version)
|
||||
and mmcv_version <= digit_version(mmcv_maximum_version)), \
|
||||
f'MMCV=={mmcv.__version__} is used but incompatible. ' \
|
||||
f'Please install mmcv>={mmcv_minimum_version}, <={mmcv_maximum_version}.'
|
||||
|
||||
__all__ = ['__version__', 'digit_version']
|
||||
@@ -1,13 +0,0 @@
|
||||
from mogen.apis import test, train
|
||||
from mogen.apis.test import (
|
||||
collect_results_cpu,
|
||||
collect_results_gpu,
|
||||
multi_gpu_test,
|
||||
single_gpu_test,
|
||||
)
|
||||
from mogen.apis.train import set_random_seed, train_model
|
||||
|
||||
__all__ = [
|
||||
'collect_results_cpu', 'collect_results_gpu', 'multi_gpu_test',
|
||||
'single_gpu_test', 'set_random_seed', 'train_model'
|
||||
]
|
||||
@@ -1,160 +0,0 @@
|
||||
import os.path as osp
|
||||
import pickle
|
||||
import shutil
|
||||
import tempfile
|
||||
import time
|
||||
|
||||
import custom_mmpkg.custom_mmcv as mmcv
|
||||
import torch
|
||||
import torch.distributed as dist
|
||||
from custom_mmpkg.custom_mmcv.runner import get_dist_info
|
||||
|
||||
|
||||
def single_gpu_test(model, data_loader):
|
||||
"""Test with single gpu."""
|
||||
model.eval()
|
||||
results = []
|
||||
dataset = data_loader.dataset
|
||||
prog_bar = mmcv.ProgressBar(len(dataset))
|
||||
for i, data in enumerate(data_loader):
|
||||
with torch.no_grad():
|
||||
result = model(return_loss=False, **data)
|
||||
|
||||
batch_size = len(result)
|
||||
if isinstance(result, list):
|
||||
results.extend(result)
|
||||
else:
|
||||
results.append(result)
|
||||
|
||||
batch_size = data['motion'].size(0)
|
||||
for _ in range(batch_size):
|
||||
prog_bar.update()
|
||||
return results
|
||||
|
||||
|
||||
def multi_gpu_test(model, data_loader, tmpdir=None, gpu_collect=False):
|
||||
"""Test model with multiple gpus.
|
||||
This method tests model with multiple gpus and collects the results
|
||||
under two different modes: gpu and cpu modes. By setting 'gpu_collect=True'
|
||||
it encodes results to gpu tensors and use gpu communication for results
|
||||
collection. On cpu mode it saves the results on different gpus to 'tmpdir'
|
||||
and collects them by the rank 0 worker.
|
||||
Args:
|
||||
model (nn.Module): Model to be tested.
|
||||
data_loader (nn.Dataloader): Pytorch data loader.
|
||||
tmpdir (str): Path of directory to save the temporary results from
|
||||
different gpus under cpu mode.
|
||||
gpu_collect (bool): Option to use either gpu or cpu to collect results.
|
||||
Returns:
|
||||
list: The prediction results.
|
||||
"""
|
||||
model.eval()
|
||||
results = []
|
||||
dataset = data_loader.dataset
|
||||
rank, world_size = get_dist_info()
|
||||
if rank == 0:
|
||||
# Check if tmpdir is valid for cpu_collect
|
||||
if (not gpu_collect) and (tmpdir is not None and osp.exists(tmpdir)):
|
||||
raise OSError((f'The tmpdir {tmpdir} already exists.',
|
||||
' Since tmpdir will be deleted after testing,',
|
||||
' please make sure you specify an empty one.'))
|
||||
prog_bar = mmcv.ProgressBar(len(dataset))
|
||||
time.sleep(2) # This line can prevent deadlock problem in some cases.
|
||||
for i, data in enumerate(data_loader):
|
||||
with torch.no_grad():
|
||||
result = model(return_loss=False, **data)
|
||||
if isinstance(result, list):
|
||||
results.extend(result)
|
||||
else:
|
||||
results.append(result)
|
||||
|
||||
if rank == 0:
|
||||
batch_size = data['motion'].size(0)
|
||||
for _ in range(batch_size * world_size):
|
||||
prog_bar.update()
|
||||
|
||||
# collect results from all ranks
|
||||
if gpu_collect:
|
||||
results = collect_results_gpu(results, len(dataset))
|
||||
else:
|
||||
results = collect_results_cpu(results, len(dataset), tmpdir)
|
||||
return results
|
||||
|
||||
|
||||
def collect_results_cpu(result_part, size, tmpdir=None):
|
||||
"""Collect results in cpu."""
|
||||
rank, world_size = get_dist_info()
|
||||
# create a tmp dir if it is not specified
|
||||
if tmpdir is None:
|
||||
MAX_LEN = 512
|
||||
# 32 is whitespace
|
||||
dir_tensor = torch.full((MAX_LEN, ),
|
||||
32,
|
||||
dtype=torch.uint8,
|
||||
device='cuda')
|
||||
if rank == 0:
|
||||
mmcv.mkdir_or_exist('.dist_test')
|
||||
tmpdir = tempfile.mkdtemp(dir='.dist_test')
|
||||
tmpdir = torch.tensor(
|
||||
bytearray(tmpdir.encode()), dtype=torch.uint8, device='cuda')
|
||||
dir_tensor[:len(tmpdir)] = tmpdir
|
||||
dist.broadcast(dir_tensor, 0)
|
||||
tmpdir = dir_tensor.cpu().numpy().tobytes().decode().rstrip()
|
||||
else:
|
||||
mmcv.mkdir_or_exist(tmpdir)
|
||||
# dump the part result to the dir
|
||||
mmcv.dump(result_part, osp.join(tmpdir, f'part_{rank}.pkl'))
|
||||
dist.barrier()
|
||||
# collect all parts
|
||||
if rank != 0:
|
||||
return None
|
||||
else:
|
||||
# load results of all parts from tmp dir
|
||||
part_list = []
|
||||
for i in range(world_size):
|
||||
part_file = osp.join(tmpdir, f'part_{i}.pkl')
|
||||
part_result = mmcv.load(part_file)
|
||||
part_list.append(part_result)
|
||||
# sort the results
|
||||
ordered_results = []
|
||||
for res in zip(*part_list):
|
||||
ordered_results.extend(list(res))
|
||||
# the dataloader may pad some samples
|
||||
ordered_results = ordered_results[:size]
|
||||
# remove tmp dir
|
||||
shutil.rmtree(tmpdir)
|
||||
return ordered_results
|
||||
|
||||
|
||||
def collect_results_gpu(result_part, size):
|
||||
"""Collect results in gpu."""
|
||||
rank, world_size = get_dist_info()
|
||||
# dump result part to tensor with pickle
|
||||
part_tensor = torch.tensor(
|
||||
bytearray(pickle.dumps(result_part)), dtype=torch.uint8, device='cuda')
|
||||
# gather all result part tensor shape
|
||||
shape_tensor = torch.tensor(part_tensor.shape, device='cuda')
|
||||
shape_list = [shape_tensor.clone() for _ in range(world_size)]
|
||||
dist.all_gather(shape_list, shape_tensor)
|
||||
# padding result part tensor to max length
|
||||
shape_max = torch.tensor(shape_list).max()
|
||||
part_send = torch.zeros(shape_max, dtype=torch.uint8, device='cuda')
|
||||
part_send[:shape_tensor[0]] = part_tensor
|
||||
part_recv_list = [
|
||||
part_tensor.new_zeros(shape_max) for _ in range(world_size)
|
||||
]
|
||||
# gather all result part
|
||||
dist.all_gather(part_recv_list, part_send)
|
||||
|
||||
if rank == 0:
|
||||
part_list = []
|
||||
for recv, shape in zip(part_recv_list, shape_list):
|
||||
part_result = pickle.loads(recv[:shape[0]].cpu().numpy().tobytes())
|
||||
part_list.append(part_result)
|
||||
# sort the results
|
||||
ordered_results = []
|
||||
for res in zip(*part_list):
|
||||
ordered_results.extend(list(res))
|
||||
# the dataloader may pad some samples
|
||||
ordered_results = ordered_results[:size]
|
||||
return ordered_results
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user