Spaces:
Runtime error
Runtime error
update generate tools
Browse files- src/generate_utils/__init__.py +1 -0
- src/generate_utils/generate.py +118 -0
- src/generate_utils/lib/ckpt/pressurepose_20251222_180032/config.yaml +40 -0
- src/generate_utils/lib/ckpt/pressurepose_20251222_180032/logs/train.log +153 -0
- src/generate_utils/lib/ckpt/pressurepose_20251222_180032/test_result.txt +20 -0
- src/generate_utils/lib/model/cvae.py +180 -0
- src/generate_utils/lib/model/loss.py +61 -0
- src/generate_utils/lib/model/mlp.py +382 -0
- src/generate_utils/lib/model/pointnet.py +319 -0
- src/generate_utils/lib/model/resnet.py +432 -0
- src/generate_utils/lib/model/unet.py +277 -0
src/generate_utils/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
from .generate import PressureGenerator
|
src/generate_utils/generate.py
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import yaml
|
| 3 |
+
import smplx
|
| 4 |
+
import torch
|
| 5 |
+
|
| 6 |
+
from .lib.model.cvae import SMPL2PressureCVAE
|
| 7 |
+
|
| 8 |
+
class PressureGenerator:
|
| 9 |
+
# 静态配置数据
|
| 10 |
+
DATASET_META = {
|
| 11 |
+
'tip': {
|
| 12 |
+
'max_p': 512.0,
|
| 13 |
+
'crop_size': [56, 40],
|
| 14 |
+
'path': "/workspace/zyk/public_data/wzy_opt_dataset_w_feats"
|
| 15 |
+
},
|
| 16 |
+
'pressurepose': {
|
| 17 |
+
'max_p': 100.0,
|
| 18 |
+
'crop_size': [64, 27],
|
| 19 |
+
'path': "/workspace/zyk/public_data/pressurepose/synth"
|
| 20 |
+
},
|
| 21 |
+
'moyo': {
|
| 22 |
+
'max_p': 64.0,
|
| 23 |
+
'crop_size': [110, 37],
|
| 24 |
+
'path': "/workspace/zyk/public_data/moyo"
|
| 25 |
+
}
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
+
def __init__(self,
|
| 29 |
+
ckpt_dir="generate_utils/lib/ckpt/pressurepose_20251222_180032",
|
| 30 |
+
smpl_model_dir="E:/pyku/smpl_models",
|
| 31 |
+
device="cpu"):
|
| 32 |
+
"""
|
| 33 |
+
初始化生成器:加载配置、权重和 SMPL 模型。
|
| 34 |
+
"""
|
| 35 |
+
self.device = torch.device(device)
|
| 36 |
+
self.ckpt_dir = ckpt_dir
|
| 37 |
+
self.smpl_model_dir = smpl_model_dir
|
| 38 |
+
|
| 39 |
+
# 1. 加载配置
|
| 40 |
+
self.cfg = self._load_config()
|
| 41 |
+
|
| 42 |
+
# 2. 设置数据集相关参数
|
| 43 |
+
dataset_name = self.cfg['dataset']['name']
|
| 44 |
+
if dataset_name not in self.DATASET_META:
|
| 45 |
+
raise ValueError(f"Unknown dataset name: {dataset_name}")
|
| 46 |
+
|
| 47 |
+
self.max_pressure = self.DATASET_META[dataset_name]['max_p']
|
| 48 |
+
self.is_normalized = self.cfg['dataset'].get('normal', False)
|
| 49 |
+
|
| 50 |
+
# 3. 加载 CVAE 模型
|
| 51 |
+
self.cvae_model = self._load_cvae()
|
| 52 |
+
|
| 53 |
+
# 4. 加载 SMPL 模型
|
| 54 |
+
self.smpl_model = self._load_smpl()
|
| 55 |
+
|
| 56 |
+
print(f"Pressure Generator loaded successfully from {self.ckpt_dir}")
|
| 57 |
+
|
| 58 |
+
def _load_config(self):
|
| 59 |
+
config_path = os.path.join(self.ckpt_dir, 'config.yaml')
|
| 60 |
+
if not os.path.exists(config_path):
|
| 61 |
+
raise FileNotFoundError(f"Config not found at {config_path}")
|
| 62 |
+
with open(config_path, 'r') as f:
|
| 63 |
+
return yaml.safe_load(f)
|
| 64 |
+
|
| 65 |
+
def _load_cvae(self):
|
| 66 |
+
model = SMPL2PressureCVAE(self.cfg).to(self.device)
|
| 67 |
+
ckpt_path = os.path.join(self.ckpt_dir, 'ckpts', 'best_model.pth')
|
| 68 |
+
|
| 69 |
+
if not os.path.exists(ckpt_path):
|
| 70 |
+
raise FileNotFoundError(f"Checkpoint not found at {ckpt_path}")
|
| 71 |
+
|
| 72 |
+
checkpoint = torch.load(ckpt_path, map_location=self.device)
|
| 73 |
+
model.load_state_dict(checkpoint['model_state_dict'])
|
| 74 |
+
model.eval()
|
| 75 |
+
return model
|
| 76 |
+
|
| 77 |
+
def _load_smpl(self):
|
| 78 |
+
# 创建 SMPL 模型 (这是一个比较耗时的操作)
|
| 79 |
+
smpl = smplx.create(
|
| 80 |
+
self.smpl_model_dir,
|
| 81 |
+
model_type='smpl',
|
| 82 |
+
gender='neutral',
|
| 83 |
+
ext='pkl'
|
| 84 |
+
).to(self.device)
|
| 85 |
+
return smpl
|
| 86 |
+
|
| 87 |
+
@torch.no_grad()
|
| 88 |
+
def generate(self, betas, transl, poses):
|
| 89 |
+
"""
|
| 90 |
+
执行推理。
|
| 91 |
+
输入参数应该是 Tensor, 维度需符合模型要求 (Batch Size, ...)。
|
| 92 |
+
"""
|
| 93 |
+
# 确保输入在正确的设备上
|
| 94 |
+
if betas.device != self.device: betas = betas.to(self.device)
|
| 95 |
+
if transl.device != self.device: transl = transl.to(self.device)
|
| 96 |
+
if poses.device != self.device: poses = poses.to(self.device)
|
| 97 |
+
|
| 98 |
+
# 1. 获取 SMPL 顶点 (Vertices)
|
| 99 |
+
output = self.smpl_model(
|
| 100 |
+
betas=betas,
|
| 101 |
+
global_orient=poses[:, :3], # 前3位是全局旋转
|
| 102 |
+
body_pose=poses[:, 3:], # 后69位是身体姿态
|
| 103 |
+
transl=transl,
|
| 104 |
+
)
|
| 105 |
+
vertices = output.vertices
|
| 106 |
+
|
| 107 |
+
# 2. 预测压力图
|
| 108 |
+
pred_pmap = self.cvae_model.inference(vertices)
|
| 109 |
+
|
| 110 |
+
# 3. 后处理 (反归一化 & 阈值过滤)
|
| 111 |
+
if self.is_normalized:
|
| 112 |
+
pred_pmap = pred_pmap * self.max_pressure
|
| 113 |
+
|
| 114 |
+
# 这里的 0.1 是硬编码的阈值,也可以提取为参数
|
| 115 |
+
pred_pmap[pred_pmap < 0.1] = 0
|
| 116 |
+
|
| 117 |
+
return pred_pmap
|
| 118 |
+
|
src/generate_utils/lib/ckpt/pressurepose_20251222_180032/config.yaml
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
dataset:
|
| 2 |
+
name: pressurepose
|
| 3 |
+
normal: true
|
| 4 |
+
device: cuda
|
| 5 |
+
model:
|
| 6 |
+
cond_decoder:
|
| 7 |
+
type: mlp
|
| 8 |
+
cond_embed_dim: 256
|
| 9 |
+
cond_encoder:
|
| 10 |
+
return_global_feature: true
|
| 11 |
+
type: pointnet
|
| 12 |
+
use_spatial_transformer: false
|
| 13 |
+
cond_type: vertices
|
| 14 |
+
dropout_rate: 0.3
|
| 15 |
+
embed_dim: 256
|
| 16 |
+
main_decoder:
|
| 17 |
+
bilinear: false
|
| 18 |
+
type: unet
|
| 19 |
+
main_encoder:
|
| 20 |
+
type: resnet18
|
| 21 |
+
output: null
|
| 22 |
+
seed: 42
|
| 23 |
+
training:
|
| 24 |
+
batch_size: 64
|
| 25 |
+
learning_rate: 0.001
|
| 26 |
+
log_freq: 10
|
| 27 |
+
loss:
|
| 28 |
+
kl_weight: 10.0
|
| 29 |
+
pcr_weight: 60.0
|
| 30 |
+
pmr_weight: 100.0
|
| 31 |
+
num_epochs: 100
|
| 32 |
+
optimizer:
|
| 33 |
+
type: adam
|
| 34 |
+
weight_decay: 0.0001
|
| 35 |
+
save_freq: 999
|
| 36 |
+
scheduler:
|
| 37 |
+
gamma: 0.1
|
| 38 |
+
step_size: 30
|
| 39 |
+
type: cosine
|
| 40 |
+
val_freq: 1
|
src/generate_utils/lib/ckpt/pressurepose_20251222_180032/logs/train.log
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
2025-12-22 18:00:32,951 - INFO - Starting training on cuda:0: pressurepose...
|
| 2 |
+
2025-12-22 18:08:37,856 - INFO - Epoch 0 | Train Loss: 3.9866 | Val Loss: 2.2416
|
| 3 |
+
2025-12-22 18:08:47,179 - INFO - --> Best model saved at epoch 0
|
| 4 |
+
2025-12-22 18:16:45,378 - INFO - Epoch 1 | Train Loss: 2.1470 | Val Loss: 2.0693
|
| 5 |
+
2025-12-22 18:16:54,269 - INFO - --> Best model saved at epoch 1
|
| 6 |
+
2025-12-22 18:24:54,079 - INFO - Epoch 2 | Train Loss: 1.9345 | Val Loss: 1.7783
|
| 7 |
+
2025-12-22 18:25:02,726 - INFO - --> Best model saved at epoch 2
|
| 8 |
+
2025-12-22 18:32:55,954 - INFO - Epoch 3 | Train Loss: 1.7918 | Val Loss: 1.5810
|
| 9 |
+
2025-12-22 18:33:03,955 - INFO - --> Best model saved at epoch 3
|
| 10 |
+
2025-12-22 18:41:00,038 - INFO - Epoch 4 | Train Loss: 1.7060 | Val Loss: 1.5400
|
| 11 |
+
2025-12-22 18:41:06,723 - INFO - --> Best model saved at epoch 4
|
| 12 |
+
2025-12-22 18:49:04,843 - INFO - Epoch 5 | Train Loss: 1.6220 | Val Loss: 1.5549
|
| 13 |
+
2025-12-22 18:57:05,040 - INFO - Epoch 6 | Train Loss: 1.5575 | Val Loss: 1.3848
|
| 14 |
+
2025-12-22 18:57:11,939 - INFO - --> Best model saved at epoch 6
|
| 15 |
+
2025-12-22 19:05:07,141 - INFO - Epoch 7 | Train Loss: 1.5061 | Val Loss: 1.3306
|
| 16 |
+
2025-12-22 19:05:17,022 - INFO - --> Best model saved at epoch 7
|
| 17 |
+
2025-12-22 19:13:12,365 - INFO - Epoch 8 | Train Loss: 1.4657 | Val Loss: 1.3127
|
| 18 |
+
2025-12-22 19:13:21,550 - INFO - --> Best model saved at epoch 8
|
| 19 |
+
2025-12-22 19:21:14,461 - INFO - Epoch 9 | Train Loss: 1.4281 | Val Loss: 1.2233
|
| 20 |
+
2025-12-22 19:21:22,432 - INFO - --> Best model saved at epoch 9
|
| 21 |
+
2025-12-22 19:29:17,334 - INFO - Epoch 10 | Train Loss: 1.3971 | Val Loss: 1.3038
|
| 22 |
+
2025-12-22 19:37:16,753 - INFO - Epoch 11 | Train Loss: 1.3737 | Val Loss: 1.2679
|
| 23 |
+
2025-12-22 19:45:15,562 - INFO - Epoch 12 | Train Loss: 1.3458 | Val Loss: 1.1456
|
| 24 |
+
2025-12-22 19:45:22,194 - INFO - --> Best model saved at epoch 12
|
| 25 |
+
2025-12-22 19:53:23,432 - INFO - Epoch 13 | Train Loss: 1.3215 | Val Loss: 1.1579
|
| 26 |
+
2025-12-22 20:01:26,931 - INFO - Epoch 14 | Train Loss: 1.2890 | Val Loss: 1.1414
|
| 27 |
+
2025-12-22 20:01:35,934 - INFO - --> Best model saved at epoch 14
|
| 28 |
+
2025-12-22 20:09:33,396 - INFO - Epoch 15 | Train Loss: 1.2620 | Val Loss: 1.0436
|
| 29 |
+
2025-12-22 20:09:42,377 - INFO - --> Best model saved at epoch 15
|
| 30 |
+
2025-12-22 20:17:41,945 - INFO - Epoch 16 | Train Loss: 1.2347 | Val Loss: 1.0598
|
| 31 |
+
2025-12-22 20:26:25,986 - INFO - Epoch 17 | Train Loss: 1.2038 | Val Loss: 1.1026
|
| 32 |
+
2025-12-22 20:35:12,185 - INFO - Epoch 18 | Train Loss: 1.1821 | Val Loss: 0.9907
|
| 33 |
+
2025-12-22 20:35:19,867 - INFO - --> Best model saved at epoch 18
|
| 34 |
+
2025-12-22 20:44:01,826 - INFO - Epoch 19 | Train Loss: 1.1594 | Val Loss: 0.9307
|
| 35 |
+
2025-12-22 20:44:11,053 - INFO - --> Best model saved at epoch 19
|
| 36 |
+
2025-12-22 20:52:25,588 - INFO - Epoch 20 | Train Loss: 1.1332 | Val Loss: 0.8907
|
| 37 |
+
2025-12-22 20:52:33,744 - INFO - --> Best model saved at epoch 20
|
| 38 |
+
2025-12-22 21:00:36,050 - INFO - Epoch 21 | Train Loss: 1.1235 | Val Loss: 0.8508
|
| 39 |
+
2025-12-22 21:00:42,396 - INFO - --> Best model saved at epoch 21
|
| 40 |
+
2025-12-22 21:08:38,121 - INFO - Epoch 22 | Train Loss: 1.1021 | Val Loss: 0.9147
|
| 41 |
+
2025-12-22 21:16:36,098 - INFO - Epoch 23 | Train Loss: 1.0872 | Val Loss: 0.9329
|
| 42 |
+
2025-12-22 21:24:32,846 - INFO - Epoch 24 | Train Loss: 1.0739 | Val Loss: 0.8428
|
| 43 |
+
2025-12-22 21:24:40,586 - INFO - --> Best model saved at epoch 24
|
| 44 |
+
2025-12-22 21:32:41,914 - INFO - Epoch 25 | Train Loss: 1.0588 | Val Loss: 0.8102
|
| 45 |
+
2025-12-22 21:32:51,039 - INFO - --> Best model saved at epoch 25
|
| 46 |
+
2025-12-22 21:40:50,536 - INFO - Epoch 26 | Train Loss: 1.0506 | Val Loss: 0.8025
|
| 47 |
+
2025-12-22 21:40:59,505 - INFO - --> Best model saved at epoch 26
|
| 48 |
+
2025-12-22 21:48:55,531 - INFO - Epoch 27 | Train Loss: 1.0417 | Val Loss: 0.7854
|
| 49 |
+
2025-12-22 21:49:03,636 - INFO - --> Best model saved at epoch 27
|
| 50 |
+
2025-12-22 21:57:02,459 - INFO - Epoch 28 | Train Loss: 1.0373 | Val Loss: 0.7912
|
| 51 |
+
2025-12-22 22:05:07,235 - INFO - Epoch 29 | Train Loss: 1.0243 | Val Loss: 0.7809
|
| 52 |
+
2025-12-22 22:05:17,305 - INFO - --> Best model saved at epoch 29
|
| 53 |
+
2025-12-22 22:13:14,376 - INFO - Epoch 30 | Train Loss: 1.0116 | Val Loss: 0.8382
|
| 54 |
+
2025-12-22 22:21:12,713 - INFO - Epoch 31 | Train Loss: 1.0017 | Val Loss: 0.7629
|
| 55 |
+
2025-12-22 22:21:21,674 - INFO - --> Best model saved at epoch 31
|
| 56 |
+
2025-12-22 22:29:18,931 - INFO - Epoch 32 | Train Loss: 0.9948 | Val Loss: 0.8141
|
| 57 |
+
2025-12-22 22:37:20,810 - INFO - Epoch 33 | Train Loss: 0.9933 | Val Loss: 0.7737
|
| 58 |
+
2025-12-22 22:45:23,264 - INFO - Epoch 34 | Train Loss: 0.9768 | Val Loss: 0.7786
|
| 59 |
+
2025-12-22 22:53:25,925 - INFO - Epoch 35 | Train Loss: 0.9706 | Val Loss: 0.7660
|
| 60 |
+
2025-12-22 23:01:28,145 - INFO - Epoch 36 | Train Loss: 0.9622 | Val Loss: 0.8475
|
| 61 |
+
2025-12-22 23:09:31,131 - INFO - Epoch 37 | Train Loss: 0.9572 | Val Loss: 0.7795
|
| 62 |
+
2025-12-22 23:17:32,236 - INFO - Epoch 38 | Train Loss: 0.9484 | Val Loss: 0.7872
|
| 63 |
+
2025-12-22 23:25:34,544 - INFO - Epoch 39 | Train Loss: 0.9437 | Val Loss: 0.7449
|
| 64 |
+
2025-12-22 23:25:43,984 - INFO - --> Best model saved at epoch 39
|
| 65 |
+
2025-12-22 23:33:42,019 - INFO - Epoch 40 | Train Loss: 0.9383 | Val Loss: 0.7207
|
| 66 |
+
2025-12-22 23:33:51,375 - INFO - --> Best model saved at epoch 40
|
| 67 |
+
2025-12-22 23:41:48,820 - INFO - Epoch 41 | Train Loss: 0.9304 | Val Loss: 0.7335
|
| 68 |
+
2025-12-22 23:49:51,966 - INFO - Epoch 42 | Train Loss: 0.9212 | Val Loss: 0.8034
|
| 69 |
+
2025-12-22 23:57:56,083 - INFO - Epoch 43 | Train Loss: 0.9181 | Val Loss: 0.7071
|
| 70 |
+
2025-12-22 23:58:05,215 - INFO - --> Best model saved at epoch 43
|
| 71 |
+
2025-12-23 00:06:03,136 - INFO - Epoch 44 | Train Loss: 0.9082 | Val Loss: 0.7624
|
| 72 |
+
2025-12-23 00:14:12,478 - INFO - Epoch 45 | Train Loss: 0.9088 | Val Loss: 0.7162
|
| 73 |
+
2025-12-23 00:22:18,581 - INFO - Epoch 46 | Train Loss: 0.8930 | Val Loss: 0.7205
|
| 74 |
+
2025-12-23 00:30:23,463 - INFO - Epoch 47 | Train Loss: 0.8928 | Val Loss: 0.7508
|
| 75 |
+
2025-12-23 00:38:20,809 - INFO - Epoch 48 | Train Loss: 0.8809 | Val Loss: 0.6766
|
| 76 |
+
2025-12-23 00:38:27,973 - INFO - --> Best model saved at epoch 48
|
| 77 |
+
2025-12-23 00:46:29,782 - INFO - Epoch 49 | Train Loss: 0.8810 | Val Loss: 0.6758
|
| 78 |
+
2025-12-23 00:46:38,221 - INFO - --> Best model saved at epoch 49
|
| 79 |
+
2025-12-23 00:54:38,092 - INFO - Epoch 50 | Train Loss: 0.8696 | Val Loss: 0.7521
|
| 80 |
+
2025-12-23 01:03:00,051 - INFO - Epoch 51 | Train Loss: 0.8633 | Val Loss: 0.6730
|
| 81 |
+
2025-12-23 01:03:07,488 - INFO - --> Best model saved at epoch 51
|
| 82 |
+
2025-12-23 01:11:19,673 - INFO - Epoch 52 | Train Loss: 0.8607 | Val Loss: 0.6512
|
| 83 |
+
2025-12-23 01:11:27,660 - INFO - --> Best model saved at epoch 52
|
| 84 |
+
2025-12-23 01:19:24,289 - INFO - Epoch 53 | Train Loss: 0.8507 | Val Loss: 0.6400
|
| 85 |
+
2025-12-23 01:19:33,796 - INFO - --> Best model saved at epoch 53
|
| 86 |
+
2025-12-23 01:27:29,840 - INFO - Epoch 54 | Train Loss: 0.8456 | Val Loss: 0.6368
|
| 87 |
+
2025-12-23 01:27:37,645 - INFO - --> Best model saved at epoch 54
|
| 88 |
+
2025-12-23 01:35:32,997 - INFO - Epoch 55 | Train Loss: 0.8410 | Val Loss: 0.6299
|
| 89 |
+
2025-12-23 01:35:42,289 - INFO - --> Best model saved at epoch 55
|
| 90 |
+
2025-12-23 01:43:44,539 - INFO - Epoch 56 | Train Loss: 0.8303 | Val Loss: 0.6753
|
| 91 |
+
2025-12-23 01:52:07,397 - INFO - Epoch 57 | Train Loss: 0.8289 | Val Loss: 0.6344
|
| 92 |
+
2025-12-23 02:00:30,969 - INFO - Epoch 58 | Train Loss: 0.8215 | Val Loss: 0.6222
|
| 93 |
+
2025-12-23 02:00:39,050 - INFO - --> Best model saved at epoch 58
|
| 94 |
+
2025-12-23 02:08:56,943 - INFO - Epoch 59 | Train Loss: 0.8130 | Val Loss: 0.6418
|
| 95 |
+
2025-12-23 02:17:16,645 - INFO - Epoch 60 | Train Loss: 0.8074 | Val Loss: 0.6353
|
| 96 |
+
2025-12-23 02:25:12,935 - INFO - Epoch 61 | Train Loss: 0.8003 | Val Loss: 0.6470
|
| 97 |
+
2025-12-23 02:33:12,804 - INFO - Epoch 62 | Train Loss: 0.7943 | Val Loss: 0.6385
|
| 98 |
+
2025-12-23 02:41:13,969 - INFO - Epoch 63 | Train Loss: 0.7891 | Val Loss: 0.5937
|
| 99 |
+
2025-12-23 02:41:22,714 - INFO - --> Best model saved at epoch 63
|
| 100 |
+
2025-12-23 02:49:17,641 - INFO - Epoch 64 | Train Loss: 0.7824 | Val Loss: 0.5996
|
| 101 |
+
2025-12-23 02:57:15,236 - INFO - Epoch 65 | Train Loss: 0.7795 | Val Loss: 0.5926
|
| 102 |
+
2025-12-23 02:57:22,741 - INFO - --> Best model saved at epoch 65
|
| 103 |
+
2025-12-23 03:05:19,890 - INFO - Epoch 66 | Train Loss: 0.7695 | Val Loss: 0.5950
|
| 104 |
+
2025-12-23 03:13:22,173 - INFO - Epoch 67 | Train Loss: 0.7681 | Val Loss: 0.6047
|
| 105 |
+
2025-12-23 03:21:19,313 - INFO - Epoch 68 | Train Loss: 0.7563 | Val Loss: 0.5717
|
| 106 |
+
2025-12-23 03:21:26,615 - INFO - --> Best model saved at epoch 68
|
| 107 |
+
2025-12-23 03:29:19,295 - INFO - Epoch 69 | Train Loss: 0.7572 | Val Loss: 0.5887
|
| 108 |
+
2025-12-23 03:37:18,966 - INFO - Epoch 70 | Train Loss: 0.7479 | Val Loss: 0.6256
|
| 109 |
+
2025-12-23 03:45:16,737 - INFO - Epoch 71 | Train Loss: 0.7422 | Val Loss: 0.5700
|
| 110 |
+
2025-12-23 03:45:25,848 - INFO - --> Best model saved at epoch 71
|
| 111 |
+
2025-12-23 03:53:20,776 - INFO - Epoch 72 | Train Loss: 0.7358 | Val Loss: 0.5686
|
| 112 |
+
2025-12-23 03:53:30,797 - INFO - --> Best model saved at epoch 72
|
| 113 |
+
2025-12-23 04:01:26,968 - INFO - Epoch 73 | Train Loss: 0.7304 | Val Loss: 0.5606
|
| 114 |
+
2025-12-23 04:01:33,584 - INFO - --> Best model saved at epoch 73
|
| 115 |
+
2025-12-23 04:09:25,824 - INFO - Epoch 74 | Train Loss: 0.7257 | Val Loss: 0.5824
|
| 116 |
+
2025-12-23 04:17:23,933 - INFO - Epoch 75 | Train Loss: 0.7163 | Val Loss: 0.5535
|
| 117 |
+
2025-12-23 04:17:32,977 - INFO - --> Best model saved at epoch 75
|
| 118 |
+
2025-12-23 04:25:27,001 - INFO - Epoch 76 | Train Loss: 0.7151 | Val Loss: 0.5496
|
| 119 |
+
2025-12-23 04:25:34,823 - INFO - --> Best model saved at epoch 76
|
| 120 |
+
2025-12-23 04:33:28,463 - INFO - Epoch 77 | Train Loss: 0.7059 | Val Loss: 0.5530
|
| 121 |
+
2025-12-23 04:41:27,898 - INFO - Epoch 78 | Train Loss: 0.7017 | Val Loss: 0.5554
|
| 122 |
+
2025-12-23 04:49:26,990 - INFO - Epoch 79 | Train Loss: 0.6985 | Val Loss: 0.5580
|
| 123 |
+
2025-12-23 04:57:24,784 - INFO - Epoch 80 | Train Loss: 0.6916 | Val Loss: 0.5468
|
| 124 |
+
2025-12-23 04:57:34,075 - INFO - --> Best model saved at epoch 80
|
| 125 |
+
2025-12-23 05:05:27,512 - INFO - Epoch 81 | Train Loss: 0.6877 | Val Loss: 0.5476
|
| 126 |
+
2025-12-23 05:13:25,319 - INFO - Epoch 82 | Train Loss: 0.6840 | Val Loss: 0.5423
|
| 127 |
+
2025-12-23 05:13:32,737 - INFO - --> Best model saved at epoch 82
|
| 128 |
+
2025-12-23 05:21:35,339 - INFO - Epoch 83 | Train Loss: 0.6788 | Val Loss: 0.5242
|
| 129 |
+
2025-12-23 05:21:43,953 - INFO - --> Best model saved at epoch 83
|
| 130 |
+
2025-12-23 05:30:01,032 - INFO - Epoch 84 | Train Loss: 0.6723 | Val Loss: 0.5558
|
| 131 |
+
2025-12-23 05:38:07,292 - INFO - Epoch 85 | Train Loss: 0.6695 | Val Loss: 0.5458
|
| 132 |
+
2025-12-23 05:46:03,208 - INFO - Epoch 86 | Train Loss: 0.6647 | Val Loss: 0.5323
|
| 133 |
+
2025-12-23 05:54:00,838 - INFO - Epoch 87 | Train Loss: 0.6594 | Val Loss: 0.5247
|
| 134 |
+
2025-12-23 06:01:57,323 - INFO - Epoch 88 | Train Loss: 0.6564 | Val Loss: 0.5216
|
| 135 |
+
2025-12-23 06:02:06,118 - INFO - --> Best model saved at epoch 88
|
| 136 |
+
2025-12-23 06:09:58,071 - INFO - Epoch 89 | Train Loss: 0.6512 | Val Loss: 0.5181
|
| 137 |
+
2025-12-23 06:10:07,271 - INFO - --> Best model saved at epoch 89
|
| 138 |
+
2025-12-23 06:18:02,564 - INFO - Epoch 90 | Train Loss: 0.6501 | Val Loss: 0.5153
|
| 139 |
+
2025-12-23 06:18:11,750 - INFO - --> Best model saved at epoch 90
|
| 140 |
+
2025-12-23 06:26:04,938 - INFO - Epoch 91 | Train Loss: 0.6478 | Val Loss: 0.5166
|
| 141 |
+
2025-12-23 06:34:05,888 - INFO - Epoch 92 | Train Loss: 0.6415 | Val Loss: 0.5184
|
| 142 |
+
2025-12-23 06:42:06,933 - INFO - Epoch 93 | Train Loss: 0.6407 | Val Loss: 0.5158
|
| 143 |
+
2025-12-23 06:50:05,590 - INFO - Epoch 94 | Train Loss: 0.6373 | Val Loss: 0.5138
|
| 144 |
+
2025-12-23 06:50:14,737 - INFO - --> Best model saved at epoch 94
|
| 145 |
+
2025-12-23 06:58:09,245 - INFO - Epoch 95 | Train Loss: 0.6369 | Val Loss: 0.5102
|
| 146 |
+
2025-12-23 06:58:16,989 - INFO - --> Best model saved at epoch 95
|
| 147 |
+
2025-12-23 07:06:09,566 - INFO - Epoch 96 | Train Loss: 0.6362 | Val Loss: 0.5114
|
| 148 |
+
2025-12-23 07:14:08,758 - INFO - Epoch 97 | Train Loss: 0.6331 | Val Loss: 0.5140
|
| 149 |
+
2025-12-23 07:22:02,795 - INFO - Epoch 98 | Train Loss: 0.6318 | Val Loss: 0.5099
|
| 150 |
+
2025-12-23 07:22:11,592 - INFO - --> Best model saved at epoch 98
|
| 151 |
+
2025-12-23 07:30:05,162 - INFO - Epoch 99 | Train Loss: 0.6318 | Val Loss: 0.5092
|
| 152 |
+
2025-12-23 07:30:12,362 - INFO - --> Best model saved at epoch 99
|
| 153 |
+
2025-12-23 07:30:12,362 - INFO - Training complete.
|
src/generate_utils/lib/ckpt/pressurepose_20251222_180032/test_result.txt
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Test Results for experiment: output/pressurepose_20251222_180032/
|
| 2 |
+
Checkpoint used: best_model.pth
|
| 3 |
+
------------------------------
|
| 4 |
+
SSIM: 0.892187
|
| 5 |
+
MAE: 1.883694
|
| 6 |
+
MSE: 31.912682
|
| 7 |
+
MRE: 0.018837
|
| 8 |
+
CoP_Dist: 0.500346
|
| 9 |
+
------------------------------
|
| 10 |
+
IPMAN SSIM: 0.527497
|
| 11 |
+
IPMAN MAE: 5.092346
|
| 12 |
+
IPMAN MSE: 221.172048
|
| 13 |
+
IPMAN MRE: 0.050923
|
| 14 |
+
IPMAN CoP_Dist: 1.602944
|
| 15 |
+
------------------------------
|
| 16 |
+
PMR SSIM: 0.541821
|
| 17 |
+
PMR MAE: 16.486778
|
| 18 |
+
PMR MSE: 2864.192610
|
| 19 |
+
PMR MRE: 0.164868
|
| 20 |
+
PMR CoP_Dist: 4.019547
|
src/generate_utils/lib/model/cvae.py
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import torch
|
| 3 |
+
import torch.nn as nn
|
| 4 |
+
from .resnet import resnet18, resnet34, resnet50
|
| 5 |
+
from .unet import UNetEncoder, UNetDecoder
|
| 6 |
+
from .pointnet import create_pointnet_encoder
|
| 7 |
+
from .mlp import create_pointcloud_decoder
|
| 8 |
+
|
| 9 |
+
DATASET_META = {
|
| 10 |
+
'tip': {
|
| 11 |
+
'max_p': 512.0,
|
| 12 |
+
'crop_size': [56, 40],
|
| 13 |
+
'path': "/workspace/zyk/public_data/wzy_opt_dataset_w_feats"
|
| 14 |
+
},
|
| 15 |
+
'pressurepose': {
|
| 16 |
+
'max_p': 100.0,
|
| 17 |
+
'crop_size': [64, 27],
|
| 18 |
+
'path': "/workspace/zyk/public_data/pressurepose/synth"
|
| 19 |
+
},
|
| 20 |
+
'moyo': {
|
| 21 |
+
'max_p': 64.0,
|
| 22 |
+
'crop_size': [110, 37],
|
| 23 |
+
'path': "/workspace/zyk/public_data/moyo"
|
| 24 |
+
}
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
class SMPL2PressureCVAE(nn.Module):
|
| 30 |
+
"""
|
| 31 |
+
SMPL2Pressure cVAE model with dual branches.
|
| 32 |
+
Main Branch: Pressure map encoding and reconstruction.
|
| 33 |
+
Condition Branch: Point cloud encoding and reconstruction.
|
| 34 |
+
"""
|
| 35 |
+
def __init__(self, cfg):
|
| 36 |
+
super(SMPL2PressureCVAE, self).__init__()
|
| 37 |
+
self.cfg = cfg
|
| 38 |
+
self.embed_dim = cfg['model']['embed_dim']
|
| 39 |
+
self.cond_embed_dim = cfg['model']['cond_embed_dim']
|
| 40 |
+
|
| 41 |
+
# 1. Main Encoder (Pressure Map -> z_params)
|
| 42 |
+
# Supports ResNet or UNet as the visual encoder
|
| 43 |
+
main_enc_type = cfg['model']['main_encoder']['type']
|
| 44 |
+
if "resnet" in main_enc_type:
|
| 45 |
+
# Using the ResNet implementation provided
|
| 46 |
+
model_func = eval(main_enc_type)
|
| 47 |
+
self.main_encoder = model_func(
|
| 48 |
+
embed_dim=self.embed_dim,
|
| 49 |
+
cond_embed_dim=self.cond_embed_dim,
|
| 50 |
+
dp_rate=cfg['model']['dropout_rate']
|
| 51 |
+
)
|
| 52 |
+
else:
|
| 53 |
+
self.main_encoder = UNetEncoder(
|
| 54 |
+
cond_dim=self.cond_embed_dim,
|
| 55 |
+
embed_dim=self.embed_dim,
|
| 56 |
+
crop=DATASET_META[cfg['dataset']['name']]['crop_size'] # Need to pass this from config
|
| 57 |
+
)
|
| 58 |
+
|
| 59 |
+
# 2. Condition Encoder (Point Cloud -> cond_features)
|
| 60 |
+
self.cond_encoder = create_pointnet_encoder(
|
| 61 |
+
input_dim=3,
|
| 62 |
+
feature_dim=self.cond_embed_dim,
|
| 63 |
+
use_spatial_transformer=cfg['model']['cond_encoder']['use_spatial_transformer'],
|
| 64 |
+
return_global_feature=cfg['model']['cond_encoder']['return_global_feature']
|
| 65 |
+
)
|
| 66 |
+
|
| 67 |
+
# 3. Main Decoder (z + cond -> Pressure Map)
|
| 68 |
+
self.main_decoder = UNetDecoder(
|
| 69 |
+
cond_dim=self.cond_embed_dim,
|
| 70 |
+
embed_dim=self.embed_dim,
|
| 71 |
+
bilinear=cfg['model']['main_decoder']['bilinear'],
|
| 72 |
+
crop=DATASET_META[cfg['dataset']['name']]['crop_size']
|
| 73 |
+
)
|
| 74 |
+
|
| 75 |
+
# 4. Condition Decoder (cond_features -> Point Cloud)
|
| 76 |
+
self.cond_decoder = create_pointcloud_decoder(
|
| 77 |
+
latent_dim=self.embed_dim,
|
| 78 |
+
num_points=6890, # SMPL vertices
|
| 79 |
+
architecture=cfg['model']['cond_decoder']['type']
|
| 80 |
+
)
|
| 81 |
+
|
| 82 |
+
def load_pretrained_cond(self, path):
|
| 83 |
+
"""
|
| 84 |
+
专门用于加载预训练的点云编解码分支权重
|
| 85 |
+
"""
|
| 86 |
+
if not os.path.exists(path):
|
| 87 |
+
print(f"Warning: Pretrained path {path} not found. Training from scratch.")
|
| 88 |
+
return
|
| 89 |
+
|
| 90 |
+
print(f"Loading pretrained condition branch from {path}...")
|
| 91 |
+
ckpt = torch.load(path, map_location='cpu')
|
| 92 |
+
|
| 93 |
+
# 加载 encoder 和 decoder
|
| 94 |
+
self.cond_encoder.load_state_dict(ckpt['cond_encoder'])
|
| 95 |
+
self.cond_decoder.load_state_dict(ckpt['cond_decoder'])
|
| 96 |
+
|
| 97 |
+
# 可选:如果你希望预训练的组件在主训练初期不被破坏,可以冻结它们
|
| 98 |
+
# for param in self.cond_encoder.parameters(): param.requires_grad = False
|
| 99 |
+
|
| 100 |
+
def reparameterize(self, mu, log_var):
|
| 101 |
+
"""Reparameterization trick to sample z from N(mu, var)"""
|
| 102 |
+
std = torch.exp(0.5 * log_var)
|
| 103 |
+
eps = torch.randn_like(std)
|
| 104 |
+
return mu + eps * std
|
| 105 |
+
|
| 106 |
+
def forward(self, pressure_map, vertices):
|
| 107 |
+
"""
|
| 108 |
+
Forward pass during training.
|
| 109 |
+
Args:
|
| 110 |
+
pressure_map: (B, 1, H, W)
|
| 111 |
+
vertices: (B, 6890, 3)
|
| 112 |
+
"""
|
| 113 |
+
# A. Encode Point Cloud to get condition (Condition branch)
|
| 114 |
+
# PointNet expects (B, 3, N)
|
| 115 |
+
pts = vertices.transpose(2, 1)
|
| 116 |
+
cond_feat = self.cond_encoder(pts) # (B, cond_embed_dim)
|
| 117 |
+
|
| 118 |
+
# B. Encode Pressure Map with condition to get latent distribution
|
| 119 |
+
# Note: resnet implementation already handles concatenation inside
|
| 120 |
+
mu, log_var = self.main_encoder(pressure_map, cond_feat)
|
| 121 |
+
|
| 122 |
+
# C. Sample z
|
| 123 |
+
z = self.reparameterize(mu, log_var)
|
| 124 |
+
|
| 125 |
+
# D. Reconstruct Pressure Map
|
| 126 |
+
recon_pressure = self.main_decoder(z, cond_feat)
|
| 127 |
+
|
| 128 |
+
# E. Reconstruct Point Cloud (Side task for better latent space)
|
| 129 |
+
recon_vertices = self.cond_decoder(cond_feat)
|
| 130 |
+
|
| 131 |
+
return {
|
| 132 |
+
'recon_pressure': recon_pressure,
|
| 133 |
+
'recon_vertices': recon_vertices,
|
| 134 |
+
'mu': mu,
|
| 135 |
+
'log_var': log_var,
|
| 136 |
+
'z': z
|
| 137 |
+
}
|
| 138 |
+
|
| 139 |
+
@torch.no_grad()
|
| 140 |
+
def inference(self, vertices):
|
| 141 |
+
"""
|
| 142 |
+
Inference: Generate pressure from Point Cloud only.
|
| 143 |
+
"""
|
| 144 |
+
self.eval()
|
| 145 |
+
# 1. Encode Point Cloud
|
| 146 |
+
pts = vertices.transpose(2, 1)
|
| 147 |
+
cond_feat = self.cond_encoder(pts)
|
| 148 |
+
|
| 149 |
+
# 2. Sample z from prior N(0, 1)
|
| 150 |
+
z = torch.randn(vertices.size(0), self.embed_dim).to(vertices.device)
|
| 151 |
+
|
| 152 |
+
# 3. Decode to Pressure Map
|
| 153 |
+
gen_pressure = self.main_decoder(z, cond_feat)
|
| 154 |
+
|
| 155 |
+
return gen_pressure
|
| 156 |
+
|
| 157 |
+
|
| 158 |
+
if __name__ == "__main__":
|
| 159 |
+
import yaml
|
| 160 |
+
|
| 161 |
+
cfg_path = 'config/config_base.yaml'
|
| 162 |
+
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
|
| 163 |
+
|
| 164 |
+
with open(cfg_path, 'r') as f:
|
| 165 |
+
cfg = yaml.safe_load(f)
|
| 166 |
+
|
| 167 |
+
model = SMPL2PressureCVAE(cfg).to(device)
|
| 168 |
+
|
| 169 |
+
pressure = torch.randn(8, 1, 56, 40).to(device)
|
| 170 |
+
vertices = torch.randn(8, 6890, 3).to(device)
|
| 171 |
+
|
| 172 |
+
res = model(pressure, vertices)
|
| 173 |
+
|
| 174 |
+
pred_pressure = model.inference(vertices)
|
| 175 |
+
|
| 176 |
+
import pdb; pdb.set_trace()
|
| 177 |
+
|
| 178 |
+
|
| 179 |
+
|
| 180 |
+
|
src/generate_utils/lib/model/loss.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
import torch.nn as nn
|
| 3 |
+
import torch.nn.functional as F
|
| 4 |
+
|
| 5 |
+
class SMPL2PressureLoss(nn.Module):
|
| 6 |
+
"""
|
| 7 |
+
Combined loss for SMPL2Pressure cVAE.
|
| 8 |
+
- Pressure Map Reconstruction (PMR): MSE Loss
|
| 9 |
+
- Point Cloud Reconstruction (PCR): MSE Loss (from condition features)
|
| 10 |
+
- KL Divergence: Regularization of latent space
|
| 11 |
+
"""
|
| 12 |
+
def __init__(self, cfg):
|
| 13 |
+
super(SMPL2PressureLoss, self).__init__()
|
| 14 |
+
self.cfg_loss = cfg['training']['loss']
|
| 15 |
+
|
| 16 |
+
# 权重配置
|
| 17 |
+
self.pmr_weight = self.cfg_loss.get('pmr_weight', 10.0)
|
| 18 |
+
self.pcr_weight = self.cfg_loss.get('pcr_weight', 6.0)
|
| 19 |
+
self.kl_weight = self.cfg_loss.get('kl_weight', 2.0)
|
| 20 |
+
|
| 21 |
+
def forward(self, outputs, target_pressure, target_vertices):
|
| 22 |
+
"""
|
| 23 |
+
Args:
|
| 24 |
+
outputs: cVAE模型的输出字典
|
| 25 |
+
target_pressure: 真值压力图 (B, H, W) 或 (B, 1, H, W)
|
| 26 |
+
target_vertices: 真值SMPL顶点 (B, 6890, 3)
|
| 27 |
+
"""
|
| 28 |
+
# 1. 压力图重建损失 (PMR)
|
| 29 |
+
recon_pressure = outputs['recon_pressure']
|
| 30 |
+
# 统一维度: 确保 target 也是 (B, 1, H, W)
|
| 31 |
+
if recon_pressure.dim() == 3:
|
| 32 |
+
recon_pressure = recon_pressure.unsqueeze(1)
|
| 33 |
+
|
| 34 |
+
if target_pressure.dim() == 3:
|
| 35 |
+
target_pressure = target_pressure.unsqueeze(1)
|
| 36 |
+
|
| 37 |
+
loss_pmr = F.mse_loss(recon_pressure, target_pressure, reduction='mean')
|
| 38 |
+
|
| 39 |
+
# 2. 点云重建损失 (PCR)
|
| 40 |
+
# 根据你的提醒,这里的 recon_vertices 应该是从 cond_features 解码出来的
|
| 41 |
+
recon_vertices = outputs['recon_vertices']
|
| 42 |
+
loss_pcr = F.mse_loss(recon_vertices, target_vertices, reduction='mean')
|
| 43 |
+
|
| 44 |
+
# 3. KL 散度损失
|
| 45 |
+
mu = outputs['mu']
|
| 46 |
+
log_var = outputs['log_var']
|
| 47 |
+
# KL = -0.5 * sum(1 + log(sigma^2) - mu^2 - sigma^2)
|
| 48 |
+
loss_kl = -0.5 * torch.mean(torch.sum(1 + log_var - mu.pow(2) - log_var.exp(), dim=1))
|
| 49 |
+
|
| 50 |
+
# 4. 总损失加权
|
| 51 |
+
total_loss = (self.pmr_weight * loss_pmr +
|
| 52 |
+
self.pcr_weight * loss_pcr +
|
| 53 |
+
self.kl_weight * loss_kl)
|
| 54 |
+
|
| 55 |
+
return {
|
| 56 |
+
'loss': total_loss,
|
| 57 |
+
'loss_pmr': loss_pmr,
|
| 58 |
+
'loss_pcr': loss_pcr,
|
| 59 |
+
'loss_kl': loss_kl
|
| 60 |
+
}
|
| 61 |
+
|
src/generate_utils/lib/model/mlp.py
ADDED
|
@@ -0,0 +1,382 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
MLP-based decoder for reconstructing point clouds from latent codes.
|
| 3 |
+
|
| 4 |
+
This module provides flexible MLP architectures for decoding latent representations
|
| 5 |
+
into point clouds, commonly used in generative models like VAE and cVAE.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
import torch
|
| 9 |
+
import torch.nn as nn
|
| 10 |
+
import torch.nn.functional as F
|
| 11 |
+
from typing import List, Optional
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
class PointCloudDecoder(nn.Module):
|
| 15 |
+
"""
|
| 16 |
+
MLP-based decoder for reconstructing point clouds from latent representations.
|
| 17 |
+
|
| 18 |
+
This decoder uses a series of fully connected layers with optional dropout
|
| 19 |
+
and normalization to transform a latent code into point cloud coordinates.
|
| 20 |
+
|
| 21 |
+
Args:
|
| 22 |
+
latent_dim: Dimensionality of input latent code
|
| 23 |
+
num_points: Number of output points in the point cloud
|
| 24 |
+
point_dim: Dimensionality of each point (default: 3 for XYZ coordinates)
|
| 25 |
+
hidden_dims: List of hidden layer dimensions (default: [1024, 2048])
|
| 26 |
+
dropout_rate: Dropout probability (default: 0.3)
|
| 27 |
+
use_batch_norm: Whether to use batch normalization (default: False)
|
| 28 |
+
activation: Activation function to use (default: 'relu')
|
| 29 |
+
|
| 30 |
+
Input:
|
| 31 |
+
Latent code of shape (B, latent_dim)
|
| 32 |
+
|
| 33 |
+
Output:
|
| 34 |
+
Point cloud of shape (B, num_points, point_dim)
|
| 35 |
+
"""
|
| 36 |
+
|
| 37 |
+
def __init__(
|
| 38 |
+
self,
|
| 39 |
+
latent_dim: int,
|
| 40 |
+
num_points: int,
|
| 41 |
+
point_dim: int = 3,
|
| 42 |
+
hidden_dims: Optional[List[int]] = None,
|
| 43 |
+
dropout_rate: float = 0.3,
|
| 44 |
+
use_batch_norm: bool = False,
|
| 45 |
+
activation: str = 'relu'
|
| 46 |
+
):
|
| 47 |
+
super(PointCloudDecoder, self).__init__()
|
| 48 |
+
self.latent_dim = latent_dim
|
| 49 |
+
self.num_points = num_points
|
| 50 |
+
self.point_dim = point_dim
|
| 51 |
+
self.dropout_rate = dropout_rate
|
| 52 |
+
self.use_batch_norm = use_batch_norm
|
| 53 |
+
|
| 54 |
+
# Default hidden dimensions
|
| 55 |
+
if hidden_dims is None:
|
| 56 |
+
hidden_dims = [1024, 2048]
|
| 57 |
+
self.hidden_dims = hidden_dims
|
| 58 |
+
|
| 59 |
+
# Select activation function
|
| 60 |
+
if activation == 'relu':
|
| 61 |
+
self.activation = nn.ReLU()
|
| 62 |
+
elif activation == 'leaky_relu':
|
| 63 |
+
self.activation = nn.LeakyReLU(0.2)
|
| 64 |
+
elif activation == 'elu':
|
| 65 |
+
self.activation = nn.ELU()
|
| 66 |
+
elif activation == 'gelu':
|
| 67 |
+
self.activation = nn.GELU()
|
| 68 |
+
else:
|
| 69 |
+
raise ValueError(f"Unsupported activation: {activation}")
|
| 70 |
+
|
| 71 |
+
# Build network layers
|
| 72 |
+
self.layers = nn.ModuleList()
|
| 73 |
+
self.batch_norms = nn.ModuleList() if use_batch_norm else None
|
| 74 |
+
self.dropouts = nn.ModuleList()
|
| 75 |
+
|
| 76 |
+
# Input layer
|
| 77 |
+
prev_dim = latent_dim
|
| 78 |
+
for hidden_dim in hidden_dims:
|
| 79 |
+
self.layers.append(nn.Linear(prev_dim, hidden_dim))
|
| 80 |
+
if use_batch_norm:
|
| 81 |
+
self.batch_norms.append(nn.BatchNorm1d(hidden_dim))
|
| 82 |
+
self.dropouts.append(nn.Dropout(dropout_rate))
|
| 83 |
+
prev_dim = hidden_dim
|
| 84 |
+
|
| 85 |
+
# Output layer
|
| 86 |
+
output_dim = num_points * point_dim
|
| 87 |
+
self.output_layer = nn.Linear(prev_dim, output_dim)
|
| 88 |
+
|
| 89 |
+
def forward(self, z: torch.Tensor) -> torch.Tensor:
|
| 90 |
+
"""
|
| 91 |
+
Decode latent code into point cloud.
|
| 92 |
+
|
| 93 |
+
Args:
|
| 94 |
+
z: Latent code of shape (B, latent_dim)
|
| 95 |
+
|
| 96 |
+
Returns:
|
| 97 |
+
Reconstructed point cloud of shape (B, num_points, point_dim)
|
| 98 |
+
"""
|
| 99 |
+
x = z
|
| 100 |
+
|
| 101 |
+
# Process through hidden layers
|
| 102 |
+
for i, layer in enumerate(self.layers):
|
| 103 |
+
x = layer(x)
|
| 104 |
+
if self.use_batch_norm:
|
| 105 |
+
x = self.batch_norms[i](x)
|
| 106 |
+
x = self.activation(x)
|
| 107 |
+
x = self.dropouts[i](x)
|
| 108 |
+
|
| 109 |
+
# Output layer
|
| 110 |
+
x = self.output_layer(x)
|
| 111 |
+
|
| 112 |
+
# Reshape to point cloud
|
| 113 |
+
x = x.view(-1, self.num_points, self.point_dim)
|
| 114 |
+
|
| 115 |
+
return x
|
| 116 |
+
|
| 117 |
+
def get_output_shape(self) -> tuple:
|
| 118 |
+
"""
|
| 119 |
+
Get the output point cloud shape (excluding batch dimension).
|
| 120 |
+
|
| 121 |
+
Returns:
|
| 122 |
+
Tuple of (num_points, point_dim)
|
| 123 |
+
"""
|
| 124 |
+
return (self.num_points, self.point_dim)
|
| 125 |
+
|
| 126 |
+
|
| 127 |
+
class ResidualMLPDecoder(nn.Module):
|
| 128 |
+
"""
|
| 129 |
+
MLP decoder with residual connections for improved gradient flow.
|
| 130 |
+
|
| 131 |
+
This decoder uses residual blocks to help with training deep networks.
|
| 132 |
+
|
| 133 |
+
Args:
|
| 134 |
+
latent_dim: Dimensionality of input latent code
|
| 135 |
+
num_points: Number of output points in the point cloud
|
| 136 |
+
point_dim: Dimensionality of each point (default: 3 for XYZ coordinates)
|
| 137 |
+
hidden_dim: Hidden layer dimension (default: 1024)
|
| 138 |
+
num_blocks: Number of residual blocks (default: 3)
|
| 139 |
+
dropout_rate: Dropout probability (default: 0.3)
|
| 140 |
+
use_batch_norm: Whether to use batch normalization (default: True)
|
| 141 |
+
|
| 142 |
+
Input:
|
| 143 |
+
Latent code of shape (B, latent_dim)
|
| 144 |
+
|
| 145 |
+
Output:
|
| 146 |
+
Point cloud of shape (B, num_points, point_dim)
|
| 147 |
+
"""
|
| 148 |
+
|
| 149 |
+
def __init__(
|
| 150 |
+
self,
|
| 151 |
+
latent_dim: int,
|
| 152 |
+
num_points: int,
|
| 153 |
+
point_dim: int = 3,
|
| 154 |
+
hidden_dim: int = 1024,
|
| 155 |
+
num_blocks: int = 3,
|
| 156 |
+
dropout_rate: float = 0.3,
|
| 157 |
+
use_batch_norm: bool = True
|
| 158 |
+
):
|
| 159 |
+
super(ResidualMLPDecoder, self).__init__()
|
| 160 |
+
self.latent_dim = latent_dim
|
| 161 |
+
self.num_points = num_points
|
| 162 |
+
self.point_dim = point_dim
|
| 163 |
+
self.hidden_dim = hidden_dim
|
| 164 |
+
|
| 165 |
+
# Initial projection
|
| 166 |
+
self.input_proj = nn.Linear(latent_dim, hidden_dim)
|
| 167 |
+
|
| 168 |
+
# Residual blocks
|
| 169 |
+
self.blocks = nn.ModuleList([
|
| 170 |
+
ResidualBlock(hidden_dim, dropout_rate, use_batch_norm)
|
| 171 |
+
for _ in range(num_blocks)
|
| 172 |
+
])
|
| 173 |
+
|
| 174 |
+
# Output projection
|
| 175 |
+
output_dim = num_points * point_dim
|
| 176 |
+
self.output_proj = nn.Linear(hidden_dim, output_dim)
|
| 177 |
+
|
| 178 |
+
def forward(self, z: torch.Tensor) -> torch.Tensor:
|
| 179 |
+
"""
|
| 180 |
+
Decode latent code into point cloud.
|
| 181 |
+
|
| 182 |
+
Args:
|
| 183 |
+
z: Latent code of shape (B, latent_dim)
|
| 184 |
+
|
| 185 |
+
Returns:
|
| 186 |
+
Reconstructed point cloud of shape (B, num_points, point_dim)
|
| 187 |
+
"""
|
| 188 |
+
# Initial projection
|
| 189 |
+
x = F.relu(self.input_proj(z))
|
| 190 |
+
|
| 191 |
+
# Residual blocks
|
| 192 |
+
for block in self.blocks:
|
| 193 |
+
x = block(x)
|
| 194 |
+
|
| 195 |
+
# Output projection
|
| 196 |
+
x = self.output_proj(x)
|
| 197 |
+
x = x.view(-1, self.num_points, self.point_dim)
|
| 198 |
+
|
| 199 |
+
return x
|
| 200 |
+
|
| 201 |
+
|
| 202 |
+
class ResidualBlock(nn.Module):
|
| 203 |
+
"""
|
| 204 |
+
Residual block with optional batch normalization and dropout.
|
| 205 |
+
|
| 206 |
+
Args:
|
| 207 |
+
hidden_dim: Dimension of the hidden layer
|
| 208 |
+
dropout_rate: Dropout probability
|
| 209 |
+
use_batch_norm: Whether to use batch normalization
|
| 210 |
+
"""
|
| 211 |
+
|
| 212 |
+
def __init__(
|
| 213 |
+
self,
|
| 214 |
+
hidden_dim: int,
|
| 215 |
+
dropout_rate: float = 0.3,
|
| 216 |
+
use_batch_norm: bool = True
|
| 217 |
+
):
|
| 218 |
+
super(ResidualBlock, self).__init__()
|
| 219 |
+
self.fc1 = nn.Linear(hidden_dim, hidden_dim)
|
| 220 |
+
self.fc2 = nn.Linear(hidden_dim, hidden_dim)
|
| 221 |
+
self.dropout = nn.Dropout(dropout_rate)
|
| 222 |
+
|
| 223 |
+
if use_batch_norm:
|
| 224 |
+
self.bn1 = nn.BatchNorm1d(hidden_dim)
|
| 225 |
+
self.bn2 = nn.BatchNorm1d(hidden_dim)
|
| 226 |
+
else:
|
| 227 |
+
self.bn1 = None
|
| 228 |
+
self.bn2 = None
|
| 229 |
+
|
| 230 |
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| 231 |
+
"""
|
| 232 |
+
Forward pass through residual block.
|
| 233 |
+
|
| 234 |
+
Args:
|
| 235 |
+
x: Input tensor of shape (B, hidden_dim)
|
| 236 |
+
|
| 237 |
+
Returns:
|
| 238 |
+
Output tensor of shape (B, hidden_dim)
|
| 239 |
+
"""
|
| 240 |
+
residual = x
|
| 241 |
+
|
| 242 |
+
out = self.fc1(x)
|
| 243 |
+
if self.bn1 is not None:
|
| 244 |
+
out = self.bn1(out)
|
| 245 |
+
out = F.relu(out)
|
| 246 |
+
out = self.dropout(out)
|
| 247 |
+
|
| 248 |
+
out = self.fc2(out)
|
| 249 |
+
if self.bn2 is not None:
|
| 250 |
+
out = self.bn2(out)
|
| 251 |
+
|
| 252 |
+
out = out + residual
|
| 253 |
+
out = F.relu(out)
|
| 254 |
+
|
| 255 |
+
return out
|
| 256 |
+
|
| 257 |
+
|
| 258 |
+
def create_pointcloud_decoder(
|
| 259 |
+
latent_dim: int,
|
| 260 |
+
num_points: int = 6890,
|
| 261 |
+
point_dim: int = 3,
|
| 262 |
+
architecture: str = 'mlp',
|
| 263 |
+
**kwargs
|
| 264 |
+
) -> nn.Module:
|
| 265 |
+
"""
|
| 266 |
+
Factory function to create a point cloud decoder with specified architecture.
|
| 267 |
+
|
| 268 |
+
Args:
|
| 269 |
+
latent_dim: Dimensionality of input latent code
|
| 270 |
+
num_points: Number of output points (default: 6890 for SMPL mesh)
|
| 271 |
+
point_dim: Dimensionality of each point (default: 3 for XYZ)
|
| 272 |
+
architecture: Decoder architecture type ('mlp' or 'residual')
|
| 273 |
+
**kwargs: Additional arguments passed to the decoder constructor
|
| 274 |
+
|
| 275 |
+
Returns:
|
| 276 |
+
Configured decoder instance
|
| 277 |
+
|
| 278 |
+
Examples:
|
| 279 |
+
>>> # Create a simple MLP decoder
|
| 280 |
+
>>> decoder = create_pointcloud_decoder(
|
| 281 |
+
... latent_dim=256,
|
| 282 |
+
... num_points=6890,
|
| 283 |
+
... architecture='mlp',
|
| 284 |
+
... hidden_dims=[1024, 2048],
|
| 285 |
+
... dropout_rate=0.3
|
| 286 |
+
... )
|
| 287 |
+
|
| 288 |
+
>>> # Create a residual decoder
|
| 289 |
+
>>> decoder = create_pointcloud_decoder(
|
| 290 |
+
... latent_dim=256,
|
| 291 |
+
... num_points=6890,
|
| 292 |
+
... architecture='residual',
|
| 293 |
+
... hidden_dim=1024,
|
| 294 |
+
... num_blocks=3
|
| 295 |
+
... )
|
| 296 |
+
"""
|
| 297 |
+
if architecture == 'mlp':
|
| 298 |
+
return PointCloudDecoder(
|
| 299 |
+
latent_dim=latent_dim,
|
| 300 |
+
num_points=num_points,
|
| 301 |
+
point_dim=point_dim,
|
| 302 |
+
**kwargs
|
| 303 |
+
)
|
| 304 |
+
elif architecture == 'residual':
|
| 305 |
+
return ResidualMLPDecoder(
|
| 306 |
+
latent_dim=latent_dim,
|
| 307 |
+
num_points=num_points,
|
| 308 |
+
point_dim=point_dim,
|
| 309 |
+
**kwargs
|
| 310 |
+
)
|
| 311 |
+
else:
|
| 312 |
+
raise ValueError(f"Unsupported architecture: {architecture}")
|
| 313 |
+
|
| 314 |
+
|
| 315 |
+
if __name__ == '__main__':
|
| 316 |
+
print("Testing Point Cloud Decoders...\n")
|
| 317 |
+
|
| 318 |
+
# Test parameters
|
| 319 |
+
batch_size = 32
|
| 320 |
+
latent_dim = 256
|
| 321 |
+
num_points = 6890 # SMPL mesh vertices
|
| 322 |
+
point_dim = 3
|
| 323 |
+
|
| 324 |
+
# Test standard MLP decoder
|
| 325 |
+
print("1. Standard MLP Decoder:")
|
| 326 |
+
mlp_decoder = create_pointcloud_decoder(
|
| 327 |
+
latent_dim=latent_dim,
|
| 328 |
+
num_points=num_points,
|
| 329 |
+
point_dim=point_dim,
|
| 330 |
+
architecture='mlp',
|
| 331 |
+
hidden_dims=[1024, 2048],
|
| 332 |
+
dropout_rate=0.3,
|
| 333 |
+
use_batch_norm=False
|
| 334 |
+
)
|
| 335 |
+
|
| 336 |
+
z = torch.randn(batch_size, latent_dim)
|
| 337 |
+
output = mlp_decoder(z)
|
| 338 |
+
print(f" Input shape: {z.shape}")
|
| 339 |
+
print(f" Output shape: {output.shape}")
|
| 340 |
+
print(f" Output expected shape: {mlp_decoder.get_output_shape()}")
|
| 341 |
+
print()
|
| 342 |
+
|
| 343 |
+
# Test MLP decoder with batch norm
|
| 344 |
+
print("2. MLP Decoder with Batch Normalization:")
|
| 345 |
+
mlp_bn_decoder = create_pointcloud_decoder(
|
| 346 |
+
latent_dim=latent_dim,
|
| 347 |
+
num_points=num_points,
|
| 348 |
+
architecture='mlp',
|
| 349 |
+
use_batch_norm=True,
|
| 350 |
+
activation='leaky_relu'
|
| 351 |
+
)
|
| 352 |
+
output = mlp_bn_decoder(z)
|
| 353 |
+
print(f" Output shape: {output.shape}")
|
| 354 |
+
print()
|
| 355 |
+
|
| 356 |
+
# Test residual MLP decoder
|
| 357 |
+
print("3. Residual MLP Decoder:")
|
| 358 |
+
residual_decoder = create_pointcloud_decoder(
|
| 359 |
+
latent_dim=latent_dim,
|
| 360 |
+
num_points=num_points,
|
| 361 |
+
architecture='residual',
|
| 362 |
+
hidden_dim=1024,
|
| 363 |
+
num_blocks=3,
|
| 364 |
+
dropout_rate=0.3
|
| 365 |
+
)
|
| 366 |
+
output = residual_decoder(z)
|
| 367 |
+
print(f" Input shape: {z.shape}")
|
| 368 |
+
print(f" Output shape: {output.shape}")
|
| 369 |
+
print()
|
| 370 |
+
|
| 371 |
+
# Test with different point dimensions
|
| 372 |
+
print("4. Decoder with 6D points (XYZ + RGB):")
|
| 373 |
+
decoder_6d = create_pointcloud_decoder(
|
| 374 |
+
latent_dim=latent_dim,
|
| 375 |
+
num_points=1000,
|
| 376 |
+
point_dim=6,
|
| 377 |
+
architecture='mlp',
|
| 378 |
+
hidden_dims=[512, 1024]
|
| 379 |
+
)
|
| 380 |
+
output = decoder_6d(z)
|
| 381 |
+
print(f" Output shape: {output.shape}")
|
| 382 |
+
print()
|
src/generate_utils/lib/model/pointnet.py
ADDED
|
@@ -0,0 +1,319 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
PointNet implementation for point cloud feature extraction.
|
| 3 |
+
|
| 4 |
+
This module implements the PointNet architecture for encoding point clouds into
|
| 5 |
+
global or per-point features. It can be used as a conditional encoder in cVAE models.
|
| 6 |
+
|
| 7 |
+
Reference:
|
| 8 |
+
PointNet: Deep Learning on Point Sets for 3D Classification and Segmentation
|
| 9 |
+
Charles R. Qi et al., CVPR 2017
|
| 10 |
+
"""
|
| 11 |
+
|
| 12 |
+
import torch
|
| 13 |
+
import torch.nn as nn
|
| 14 |
+
import torch.nn.functional as F
|
| 15 |
+
from typing import Optional, Tuple, Union
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
class SpatialTransformer3D(nn.Module):
|
| 19 |
+
"""
|
| 20 |
+
Spatial Transformer Network for 3D point clouds.
|
| 21 |
+
|
| 22 |
+
Predicts a 3x3 transformation matrix to canonicalize input point clouds.
|
| 23 |
+
"""
|
| 24 |
+
|
| 25 |
+
def __init__(self):
|
| 26 |
+
super(SpatialTransformer3D, self).__init__()
|
| 27 |
+
self.conv1 = nn.Conv1d(3, 64, 1)
|
| 28 |
+
self.conv2 = nn.Conv1d(64, 128, 1)
|
| 29 |
+
self.conv3 = nn.Conv1d(128, 1024, 1)
|
| 30 |
+
|
| 31 |
+
self.fc1 = nn.Linear(1024, 512)
|
| 32 |
+
self.fc2 = nn.Linear(512, 256)
|
| 33 |
+
self.fc3 = nn.Linear(256, 9)
|
| 34 |
+
|
| 35 |
+
self.bn1 = nn.BatchNorm1d(64)
|
| 36 |
+
self.bn2 = nn.BatchNorm1d(128)
|
| 37 |
+
self.bn3 = nn.BatchNorm1d(1024)
|
| 38 |
+
self.bn4 = nn.BatchNorm1d(512)
|
| 39 |
+
self.bn5 = nn.BatchNorm1d(256)
|
| 40 |
+
|
| 41 |
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| 42 |
+
"""
|
| 43 |
+
Forward pass of the spatial transformer.
|
| 44 |
+
|
| 45 |
+
Args:
|
| 46 |
+
x: Input point cloud of shape (B, 3, N)
|
| 47 |
+
|
| 48 |
+
Returns:
|
| 49 |
+
Transformation matrix of shape (B, 3, 3)
|
| 50 |
+
"""
|
| 51 |
+
batch_size = x.size(0)
|
| 52 |
+
device = x.device
|
| 53 |
+
|
| 54 |
+
# Encode point cloud
|
| 55 |
+
x = F.relu(self.bn1(self.conv1(x)))
|
| 56 |
+
x = F.relu(self.bn2(self.conv2(x)))
|
| 57 |
+
x = F.relu(self.bn3(self.conv3(x)))
|
| 58 |
+
|
| 59 |
+
# Global max pooling
|
| 60 |
+
x = torch.max(x, 2, keepdim=True)[0]
|
| 61 |
+
x = x.view(batch_size, -1)
|
| 62 |
+
|
| 63 |
+
# Predict transformation
|
| 64 |
+
x = F.relu(self.bn4(self.fc1(x)))
|
| 65 |
+
x = F.relu(self.bn5(self.fc2(x)))
|
| 66 |
+
x = self.fc3(x)
|
| 67 |
+
|
| 68 |
+
# Add identity matrix as residual
|
| 69 |
+
identity = torch.eye(3, dtype=x.dtype, device=device).view(1, 9)
|
| 70 |
+
identity = identity.repeat(batch_size, 1)
|
| 71 |
+
x = x + identity
|
| 72 |
+
x = x.view(batch_size, 3, 3)
|
| 73 |
+
|
| 74 |
+
return x
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
class SpatialTransformerKD(nn.Module):
|
| 78 |
+
"""
|
| 79 |
+
Spatial Transformer Network for K-dimensional features.
|
| 80 |
+
|
| 81 |
+
Predicts a KxK transformation matrix for feature space alignment.
|
| 82 |
+
|
| 83 |
+
Args:
|
| 84 |
+
input_dim: Dimensionality of input features
|
| 85 |
+
feature_dim: Dimensionality of intermediate features (default: 1024)
|
| 86 |
+
"""
|
| 87 |
+
|
| 88 |
+
def __init__(self, input_dim: int = 64, feature_dim: int = 1024):
|
| 89 |
+
super(SpatialTransformerKD, self).__init__()
|
| 90 |
+
self.input_dim = input_dim
|
| 91 |
+
self.feature_dim = feature_dim
|
| 92 |
+
|
| 93 |
+
self.conv1 = nn.Conv1d(input_dim, 64, 1)
|
| 94 |
+
self.conv2 = nn.Conv1d(64, 128, 1)
|
| 95 |
+
self.conv3 = nn.Conv1d(128, feature_dim, 1)
|
| 96 |
+
|
| 97 |
+
self.fc1 = nn.Linear(feature_dim, 512)
|
| 98 |
+
self.fc2 = nn.Linear(512, 256)
|
| 99 |
+
self.fc3 = nn.Linear(256, input_dim * input_dim)
|
| 100 |
+
|
| 101 |
+
self.bn1 = nn.BatchNorm1d(64)
|
| 102 |
+
self.bn2 = nn.BatchNorm1d(128)
|
| 103 |
+
self.bn3 = nn.BatchNorm1d(feature_dim)
|
| 104 |
+
self.bn4 = nn.BatchNorm1d(512)
|
| 105 |
+
self.bn5 = nn.BatchNorm1d(256)
|
| 106 |
+
|
| 107 |
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| 108 |
+
"""
|
| 109 |
+
Forward pass of the spatial transformer.
|
| 110 |
+
|
| 111 |
+
Args:
|
| 112 |
+
x: Input features of shape (B, K, N)
|
| 113 |
+
|
| 114 |
+
Returns:
|
| 115 |
+
Transformation matrix of shape (B, K, K)
|
| 116 |
+
"""
|
| 117 |
+
batch_size = x.size(0)
|
| 118 |
+
device = x.device
|
| 119 |
+
|
| 120 |
+
# Encode features
|
| 121 |
+
x = F.relu(self.bn1(self.conv1(x)))
|
| 122 |
+
x = F.relu(self.bn2(self.conv2(x)))
|
| 123 |
+
x = F.relu(self.bn3(self.conv3(x)))
|
| 124 |
+
|
| 125 |
+
# Global max pooling
|
| 126 |
+
x = torch.max(x, 2, keepdim=True)[0]
|
| 127 |
+
x = x.view(batch_size, -1)
|
| 128 |
+
|
| 129 |
+
# Predict transformation
|
| 130 |
+
x = F.relu(self.bn4(self.fc1(x)))
|
| 131 |
+
x = F.relu(self.bn5(self.fc2(x)))
|
| 132 |
+
x = self.fc3(x)
|
| 133 |
+
|
| 134 |
+
# Add identity matrix as residual
|
| 135 |
+
identity = torch.eye(self.input_dim, dtype=x.dtype, device=device)
|
| 136 |
+
identity = identity.view(1, self.input_dim * self.input_dim)
|
| 137 |
+
identity = identity.repeat(batch_size, 1)
|
| 138 |
+
x = x + identity
|
| 139 |
+
x = x.view(batch_size, self.input_dim, self.input_dim)
|
| 140 |
+
|
| 141 |
+
return x
|
| 142 |
+
|
| 143 |
+
|
| 144 |
+
class PointNetEncoder(nn.Module):
|
| 145 |
+
"""
|
| 146 |
+
PointNet encoder for extracting features from point clouds.
|
| 147 |
+
|
| 148 |
+
This encoder can output either global features (for the entire point cloud)
|
| 149 |
+
or per-point features (combining global and local information).
|
| 150 |
+
|
| 151 |
+
Args:
|
| 152 |
+
input_dim: Dimensionality of input point features (default: 3 for XYZ)
|
| 153 |
+
feature_dim: Dimensionality of output global features (default: 256)
|
| 154 |
+
use_spatial_transformer: Whether to use spatial transformer network (default: False)
|
| 155 |
+
return_global_feature: If True, return global feature; if False, return per-point features (default: True)
|
| 156 |
+
|
| 157 |
+
Input:
|
| 158 |
+
Point cloud of shape (B, input_dim, N) where:
|
| 159 |
+
B = batch size
|
| 160 |
+
input_dim = feature dimension per point (e.g., 3 for XYZ)
|
| 161 |
+
N = number of points
|
| 162 |
+
|
| 163 |
+
Output:
|
| 164 |
+
If return_global_feature=True:
|
| 165 |
+
Global feature of shape (B, feature_dim)
|
| 166 |
+
If return_global_feature=False:
|
| 167 |
+
Per-point features of shape (B, N, feature_dim + 64)
|
| 168 |
+
"""
|
| 169 |
+
|
| 170 |
+
def __init__(
|
| 171 |
+
self,
|
| 172 |
+
input_dim: int = 3,
|
| 173 |
+
feature_dim: int = 256,
|
| 174 |
+
use_spatial_transformer: bool = False,
|
| 175 |
+
return_global_feature: bool = True
|
| 176 |
+
):
|
| 177 |
+
super(PointNetEncoder, self).__init__()
|
| 178 |
+
self.input_dim = input_dim
|
| 179 |
+
self.feature_dim = feature_dim
|
| 180 |
+
self.use_spatial_transformer = use_spatial_transformer
|
| 181 |
+
self.return_global_feature = return_global_feature
|
| 182 |
+
|
| 183 |
+
# Spatial transformer
|
| 184 |
+
if use_spatial_transformer:
|
| 185 |
+
self.stn = SpatialTransformerKD(input_dim=input_dim, feature_dim=feature_dim)
|
| 186 |
+
|
| 187 |
+
# Feature extraction layers
|
| 188 |
+
self.conv1 = nn.Conv1d(input_dim, 64, 1)
|
| 189 |
+
self.conv2 = nn.Conv1d(64, 128, 1)
|
| 190 |
+
self.conv3 = nn.Conv1d(128, feature_dim, 1)
|
| 191 |
+
|
| 192 |
+
self.bn1 = nn.BatchNorm1d(64)
|
| 193 |
+
self.bn2 = nn.BatchNorm1d(128)
|
| 194 |
+
self.bn3 = nn.BatchNorm1d(feature_dim)
|
| 195 |
+
|
| 196 |
+
def forward(
|
| 197 |
+
self,
|
| 198 |
+
x: torch.Tensor,
|
| 199 |
+
return_transform: bool = False
|
| 200 |
+
) -> Union[torch.Tensor, Tuple[torch.Tensor, Optional[torch.Tensor]]]:
|
| 201 |
+
"""
|
| 202 |
+
Forward pass of PointNet encoder.
|
| 203 |
+
|
| 204 |
+
Args:
|
| 205 |
+
x: Input point cloud of shape (B, input_dim, N)
|
| 206 |
+
return_transform: If True, also return the transformation matrix (default: False)
|
| 207 |
+
|
| 208 |
+
Returns:
|
| 209 |
+
If return_transform=False:
|
| 210 |
+
features: Encoded features
|
| 211 |
+
If return_transform=True:
|
| 212 |
+
(features, transform): Tuple of features and transformation matrix
|
| 213 |
+
"""
|
| 214 |
+
num_points = x.size(2)
|
| 215 |
+
transform = None
|
| 216 |
+
|
| 217 |
+
# Apply spatial transformation
|
| 218 |
+
if self.use_spatial_transformer:
|
| 219 |
+
transform = self.stn(x)
|
| 220 |
+
x = x.transpose(2, 1) # (B, N, K)
|
| 221 |
+
x = torch.bmm(x, transform) # (B, N, K)
|
| 222 |
+
x = x.transpose(2, 1) # (B, K, N)
|
| 223 |
+
|
| 224 |
+
# Extract features
|
| 225 |
+
x = F.relu(self.bn1(self.conv1(x)))
|
| 226 |
+
local_features = x # Save for per-point features
|
| 227 |
+
|
| 228 |
+
x = F.relu(self.bn2(self.conv2(x)))
|
| 229 |
+
x = self.bn3(self.conv3(x))
|
| 230 |
+
|
| 231 |
+
# Global max pooling
|
| 232 |
+
global_features = torch.max(x, 2, keepdim=True)[0]
|
| 233 |
+
global_features = global_features.view(-1, self.feature_dim)
|
| 234 |
+
|
| 235 |
+
# Return based on mode
|
| 236 |
+
if self.return_global_feature:
|
| 237 |
+
features = global_features
|
| 238 |
+
else:
|
| 239 |
+
# Concatenate global and local features for per-point features
|
| 240 |
+
global_features_expanded = global_features.view(-1, self.feature_dim, 1)
|
| 241 |
+
global_features_expanded = global_features_expanded.repeat(1, 1, num_points)
|
| 242 |
+
features = torch.cat([global_features_expanded, local_features], dim=1)
|
| 243 |
+
# Transpose to (B, N, feature_dim + 64)
|
| 244 |
+
features = features.transpose(1, 2)
|
| 245 |
+
|
| 246 |
+
if return_transform:
|
| 247 |
+
return features, transform
|
| 248 |
+
return features
|
| 249 |
+
|
| 250 |
+
def get_output_dim(self) -> int:
|
| 251 |
+
"""
|
| 252 |
+
Get the output feature dimensionality.
|
| 253 |
+
|
| 254 |
+
Returns:
|
| 255 |
+
Output dimension based on the configuration
|
| 256 |
+
"""
|
| 257 |
+
if self.return_global_feature:
|
| 258 |
+
return self.feature_dim
|
| 259 |
+
else:
|
| 260 |
+
return self.feature_dim + 64
|
| 261 |
+
|
| 262 |
+
|
| 263 |
+
def create_pointnet_encoder(
|
| 264 |
+
input_dim: int = 3,
|
| 265 |
+
feature_dim: int = 256,
|
| 266 |
+
use_spatial_transformer: bool = False,
|
| 267 |
+
return_global_feature: bool = True
|
| 268 |
+
) -> PointNetEncoder:
|
| 269 |
+
"""
|
| 270 |
+
Factory function to create a PointNet encoder with specified configuration.
|
| 271 |
+
|
| 272 |
+
Args:
|
| 273 |
+
input_dim: Dimensionality of input point features
|
| 274 |
+
feature_dim: Dimensionality of output global features
|
| 275 |
+
use_spatial_transformer: Whether to use spatial transformer network
|
| 276 |
+
return_global_feature: Whether to return global or per-point features
|
| 277 |
+
|
| 278 |
+
Returns:
|
| 279 |
+
Configured PointNetEncoder instance
|
| 280 |
+
"""
|
| 281 |
+
return PointNetEncoder(
|
| 282 |
+
input_dim=input_dim,
|
| 283 |
+
feature_dim=feature_dim,
|
| 284 |
+
use_spatial_transformer=use_spatial_transformer,
|
| 285 |
+
return_global_feature=return_global_feature
|
| 286 |
+
)
|
| 287 |
+
|
| 288 |
+
|
| 289 |
+
if __name__ == '__main__':
|
| 290 |
+
print("Testing PointNetEncoder with different configurations...\n")
|
| 291 |
+
|
| 292 |
+
# Test data
|
| 293 |
+
batch_size = 32
|
| 294 |
+
num_points = 6890 # SMPL mesh vertices
|
| 295 |
+
input_dim = 3
|
| 296 |
+
|
| 297 |
+
sim_data = torch.randn(batch_size, input_dim, num_points)
|
| 298 |
+
|
| 299 |
+
configs = [
|
| 300 |
+
(True, False, "Global features without STN"),
|
| 301 |
+
(True, True, "Global features with STN"),
|
| 302 |
+
(False, False, "Per-point features without STN"),
|
| 303 |
+
(False, True, "Per-point features with STN"),
|
| 304 |
+
]
|
| 305 |
+
|
| 306 |
+
for return_global, use_stn, desc in configs:
|
| 307 |
+
encoder = create_pointnet_encoder(
|
| 308 |
+
input_dim=input_dim,
|
| 309 |
+
feature_dim=256,
|
| 310 |
+
use_spatial_transformer=use_stn,
|
| 311 |
+
return_global_feature=return_global
|
| 312 |
+
)
|
| 313 |
+
|
| 314 |
+
output = encoder(sim_data)
|
| 315 |
+
print(f"{desc}:")
|
| 316 |
+
print(f" Input shape: {sim_data.shape}")
|
| 317 |
+
print(f" Output shape: {output.shape}")
|
| 318 |
+
print(f" Output dim: {encoder.get_output_dim()}")
|
| 319 |
+
print()
|
src/generate_utils/lib/model/resnet.py
ADDED
|
@@ -0,0 +1,432 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
import torch.nn as nn
|
| 3 |
+
from torch.hub import load_state_dict_from_url
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
__all__ = ['ResNet', 'resnet18', 'resnet34', 'resnet50', 'resnet101',
|
| 7 |
+
'resnet152', 'resnext50_32x4d', 'resnext101_32x8d',
|
| 8 |
+
'wide_resnet50_2', 'wide_resnet101_2']
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
model_urls = {
|
| 12 |
+
'resnet18': 'https://download.pytorch.org/models/resnet18-5c106cde.pth',
|
| 13 |
+
'resnet34': 'https://download.pytorch.org/models/resnet34-333f7ec4.pth',
|
| 14 |
+
'resnet50': 'https://download.pytorch.org/models/resnet50-19c8e357.pth',
|
| 15 |
+
'resnet101': 'https://download.pytorch.org/models/resnet101-5d3b4d8f.pth',
|
| 16 |
+
'resnet152': 'https://download.pytorch.org/models/resnet152-b121ed2d.pth',
|
| 17 |
+
'resnext50_32x4d': 'https://download.pytorch.org/models/resnext50_32x4d-7cdf4587.pth',
|
| 18 |
+
'resnext101_32x8d': 'https://download.pytorch.org/models/resnext101_32x8d-8ba56ff5.pth',
|
| 19 |
+
'wide_resnet50_2': 'https://download.pytorch.org/models/wide_resnet50_2-95faca4d.pth',
|
| 20 |
+
'wide_resnet101_2': 'https://download.pytorch.org/models/wide_resnet101_2-32ee1156.pth',
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
def conv3x3(in_planes, out_planes, stride=1, groups=1, dilation=1):
|
| 25 |
+
"""3x3 convolution with padding"""
|
| 26 |
+
return nn.Conv2d(in_planes, out_planes, kernel_size=3, stride=stride,
|
| 27 |
+
padding=dilation, groups=groups, bias=False, dilation=dilation)
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def conv1x1(in_planes, out_planes, stride=1):
|
| 31 |
+
"""1x1 convolution"""
|
| 32 |
+
return nn.Conv2d(in_planes, out_planes, kernel_size=1, stride=stride, bias=False)
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
class BasicBlock(nn.Module):
|
| 36 |
+
expansion = 1
|
| 37 |
+
|
| 38 |
+
def __init__(self, inplanes, planes, stride=1, downsample=None, groups=1,
|
| 39 |
+
base_width=64, dilation=1, norm_layer=None):
|
| 40 |
+
super(BasicBlock, self).__init__()
|
| 41 |
+
if norm_layer is None:
|
| 42 |
+
norm_layer = nn.BatchNorm2d
|
| 43 |
+
if groups != 1 or base_width != 64:
|
| 44 |
+
raise ValueError('BasicBlock only supports groups=1 and base_width=64')
|
| 45 |
+
if dilation > 1:
|
| 46 |
+
raise NotImplementedError("Dilation > 1 not supported in BasicBlock")
|
| 47 |
+
# Both self.conv1 and self.downsample layers downsample the input when stride != 1
|
| 48 |
+
self.conv1 = conv3x3(inplanes, planes, stride)
|
| 49 |
+
self.bn1 = norm_layer(planes)
|
| 50 |
+
self.relu = nn.ReLU(inplace=True)
|
| 51 |
+
self.conv2 = conv3x3(planes, planes)
|
| 52 |
+
self.bn2 = norm_layer(planes)
|
| 53 |
+
self.downsample = downsample
|
| 54 |
+
self.stride = stride
|
| 55 |
+
|
| 56 |
+
def forward(self, x):
|
| 57 |
+
identity = x
|
| 58 |
+
|
| 59 |
+
out = self.conv1(x)
|
| 60 |
+
out = self.bn1(out)
|
| 61 |
+
out = self.relu(out)
|
| 62 |
+
|
| 63 |
+
out = self.conv2(out)
|
| 64 |
+
out = self.bn2(out)
|
| 65 |
+
|
| 66 |
+
if self.downsample is not None:
|
| 67 |
+
identity = self.downsample(x)
|
| 68 |
+
|
| 69 |
+
out += identity
|
| 70 |
+
out = self.relu(out)
|
| 71 |
+
|
| 72 |
+
return out
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
class Bottleneck(nn.Module):
|
| 76 |
+
# Bottleneck in torchvision places the stride for downsampling at 3x3 convolution(self.conv2)
|
| 77 |
+
# while original implementation places the stride at the first 1x1 convolution(self.conv1)
|
| 78 |
+
# according to "Deep residual learning for image recognition"https://arxiv.org/abs/1512.03385.
|
| 79 |
+
# This variant is also known as ResNet V1.5 and improves accuracy according to
|
| 80 |
+
# https://ngc.nvidia.com/catalog/model-scripts/nvidia:resnet_50_v1_5_for_pytorch.
|
| 81 |
+
|
| 82 |
+
expansion = 4
|
| 83 |
+
|
| 84 |
+
def __init__(self, inplanes, planes, stride=1, downsample=None, groups=1,
|
| 85 |
+
base_width=64, dilation=1, norm_layer=None):
|
| 86 |
+
super(Bottleneck, self).__init__()
|
| 87 |
+
if norm_layer is None:
|
| 88 |
+
norm_layer = nn.BatchNorm2d
|
| 89 |
+
width = int(planes * (base_width / 64.)) * groups
|
| 90 |
+
# Both self.conv2 and self.downsample layers downsample the input when stride != 1
|
| 91 |
+
self.conv1 = conv1x1(inplanes, width)
|
| 92 |
+
self.bn1 = norm_layer(width)
|
| 93 |
+
self.conv2 = conv3x3(width, width, stride, groups, dilation)
|
| 94 |
+
self.bn2 = norm_layer(width)
|
| 95 |
+
self.conv3 = conv1x1(width, planes * self.expansion)
|
| 96 |
+
self.bn3 = norm_layer(planes * self.expansion)
|
| 97 |
+
self.relu = nn.ReLU(inplace=True)
|
| 98 |
+
self.downsample = downsample
|
| 99 |
+
self.stride = stride
|
| 100 |
+
|
| 101 |
+
def forward(self, x):
|
| 102 |
+
identity = x
|
| 103 |
+
|
| 104 |
+
out = self.conv1(x)
|
| 105 |
+
out = self.bn1(out)
|
| 106 |
+
out = self.relu(out)
|
| 107 |
+
|
| 108 |
+
out = self.conv2(out)
|
| 109 |
+
out = self.bn2(out)
|
| 110 |
+
out = self.relu(out)
|
| 111 |
+
|
| 112 |
+
out = self.conv3(out)
|
| 113 |
+
out = self.bn3(out)
|
| 114 |
+
|
| 115 |
+
if self.downsample is not None:
|
| 116 |
+
identity = self.downsample(x)
|
| 117 |
+
|
| 118 |
+
out += identity
|
| 119 |
+
out = self.relu(out)
|
| 120 |
+
|
| 121 |
+
return out
|
| 122 |
+
|
| 123 |
+
|
| 124 |
+
class ResNet(nn.Module):
|
| 125 |
+
"""
|
| 126 |
+
ResNet encoder for conditional VAE.
|
| 127 |
+
|
| 128 |
+
Args:
|
| 129 |
+
block: Type of residual block (BasicBlock or Bottleneck)
|
| 130 |
+
layers: List of integers indicating the number of blocks in each layer
|
| 131 |
+
embed_dim: Dimension of the latent embedding (output dimension)
|
| 132 |
+
zero_init_residual: Whether to zero-initialize the last BN in each residual branch
|
| 133 |
+
groups: Number of groups for grouped convolutions
|
| 134 |
+
width_per_group: Width per group for grouped convolutions
|
| 135 |
+
replace_stride_with_dilation: List of booleans for using dilated convolutions
|
| 136 |
+
norm_layer: Normalization layer to use (default: BatchNorm2d)
|
| 137 |
+
cond_embed_dim: Dimension of the conditional embedding input
|
| 138 |
+
dp_rate: Dropout rate applied before the final linear layers
|
| 139 |
+
|
| 140 |
+
Inputs:
|
| 141 |
+
x: Input tensor of shape (batch_size, 1, height, width)
|
| 142 |
+
cond: Conditional embedding of shape (batch_size, cond_embed_dim)
|
| 143 |
+
|
| 144 |
+
Outputs:
|
| 145 |
+
mu: Mean of the latent distribution, shape (batch_size, embed_dim)
|
| 146 |
+
log_var: Log variance of the latent distribution, shape (batch_size, embed_dim)
|
| 147 |
+
"""
|
| 148 |
+
def __init__(self, block, layers, embed_dim=256, zero_init_residual=False,
|
| 149 |
+
groups=1, width_per_group=64, replace_stride_with_dilation=None,
|
| 150 |
+
norm_layer=None, cond_embed_dim=256, dp_rate=0.3):
|
| 151 |
+
super(ResNet, self).__init__()
|
| 152 |
+
if norm_layer is None:
|
| 153 |
+
norm_layer = nn.BatchNorm2d
|
| 154 |
+
self._norm_layer = norm_layer
|
| 155 |
+
|
| 156 |
+
self.inplanes = 64
|
| 157 |
+
self.dilation = 1
|
| 158 |
+
if replace_stride_with_dilation is None:
|
| 159 |
+
# each element in the tuple indicates if we should replace
|
| 160 |
+
# the 2x2 stride with a dilated convolution instead
|
| 161 |
+
replace_stride_with_dilation = [False, False, False]
|
| 162 |
+
if len(replace_stride_with_dilation) != 3:
|
| 163 |
+
raise ValueError("replace_stride_with_dilation should be None "
|
| 164 |
+
"or a 3-element tuple, got {}".format(replace_stride_with_dilation))
|
| 165 |
+
self.groups = groups
|
| 166 |
+
self.base_width = width_per_group
|
| 167 |
+
self.conv1 = nn.Conv2d(1, self.inplanes, kernel_size=7, stride=2, padding=3,
|
| 168 |
+
bias=False)
|
| 169 |
+
self.bn1 = norm_layer(self.inplanes)
|
| 170 |
+
self.relu = nn.ReLU(inplace=True)
|
| 171 |
+
self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
|
| 172 |
+
self.layer1 = self._make_layer(block, 64, layers[0])
|
| 173 |
+
self.layer2 = self._make_layer(block, 128, layers[1], stride=2,
|
| 174 |
+
dilate=replace_stride_with_dilation[0])
|
| 175 |
+
self.layer3 = self._make_layer(block, 256, layers[2], stride=2,
|
| 176 |
+
dilate=replace_stride_with_dilation[1])
|
| 177 |
+
self.layer4 = self._make_layer(block, 512, layers[3], stride=2,
|
| 178 |
+
dilate=replace_stride_with_dilation[2])
|
| 179 |
+
self.avgpool = nn.AdaptiveAvgPool2d((1, 1))
|
| 180 |
+
|
| 181 |
+
# Condition embedding network to align with visual features
|
| 182 |
+
self.cond_fc = nn.Sequential(
|
| 183 |
+
nn.Linear(cond_embed_dim, 512*block.expansion),
|
| 184 |
+
nn.ReLU(inplace=True),
|
| 185 |
+
nn.Dropout(dp_rate)
|
| 186 |
+
)
|
| 187 |
+
|
| 188 |
+
# Shared fusion layer before mu and log_var prediction
|
| 189 |
+
fusion_dim = 512 * block.expansion
|
| 190 |
+
self.fusion_fc = nn.Sequential(
|
| 191 |
+
nn.Linear(512*block.expansion*2, fusion_dim),
|
| 192 |
+
nn.ReLU(inplace=True),
|
| 193 |
+
nn.Dropout(dp_rate)
|
| 194 |
+
)
|
| 195 |
+
|
| 196 |
+
# Separate heads for mu and log_var with independent dropout
|
| 197 |
+
self.fc_mu = nn.Sequential(
|
| 198 |
+
nn.Dropout(dp_rate),
|
| 199 |
+
nn.Linear(fusion_dim, embed_dim)
|
| 200 |
+
)
|
| 201 |
+
self.fc_logvar = nn.Sequential(
|
| 202 |
+
nn.Dropout(dp_rate),
|
| 203 |
+
nn.Linear(fusion_dim, embed_dim)
|
| 204 |
+
)
|
| 205 |
+
|
| 206 |
+
for m in self.modules():
|
| 207 |
+
if isinstance(m, nn.Conv2d):
|
| 208 |
+
nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
|
| 209 |
+
elif isinstance(m, (nn.BatchNorm2d, nn.GroupNorm)):
|
| 210 |
+
nn.init.constant_(m.weight, 1)
|
| 211 |
+
nn.init.constant_(m.bias, 0)
|
| 212 |
+
|
| 213 |
+
# Zero-initialize the last BN in each residual branch,
|
| 214 |
+
# so that the residual branch starts with zeros, and each residual block behaves like an identity.
|
| 215 |
+
# This improves the model by 0.2~0.3% according to https://arxiv.org/abs/1706.02677
|
| 216 |
+
if zero_init_residual:
|
| 217 |
+
for m in self.modules():
|
| 218 |
+
if isinstance(m, Bottleneck):
|
| 219 |
+
nn.init.constant_(m.bn3.weight, 0)
|
| 220 |
+
elif isinstance(m, BasicBlock):
|
| 221 |
+
nn.init.constant_(m.bn2.weight, 0)
|
| 222 |
+
|
| 223 |
+
def _make_layer(self, block, planes, blocks, stride=1, dilate=False):
|
| 224 |
+
norm_layer = self._norm_layer
|
| 225 |
+
downsample = None
|
| 226 |
+
previous_dilation = self.dilation
|
| 227 |
+
if dilate:
|
| 228 |
+
self.dilation *= stride
|
| 229 |
+
stride = 1
|
| 230 |
+
if stride != 1 or self.inplanes != planes * block.expansion:
|
| 231 |
+
downsample = nn.Sequential(
|
| 232 |
+
conv1x1(self.inplanes, planes * block.expansion, stride),
|
| 233 |
+
norm_layer(planes * block.expansion),
|
| 234 |
+
)
|
| 235 |
+
|
| 236 |
+
layers = []
|
| 237 |
+
layers.append(block(self.inplanes, planes, stride, downsample, self.groups,
|
| 238 |
+
self.base_width, previous_dilation, norm_layer))
|
| 239 |
+
self.inplanes = planes * block.expansion
|
| 240 |
+
for _ in range(1, blocks):
|
| 241 |
+
layers.append(block(self.inplanes, planes, groups=self.groups,
|
| 242 |
+
base_width=self.base_width, dilation=self.dilation,
|
| 243 |
+
norm_layer=norm_layer))
|
| 244 |
+
|
| 245 |
+
return nn.Sequential(*layers)
|
| 246 |
+
|
| 247 |
+
def _forward_impl(self, x, cond):
|
| 248 |
+
# Extract visual features
|
| 249 |
+
x = self.conv1(x)
|
| 250 |
+
x = self.bn1(x)
|
| 251 |
+
x = self.relu(x)
|
| 252 |
+
x = self.maxpool(x)
|
| 253 |
+
|
| 254 |
+
x = self.layer1(x)
|
| 255 |
+
x = self.layer2(x)
|
| 256 |
+
x = self.layer3(x)
|
| 257 |
+
x = self.layer4(x)
|
| 258 |
+
|
| 259 |
+
x = self.avgpool(x)
|
| 260 |
+
x = torch.flatten(x, 1)
|
| 261 |
+
|
| 262 |
+
# Process condition through embedding network
|
| 263 |
+
cond_embed = self.cond_fc(cond)
|
| 264 |
+
|
| 265 |
+
# Concatenate visual features and condition embedding
|
| 266 |
+
x = torch.cat((x, cond_embed), dim=1)
|
| 267 |
+
|
| 268 |
+
# Shared fusion
|
| 269 |
+
x = self.fusion_fc(x)
|
| 270 |
+
|
| 271 |
+
# Predict mu and log_var with independent dropout
|
| 272 |
+
mu = self.fc_mu(x)
|
| 273 |
+
log_var = self.fc_logvar(x)
|
| 274 |
+
|
| 275 |
+
return mu, log_var
|
| 276 |
+
|
| 277 |
+
def forward(self, x, cond):
|
| 278 |
+
return self._forward_impl(x, cond)
|
| 279 |
+
|
| 280 |
+
|
| 281 |
+
def _resnet(arch, block, layers, pretrained, progress, **kwargs):
|
| 282 |
+
model = ResNet(block, layers, **kwargs)
|
| 283 |
+
if pretrained:
|
| 284 |
+
state_dict = load_state_dict_from_url(model_urls[arch], progress=progress)
|
| 285 |
+
# Handle the mismatch in conv1 (3 channels -> 1 channel)
|
| 286 |
+
if 'conv1.weight' in state_dict:
|
| 287 |
+
pretrained_conv1 = state_dict['conv1.weight']
|
| 288 |
+
# Average the weights across RGB channels for single channel input
|
| 289 |
+
state_dict['conv1.weight'] = pretrained_conv1.mean(dim=1, keepdim=True)
|
| 290 |
+
|
| 291 |
+
# Remove classifier weights as we have custom fc layers
|
| 292 |
+
state_dict = {k: v for k, v in state_dict.items() if not k.startswith('fc')}
|
| 293 |
+
model.load_state_dict(state_dict, strict=False)
|
| 294 |
+
return model
|
| 295 |
+
|
| 296 |
+
|
| 297 |
+
def resnet18(pretrained=False, progress=True, **kwargs):
|
| 298 |
+
r"""ResNet-18 model from
|
| 299 |
+
`"Deep Residual Learning for Image Recognition" <https://arxiv.org/pdf/1512.03385.pdf>`_
|
| 300 |
+
|
| 301 |
+
Args:
|
| 302 |
+
pretrained (bool): If True, returns a model pre-trained on ImageNet
|
| 303 |
+
progress (bool): If True, displays a progress bar of the download to stderr
|
| 304 |
+
"""
|
| 305 |
+
return _resnet('resnet18', BasicBlock, [2, 2, 2, 2], pretrained, progress, **kwargs)
|
| 306 |
+
|
| 307 |
+
|
| 308 |
+
def resnet34(pretrained=False, progress=True, **kwargs):
|
| 309 |
+
r"""ResNet-34 model from
|
| 310 |
+
`"Deep Residual Learning for Image Recognition" <https://arxiv.org/pdf/1512.03385.pdf>`_
|
| 311 |
+
|
| 312 |
+
Args:
|
| 313 |
+
pretrained (bool): If True, returns a model pre-trained on ImageNet
|
| 314 |
+
progress (bool): If True, displays a progress bar of the download to stderr
|
| 315 |
+
"""
|
| 316 |
+
return _resnet('resnet34', BasicBlock, [3, 4, 6, 3], pretrained, progress, **kwargs)
|
| 317 |
+
|
| 318 |
+
|
| 319 |
+
def resnet50(pretrained=False, progress=True, **kwargs):
|
| 320 |
+
r"""ResNet-50 model from
|
| 321 |
+
`"Deep Residual Learning for Image Recognition" <https://arxiv.org/pdf/1512.03385.pdf>`_
|
| 322 |
+
|
| 323 |
+
Args:
|
| 324 |
+
pretrained (bool): If True, returns a model pre-trained on ImageNet
|
| 325 |
+
progress (bool): If True, displays a progress bar of the download to stderr
|
| 326 |
+
"""
|
| 327 |
+
return _resnet('resnet50', Bottleneck, [3, 4, 6, 3], pretrained, progress, **kwargs)
|
| 328 |
+
|
| 329 |
+
|
| 330 |
+
def resnet101(pretrained=False, progress=True, **kwargs):
|
| 331 |
+
r"""ResNet-101 model from
|
| 332 |
+
`"Deep Residual Learning for Image Recognition" <https://arxiv.org/pdf/1512.03385.pdf>`_
|
| 333 |
+
|
| 334 |
+
Args:
|
| 335 |
+
pretrained (bool): If True, returns a model pre-trained on ImageNet
|
| 336 |
+
progress (bool): If True, displays a progress bar of the download to stderr
|
| 337 |
+
"""
|
| 338 |
+
return _resnet('resnet101', Bottleneck, [3, 4, 23, 3], pretrained, progress, **kwargs)
|
| 339 |
+
|
| 340 |
+
|
| 341 |
+
def resnet152(pretrained=False, progress=True, **kwargs):
|
| 342 |
+
r"""ResNet-152 model from
|
| 343 |
+
`"Deep Residual Learning for Image Recognition" <https://arxiv.org/pdf/1512.03385.pdf>`_
|
| 344 |
+
|
| 345 |
+
Args:
|
| 346 |
+
pretrained (bool): If True, returns a model pre-trained on ImageNet
|
| 347 |
+
progress (bool): If True, displays a progress bar of the download to stderr
|
| 348 |
+
"""
|
| 349 |
+
return _resnet('resnet152', Bottleneck, [3, 8, 36, 3], pretrained, progress, **kwargs)
|
| 350 |
+
|
| 351 |
+
|
| 352 |
+
def resnext50_32x4d(pretrained=False, progress=True, **kwargs):
|
| 353 |
+
r"""ResNeXt-50 32x4d model from
|
| 354 |
+
`"Aggregated Residual Transformation for Deep Neural Networks" <https://arxiv.org/pdf/1611.05431.pdf>`_
|
| 355 |
+
|
| 356 |
+
Args:
|
| 357 |
+
pretrained (bool): If True, returns a model pre-trained on ImageNet
|
| 358 |
+
progress (bool): If True, displays a progress bar of the download to stderr
|
| 359 |
+
"""
|
| 360 |
+
kwargs['groups'] = 32
|
| 361 |
+
kwargs['width_per_group'] = 4
|
| 362 |
+
return _resnet('resnext50_32x4d', Bottleneck, [3, 4, 6, 3],
|
| 363 |
+
pretrained, progress, **kwargs)
|
| 364 |
+
|
| 365 |
+
|
| 366 |
+
def resnext101_32x8d(pretrained=False, progress=True, **kwargs):
|
| 367 |
+
r"""ResNeXt-101 32x8d model from
|
| 368 |
+
`"Aggregated Residual Transformation for Deep Neural Networks" <https://arxiv.org/pdf/1611.05431.pdf>`_
|
| 369 |
+
|
| 370 |
+
Args:
|
| 371 |
+
pretrained (bool): If True, returns a model pre-trained on ImageNet
|
| 372 |
+
progress (bool): If True, displays a progress bar of the download to stderr
|
| 373 |
+
"""
|
| 374 |
+
kwargs['groups'] = 32
|
| 375 |
+
kwargs['width_per_group'] = 8
|
| 376 |
+
return _resnet('resnext101_32x8d', Bottleneck, [3, 4, 23, 3],
|
| 377 |
+
pretrained, progress, **kwargs)
|
| 378 |
+
|
| 379 |
+
|
| 380 |
+
def wide_resnet50_2(pretrained=False, progress=True, **kwargs):
|
| 381 |
+
r"""Wide ResNet-50-2 model from
|
| 382 |
+
`"Wide Residual Networks" <https://arxiv.org/pdf/1605.07146.pdf>`_
|
| 383 |
+
|
| 384 |
+
The model is the same as ResNet except for the bottleneck number of channels
|
| 385 |
+
which is twice larger in every block. The number of channels in outer 1x1
|
| 386 |
+
convolutions is the same, e.g. last block in ResNet-50 has 2048-512-2048
|
| 387 |
+
channels, and in Wide ResNet-50-2 has 2048-1024-2048.
|
| 388 |
+
|
| 389 |
+
Args:
|
| 390 |
+
pretrained (bool): If True, returns a model pre-trained on ImageNet
|
| 391 |
+
progress (bool): If True, displays a progress bar of the download to stderr
|
| 392 |
+
"""
|
| 393 |
+
kwargs['width_per_group'] = 64 * 2
|
| 394 |
+
return _resnet('wide_resnet50_2', Bottleneck, [3, 4, 6, 3],
|
| 395 |
+
pretrained, progress, **kwargs)
|
| 396 |
+
|
| 397 |
+
|
| 398 |
+
def wide_resnet101_2(pretrained=False, progress=True, **kwargs):
|
| 399 |
+
r"""Wide ResNet-101-2 model from
|
| 400 |
+
`"Wide Residual Networks" <https://arxiv.org/pdf/1605.07146.pdf>`_
|
| 401 |
+
|
| 402 |
+
The model is the same as ResNet except for the bottleneck number of channels
|
| 403 |
+
which is twice larger in every block. The number of channels in outer 1x1
|
| 404 |
+
convolutions is the same, e.g. last block in ResNet-50 has 2048-512-2048
|
| 405 |
+
channels, and in Wide ResNet-50-2 has 2048-1024-2048.
|
| 406 |
+
|
| 407 |
+
Args:
|
| 408 |
+
pretrained (bool): If True, returns a model pre-trained on ImageNet
|
| 409 |
+
progress (bool): If True, displays a progress bar of the download to stderr
|
| 410 |
+
"""
|
| 411 |
+
kwargs['width_per_group'] = 64 * 2
|
| 412 |
+
return _resnet('wide_resnet101_2', Bottleneck, [3, 4, 23, 3],
|
| 413 |
+
pretrained, progress, **kwargs)
|
| 414 |
+
|
| 415 |
+
|
| 416 |
+
if __name__ == "__main__":
|
| 417 |
+
a = torch.randn(8, 1, 110, 37)
|
| 418 |
+
cond = torch.randn(8, 85)
|
| 419 |
+
encoder = resnet18(embed_dim=256, cond_embed_dim=85)
|
| 420 |
+
b, var = encoder(a, cond)
|
| 421 |
+
print(b.shape)
|
| 422 |
+
print(var.shape)
|
| 423 |
+
|
| 424 |
+
encoder = resnet34(embed_dim=256, cond_embed_dim=85)
|
| 425 |
+
b, var = encoder(a, cond)
|
| 426 |
+
print(b.shape)
|
| 427 |
+
print(var.shape)
|
| 428 |
+
|
| 429 |
+
encoder = resnet50(embed_dim=1024, cond_embed_dim=85)
|
| 430 |
+
b, var = encoder(a, cond)
|
| 431 |
+
print(b.shape)
|
| 432 |
+
print(var.shape)
|
src/generate_utils/lib/model/unet.py
ADDED
|
@@ -0,0 +1,277 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
import torch.nn as nn
|
| 3 |
+
import torch.nn.functional as F
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
def get_crop_config(crop):
|
| 7 |
+
"""
|
| 8 |
+
Get configuration parameters based on crop size.
|
| 9 |
+
|
| 10 |
+
Args:
|
| 11 |
+
crop: List of [height, width] or None
|
| 12 |
+
|
| 13 |
+
Returns:
|
| 14 |
+
dict: Configuration including alpha, h, w, diffx, diffy
|
| 15 |
+
"""
|
| 16 |
+
if crop is None:
|
| 17 |
+
# Default values for TIP dataset (56x40)
|
| 18 |
+
return {
|
| 19 |
+
'alpha': 6,
|
| 20 |
+
'h': 3,
|
| 21 |
+
'w': 2,
|
| 22 |
+
'diffx': [1, 0, 0, 0],
|
| 23 |
+
'diffy': [1, 0, 0, 0]
|
| 24 |
+
}
|
| 25 |
+
elif crop == [64, 27]: # PressurePose dataset
|
| 26 |
+
return {
|
| 27 |
+
'alpha': 4,
|
| 28 |
+
'h': 4,
|
| 29 |
+
'w': 1,
|
| 30 |
+
'diffx': [1, 0, 1, 1],
|
| 31 |
+
'diffy': [0, 0, 0, 0]
|
| 32 |
+
}
|
| 33 |
+
elif crop == [110, 37]: # MOYO dataset
|
| 34 |
+
return {
|
| 35 |
+
'alpha': 12,
|
| 36 |
+
'h': 6,
|
| 37 |
+
'w': 2,
|
| 38 |
+
'diffx': [0, 1, 0, 1],
|
| 39 |
+
'diffy': [1, 1, 1, 0]
|
| 40 |
+
}
|
| 41 |
+
else: # Default for other sizes
|
| 42 |
+
return {
|
| 43 |
+
'alpha': 6,
|
| 44 |
+
'h': 3,
|
| 45 |
+
'w': 2,
|
| 46 |
+
'diffx': [1, 0, 0, 0],
|
| 47 |
+
'diffy': [1, 0, 0, 0]
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
class DoubleConv(nn.Module):
|
| 52 |
+
"""Double Convolutional Block with BN and ReLU"""
|
| 53 |
+
|
| 54 |
+
def __init__(self, in_channels, out_channels, mid_channels=None):
|
| 55 |
+
super().__init__()
|
| 56 |
+
if not mid_channels:
|
| 57 |
+
mid_channels = out_channels
|
| 58 |
+
self.double_conv = nn.Sequential(
|
| 59 |
+
nn.Conv2d(in_channels, mid_channels, kernel_size=3, padding=1, bias=False),
|
| 60 |
+
nn.BatchNorm2d(mid_channels),
|
| 61 |
+
nn.ReLU(inplace=True),
|
| 62 |
+
nn.Conv2d(mid_channels, out_channels, kernel_size=3, padding=1, bias=False),
|
| 63 |
+
nn.BatchNorm2d(out_channels),
|
| 64 |
+
nn.ReLU(inplace=True)
|
| 65 |
+
)
|
| 66 |
+
|
| 67 |
+
def forward(self, x):
|
| 68 |
+
return self.double_conv(x)
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
class Down(nn.Module):
|
| 72 |
+
"""Downscaling with maxpool then double conv"""
|
| 73 |
+
|
| 74 |
+
def __init__(self, in_channels, out_channels):
|
| 75 |
+
super().__init__()
|
| 76 |
+
self.maxpool_conv = nn.Sequential(
|
| 77 |
+
nn.MaxPool2d(2),
|
| 78 |
+
DoubleConv(in_channels, out_channels)
|
| 79 |
+
)
|
| 80 |
+
|
| 81 |
+
def forward(self, x):
|
| 82 |
+
return self.maxpool_conv(x)
|
| 83 |
+
|
| 84 |
+
|
| 85 |
+
class Up(nn.Module):
|
| 86 |
+
"""Upscaling then double conv"""
|
| 87 |
+
|
| 88 |
+
def __init__(self, in_channels, out_channels, bilinear=True):
|
| 89 |
+
super().__init__()
|
| 90 |
+
if bilinear:
|
| 91 |
+
self.up = nn.Upsample(scale_factor=2, mode='bilinear', align_corners=True)
|
| 92 |
+
self.conv = DoubleConv(in_channels, out_channels, in_channels // 2)
|
| 93 |
+
else:
|
| 94 |
+
self.up = nn.ConvTranspose2d(in_channels, in_channels // 2, kernel_size=2, stride=2)
|
| 95 |
+
self.conv = DoubleConv(in_channels, out_channels)
|
| 96 |
+
|
| 97 |
+
def forward(self, x1, x2):
|
| 98 |
+
x1 = self.up(x1)
|
| 99 |
+
diffY = x2.size()[2] - x1.size()[2]
|
| 100 |
+
diffX = x2.size()[3] - x1.size()[3]
|
| 101 |
+
|
| 102 |
+
x1 = F.pad(x1, [diffX // 2, diffX - diffX // 2, diffY // 2, diffY - diffY // 2])
|
| 103 |
+
x = torch.cat([x2, x1], dim=1)
|
| 104 |
+
return self.conv(x)
|
| 105 |
+
|
| 106 |
+
|
| 107 |
+
class OutConv(nn.Module):
|
| 108 |
+
def __init__(self, in_channels, out_channels):
|
| 109 |
+
super(OutConv, self).__init__()
|
| 110 |
+
self.conv = nn.Conv2d(in_channels, out_channels, kernel_size=1)
|
| 111 |
+
|
| 112 |
+
def forward(self, x):
|
| 113 |
+
return self.conv(x)
|
| 114 |
+
|
| 115 |
+
|
| 116 |
+
class ConditionalBatchNorm2d(nn.Module):
|
| 117 |
+
"""Conditional BatchNorm2d to modulate output with condition vector"""
|
| 118 |
+
|
| 119 |
+
def __init__(self, num_features, cond_dim):
|
| 120 |
+
super(ConditionalBatchNorm2d, self).__init__()
|
| 121 |
+
self.num_features = num_features
|
| 122 |
+
self.bn = nn.BatchNorm2d(num_features, affine=False)
|
| 123 |
+
self.gamma = nn.Linear(cond_dim, num_features)
|
| 124 |
+
self.beta = nn.Linear(cond_dim, num_features)
|
| 125 |
+
|
| 126 |
+
def forward(self, x, cond):
|
| 127 |
+
gamma = self.gamma(cond).view(-1, self.num_features, 1, 1)
|
| 128 |
+
beta = self.beta(cond).view(-1, self.num_features, 1, 1)
|
| 129 |
+
return self.bn(x) * gamma + beta
|
| 130 |
+
|
| 131 |
+
|
| 132 |
+
class UpWithCondition(nn.Module):
|
| 133 |
+
"""Upscaling then double conv with condition modulation"""
|
| 134 |
+
|
| 135 |
+
def __init__(self, in_channels, out_channels, cond_dim, diffX, diffY, bilinear=True):
|
| 136 |
+
super(UpWithCondition, self).__init__()
|
| 137 |
+
self.diffX = diffX
|
| 138 |
+
self.diffY = diffY
|
| 139 |
+
if bilinear:
|
| 140 |
+
self.up = nn.Upsample(scale_factor=2, mode='bilinear', align_corners=True)
|
| 141 |
+
self.conv = DoubleConv(in_channels, out_channels, in_channels // 2)
|
| 142 |
+
else:
|
| 143 |
+
self.up = nn.ConvTranspose2d(in_channels, in_channels // 2, kernel_size=2, stride=2)
|
| 144 |
+
self.conv = DoubleConv(in_channels // 2, out_channels)
|
| 145 |
+
|
| 146 |
+
# Conditional batch norm for output modulation
|
| 147 |
+
self.cond_bn = ConditionalBatchNorm2d(out_channels, cond_dim)
|
| 148 |
+
|
| 149 |
+
def forward(self, x, cond):
|
| 150 |
+
x = self.up(x)
|
| 151 |
+
x = F.pad(x, [self.diffX // 2, self.diffX - self.diffX // 2, self.diffY // 2, self.diffY - self.diffY // 2])
|
| 152 |
+
x = self.conv(x)
|
| 153 |
+
x = self.cond_bn(x, cond) # Modulate with condition
|
| 154 |
+
return x
|
| 155 |
+
|
| 156 |
+
|
| 157 |
+
class UNetEncoder(nn.Module):
|
| 158 |
+
def __init__(self, cond_dim=256, embed_dim=256, dp_rate=0.0, bilinear=False, crop=None):
|
| 159 |
+
super(UNetEncoder, self).__init__()
|
| 160 |
+
self.cond_dim = cond_dim
|
| 161 |
+
self.crop = crop
|
| 162 |
+
|
| 163 |
+
# Get crop-specific configuration
|
| 164 |
+
crop_config = get_crop_config(crop)
|
| 165 |
+
alpha = crop_config['alpha']
|
| 166 |
+
|
| 167 |
+
# Encoder
|
| 168 |
+
self.inc = DoubleConv(1, 64)
|
| 169 |
+
self.down1 = Down(64, 128)
|
| 170 |
+
self.down2 = Down(128, 256)
|
| 171 |
+
self.down3 = Down(256, 512)
|
| 172 |
+
factor = 2 if bilinear else 1
|
| 173 |
+
self.down4 = Down(512, 1024 // factor)
|
| 174 |
+
|
| 175 |
+
self.dropout = nn.Dropout(dp_rate)
|
| 176 |
+
|
| 177 |
+
# VAE latent space parameters
|
| 178 |
+
self.fc_mu = nn.Linear((1024 // factor) * alpha + cond_dim, embed_dim)
|
| 179 |
+
self.fc_log_var = nn.Linear((1024 // factor) * alpha + cond_dim, embed_dim)
|
| 180 |
+
|
| 181 |
+
def forward(self, x, cond):
|
| 182 |
+
# Encoder path
|
| 183 |
+
x1 = self.inc(x)
|
| 184 |
+
x2 = self.down1(x1)
|
| 185 |
+
x3 = self.down2(x2)
|
| 186 |
+
x4 = self.down3(x3)
|
| 187 |
+
x5 = self.down4(x4) # B x (1024 // factor) x H x W
|
| 188 |
+
|
| 189 |
+
# Flatten and concatenate with condition vector
|
| 190 |
+
x5_flat = x5.view(x5.size(0), -1)
|
| 191 |
+
x5_cond = torch.cat([x5_flat, cond], dim=1)
|
| 192 |
+
|
| 193 |
+
# Compute mu and log_var for latent space
|
| 194 |
+
mu = self.fc_mu(x5_cond)
|
| 195 |
+
log_var = self.fc_log_var(x5_cond)
|
| 196 |
+
|
| 197 |
+
return mu, log_var
|
| 198 |
+
|
| 199 |
+
|
| 200 |
+
class UNetDecoder(nn.Module):
|
| 201 |
+
def __init__(self, cond_dim=256, embed_dim=256, dp_rate=0.0, bilinear=False, crop=None):
|
| 202 |
+
super(UNetDecoder, self).__init__()
|
| 203 |
+
self.bilinear = bilinear
|
| 204 |
+
self.cond_dim = cond_dim
|
| 205 |
+
self.crop = crop
|
| 206 |
+
|
| 207 |
+
# Get crop-specific configuration
|
| 208 |
+
crop_config = get_crop_config(crop)
|
| 209 |
+
alpha = crop_config['alpha']
|
| 210 |
+
self.h = crop_config['h']
|
| 211 |
+
self.w = crop_config['w']
|
| 212 |
+
diffx = crop_config['diffx']
|
| 213 |
+
diffy = crop_config['diffy']
|
| 214 |
+
|
| 215 |
+
factor = 2 if bilinear else 1
|
| 216 |
+
|
| 217 |
+
# Map latent vector and condition back to decoder size
|
| 218 |
+
self.fc_z = nn.Linear(embed_dim + cond_dim, (1024 // factor) * alpha)
|
| 219 |
+
self.dropout = nn.Dropout(dp_rate)
|
| 220 |
+
|
| 221 |
+
# Decoder with conditional batch norm
|
| 222 |
+
self.up1 = UpWithCondition(1024, 512 // factor, cond_dim, diffX=diffx[0], diffY=diffy[0], bilinear=bilinear)
|
| 223 |
+
self.up2 = UpWithCondition(512, 256 // factor, cond_dim, diffX=diffx[1], diffY=diffy[1], bilinear=bilinear)
|
| 224 |
+
self.up3 = UpWithCondition(256, 128 // factor, cond_dim, diffX=diffx[2], diffY=diffy[2], bilinear=bilinear)
|
| 225 |
+
self.up4 = UpWithCondition(128, 64, cond_dim, diffX=diffx[3], diffY=diffy[3], bilinear=bilinear)
|
| 226 |
+
self.outc = OutConv(64, 1)
|
| 227 |
+
|
| 228 |
+
def forward(self, z, cond):
|
| 229 |
+
b, _ = z.shape
|
| 230 |
+
factor = 2 if self.bilinear else 1
|
| 231 |
+
|
| 232 |
+
# Decode latent vector concatenated with condition
|
| 233 |
+
z_cond = torch.cat([z, cond], dim=1)
|
| 234 |
+
z_decoded = self.fc_z(z_cond).view(b, 1024 // factor, self.h, self.w)
|
| 235 |
+
z_decoded = self.dropout(z_decoded)
|
| 236 |
+
|
| 237 |
+
# Decoder path with conditional modulation
|
| 238 |
+
x = self.up1(z_decoded, cond)
|
| 239 |
+
x = self.up2(x, cond)
|
| 240 |
+
x = self.up3(x, cond)
|
| 241 |
+
x = self.up4(x, cond)
|
| 242 |
+
x = self.outc(x)
|
| 243 |
+
|
| 244 |
+
return x.view(b, self.crop[0], self.crop[1])
|
| 245 |
+
|
| 246 |
+
|
| 247 |
+
if __name__ == "__main__":
|
| 248 |
+
def reparameterize(mu, logvar):
|
| 249 |
+
"""VAE reparameterization trick"""
|
| 250 |
+
std = torch.exp(0.5 * logvar)
|
| 251 |
+
eps = torch.randn_like(std)
|
| 252 |
+
return mu + eps * std
|
| 253 |
+
|
| 254 |
+
# Test data
|
| 255 |
+
data = torch.randn(4, 1, 56, 40)
|
| 256 |
+
condition = torch.randn(4, 85)
|
| 257 |
+
|
| 258 |
+
# Initialize encoder and decoder
|
| 259 |
+
encoder = UNetEncoder(cond_dim=85, embed_dim=256, bilinear=False, crop=[56, 40])
|
| 260 |
+
decoder = UNetDecoder(cond_dim=85, embed_dim=256, bilinear=False, crop=[56, 40])
|
| 261 |
+
|
| 262 |
+
# Forward pass through encoder
|
| 263 |
+
mu, logvar = encoder(data, condition)
|
| 264 |
+
print(f"Mu shape: {mu.shape}, Logvar shape: {logvar.shape}")
|
| 265 |
+
|
| 266 |
+
# Reparameterization trick
|
| 267 |
+
z = reparameterize(mu, logvar)
|
| 268 |
+
print(f"Latent z shape: {z.shape}")
|
| 269 |
+
|
| 270 |
+
# Forward pass through decoder
|
| 271 |
+
reconstructed = decoder(z, condition)
|
| 272 |
+
print(f"Reconstructed shape: {reconstructed.shape}")
|
| 273 |
+
print(f"Expected shape: [4, 56, 40]")
|
| 274 |
+
|
| 275 |
+
assert reconstructed.shape == (4, 56, 40), f"Shape mismatch: {reconstructed.shape} vs (4, 56, 40)"
|
| 276 |
+
print("Test passed!")
|
| 277 |
+
|