yolozyk commited on
Commit
c3ec853
·
1 Parent(s): ed256cd

update generate tools

Browse files
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
+