Upload folder using huggingface_hub
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- .gitattributes +49 -0
- home/ubuntu/aaaaa/data/rgbmr/.gitignore +46 -0
- home/ubuntu/aaaaa/data/rgbmr/DifferentiableRenderer/MeshRender.py +1519 -0
- home/ubuntu/aaaaa/data/rgbmr/DifferentiableRenderer/__init__.py +0 -0
- home/ubuntu/aaaaa/data/rgbmr/DifferentiableRenderer/camera_utils.py +93 -0
- home/ubuntu/aaaaa/data/rgbmr/DifferentiableRenderer/compile_mesh_painter.sh +1 -0
- home/ubuntu/aaaaa/data/rgbmr/DifferentiableRenderer/mesh_inpaint_processor.cpp +550 -0
- home/ubuntu/aaaaa/data/rgbmr/DifferentiableRenderer/mesh_inpaint_processor.cpython-310-x86_64-linux-gnu.so +3 -0
- home/ubuntu/aaaaa/data/rgbmr/DifferentiableRenderer/mesh_utils.py +270 -0
- home/ubuntu/aaaaa/data/rgbmr/DifferentiableRenderer/obj_to_glb.py +332 -0
- home/ubuntu/aaaaa/data/rgbmr/MCVAE_CONFIG_UPGRADE_SUMMARY.md +212 -0
- home/ubuntu/aaaaa/data/rgbmr/README.md +207 -0
- home/ubuntu/aaaaa/data/rgbmr/complex_object_ids.json +1678 -0
- home/ubuntu/aaaaa/data/rgbmr/configs/mcdiff/default.yaml +130 -0
- home/ubuntu/aaaaa/data/rgbmr/configs/mcdiff/dual_caa.yaml +22 -0
- home/ubuntu/aaaaa/data/rgbmr/configs/mcdiff/dual_full.yaml +21 -0
- home/ubuntu/aaaaa/data/rgbmr/configs/mcdiff/single_caa.yaml +21 -0
- home/ubuntu/aaaaa/data/rgbmr/configs/mcdiff/single_caa_100k_vae.yaml +21 -0
- home/ubuntu/aaaaa/data/rgbmr/configs/mcdiff/single_caa_global_pos.yaml +21 -0
- home/ubuntu/aaaaa/data/rgbmr/configs/mcdiff/single_caa_global_token.yaml +21 -0
- home/ubuntu/aaaaa/data/rgbmr/configs/mcdiff/single_caa_tmp.yaml +21 -0
- home/ubuntu/aaaaa/data/rgbmr/configs/mcdiff/single_caaa.yaml +21 -0
- home/ubuntu/aaaaa/data/rgbmr/configs/mcdiff/single_full.yaml +21 -0
- home/ubuntu/aaaaa/data/rgbmr/configs/mcdiff/single_fulll.yaml +21 -0
- home/ubuntu/aaaaa/data/rgbmr/configs/mcvae/config.json +38 -0
- home/ubuntu/aaaaa/data/rgbmr/configs/mcvae/default.yaml +92 -0
- home/ubuntu/aaaaa/data/rgbmr/configs/mcvae/layerdiffuse.yaml +34 -0
- home/ubuntu/aaaaa/data/rgbmr/configs/mcvae/no_crop.yaml +29 -0
- home/ubuntu/aaaaa/data/rgbmr/configs/mcvae/orchid.yaml +29 -0
- home/ubuntu/aaaaa/data/rgbmr/configs/mcvae/ours.yaml +29 -0
- home/ubuntu/aaaaa/data/rgbmr/configs/mcvae/variant_example.yaml +24 -0
- home/ubuntu/aaaaa/data/rgbmr/custom_rasterizer/custom_rasterizer/__init__.py +4 -0
- home/ubuntu/aaaaa/data/rgbmr/custom_rasterizer/custom_rasterizer/render.py +18 -0
- home/ubuntu/aaaaa/data/rgbmr/custom_rasterizer/lib/custom_rasterizer_kernel/__init__.py +0 -0
- home/ubuntu/aaaaa/data/rgbmr/custom_rasterizer/lib/custom_rasterizer_kernel/grid_neighbor.cpp +574 -0
- home/ubuntu/aaaaa/data/rgbmr/custom_rasterizer/lib/custom_rasterizer_kernel/rasterizer.cpp +139 -0
- home/ubuntu/aaaaa/data/rgbmr/custom_rasterizer/lib/custom_rasterizer_kernel/rasterizer.h +54 -0
- home/ubuntu/aaaaa/data/rgbmr/custom_rasterizer/lib/custom_rasterizer_kernel/rasterizer_gpu.cu +127 -0
- home/ubuntu/aaaaa/data/rgbmr/custom_rasterizer/setup.py +26 -0
- home/ubuntu/aaaaa/data/rgbmr/data/__pycache__/rgbmr_dataset.cpython-310.pyc +0 -0
- home/ubuntu/aaaaa/data/rgbmr/data/generate_rgbmr_dataset.py +389 -0
- home/ubuntu/aaaaa/data/rgbmr/data/rgbmr_dataset.py +845 -0
- home/ubuntu/aaaaa/data/rgbmr/debug_uv_mask.png +0 -0
- home/ubuntu/aaaaa/data/rgbmr/filter_complex.py +165 -0
- home/ubuntu/aaaaa/data/rgbmr/inference.py +1211 -0
- home/ubuntu/aaaaa/data/rgbmr/inference_batch.py +658 -0
- home/ubuntu/aaaaa/data/rgbmr/inference_list.py +1270 -0
- home/ubuntu/aaaaa/data/rgbmr/latent_vis/base.png +0 -0
- home/ubuntu/aaaaa/data/rgbmr/latent_vis/full.png +0 -0
- home/ubuntu/aaaaa/data/rgbmr/latent_vis/offset.png +0 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,52 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
home/ubuntu/aaaaa/data/rgbmr/DifferentiableRenderer/mesh_inpaint_processor.cpython-310-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
|
| 37 |
+
home/ubuntu/aaaaa/data/rgbmr/outputs/MCVAE_v1.1.0/wandb/run-20251109_135527-ak3g766j/files/media/images/val/images/01_0_818ac8b7e01b99f90b70.png filter=lfs diff=lfs merge=lfs -text
|
| 38 |
+
home/ubuntu/aaaaa/data/rgbmr/outputs/MCVAE_v1.1.0/wandb/run-20251109_135527-ak3g766j/files/media/images/val/images/03_0_49256f230cfc33e43100.png filter=lfs diff=lfs merge=lfs -text
|
| 39 |
+
home/ubuntu/aaaaa/data/rgbmr/outputs/MCVAE_v1.1.0/wandb/run-20251109_135527-ak3g766j/files/media/images/val/images/04_0_7b88252be613ce946fe4.png filter=lfs diff=lfs merge=lfs -text
|
| 40 |
+
home/ubuntu/aaaaa/data/rgbmr/outputs/MCVAE_v1.1.0/wandb/run-20251109_135527-ak3g766j/files/media/images/val/images/05_0_b250851f3c9169b00a69.png filter=lfs diff=lfs merge=lfs -text
|
| 41 |
+
home/ubuntu/aaaaa/data/rgbmr/outputs/MCVAE_v1.1.0/wandb/run-20251109_135527-ak3g766j/files/media/images/val_crop/images/01_0_adea7798f8f45252a73c.png filter=lfs diff=lfs merge=lfs -text
|
| 42 |
+
home/ubuntu/aaaaa/data/rgbmr/outputs/MCVAE_v1.1.0/wandb/run-20251109_135527-ak3g766j/files/media/images/val_crop/images/03_0_904944c01f3ae7bbd5f0.png filter=lfs diff=lfs merge=lfs -text
|
| 43 |
+
home/ubuntu/aaaaa/data/rgbmr/outputs/MCVAE_v1.1.0/wandb/run-20251109_135527-ak3g766j/files/media/images/val_rot180/images/00_0_a606811f00914a368e75.png filter=lfs diff=lfs merge=lfs -text
|
| 44 |
+
home/ubuntu/aaaaa/data/rgbmr/outputs/MCVAE_v1.1.0/wandb/run-20251109_135527-ak3g766j/files/media/images/val_rot180/images/01_0_4dded08813b724931309.png filter=lfs diff=lfs merge=lfs -text
|
| 45 |
+
home/ubuntu/aaaaa/data/rgbmr/outputs/MCVAE_v1.1.0/wandb/run-20251109_135527-ak3g766j/files/media/images/val_rot180/images/02_0_ab41c15b1bb8525149c9.png filter=lfs diff=lfs merge=lfs -text
|
| 46 |
+
home/ubuntu/aaaaa/data/rgbmr/outputs/MCVAE_v1.1.0/wandb/run-20251109_135527-ak3g766j/files/media/images/val_rot180/images/03_0_659fedbaf33a6c219944.png filter=lfs diff=lfs merge=lfs -text
|
| 47 |
+
home/ubuntu/aaaaa/data/rgbmr/outputs/MCVAE_v1.1.0/wandb/run-20251109_135527-ak3g766j/files/media/images/val_rot180/images/04_0_b049d61d3085ba5f13ce.png filter=lfs diff=lfs merge=lfs -text
|
| 48 |
+
home/ubuntu/aaaaa/data/rgbmr/outputs/MCVAE_v1.1.0/wandb/run-20251109_135527-ak3g766j/files/media/images/val_rot180/images/05_0_cc7d3bfcb29578a7e53d.png filter=lfs diff=lfs merge=lfs -text
|
| 49 |
+
home/ubuntu/aaaaa/data/rgbmr/outputs/MCVAE_v1.1.0/wandb/run-20251109_135527-ak3g766j/files/media/images/val_scale/images/01_0_403bc0d6d88454fc8e84.png filter=lfs diff=lfs merge=lfs -text
|
| 50 |
+
home/ubuntu/aaaaa/data/rgbmr/outputs/MCVAE_v1.1.0/wandb/run-20251109_135527-ak3g766j/files/media/images/val_scale/images/03_0_ea5147228ff3b0d1b87a.png filter=lfs diff=lfs merge=lfs -text
|
| 51 |
+
home/ubuntu/aaaaa/data/rgbmr/outputs/MCVAE_v1.1.0/wandb/run-20251109_135634-urqlwymz/files/media/images/train/examples/grid_0_661a5d74ca7dfd67e0b4.png filter=lfs diff=lfs merge=lfs -text
|
| 52 |
+
home/ubuntu/aaaaa/data/rgbmr/outputs/MCVAE_v1.1.0/wandb/run-20251109_135634-urqlwymz/files/media/images/val/images/01_0_2c96edd5ae4a47af8f53.png filter=lfs diff=lfs merge=lfs -text
|
| 53 |
+
home/ubuntu/aaaaa/data/rgbmr/outputs/MCVAE_v1.1.0/wandb/run-20251109_135634-urqlwymz/files/media/images/val/images/03_0_5e7b75cb7860a73cb9d1.png filter=lfs diff=lfs merge=lfs -text
|
| 54 |
+
home/ubuntu/aaaaa/data/rgbmr/outputs/MCVAE_v1.1.0/wandb/run-20251109_135634-urqlwymz/files/media/images/val/images/04_0_abb699ae435cf8b83003.png filter=lfs diff=lfs merge=lfs -text
|
| 55 |
+
home/ubuntu/aaaaa/data/rgbmr/outputs/MCVAE_v1.1.0/wandb/run-20251109_135634-urqlwymz/files/media/images/val/images/05_0_212056244411ef30fddf.png filter=lfs diff=lfs merge=lfs -text
|
| 56 |
+
home/ubuntu/aaaaa/data/rgbmr/outputs/MCVAE_v1.1.0/wandb/run-20251109_135634-urqlwymz/files/media/images/val_crop/images/01_0_28ff21fc45bd9c10180a.png filter=lfs diff=lfs merge=lfs -text
|
| 57 |
+
home/ubuntu/aaaaa/data/rgbmr/outputs/MCVAE_v1.1.0/wandb/run-20251109_135634-urqlwymz/files/media/images/val_crop/images/03_0_2b30a4f246fdc42315cc.png filter=lfs diff=lfs merge=lfs -text
|
| 58 |
+
home/ubuntu/aaaaa/data/rgbmr/outputs/MCVAE_v1.1.0/wandb/run-20251109_135634-urqlwymz/files/media/images/val_rot180/images/00_0_c0472612a8a5d6d1d45a.png filter=lfs diff=lfs merge=lfs -text
|
| 59 |
+
home/ubuntu/aaaaa/data/rgbmr/outputs/MCVAE_v1.1.0/wandb/run-20251109_135634-urqlwymz/files/media/images/val_rot180/images/01_0_25e541b74f270bb6fb37.png filter=lfs diff=lfs merge=lfs -text
|
| 60 |
+
home/ubuntu/aaaaa/data/rgbmr/outputs/MCVAE_v1.1.0/wandb/run-20251109_135634-urqlwymz/files/media/images/val_rot180/images/02_0_d52eb7d14cbb426697f9.png filter=lfs diff=lfs merge=lfs -text
|
| 61 |
+
home/ubuntu/aaaaa/data/rgbmr/outputs/MCVAE_v1.1.0/wandb/run-20251109_135634-urqlwymz/files/media/images/val_rot180/images/03_0_a3e9121765db9014f693.png filter=lfs diff=lfs merge=lfs -text
|
| 62 |
+
home/ubuntu/aaaaa/data/rgbmr/outputs/MCVAE_v1.1.0/wandb/run-20251109_135634-urqlwymz/files/media/images/val_rot180/images/04_0_5c43ab551120d3156681.png filter=lfs diff=lfs merge=lfs -text
|
| 63 |
+
home/ubuntu/aaaaa/data/rgbmr/outputs/MCVAE_v1.1.0/wandb/run-20251109_135634-urqlwymz/files/media/images/val_rot180/images/05_0_2bdfa81bfae10c494d15.png filter=lfs diff=lfs merge=lfs -text
|
| 64 |
+
home/ubuntu/aaaaa/data/rgbmr/outputs/MCVAE_v1.1.0/wandb/run-20251109_135634-urqlwymz/files/media/images/val_scale/images/01_0_027e0601542d52d31398.png filter=lfs diff=lfs merge=lfs -text
|
| 65 |
+
home/ubuntu/aaaaa/data/rgbmr/outputs/MCVAE_v1.1.0/wandb/run-20251109_135634-urqlwymz/files/media/images/val_scale/images/03_0_64a50028a9bbc978f275.png filter=lfs diff=lfs merge=lfs -text
|
| 66 |
+
home/ubuntu/aaaaa/data/rgbmr/outputs/MCVAE_v1.1.0/wandb/run-20251109_135634-urqlwymz/run-urqlwymz.wandb filter=lfs diff=lfs merge=lfs -text
|
| 67 |
+
home/ubuntu/aaaaa/data/rgbmr/outputs/MCVAE_v1.1.0/wandb/run-20251109_143631-069z520z/files/media/images/train/examples/grid_0_3e76f9aa156a193517c5.png filter=lfs diff=lfs merge=lfs -text
|
| 68 |
+
home/ubuntu/aaaaa/data/rgbmr/outputs/MCVAE_v1.1.0/wandb/run-20251109_143631-069z520z/files/media/images/val/images/00_0_7024f940af3f95bd447f.png filter=lfs diff=lfs merge=lfs -text
|
| 69 |
+
home/ubuntu/aaaaa/data/rgbmr/outputs/MCVAE_v1.1.0/wandb/run-20251109_143631-069z520z/files/media/images/val/images/01_0_85da5ed7facb325c1059.png filter=lfs diff=lfs merge=lfs -text
|
| 70 |
+
home/ubuntu/aaaaa/data/rgbmr/outputs/MCVAE_v1.1.0/wandb/run-20251109_143631-069z520z/files/media/images/val/images/02_0_5586337d302b720bd290.png filter=lfs diff=lfs merge=lfs -text
|
| 71 |
+
home/ubuntu/aaaaa/data/rgbmr/outputs/MCVAE_v1.1.0/wandb/run-20251109_143631-069z520z/files/media/images/val/images/03_0_d8179a4c647bd3e2936a.png filter=lfs diff=lfs merge=lfs -text
|
| 72 |
+
home/ubuntu/aaaaa/data/rgbmr/outputs/MCVAE_v1.1.0/wandb/run-20251109_143631-069z520z/files/media/images/val/images/04_0_c79975a8744557d039ea.png filter=lfs diff=lfs merge=lfs -text
|
| 73 |
+
home/ubuntu/aaaaa/data/rgbmr/outputs/MCVAE_v1.1.0/wandb/run-20251109_143631-069z520z/files/media/images/val/images/05_0_1bd6ec356b4f77ccd60b.png filter=lfs diff=lfs merge=lfs -text
|
| 74 |
+
home/ubuntu/aaaaa/data/rgbmr/outputs/MCVAE_v1.1.0/wandb/run-20251109_143631-069z520z/files/media/images/val_crop/images/01_0_681e3f44716ca0c74ce5.png filter=lfs diff=lfs merge=lfs -text
|
| 75 |
+
home/ubuntu/aaaaa/data/rgbmr/outputs/MCVAE_v1.1.0/wandb/run-20251109_143631-069z520z/files/media/images/val_crop/images/03_0_9bd579ebc1f440a1c78c.png filter=lfs diff=lfs merge=lfs -text
|
| 76 |
+
home/ubuntu/aaaaa/data/rgbmr/outputs/MCVAE_v1.1.0/wandb/run-20251109_143631-069z520z/files/media/images/val_rot180/images/00_0_b0daad34dff08759d68e.png filter=lfs diff=lfs merge=lfs -text
|
| 77 |
+
home/ubuntu/aaaaa/data/rgbmr/outputs/MCVAE_v1.1.0/wandb/run-20251109_143631-069z520z/files/media/images/val_rot180/images/01_0_dc0c32e37935a5371ce7.png filter=lfs diff=lfs merge=lfs -text
|
| 78 |
+
home/ubuntu/aaaaa/data/rgbmr/outputs/MCVAE_v1.1.0/wandb/run-20251109_143631-069z520z/files/media/images/val_rot180/images/02_0_cd238a63711660b82256.png filter=lfs diff=lfs merge=lfs -text
|
| 79 |
+
home/ubuntu/aaaaa/data/rgbmr/outputs/MCVAE_v1.1.0/wandb/run-20251109_143631-069z520z/files/media/images/val_rot180/images/03_0_18e54e8add02725ae043.png filter=lfs diff=lfs merge=lfs -text
|
| 80 |
+
home/ubuntu/aaaaa/data/rgbmr/outputs/MCVAE_v1.1.0/wandb/run-20251109_143631-069z520z/files/media/images/val_rot180/images/04_0_bc02825b394e9c404d4d.png filter=lfs diff=lfs merge=lfs -text
|
| 81 |
+
home/ubuntu/aaaaa/data/rgbmr/outputs/MCVAE_v1.1.0/wandb/run-20251109_143631-069z520z/files/media/images/val_rot180/images/05_0_28a6eacbf019405eb25a.png filter=lfs diff=lfs merge=lfs -text
|
| 82 |
+
home/ubuntu/aaaaa/data/rgbmr/outputs/MCVAE_v1.1.0/wandb/run-20251109_143631-069z520z/files/media/images/val_scale/images/01_0_920e55b3eed4248203e4.png filter=lfs diff=lfs merge=lfs -text
|
| 83 |
+
home/ubuntu/aaaaa/data/rgbmr/outputs/MCVAE_v1.1.0/wandb/run-20251109_143631-069z520z/files/media/images/val_scale/images/03_0_49e0c325869d63e8686c.png filter=lfs diff=lfs merge=lfs -text
|
| 84 |
+
home/ubuntu/aaaaa/data/rgbmr/outputs/MCVAE_v1.1.0/wandb/run-20251109_143631-069z520z/run-069z520z.wandb filter=lfs diff=lfs merge=lfs -text
|
home/ubuntu/aaaaa/data/rgbmr/.gitignore
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# .gitignore for mcgen project
|
| 2 |
+
|
| 3 |
+
# Training / experiment outputs
|
| 4 |
+
outputs/
|
| 5 |
+
wandb/
|
| 6 |
+
|
| 7 |
+
# Model / checkpoint files
|
| 8 |
+
*.pt
|
| 9 |
+
*.pth
|
| 10 |
+
*.ckpt
|
| 11 |
+
*.safetensors
|
| 12 |
+
|
| 13 |
+
# Notebooks
|
| 14 |
+
*.ipynb
|
| 15 |
+
.ipynb_checkpoints/
|
| 16 |
+
|
| 17 |
+
# Python cache / build
|
| 18 |
+
__pycache__/
|
| 19 |
+
*.py[cod]
|
| 20 |
+
*.egg-info/
|
| 21 |
+
dist/
|
| 22 |
+
build/
|
| 23 |
+
|
| 24 |
+
# Virtual environments
|
| 25 |
+
.venv/
|
| 26 |
+
venv/
|
| 27 |
+
env/
|
| 28 |
+
|
| 29 |
+
# Numpy array dumps
|
| 30 |
+
*.npy
|
| 31 |
+
*.npz
|
| 32 |
+
|
| 33 |
+
# Editors / OS noise
|
| 34 |
+
.vscode/
|
| 35 |
+
.idea/
|
| 36 |
+
.DS_Store
|
| 37 |
+
|
| 38 |
+
# MCGEN stuff
|
| 39 |
+
taming/
|
| 40 |
+
outputs*/
|
| 41 |
+
temp_*/
|
| 42 |
+
metrics_eval/
|
| 43 |
+
metrics_eval_final/
|
| 44 |
+
removethis/
|
| 45 |
+
timing_results/
|
| 46 |
+
metrics_summary.csv
|
home/ubuntu/aaaaa/data/rgbmr/DifferentiableRenderer/MeshRender.py
ADDED
|
@@ -0,0 +1,1519 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import cv2
|
| 2 |
+
import torch
|
| 3 |
+
import trimesh
|
| 4 |
+
import numpy as np
|
| 5 |
+
from PIL import Image
|
| 6 |
+
import torch.nn.functional as F
|
| 7 |
+
from typing import Union, Optional, Tuple, List, Any, Callable
|
| 8 |
+
from dataclasses import dataclass
|
| 9 |
+
from enum import Enum
|
| 10 |
+
from .camera_utils import (
|
| 11 |
+
transform_pos,
|
| 12 |
+
get_mv_matrix,
|
| 13 |
+
get_orthographic_projection_matrix,
|
| 14 |
+
get_perspective_projection_matrix,
|
| 15 |
+
)
|
| 16 |
+
|
| 17 |
+
try:
|
| 18 |
+
from .mesh_utils import load_mesh, save_mesh
|
| 19 |
+
except:
|
| 20 |
+
print("Bpy IO CAN NOT BE Imported!!!")
|
| 21 |
+
|
| 22 |
+
from .obj_to_glb import obj_to_pbr_glb
|
| 23 |
+
|
| 24 |
+
try:
|
| 25 |
+
from .mesh_inpaint_processor import meshVerticeInpaint # , meshVerticeColor
|
| 26 |
+
except ImportError as e:
|
| 27 |
+
print("InPaint Function CAN NOT BE Imported!!!")
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
class RenderMode(Enum):
|
| 31 |
+
"""Rendering mode enumeration."""
|
| 32 |
+
|
| 33 |
+
NORMAL = "normal"
|
| 34 |
+
POSITION = "position"
|
| 35 |
+
ALPHA = "alpha"
|
| 36 |
+
UV_POS = "uvpos"
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
class ReturnType(Enum):
|
| 40 |
+
"""Return type enumeration."""
|
| 41 |
+
|
| 42 |
+
TENSOR = "th"
|
| 43 |
+
NUMPY = "np"
|
| 44 |
+
PIL = "pl"
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
class TextureType(Enum):
|
| 48 |
+
"""Texture type enumeration."""
|
| 49 |
+
|
| 50 |
+
DIFFUSE = "diffuse"
|
| 51 |
+
METALLIC_ROUGHNESS = "mr"
|
| 52 |
+
NORMAL = "normal"
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
@dataclass
|
| 56 |
+
class RenderConfig:
|
| 57 |
+
"""Unified rendering configuration."""
|
| 58 |
+
|
| 59 |
+
elev: float = 0
|
| 60 |
+
azim: float = 0
|
| 61 |
+
camera_distance: Optional[float] = None
|
| 62 |
+
center: Optional[List[float]] = None
|
| 63 |
+
resolution: Optional[Union[int, Tuple[int, int]]] = None
|
| 64 |
+
bg_color: List[float] = None
|
| 65 |
+
return_type: str = "th"
|
| 66 |
+
|
| 67 |
+
def __post_init__(self):
|
| 68 |
+
if self.bg_color is None:
|
| 69 |
+
self.bg_color = [1, 1, 1]
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
@dataclass
|
| 73 |
+
class ViewState:
|
| 74 |
+
"""Camera view state for rendering pipeline."""
|
| 75 |
+
|
| 76 |
+
proj_mat: torch.Tensor
|
| 77 |
+
mv_mat: torch.Tensor
|
| 78 |
+
pos_camera: torch.Tensor
|
| 79 |
+
pos_clip: torch.Tensor
|
| 80 |
+
resolution: Tuple[int, int]
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
def stride_from_shape(shape):
|
| 84 |
+
"""
|
| 85 |
+
Calculate stride values from a given shape for multi-dimensional indexing.
|
| 86 |
+
|
| 87 |
+
Args:
|
| 88 |
+
shape: Tuple or list representing tensor dimensions
|
| 89 |
+
|
| 90 |
+
Returns:
|
| 91 |
+
List of stride values for each dimension
|
| 92 |
+
"""
|
| 93 |
+
stride = [1]
|
| 94 |
+
for x in reversed(shape[1:]):
|
| 95 |
+
stride.append(stride[-1] * x)
|
| 96 |
+
return list(reversed(stride))
|
| 97 |
+
|
| 98 |
+
|
| 99 |
+
def scatter_add_nd_with_count(input, count, indices, values, weights=None):
|
| 100 |
+
"""
|
| 101 |
+
Perform scatter-add operation on N-dimensional tensors with counting.
|
| 102 |
+
|
| 103 |
+
Args:
|
| 104 |
+
input: Input tensor [..., C] with D dimensions + C channels
|
| 105 |
+
count: Count tensor [..., 1] with D dimensions
|
| 106 |
+
indices: Index tensor [N, D] of type long
|
| 107 |
+
values: Value tensor [N, C] to scatter
|
| 108 |
+
weights: Optional weight tensor [N, C], defaults to ones if None
|
| 109 |
+
|
| 110 |
+
Returns:
|
| 111 |
+
Tuple of (updated_input, updated_count) tensors
|
| 112 |
+
"""
|
| 113 |
+
# input: [..., C], D dimension + C channel
|
| 114 |
+
# count: [..., 1], D dimension
|
| 115 |
+
# indices: [N, D], long
|
| 116 |
+
# values: [N, C]
|
| 117 |
+
|
| 118 |
+
D = indices.shape[-1]
|
| 119 |
+
C = input.shape[-1]
|
| 120 |
+
size = input.shape[:-1]
|
| 121 |
+
stride = stride_from_shape(size)
|
| 122 |
+
|
| 123 |
+
assert len(size) == D
|
| 124 |
+
|
| 125 |
+
input = input.view(-1, C) # [HW, C]
|
| 126 |
+
count = count.view(-1, 1)
|
| 127 |
+
|
| 128 |
+
flatten_indices = (indices * torch.tensor(stride, dtype=torch.long, device=indices.device)).sum(-1) # [N]
|
| 129 |
+
|
| 130 |
+
if weights is None:
|
| 131 |
+
weights = torch.ones_like(values[..., :1])
|
| 132 |
+
|
| 133 |
+
input.scatter_add_(0, flatten_indices.unsqueeze(1).repeat(1, C), values)
|
| 134 |
+
count.scatter_add_(0, flatten_indices.unsqueeze(1), weights)
|
| 135 |
+
|
| 136 |
+
return input.view(*size, C), count.view(*size, 1)
|
| 137 |
+
|
| 138 |
+
|
| 139 |
+
def linear_grid_put_2d(H, W, coords, values, return_count=False):
|
| 140 |
+
"""
|
| 141 |
+
Place values on a 2D grid using bilinear interpolation.
|
| 142 |
+
|
| 143 |
+
Args:
|
| 144 |
+
H: Grid height
|
| 145 |
+
W: Grid width
|
| 146 |
+
coords: Coordinate tensor [N, 2] with values in range [0, 1]
|
| 147 |
+
values: Value tensor [N, C] to place on grid
|
| 148 |
+
return_count: Whether to return count information
|
| 149 |
+
|
| 150 |
+
Returns:
|
| 151 |
+
2D grid tensor [H, W, C] with interpolated values, optionally with count tensor
|
| 152 |
+
"""
|
| 153 |
+
# coords: [N, 2], float in [0, 1]
|
| 154 |
+
# values: [N, C]
|
| 155 |
+
|
| 156 |
+
C = values.shape[-1]
|
| 157 |
+
|
| 158 |
+
indices = coords * torch.tensor([H - 1, W - 1], dtype=torch.float32, device=coords.device)
|
| 159 |
+
indices_00 = indices.floor().long() # [N, 2]
|
| 160 |
+
indices_00[:, 0].clamp_(0, H - 2)
|
| 161 |
+
indices_00[:, 1].clamp_(0, W - 2)
|
| 162 |
+
indices_01 = indices_00 + torch.tensor([0, 1], dtype=torch.long, device=indices.device)
|
| 163 |
+
indices_10 = indices_00 + torch.tensor([1, 0], dtype=torch.long, device=indices.device)
|
| 164 |
+
indices_11 = indices_00 + torch.tensor([1, 1], dtype=torch.long, device=indices.device)
|
| 165 |
+
|
| 166 |
+
h = indices[..., 0] - indices_00[..., 0].float()
|
| 167 |
+
w = indices[..., 1] - indices_00[..., 1].float()
|
| 168 |
+
w_00 = (1 - h) * (1 - w)
|
| 169 |
+
w_01 = (1 - h) * w
|
| 170 |
+
w_10 = h * (1 - w)
|
| 171 |
+
w_11 = h * w
|
| 172 |
+
|
| 173 |
+
result = torch.zeros(H, W, C, device=values.device, dtype=values.dtype) # [H, W, C]
|
| 174 |
+
count = torch.zeros(H, W, 1, device=values.device, dtype=values.dtype) # [H, W, 1]
|
| 175 |
+
weights = torch.ones_like(values[..., :1]) # [N, 1]
|
| 176 |
+
|
| 177 |
+
result, count = scatter_add_nd_with_count(
|
| 178 |
+
result, count, indices_00, values * w_00.unsqueeze(1), weights * w_00.unsqueeze(1)
|
| 179 |
+
)
|
| 180 |
+
result, count = scatter_add_nd_with_count(
|
| 181 |
+
result, count, indices_01, values * w_01.unsqueeze(1), weights * w_01.unsqueeze(1)
|
| 182 |
+
)
|
| 183 |
+
result, count = scatter_add_nd_with_count(
|
| 184 |
+
result, count, indices_10, values * w_10.unsqueeze(1), weights * w_10.unsqueeze(1)
|
| 185 |
+
)
|
| 186 |
+
result, count = scatter_add_nd_with_count(
|
| 187 |
+
result, count, indices_11, values * w_11.unsqueeze(1), weights * w_11.unsqueeze(1)
|
| 188 |
+
)
|
| 189 |
+
|
| 190 |
+
if return_count:
|
| 191 |
+
return result, count
|
| 192 |
+
|
| 193 |
+
mask = count.squeeze(-1) > 0
|
| 194 |
+
result[mask] = result[mask] / count[mask].repeat(1, C)
|
| 195 |
+
|
| 196 |
+
return result
|
| 197 |
+
|
| 198 |
+
|
| 199 |
+
def mipmap_linear_grid_put_2d(H, W, coords, values, min_resolution=128, return_count=False):
|
| 200 |
+
"""
|
| 201 |
+
Place values on 2D grid using mipmap-based multiresolution interpolation to fill holes.
|
| 202 |
+
|
| 203 |
+
Args:
|
| 204 |
+
H: Grid height
|
| 205 |
+
W: Grid width
|
| 206 |
+
coords: Coordinate tensor [N, 2] with values in range [0, 1]
|
| 207 |
+
values: Value tensor [N, C] to place on grid
|
| 208 |
+
min_resolution: Minimum resolution for mipmap levels
|
| 209 |
+
return_count: Whether to return count information
|
| 210 |
+
|
| 211 |
+
Returns:
|
| 212 |
+
2D grid tensor [H, W, C] with filled values, optionally with count tensor
|
| 213 |
+
"""
|
| 214 |
+
# coords: [N, 2], float in [0, 1]
|
| 215 |
+
# values: [N, C]
|
| 216 |
+
|
| 217 |
+
C = values.shape[-1]
|
| 218 |
+
|
| 219 |
+
result = torch.zeros(H, W, C, device=values.device, dtype=values.dtype) # [H, W, C]
|
| 220 |
+
count = torch.zeros(H, W, 1, device=values.device, dtype=values.dtype) # [H, W, 1]
|
| 221 |
+
|
| 222 |
+
cur_H, cur_W = H, W
|
| 223 |
+
|
| 224 |
+
while min(cur_H, cur_W) > min_resolution:
|
| 225 |
+
|
| 226 |
+
# try to fill the holes
|
| 227 |
+
mask = count.squeeze(-1) == 0
|
| 228 |
+
if not mask.any():
|
| 229 |
+
break
|
| 230 |
+
|
| 231 |
+
cur_result, cur_count = linear_grid_put_2d(cur_H, cur_W, coords, values, return_count=True)
|
| 232 |
+
result[mask] = (
|
| 233 |
+
result[mask]
|
| 234 |
+
+ F.interpolate(
|
| 235 |
+
cur_result.permute(2, 0, 1).unsqueeze(0).contiguous(), (H, W), mode="bilinear", align_corners=False
|
| 236 |
+
)
|
| 237 |
+
.squeeze(0)
|
| 238 |
+
.permute(1, 2, 0)
|
| 239 |
+
.contiguous()[mask]
|
| 240 |
+
)
|
| 241 |
+
count[mask] = (
|
| 242 |
+
count[mask]
|
| 243 |
+
+ F.interpolate(cur_count.view(1, 1, cur_H, cur_W), (H, W), mode="bilinear", align_corners=False).view(
|
| 244 |
+
H, W, 1
|
| 245 |
+
)[mask]
|
| 246 |
+
)
|
| 247 |
+
cur_H //= 2
|
| 248 |
+
cur_W //= 2
|
| 249 |
+
|
| 250 |
+
if return_count:
|
| 251 |
+
return result, count
|
| 252 |
+
|
| 253 |
+
mask = count.squeeze(-1) > 0
|
| 254 |
+
result[mask] = result[mask] / count[mask].repeat(1, C)
|
| 255 |
+
|
| 256 |
+
return result
|
| 257 |
+
|
| 258 |
+
|
| 259 |
+
# ============ Core utility functions for reducing duplication ============
|
| 260 |
+
|
| 261 |
+
|
| 262 |
+
def _normalize_image_input(image: Union[np.ndarray, torch.Tensor, Image.Image]) -> Union[np.ndarray, torch.Tensor]:
|
| 263 |
+
"""Normalize image input to consistent format."""
|
| 264 |
+
if isinstance(image, Image.Image):
|
| 265 |
+
return np.array(image) / 255.0
|
| 266 |
+
elif isinstance(image, torch.Tensor):
|
| 267 |
+
return image.cpu().numpy() if image.is_cuda else image
|
| 268 |
+
return image
|
| 269 |
+
|
| 270 |
+
|
| 271 |
+
def _convert_texture_format(
|
| 272 |
+
tex: Union[np.ndarray, torch.Tensor, Image.Image],
|
| 273 |
+
texture_size: Tuple[int, int],
|
| 274 |
+
device: str,
|
| 275 |
+
force_set: bool = False,
|
| 276 |
+
) -> torch.Tensor:
|
| 277 |
+
"""Unified texture format conversion logic."""
|
| 278 |
+
if not force_set:
|
| 279 |
+
if isinstance(tex, np.ndarray):
|
| 280 |
+
tex = Image.fromarray((tex * 255).astype(np.uint8))
|
| 281 |
+
elif isinstance(tex, torch.Tensor):
|
| 282 |
+
tex_np = tex.cpu().numpy()
|
| 283 |
+
tex = Image.fromarray((tex_np * 255).astype(np.uint8))
|
| 284 |
+
|
| 285 |
+
tex = tex.resize(texture_size).convert("RGB")
|
| 286 |
+
tex = np.array(tex) / 255.0
|
| 287 |
+
return torch.from_numpy(tex).to(device).float()
|
| 288 |
+
else:
|
| 289 |
+
if isinstance(tex, np.ndarray):
|
| 290 |
+
tex = torch.from_numpy(tex)
|
| 291 |
+
return tex.to(device).float()
|
| 292 |
+
|
| 293 |
+
|
| 294 |
+
def _format_output(image: torch.Tensor, return_type: str) -> Union[torch.Tensor, np.ndarray, Image.Image]:
|
| 295 |
+
"""Convert output to requested format."""
|
| 296 |
+
if return_type == ReturnType.NUMPY.value:
|
| 297 |
+
return image.cpu().numpy()
|
| 298 |
+
elif return_type == ReturnType.PIL.value:
|
| 299 |
+
img_np = image.cpu().numpy() * 255
|
| 300 |
+
return Image.fromarray(img_np.astype(np.uint8))
|
| 301 |
+
return image
|
| 302 |
+
|
| 303 |
+
|
| 304 |
+
def _ensure_resolution_format(
|
| 305 |
+
resolution: Optional[Union[int, Tuple[int, int]]], default: Tuple[int, int]
|
| 306 |
+
) -> Tuple[int, int]:
|
| 307 |
+
"""Ensure resolution is in (height, width) format."""
|
| 308 |
+
if resolution is None:
|
| 309 |
+
return default
|
| 310 |
+
if isinstance(resolution, (int, float)):
|
| 311 |
+
return (int(resolution), int(resolution))
|
| 312 |
+
return tuple(resolution)
|
| 313 |
+
|
| 314 |
+
|
| 315 |
+
def _apply_background_mask(
|
| 316 |
+
content: torch.Tensor, visible_mask: torch.Tensor, bg_color: List[float], device: str
|
| 317 |
+
) -> torch.Tensor:
|
| 318 |
+
"""Apply background color to masked regions."""
|
| 319 |
+
bg_tensor = torch.tensor(bg_color, dtype=torch.float32, device=device)
|
| 320 |
+
return content * visible_mask + bg_tensor * (1 - visible_mask)
|
| 321 |
+
|
| 322 |
+
|
| 323 |
+
class MeshRender:
|
| 324 |
+
def __init__(
|
| 325 |
+
self,
|
| 326 |
+
camera_distance=1.45,
|
| 327 |
+
camera_type="orth",
|
| 328 |
+
default_resolution=1024,
|
| 329 |
+
texture_size=1024,
|
| 330 |
+
use_antialias=True,
|
| 331 |
+
max_mip_level=None,
|
| 332 |
+
filter_mode="linear-mipmap-linear",
|
| 333 |
+
bake_mode="back_sample",
|
| 334 |
+
raster_mode="cr",
|
| 335 |
+
shader_type="face",
|
| 336 |
+
use_opengl=False,
|
| 337 |
+
device="cuda",
|
| 338 |
+
):
|
| 339 |
+
"""
|
| 340 |
+
Initialize mesh renderer with configurable parameters.
|
| 341 |
+
|
| 342 |
+
Args:
|
| 343 |
+
camera_distance: Distance from camera to object center
|
| 344 |
+
camera_type: Type of camera projection ("orth" or "perspective")
|
| 345 |
+
default_resolution: Default rendering resolution
|
| 346 |
+
texture_size: Size of texture maps
|
| 347 |
+
use_antialias: Whether to use antialiasing
|
| 348 |
+
max_mip_level: Maximum mipmap level for texture filtering
|
| 349 |
+
filter_mode: Texture filtering mode
|
| 350 |
+
bake_mode: Texture baking method ("back_sample", "linear", "mip-map")
|
| 351 |
+
raster_mode: Rasterization backend ("cr" for custom rasterizer)
|
| 352 |
+
shader_type: Shading type ("face" or "vertex")
|
| 353 |
+
use_opengl: Whether to use OpenGL backend (deprecated)
|
| 354 |
+
device: Computing device ("cuda" or "cpu")
|
| 355 |
+
"""
|
| 356 |
+
|
| 357 |
+
self.device = device
|
| 358 |
+
|
| 359 |
+
self.set_default_render_resolution(default_resolution)
|
| 360 |
+
self.set_default_texture_resolution(texture_size)
|
| 361 |
+
|
| 362 |
+
self.camera_distance = camera_distance
|
| 363 |
+
self.use_antialias = use_antialias
|
| 364 |
+
self.max_mip_level = max_mip_level
|
| 365 |
+
self.filter_mode = filter_mode
|
| 366 |
+
self.bake_angle_thres = 75
|
| 367 |
+
self.set_boundary_unreliable_scale(4)
|
| 368 |
+
self.bake_mode = bake_mode
|
| 369 |
+
self.shader_type = shader_type
|
| 370 |
+
|
| 371 |
+
self.raster_mode = raster_mode
|
| 372 |
+
if self.raster_mode == "cr":
|
| 373 |
+
import custom_rasterizer as cr
|
| 374 |
+
|
| 375 |
+
self.raster = cr
|
| 376 |
+
else:
|
| 377 |
+
raise f"No raster named {self.raster_mode}"
|
| 378 |
+
|
| 379 |
+
if camera_type == "orth":
|
| 380 |
+
self.set_orth_scale(1.1)
|
| 381 |
+
elif camera_type == "perspective":
|
| 382 |
+
self.camera_proj_mat = get_perspective_projection_matrix(
|
| 383 |
+
49.13, self.default_resolution[1] / self.default_resolution[0], 0.01, 100.0
|
| 384 |
+
)
|
| 385 |
+
else:
|
| 386 |
+
raise f"No camera type {camera_type}"
|
| 387 |
+
|
| 388 |
+
# Removed multiprocessing components for single-threaded version
|
| 389 |
+
|
| 390 |
+
def _create_view_state(self, config: RenderConfig) -> ViewState:
|
| 391 |
+
"""Create unified view state for rendering pipeline."""
|
| 392 |
+
proj = self.camera_proj_mat
|
| 393 |
+
r_mv = get_mv_matrix(
|
| 394 |
+
elev=config.elev,
|
| 395 |
+
azim=config.azim,
|
| 396 |
+
camera_distance=self.camera_distance if config.camera_distance is None else config.camera_distance,
|
| 397 |
+
center=config.center,
|
| 398 |
+
)
|
| 399 |
+
|
| 400 |
+
pos_camera = transform_pos(r_mv, self.vtx_pos, keepdim=True)
|
| 401 |
+
pos_clip = transform_pos(proj, pos_camera)
|
| 402 |
+
resolution = _ensure_resolution_format(config.resolution, self.default_resolution)
|
| 403 |
+
|
| 404 |
+
return ViewState(proj, r_mv, pos_camera, pos_clip, resolution)
|
| 405 |
+
|
| 406 |
+
def _compute_face_normals(self, triangles: torch.Tensor) -> torch.Tensor:
|
| 407 |
+
"""Compute face normals from triangle vertices."""
|
| 408 |
+
return F.normalize(
|
| 409 |
+
torch.cross(
|
| 410 |
+
triangles[:, 1, :] - triangles[:, 0, :],
|
| 411 |
+
triangles[:, 2, :] - triangles[:, 0, :],
|
| 412 |
+
dim=-1,
|
| 413 |
+
),
|
| 414 |
+
dim=-1,
|
| 415 |
+
)
|
| 416 |
+
|
| 417 |
+
def _get_normals_for_shading(self, view_state: ViewState, use_abs_coor: bool = False) -> torch.Tensor:
|
| 418 |
+
"""Get normals based on shader type and coordinate system."""
|
| 419 |
+
if use_abs_coor:
|
| 420 |
+
mesh_triangles = self.vtx_pos[self.pos_idx[:, :3], :]
|
| 421 |
+
else:
|
| 422 |
+
pos_camera = view_state.pos_camera[:, :3] / view_state.pos_camera[:, 3:4]
|
| 423 |
+
mesh_triangles = pos_camera[self.pos_idx[:, :3], :]
|
| 424 |
+
|
| 425 |
+
face_normals = self._compute_face_normals(mesh_triangles)
|
| 426 |
+
|
| 427 |
+
# Common rasterization
|
| 428 |
+
rast_out, _ = self.raster_rasterize(view_state.pos_clip, self.pos_idx, resolution=view_state.resolution)
|
| 429 |
+
|
| 430 |
+
if self.shader_type == "vertex":
|
| 431 |
+
vertex_normals = trimesh.geometry.mean_vertex_normals(
|
| 432 |
+
vertex_count=self.vtx_pos.shape[0],
|
| 433 |
+
faces=self.pos_idx.cpu(),
|
| 434 |
+
face_normals=face_normals.cpu(),
|
| 435 |
+
)
|
| 436 |
+
vertex_normals = torch.from_numpy(vertex_normals).float().to(self.device).contiguous()
|
| 437 |
+
normal, _ = self.raster_interpolate(vertex_normals[None, ...], rast_out, self.pos_idx)
|
| 438 |
+
|
| 439 |
+
elif self.shader_type == "face":
|
| 440 |
+
tri_ids = rast_out[..., 3]
|
| 441 |
+
tri_ids_mask = tri_ids > 0
|
| 442 |
+
tri_ids = ((tri_ids - 1) * tri_ids_mask).long()
|
| 443 |
+
normal = torch.zeros(rast_out.shape[0], rast_out.shape[1], rast_out.shape[2], 3).to(rast_out)
|
| 444 |
+
normal.reshape(-1, 3)[tri_ids_mask.view(-1)] = face_normals.reshape(-1, 3)[tri_ids[tri_ids_mask].view(-1)]
|
| 445 |
+
|
| 446 |
+
return normal, rast_out
|
| 447 |
+
|
| 448 |
+
def _unified_render_pipeline(self, config: RenderConfig, mode: RenderMode, **kwargs) -> torch.Tensor:
|
| 449 |
+
"""Unified rendering pipeline for all render modes."""
|
| 450 |
+
view_state = self._create_view_state(config)
|
| 451 |
+
|
| 452 |
+
if mode == RenderMode.ALPHA:
|
| 453 |
+
rast_out, _ = self.raster_rasterize(view_state.pos_clip, self.pos_idx, resolution=view_state.resolution)
|
| 454 |
+
return rast_out[..., -1:].long()
|
| 455 |
+
|
| 456 |
+
elif mode == RenderMode.UV_POS:
|
| 457 |
+
return self.uv_feature_map(self.vtx_pos * 0.5 + 0.5)
|
| 458 |
+
|
| 459 |
+
elif mode == RenderMode.NORMAL:
|
| 460 |
+
use_abs_coor = kwargs.get("use_abs_coor", False)
|
| 461 |
+
normalize_rgb = kwargs.get("normalize_rgb", True)
|
| 462 |
+
|
| 463 |
+
normal, rast_out = self._get_normals_for_shading(view_state, use_abs_coor)
|
| 464 |
+
visible_mask = torch.clamp(rast_out[..., -1:], 0, 1)
|
| 465 |
+
|
| 466 |
+
result = _apply_background_mask(normal, visible_mask, config.bg_color, self.device)
|
| 467 |
+
|
| 468 |
+
if normalize_rgb:
|
| 469 |
+
result = (result + 1) * 0.5
|
| 470 |
+
|
| 471 |
+
if self.use_antialias:
|
| 472 |
+
result = self.raster_antialias(result, rast_out, view_state.pos_clip, self.pos_idx)
|
| 473 |
+
|
| 474 |
+
return result[0, ...]
|
| 475 |
+
|
| 476 |
+
elif mode == RenderMode.POSITION:
|
| 477 |
+
rast_out, _ = self.raster_rasterize(view_state.pos_clip, self.pos_idx, resolution=view_state.resolution)
|
| 478 |
+
|
| 479 |
+
tex_position = 0.5 - self.vtx_pos[:, :3] / self.scale_factor
|
| 480 |
+
tex_position = tex_position.contiguous()
|
| 481 |
+
|
| 482 |
+
position, _ = self.raster_interpolate(tex_position[None, ...], rast_out, self.pos_idx)
|
| 483 |
+
visible_mask = torch.clamp(rast_out[..., -1:], 0, 1)
|
| 484 |
+
|
| 485 |
+
result = _apply_background_mask(position, visible_mask, config.bg_color, self.device)
|
| 486 |
+
|
| 487 |
+
if self.use_antialias:
|
| 488 |
+
result = self.raster_antialias(result, rast_out, view_state.pos_clip, self.pos_idx)
|
| 489 |
+
|
| 490 |
+
return result[0, ...]
|
| 491 |
+
|
| 492 |
+
def set_orth_scale(self, ortho_scale):
|
| 493 |
+
"""
|
| 494 |
+
Set the orthographic projection scale and update camera projection matrix.
|
| 495 |
+
|
| 496 |
+
Args:
|
| 497 |
+
ortho_scale: Scale factor for orthographic projection
|
| 498 |
+
"""
|
| 499 |
+
self.ortho_scale = ortho_scale
|
| 500 |
+
self.camera_proj_mat = get_orthographic_projection_matrix(
|
| 501 |
+
left=-self.ortho_scale * 0.5,
|
| 502 |
+
right=self.ortho_scale * 0.5,
|
| 503 |
+
bottom=-self.ortho_scale * 0.5,
|
| 504 |
+
top=self.ortho_scale * 0.5,
|
| 505 |
+
near=0.1,
|
| 506 |
+
far=100,
|
| 507 |
+
)
|
| 508 |
+
|
| 509 |
+
def raster_rasterize(self, pos, tri, resolution, ranges=None, grad_db=True):
|
| 510 |
+
"""
|
| 511 |
+
Rasterize triangular mesh using the configured rasterization backend.
|
| 512 |
+
|
| 513 |
+
Args:
|
| 514 |
+
pos: Vertex positions in clip space
|
| 515 |
+
tri: Triangle indices
|
| 516 |
+
resolution: Rendering resolution [height, width]
|
| 517 |
+
ranges: Optional rendering ranges (unused in current implementation)
|
| 518 |
+
grad_db: Whether to compute gradients (unused in current implementation)
|
| 519 |
+
|
| 520 |
+
Returns:
|
| 521 |
+
Tuple of (rasterization_output, gradient_info)
|
| 522 |
+
"""
|
| 523 |
+
|
| 524 |
+
if self.raster_mode == "cr":
|
| 525 |
+
rast_out_db = None
|
| 526 |
+
if pos.dim() == 2:
|
| 527 |
+
pos = pos.unsqueeze(0)
|
| 528 |
+
|
| 529 |
+
# 确保pos是float32类型
|
| 530 |
+
if pos.dtype == torch.float64:
|
| 531 |
+
pos = pos.to(torch.float32)
|
| 532 |
+
|
| 533 |
+
# 确保tri是int32类型
|
| 534 |
+
if tri.dtype == torch.int64:
|
| 535 |
+
tri = tri.to(torch.int32)
|
| 536 |
+
|
| 537 |
+
findices, barycentric = self.raster.rasterize(pos, tri, resolution)
|
| 538 |
+
rast_out = torch.cat((barycentric, findices.unsqueeze(-1)), dim=-1)
|
| 539 |
+
rast_out = rast_out.unsqueeze(0)
|
| 540 |
+
else:
|
| 541 |
+
raise f"No raster named {self.raster_mode}"
|
| 542 |
+
|
| 543 |
+
return rast_out, rast_out_db
|
| 544 |
+
|
| 545 |
+
def raster_interpolate(self, uv, rast_out, uv_idx):
|
| 546 |
+
"""
|
| 547 |
+
Interpolate texture coordinates or vertex attributes across rasterized triangles.
|
| 548 |
+
|
| 549 |
+
Args:
|
| 550 |
+
uv: UV coordinates or vertex attributes to interpolate
|
| 551 |
+
rast_out: Rasterization output containing barycentric coordinates
|
| 552 |
+
uv_idx: UV or vertex indices for triangles
|
| 553 |
+
|
| 554 |
+
Returns:
|
| 555 |
+
Tuple of (interpolated_values, gradient_info)
|
| 556 |
+
"""
|
| 557 |
+
|
| 558 |
+
if self.raster_mode == "cr":
|
| 559 |
+
textd = None
|
| 560 |
+
barycentric = rast_out[0, ..., :-1]
|
| 561 |
+
findices = rast_out[0, ..., -1]
|
| 562 |
+
if uv.dim() == 2:
|
| 563 |
+
uv = uv.unsqueeze(0)
|
| 564 |
+
textc = self.raster.interpolate(uv, findices, barycentric, uv_idx)
|
| 565 |
+
else:
|
| 566 |
+
raise f"No raster named {self.raster_mode}"
|
| 567 |
+
|
| 568 |
+
return textc, textd
|
| 569 |
+
|
| 570 |
+
def raster_antialias(self, color, rast, pos, tri, topology_hash=None, pos_gradient_boost=1.0):
|
| 571 |
+
"""
|
| 572 |
+
Apply antialiasing to rendered colors (currently returns input unchanged).
|
| 573 |
+
|
| 574 |
+
Args:
|
| 575 |
+
color: Input color values
|
| 576 |
+
rast: Rasterization output
|
| 577 |
+
pos: Vertex positions
|
| 578 |
+
tri: Triangle indices
|
| 579 |
+
topology_hash: Optional topology hash for optimization
|
| 580 |
+
pos_gradient_boost: Gradient boosting factor
|
| 581 |
+
|
| 582 |
+
Returns:
|
| 583 |
+
Antialiased color values
|
| 584 |
+
"""
|
| 585 |
+
|
| 586 |
+
if self.raster_mode == "cr":
|
| 587 |
+
color = color
|
| 588 |
+
else:
|
| 589 |
+
raise f"No raster named {self.raster_mode}"
|
| 590 |
+
|
| 591 |
+
return color
|
| 592 |
+
|
| 593 |
+
def set_boundary_unreliable_scale(self, scale):
|
| 594 |
+
"""
|
| 595 |
+
Set the kernel size for boundary unreliable region detection during texture baking.
|
| 596 |
+
|
| 597 |
+
Args:
|
| 598 |
+
scale: Scale factor relative to 512 resolution baseline
|
| 599 |
+
"""
|
| 600 |
+
self.bake_unreliable_kernel_size = int(
|
| 601 |
+
(scale / 512) * max(self.default_resolution[0], self.default_resolution[1])
|
| 602 |
+
)
|
| 603 |
+
|
| 604 |
+
def load_mesh(
|
| 605 |
+
self,
|
| 606 |
+
mesh,
|
| 607 |
+
scale_factor=1.0,
|
| 608 |
+
auto_center=True,
|
| 609 |
+
):
|
| 610 |
+
"""
|
| 611 |
+
Load mesh from file and set up rendering data structures.
|
| 612 |
+
|
| 613 |
+
Args:
|
| 614 |
+
mesh: Path to mesh file or mesh object
|
| 615 |
+
scale_factor: Scaling factor for mesh normalization
|
| 616 |
+
auto_center: Whether to automatically center the mesh
|
| 617 |
+
"""
|
| 618 |
+
vtx_pos, pos_idx, vtx_uv, uv_idx, texture_data = load_mesh(mesh)
|
| 619 |
+
self.set_mesh(
|
| 620 |
+
vtx_pos, pos_idx, vtx_uv=vtx_uv, uv_idx=uv_idx, scale_factor=scale_factor, auto_center=auto_center
|
| 621 |
+
)
|
| 622 |
+
if texture_data is not None:
|
| 623 |
+
self.set_texture(texture_data)
|
| 624 |
+
|
| 625 |
+
def save_mesh(self, mesh_path, downsample=False):
|
| 626 |
+
"""
|
| 627 |
+
Save current mesh with textures to GLB file.
|
| 628 |
+
|
| 629 |
+
Args:
|
| 630 |
+
mesh_path: Output file path (will be saved as .glb)
|
| 631 |
+
downsample: Whether to downsample textures by half
|
| 632 |
+
"""
|
| 633 |
+
# Ensure the output path has .glb extension
|
| 634 |
+
if not mesh_path.endswith('.glb'):
|
| 635 |
+
mesh_path = mesh_path.rsplit('.', 1)[0] + '.glb'
|
| 636 |
+
|
| 637 |
+
# Get mesh geometry
|
| 638 |
+
vtx_pos, pos_idx, vtx_uv, uv_idx = self.get_mesh(normalize=False)
|
| 639 |
+
|
| 640 |
+
# Get textures
|
| 641 |
+
texture_data = self.get_texture() # [H, W, 3], numpy array in [0, 1]
|
| 642 |
+
texture_metallic, texture_roughness = self.get_texture_mr() # each [H, W, 3] or None
|
| 643 |
+
|
| 644 |
+
# Downsample if requested
|
| 645 |
+
if downsample:
|
| 646 |
+
texture_data = cv2.resize(texture_data, (texture_data.shape[1] // 2, texture_data.shape[0] // 2))
|
| 647 |
+
if texture_metallic is not None:
|
| 648 |
+
texture_metallic = cv2.resize(
|
| 649 |
+
texture_metallic, (texture_metallic.shape[1] // 2, texture_metallic.shape[0] // 2)
|
| 650 |
+
)
|
| 651 |
+
if texture_roughness is not None:
|
| 652 |
+
texture_roughness = cv2.resize(
|
| 653 |
+
texture_roughness, (texture_roughness.shape[1] // 2, texture_roughness.shape[0] // 2)
|
| 654 |
+
)
|
| 655 |
+
|
| 656 |
+
# Convert texture_data from [0, 1] float to [0, 255] uint8 for PIL
|
| 657 |
+
albedo_uint8 = (np.clip(texture_data, 0, 1) * 255).astype(np.uint8)
|
| 658 |
+
albedo_pil = Image.fromarray(albedo_uint8, mode='RGB')
|
| 659 |
+
|
| 660 |
+
# Create ORM texture (Occlusion, Roughness, Metallic)
|
| 661 |
+
# glTF standard: R=unused/occlusion, G=roughness, B=metallic
|
| 662 |
+
orm_pil = None
|
| 663 |
+
if texture_metallic is not None and texture_roughness is not None:
|
| 664 |
+
# Extract single channel from the repeated 3-channel textures
|
| 665 |
+
# texture_metallic and texture_roughness are [H, W, 3] with repeated channels
|
| 666 |
+
roughness_channel = (np.clip(texture_roughness[:, :, 0], 0, 1) * 255).astype(np.uint8)
|
| 667 |
+
metallic_channel = (np.clip(texture_metallic[:, :, 0], 0, 1) * 255).astype(np.uint8)
|
| 668 |
+
|
| 669 |
+
# Create ORM image: R=1 (full occlusion/unused), G=roughness, B=metallic
|
| 670 |
+
h, w = roughness_channel.shape
|
| 671 |
+
orm_array = np.ones((h, w, 3), dtype=np.uint8) * 255
|
| 672 |
+
orm_array[:, :, 1] = roughness_channel # G channel = roughness
|
| 673 |
+
orm_array[:, :, 2] = metallic_channel # B channel = metallic
|
| 674 |
+
|
| 675 |
+
orm_pil = Image.fromarray(orm_array, mode='RGB')
|
| 676 |
+
|
| 677 |
+
# Create trimesh object
|
| 678 |
+
# Note: trimesh expects vertices and faces, UV data will be set via visual
|
| 679 |
+
mesh = trimesh.Trimesh(
|
| 680 |
+
vertices=vtx_pos,
|
| 681 |
+
faces=pos_idx,
|
| 682 |
+
process=False # Don't process to preserve UV mapping
|
| 683 |
+
)
|
| 684 |
+
|
| 685 |
+
# Set UV coordinates
|
| 686 |
+
# trimesh uses per-vertex UVs, but we have per-face-vertex UVs
|
| 687 |
+
# We need to create a TextureVisuals object
|
| 688 |
+
if vtx_uv is not None and uv_idx is not None:
|
| 689 |
+
# Check if UV indices match position indices (simple case)
|
| 690 |
+
if np.array_equal(uv_idx, pos_idx):
|
| 691 |
+
# Simple case: per-vertex UVs
|
| 692 |
+
mesh.visual = trimesh.visual.TextureVisuals(uv=vtx_uv)
|
| 693 |
+
else:
|
| 694 |
+
# Complex case: need to expand vertices to match UV mapping
|
| 695 |
+
# Create new vertices where each face corner gets its own vertex
|
| 696 |
+
new_vertices = vtx_pos[pos_idx.flatten()]
|
| 697 |
+
new_uvs = vtx_uv[uv_idx.flatten()]
|
| 698 |
+
new_faces = np.arange(len(pos_idx) * 3).reshape(-1, 3)
|
| 699 |
+
|
| 700 |
+
mesh = trimesh.Trimesh(
|
| 701 |
+
vertices=new_vertices,
|
| 702 |
+
faces=new_faces,
|
| 703 |
+
process=False
|
| 704 |
+
)
|
| 705 |
+
mesh.visual = trimesh.visual.TextureVisuals(uv=new_uvs)
|
| 706 |
+
|
| 707 |
+
# Use obj_to_pbr_glb to save as GLB
|
| 708 |
+
if obj_to_pbr_glb is not None:
|
| 709 |
+
obj_to_pbr_glb(
|
| 710 |
+
obj_path_or_mesh=mesh,
|
| 711 |
+
base_color_path=albedo_pil,
|
| 712 |
+
orm_path=orm_pil,
|
| 713 |
+
output_glb_path=mesh_path,
|
| 714 |
+
flip_uv=False,
|
| 715 |
+
center=False,
|
| 716 |
+
scale_to_unit=False,
|
| 717 |
+
)
|
| 718 |
+
else:
|
| 719 |
+
# Fallback to old method if obj_to_pbr_glb is not available
|
| 720 |
+
print("Warning: obj_to_pbr_glb not available, falling back to OBJ export")
|
| 721 |
+
texture_normal = self.get_texture_normal()
|
| 722 |
+
if downsample and texture_normal is not None:
|
| 723 |
+
texture_normal = cv2.resize(
|
| 724 |
+
texture_normal, (texture_normal.shape[1] // 2, texture_normal.shape[0] // 2)
|
| 725 |
+
)
|
| 726 |
+
save_mesh(
|
| 727 |
+
mesh_path.replace('.glb', '.obj'),
|
| 728 |
+
vtx_pos,
|
| 729 |
+
pos_idx,
|
| 730 |
+
vtx_uv,
|
| 731 |
+
uv_idx,
|
| 732 |
+
texture_data,
|
| 733 |
+
metallic=texture_metallic,
|
| 734 |
+
roughness=texture_roughness,
|
| 735 |
+
normal=texture_normal,
|
| 736 |
+
)
|
| 737 |
+
|
| 738 |
+
def set_mesh(self, vtx_pos, pos_idx, vtx_uv=None, uv_idx=None, scale_factor=1.0, auto_center=True):
|
| 739 |
+
"""
|
| 740 |
+
Set mesh geometry data and perform coordinate transformations.
|
| 741 |
+
|
| 742 |
+
Args:
|
| 743 |
+
vtx_pos: Vertex positions [N, 3]
|
| 744 |
+
pos_idx: Triangle vertex indices [F, 3]
|
| 745 |
+
vtx_uv: UV coordinates [N, 2], optional
|
| 746 |
+
uv_idx: Triangle UV indices [F, 3], optional
|
| 747 |
+
scale_factor: Scaling factor for mesh normalization
|
| 748 |
+
auto_center: Whether to automatically center and scale the mesh
|
| 749 |
+
"""
|
| 750 |
+
self.vtx_pos = torch.from_numpy(vtx_pos).to(self.device)
|
| 751 |
+
self.pos_idx = torch.from_numpy(pos_idx).to(self.device)
|
| 752 |
+
|
| 753 |
+
# 确保顶点位置是float32类型
|
| 754 |
+
if self.vtx_pos.dtype == torch.float64:
|
| 755 |
+
self.vtx_pos = self.vtx_pos.to(torch.float32)
|
| 756 |
+
|
| 757 |
+
# 确保索引类型为int32
|
| 758 |
+
if self.pos_idx.dtype == torch.int64:
|
| 759 |
+
self.pos_idx = self.pos_idx.to(torch.int32)
|
| 760 |
+
|
| 761 |
+
if (vtx_uv is not None) and (uv_idx is not None):
|
| 762 |
+
self.vtx_uv = torch.from_numpy(vtx_uv).to(self.device)
|
| 763 |
+
self.uv_idx = torch.from_numpy(uv_idx).to(self.device)
|
| 764 |
+
|
| 765 |
+
# 确保UV坐标是float32类型
|
| 766 |
+
if self.vtx_uv.dtype == torch.float64:
|
| 767 |
+
self.vtx_uv = self.vtx_uv.to(torch.float32)
|
| 768 |
+
|
| 769 |
+
# 确保UV索引类型为int32
|
| 770 |
+
if self.uv_idx.dtype == torch.int64:
|
| 771 |
+
self.uv_idx = self.uv_idx.to(torch.int32)
|
| 772 |
+
else:
|
| 773 |
+
self.vtx_uv = None
|
| 774 |
+
self.uv_idx = None
|
| 775 |
+
|
| 776 |
+
self.vtx_pos[:, [0, 1]] = -self.vtx_pos[:, [0, 1]]
|
| 777 |
+
self.vtx_pos[:, [1, 2]] = self.vtx_pos[:, [2, 1]]
|
| 778 |
+
# if (vtx_uv is not None) and (uv_idx is not None):
|
| 779 |
+
# self.vtx_uv[:, 1] = 1.0 - self.vtx_uv[:, 1]
|
| 780 |
+
# pass
|
| 781 |
+
|
| 782 |
+
if auto_center:
|
| 783 |
+
# Calculate bounding box center (equivalent to mesh.bounds.mean(axis=0))
|
| 784 |
+
max_bb = self.vtx_pos.max(0)[0]
|
| 785 |
+
min_bb = self.vtx_pos.min(0)[0]
|
| 786 |
+
bbox_center = (max_bb + min_bb) / 2
|
| 787 |
+
|
| 788 |
+
# Move to center (equivalent to: mesh.vertices -= bbox_center)
|
| 789 |
+
self.vtx_pos = self.vtx_pos - bbox_center
|
| 790 |
+
transform_offset = bbox_center
|
| 791 |
+
|
| 792 |
+
# Rescale (equivalent to: max_scale = np.abs(mesh.vertices).max())
|
| 793 |
+
max_scale = torch.abs(self.vtx_pos).max()
|
| 794 |
+
if max_scale > 0:
|
| 795 |
+
transform_scale = max_scale / scale_factor * 2
|
| 796 |
+
self.vtx_pos = self.vtx_pos / transform_scale
|
| 797 |
+
else:
|
| 798 |
+
transform_scale = torch.tensor(1.0, device=self.device)
|
| 799 |
+
|
| 800 |
+
# Store transformation parameters for inverse operation
|
| 801 |
+
self.scale_factor = scale_factor
|
| 802 |
+
self.mesh_normalize_scale_factor = float(transform_scale)
|
| 803 |
+
self.mesh_normalize_scale_center = transform_offset.unsqueeze(0).cpu().numpy()
|
| 804 |
+
else:
|
| 805 |
+
self.scale_factor = 1.0
|
| 806 |
+
self.mesh_normalize_scale_factor = 1.0
|
| 807 |
+
self.mesh_normalize_scale_center = np.array([[0, 0, 0]])
|
| 808 |
+
|
| 809 |
+
if uv_idx is not None:
|
| 810 |
+
self.extract_textiles()
|
| 811 |
+
|
| 812 |
+
def _set_texture_unified(
|
| 813 |
+
self, tex: Union[np.ndarray, torch.Tensor, Image.Image], texture_type: TextureType, force_set: bool = False
|
| 814 |
+
):
|
| 815 |
+
"""Unified texture setting method."""
|
| 816 |
+
converted_tex = _convert_texture_format(tex, self.texture_size, self.device, force_set)
|
| 817 |
+
|
| 818 |
+
if texture_type == TextureType.DIFFUSE:
|
| 819 |
+
self.tex = converted_tex
|
| 820 |
+
elif texture_type == TextureType.METALLIC_ROUGHNESS:
|
| 821 |
+
self.tex_mr = converted_tex
|
| 822 |
+
elif texture_type == TextureType.NORMAL:
|
| 823 |
+
self.tex_normalMap = converted_tex
|
| 824 |
+
|
| 825 |
+
def set_texture(self, tex, force_set=False):
|
| 826 |
+
"""Set the main diffuse texture for the mesh."""
|
| 827 |
+
self._set_texture_unified(tex, TextureType.DIFFUSE, force_set)
|
| 828 |
+
|
| 829 |
+
def set_texture_mr(self, mr, force_set=False):
|
| 830 |
+
"""Set metallic-roughness texture for PBR rendering."""
|
| 831 |
+
self._set_texture_unified(mr, TextureType.METALLIC_ROUGHNESS, force_set)
|
| 832 |
+
|
| 833 |
+
def set_texture_normal(self, normal, force_set=False):
|
| 834 |
+
"""Set normal map texture for surface detail."""
|
| 835 |
+
self._set_texture_unified(normal, TextureType.NORMAL, force_set)
|
| 836 |
+
|
| 837 |
+
def set_default_render_resolution(self, default_resolution):
|
| 838 |
+
"""
|
| 839 |
+
Set the default resolution for rendering operations.
|
| 840 |
+
|
| 841 |
+
Args:
|
| 842 |
+
default_resolution: Resolution as int (square) or tuple (height, width)
|
| 843 |
+
"""
|
| 844 |
+
if isinstance(default_resolution, int):
|
| 845 |
+
default_resolution = (default_resolution, default_resolution)
|
| 846 |
+
self.default_resolution = default_resolution
|
| 847 |
+
|
| 848 |
+
def set_default_texture_resolution(self, texture_size):
|
| 849 |
+
"""
|
| 850 |
+
Set the default texture resolution for UV mapping operations.
|
| 851 |
+
|
| 852 |
+
Args:
|
| 853 |
+
texture_size: Texture size as int (square) or tuple (height, width)
|
| 854 |
+
"""
|
| 855 |
+
if isinstance(texture_size, int):
|
| 856 |
+
texture_size = (texture_size, texture_size)
|
| 857 |
+
self.texture_size = texture_size
|
| 858 |
+
|
| 859 |
+
def get_face_num(self):
|
| 860 |
+
"""
|
| 861 |
+
Get the number of triangular faces in the mesh.
|
| 862 |
+
|
| 863 |
+
Returns:
|
| 864 |
+
Number of faces as integer
|
| 865 |
+
"""
|
| 866 |
+
return self.pos_idx.shape[0]
|
| 867 |
+
|
| 868 |
+
def get_vertex_num(self):
|
| 869 |
+
"""
|
| 870 |
+
Get the number of vertices in the mesh.
|
| 871 |
+
|
| 872 |
+
Returns:
|
| 873 |
+
Number of vertices as integer
|
| 874 |
+
"""
|
| 875 |
+
return self.vtx_pos.shape[0]
|
| 876 |
+
|
| 877 |
+
def get_face_areas(self, from_one_index=False):
|
| 878 |
+
"""
|
| 879 |
+
Calculate the area of each triangular face in the mesh.
|
| 880 |
+
|
| 881 |
+
Args:
|
| 882 |
+
from_one_index: If True, insert zero at beginning for 1-indexed face IDs
|
| 883 |
+
|
| 884 |
+
Returns:
|
| 885 |
+
Numpy array of face areas
|
| 886 |
+
"""
|
| 887 |
+
v0 = self.vtx_pos[self.pos_idx[:, 0], :]
|
| 888 |
+
v1 = self.vtx_pos[self.pos_idx[:, 1], :]
|
| 889 |
+
v2 = self.vtx_pos[self.pos_idx[:, 2], :]
|
| 890 |
+
|
| 891 |
+
# 计算两个边向量
|
| 892 |
+
edge1 = v1 - v0
|
| 893 |
+
edge2 = v2 - v0
|
| 894 |
+
|
| 895 |
+
# 计算叉积的模长的一半即为面积
|
| 896 |
+
areas = torch.norm(torch.cross(edge1, edge2, dim=-1), dim=-1) * 0.5
|
| 897 |
+
|
| 898 |
+
areas = areas.cpu().numpy()
|
| 899 |
+
|
| 900 |
+
if from_one_index:
|
| 901 |
+
# 在数组前面插入一个0,因为三角片索引是从1开始的
|
| 902 |
+
areas = np.insert(areas, 0, 0)
|
| 903 |
+
|
| 904 |
+
return areas
|
| 905 |
+
|
| 906 |
+
def get_mesh(self, normalize=True):
|
| 907 |
+
"""
|
| 908 |
+
Get mesh geometry with optional coordinate denormalization.
|
| 909 |
+
|
| 910 |
+
Args:
|
| 911 |
+
normalize: Whether to keep normalized coordinates (True) or restore original scale (False)
|
| 912 |
+
|
| 913 |
+
Returns:
|
| 914 |
+
Tuple of (vertex_positions, face_indices, uv_coordinates, uv_indices)
|
| 915 |
+
"""
|
| 916 |
+
vtx_pos = self.vtx_pos.cpu().numpy()
|
| 917 |
+
pos_idx = self.pos_idx.cpu().numpy()
|
| 918 |
+
vtx_uv = self.vtx_uv.cpu().numpy()
|
| 919 |
+
uv_idx = self.uv_idx.cpu().numpy()
|
| 920 |
+
|
| 921 |
+
# 坐标变换的逆变换
|
| 922 |
+
if not normalize:
|
| 923 |
+
vtx_pos = vtx_pos / self.mesh_normalize_scale_factor
|
| 924 |
+
vtx_pos = vtx_pos + self.mesh_normalize_scale_center
|
| 925 |
+
vtx_pos[:, [1, 2]] = vtx_pos[:, [2, 1]]
|
| 926 |
+
vtx_pos[:, [0, 1]] = -vtx_pos[:, [0, 1]]
|
| 927 |
+
|
| 928 |
+
# vtx_uv[:, 1] = 1.0 - vtx_uv[:, 1]
|
| 929 |
+
return vtx_pos, pos_idx, vtx_uv, uv_idx
|
| 930 |
+
|
| 931 |
+
def get_texture(self):
|
| 932 |
+
"""
|
| 933 |
+
Get the current diffuse texture as numpy array.
|
| 934 |
+
|
| 935 |
+
Returns:
|
| 936 |
+
Texture as numpy array in range [0, 1]
|
| 937 |
+
"""
|
| 938 |
+
return self.tex.cpu().numpy()
|
| 939 |
+
|
| 940 |
+
def get_texture_mr(self):
|
| 941 |
+
"""
|
| 942 |
+
Get metallic and roughness textures as separate channels.
|
| 943 |
+
|
| 944 |
+
Returns:
|
| 945 |
+
Tuple of (metallic_texture, roughness_texture) as numpy arrays, or (None, None) if not set
|
| 946 |
+
"""
|
| 947 |
+
metallic, roughness = None, None
|
| 948 |
+
if hasattr(self, "tex_mr"):
|
| 949 |
+
mr = self.tex_mr.cpu().numpy()
|
| 950 |
+
metallic = np.repeat(mr[:, :, 2:3], repeats=3, axis=2)
|
| 951 |
+
roughness = np.repeat(mr[:, :, 1:2], repeats=3, axis=2)
|
| 952 |
+
return metallic, roughness
|
| 953 |
+
|
| 954 |
+
def get_texture_normal(self):
|
| 955 |
+
"""
|
| 956 |
+
Get the normal map texture as numpy array.
|
| 957 |
+
|
| 958 |
+
Returns:
|
| 959 |
+
Normal map as numpy array, or None if not set
|
| 960 |
+
"""
|
| 961 |
+
normal = None
|
| 962 |
+
if hasattr(self, "tex_normalMap"):
|
| 963 |
+
normal = self.tex_normalMap.cpu().numpy()
|
| 964 |
+
return normal
|
| 965 |
+
|
| 966 |
+
def to(self, device):
|
| 967 |
+
"""
|
| 968 |
+
Move all tensor attributes to the specified device.
|
| 969 |
+
|
| 970 |
+
Args:
|
| 971 |
+
device: Target device ("cuda", "cpu", etc.)
|
| 972 |
+
"""
|
| 973 |
+
self.device = device
|
| 974 |
+
|
| 975 |
+
for attr_name in dir(self):
|
| 976 |
+
attr_value = getattr(self, attr_name)
|
| 977 |
+
if isinstance(attr_value, torch.Tensor):
|
| 978 |
+
setattr(self, attr_name, attr_value.to(self.device))
|
| 979 |
+
|
| 980 |
+
def color_rgb_to_srgb(self, image):
|
| 981 |
+
"""
|
| 982 |
+
Convert RGB color values to sRGB color space using gamma correction.
|
| 983 |
+
|
| 984 |
+
Args:
|
| 985 |
+
image: Input image as PIL Image, numpy array, or torch tensor
|
| 986 |
+
|
| 987 |
+
Returns:
|
| 988 |
+
sRGB corrected image in same format as input
|
| 989 |
+
"""
|
| 990 |
+
if isinstance(image, Image.Image):
|
| 991 |
+
image_rgb = torch.tesnor(np.array(image) / 255.0).float().to(self.device)
|
| 992 |
+
elif isinstance(image, np.ndarray):
|
| 993 |
+
image_rgb = torch.tensor(image).float()
|
| 994 |
+
else:
|
| 995 |
+
image_rgb = image.to(self.device)
|
| 996 |
+
|
| 997 |
+
image_srgb = torch.where(
|
| 998 |
+
image_rgb <= 0.0031308, 12.92 * image_rgb, 1.055 * torch.pow(image_rgb, 1 / 2.4) - 0.055
|
| 999 |
+
)
|
| 1000 |
+
|
| 1001 |
+
if isinstance(image, Image.Image):
|
| 1002 |
+
image_srgb = Image.fromarray((image_srgb.cpu().numpy() * 255).astype(np.uint8))
|
| 1003 |
+
elif isinstance(image, np.ndarray):
|
| 1004 |
+
image_srgb = image_srgb.cpu().numpy()
|
| 1005 |
+
else:
|
| 1006 |
+
image_srgb = image_srgb.to(image.device)
|
| 1007 |
+
|
| 1008 |
+
return image_srgb
|
| 1009 |
+
|
| 1010 |
+
def extract_textiles(self):
|
| 1011 |
+
"""
|
| 1012 |
+
Extract texture-space position and normal information by rasterizing
|
| 1013 |
+
the mesh in UV coordinate space. Creates texture-space geometry mappings.
|
| 1014 |
+
"""
|
| 1015 |
+
|
| 1016 |
+
vnum = self.vtx_uv.shape[0]
|
| 1017 |
+
vtx_uv = torch.cat(
|
| 1018 |
+
(self.vtx_uv, torch.zeros_like(self.vtx_uv[:, 0:1]), torch.ones_like(self.vtx_uv[:, 0:1])), axis=1
|
| 1019 |
+
)
|
| 1020 |
+
vtx_uv = vtx_uv.view(1, vnum, 4) * 2 - 1
|
| 1021 |
+
|
| 1022 |
+
rast_out, rast_out_db = self.raster_rasterize(vtx_uv, self.uv_idx, resolution=self.texture_size)
|
| 1023 |
+
position, _ = self.raster_interpolate(self.vtx_pos, rast_out, self.pos_idx)
|
| 1024 |
+
|
| 1025 |
+
v0 = self.vtx_pos[self.pos_idx[:, 0], :]
|
| 1026 |
+
v1 = self.vtx_pos[self.pos_idx[:, 1], :]
|
| 1027 |
+
v2 = self.vtx_pos[self.pos_idx[:, 2], :]
|
| 1028 |
+
face_normals = F.normalize(torch.cross(v1 - v0, v2 - v0, dim=-1), dim=-1)
|
| 1029 |
+
vertex_normals = trimesh.geometry.mean_vertex_normals(
|
| 1030 |
+
vertex_count=self.vtx_pos.shape[0],
|
| 1031 |
+
faces=self.pos_idx.cpu(),
|
| 1032 |
+
face_normals=face_normals.cpu(),
|
| 1033 |
+
)
|
| 1034 |
+
vertex_normals = torch.from_numpy(vertex_normals).to(self.vtx_pos).contiguous()
|
| 1035 |
+
position_normal, _ = self.raster_interpolate(vertex_normals[None, ...], rast_out, self.pos_idx)
|
| 1036 |
+
visible_mask = torch.clamp(rast_out[..., -1:], 0, 1)[0, ..., 0]
|
| 1037 |
+
position = position[0]
|
| 1038 |
+
position_normal = position_normal[0]
|
| 1039 |
+
tri_ids = rast_out[0, ..., 3]
|
| 1040 |
+
tri_ids_mask = tri_ids > 0
|
| 1041 |
+
tri_ids = ((tri_ids - 1) * tri_ids_mask).long()
|
| 1042 |
+
position_normal.reshape(-1, 3)[tri_ids_mask.view(-1)] = face_normals.reshape(-1, 3)[
|
| 1043 |
+
tri_ids[tri_ids_mask].view(-1)
|
| 1044 |
+
]
|
| 1045 |
+
|
| 1046 |
+
row = torch.arange(position.shape[0]).to(visible_mask.device)
|
| 1047 |
+
col = torch.arange(position.shape[1]).to(visible_mask.device)
|
| 1048 |
+
grid_i, grid_j = torch.meshgrid(row, col, indexing="ij")
|
| 1049 |
+
|
| 1050 |
+
mask = visible_mask.reshape(-1) > 0
|
| 1051 |
+
position = position.reshape(-1, 3)[mask]
|
| 1052 |
+
position_normal = position_normal.reshape(-1, 3)[mask]
|
| 1053 |
+
position = torch.cat((position, torch.ones_like(position[:, :1])), axis=-1)
|
| 1054 |
+
grid = torch.stack((grid_i, grid_j), -1).reshape(-1, 2)[mask]
|
| 1055 |
+
|
| 1056 |
+
texture_indices = (
|
| 1057 |
+
torch.ones(self.texture_size[0], self.texture_size[1], device=self.device, dtype=torch.long) * -1
|
| 1058 |
+
)
|
| 1059 |
+
texture_indices.view(-1)[grid[:, 0] * self.texture_size[1] + grid[:, 1]] = torch.arange(grid.shape[0]).to(
|
| 1060 |
+
device=self.device, dtype=torch.long
|
| 1061 |
+
)
|
| 1062 |
+
|
| 1063 |
+
self.tex_position = position
|
| 1064 |
+
self.tex_normal = position_normal
|
| 1065 |
+
self.tex_grid = grid
|
| 1066 |
+
self.texture_indices = texture_indices
|
| 1067 |
+
|
| 1068 |
+
def render_normal(
|
| 1069 |
+
self,
|
| 1070 |
+
elev,
|
| 1071 |
+
azim,
|
| 1072 |
+
camera_distance=None,
|
| 1073 |
+
center=None,
|
| 1074 |
+
resolution=None,
|
| 1075 |
+
bg_color=[1, 1, 1],
|
| 1076 |
+
use_abs_coor=False,
|
| 1077 |
+
normalize_rgb=True,
|
| 1078 |
+
return_type="th",
|
| 1079 |
+
):
|
| 1080 |
+
"""Render surface normals of the mesh from specified viewpoint."""
|
| 1081 |
+
config = RenderConfig(elev, azim, camera_distance, center, resolution, bg_color, return_type)
|
| 1082 |
+
image = self._unified_render_pipeline(
|
| 1083 |
+
config, RenderMode.NORMAL, use_abs_coor=use_abs_coor, normalize_rgb=normalize_rgb
|
| 1084 |
+
)
|
| 1085 |
+
return _format_output(image, return_type)
|
| 1086 |
+
|
| 1087 |
+
def convert_normal_map(self, image):
|
| 1088 |
+
"""
|
| 1089 |
+
Convert normal map from standard format to renderer's coordinate system.
|
| 1090 |
+
Applies coordinate transformations for proper normal interpretation.
|
| 1091 |
+
|
| 1092 |
+
Args:
|
| 1093 |
+
image: Input normal map as PIL Image or numpy array
|
| 1094 |
+
|
| 1095 |
+
Returns:
|
| 1096 |
+
Converted normal map as PIL Image
|
| 1097 |
+
"""
|
| 1098 |
+
# blue is front, red is left, green is top
|
| 1099 |
+
if isinstance(image, Image.Image):
|
| 1100 |
+
image = np.array(image)
|
| 1101 |
+
mask = (image == [255, 255, 255]).all(axis=-1)
|
| 1102 |
+
|
| 1103 |
+
image = (image / 255.0) * 2.0 - 1.0
|
| 1104 |
+
|
| 1105 |
+
image[..., [1]] = -image[..., [1]]
|
| 1106 |
+
image[..., [1, 2]] = image[..., [2, 1]]
|
| 1107 |
+
image[..., [0]] = -image[..., [0]]
|
| 1108 |
+
|
| 1109 |
+
image = (image + 1.0) * 0.5
|
| 1110 |
+
|
| 1111 |
+
image = (image * 255).astype(np.uint8)
|
| 1112 |
+
image[mask] = [127, 127, 255]
|
| 1113 |
+
|
| 1114 |
+
return Image.fromarray(image)
|
| 1115 |
+
|
| 1116 |
+
def render_position(
|
| 1117 |
+
self, elev, azim, camera_distance=None, center=None, resolution=None, bg_color=[1, 1, 1], return_type="th"
|
| 1118 |
+
):
|
| 1119 |
+
"""Render world-space positions of visible mesh surface points."""
|
| 1120 |
+
config = RenderConfig(elev, azim, camera_distance, center, resolution, bg_color, return_type)
|
| 1121 |
+
image = self._unified_render_pipeline(config, RenderMode.POSITION)
|
| 1122 |
+
|
| 1123 |
+
if return_type == ReturnType.PIL.value:
|
| 1124 |
+
image = image.squeeze(-1).cpu().numpy() * 255
|
| 1125 |
+
return Image.fromarray(image.astype(np.uint8))
|
| 1126 |
+
return _format_output(image, return_type)
|
| 1127 |
+
|
| 1128 |
+
def render_uvpos(self, return_type="th"):
|
| 1129 |
+
"""Render vertex positions mapped to UV texture space."""
|
| 1130 |
+
config = RenderConfig(return_type=return_type)
|
| 1131 |
+
image = self._unified_render_pipeline(config, RenderMode.UV_POS)
|
| 1132 |
+
return _format_output(image, return_type)
|
| 1133 |
+
|
| 1134 |
+
def render_alpha(self, elev, azim, camera_distance=None, center=None, resolution=None, return_type="th"):
|
| 1135 |
+
"""Render binary alpha mask indicating visible mesh regions."""
|
| 1136 |
+
config = RenderConfig(elev, azim, camera_distance, center, resolution, return_type=return_type)
|
| 1137 |
+
image = self._unified_render_pipeline(config, RenderMode.ALPHA)
|
| 1138 |
+
|
| 1139 |
+
if return_type == ReturnType.PIL.value:
|
| 1140 |
+
raise Exception("PIL format not supported for alpha rendering")
|
| 1141 |
+
return _format_output(image, return_type)
|
| 1142 |
+
|
| 1143 |
+
def uv_feature_map(self, vert_feat, bg=None):
|
| 1144 |
+
"""
|
| 1145 |
+
Map per-vertex features to UV texture space using mesh topology.
|
| 1146 |
+
|
| 1147 |
+
Args:
|
| 1148 |
+
vert_feat: Per-vertex feature tensor [N, C]
|
| 1149 |
+
bg: Background value for unmapped regions (optional)
|
| 1150 |
+
|
| 1151 |
+
Returns:
|
| 1152 |
+
Feature map in UV texture space [H, W, C]
|
| 1153 |
+
"""
|
| 1154 |
+
vtx_uv = self.vtx_uv * 2 - 1.0
|
| 1155 |
+
vtx_uv = torch.cat([vtx_uv, torch.zeros_like(self.vtx_uv)], dim=1).unsqueeze(0)
|
| 1156 |
+
vtx_uv[..., -1] = 1
|
| 1157 |
+
uv_idx = self.uv_idx
|
| 1158 |
+
rast_out, rast_out_db = self.raster_rasterize(vtx_uv, uv_idx, resolution=self.texture_size)
|
| 1159 |
+
feat_map, _ = self.raster_interpolate(vert_feat[None, ...], rast_out, uv_idx)
|
| 1160 |
+
feat_map = feat_map[0, ...]
|
| 1161 |
+
if bg is not None:
|
| 1162 |
+
visible_mask = torch.clamp(rast_out[..., -1:], 0, 1)[0, ...]
|
| 1163 |
+
feat_map[visible_mask == 0] = bg
|
| 1164 |
+
return feat_map
|
| 1165 |
+
|
| 1166 |
+
def render_sketch_from_geometry(self, normal_image, depth_image):
|
| 1167 |
+
"""
|
| 1168 |
+
Generate sketch-style edge image from rendered normal and depth maps.
|
| 1169 |
+
|
| 1170 |
+
Args:
|
| 1171 |
+
normal_image: Rendered normal map tensor
|
| 1172 |
+
depth_image: Rendered depth map tensor
|
| 1173 |
+
|
| 1174 |
+
Returns:
|
| 1175 |
+
Binary edge sketch image as tensor
|
| 1176 |
+
"""
|
| 1177 |
+
normal_image_np = normal_image.cpu().numpy()
|
| 1178 |
+
depth_image_np = depth_image.cpu().numpy()
|
| 1179 |
+
|
| 1180 |
+
normal_image_np = (normal_image_np * 255).astype(np.uint8)
|
| 1181 |
+
depth_image_np = (depth_image_np * 255).astype(np.uint8)
|
| 1182 |
+
normal_image_np = cv2.cvtColor(normal_image_np, cv2.COLOR_RGB2GRAY)
|
| 1183 |
+
|
| 1184 |
+
normal_edges = cv2.Canny(normal_image_np, 80, 150)
|
| 1185 |
+
depth_edges = cv2.Canny(depth_image_np, 30, 80)
|
| 1186 |
+
|
| 1187 |
+
combined_edges = np.maximum(normal_edges, depth_edges)
|
| 1188 |
+
|
| 1189 |
+
sketch_image = torch.from_numpy(combined_edges).to(normal_image.device).float() / 255.0
|
| 1190 |
+
sketch_image = sketch_image.unsqueeze(-1)
|
| 1191 |
+
|
| 1192 |
+
return sketch_image
|
| 1193 |
+
|
| 1194 |
+
def render_sketch_from_depth(self, depth_image):
|
| 1195 |
+
"""
|
| 1196 |
+
Generate sketch-style edge image from depth map using edge detection.
|
| 1197 |
+
|
| 1198 |
+
Args:
|
| 1199 |
+
depth_image: Input depth map tensor
|
| 1200 |
+
|
| 1201 |
+
Returns:
|
| 1202 |
+
Binary edge sketch image as tensor
|
| 1203 |
+
"""
|
| 1204 |
+
depth_image_np = depth_image.cpu().numpy()
|
| 1205 |
+
depth_image_np = (depth_image_np * 255).astype(np.uint8)
|
| 1206 |
+
depth_edges = cv2.Canny(depth_image_np, 30, 80)
|
| 1207 |
+
combined_edges = depth_edges
|
| 1208 |
+
sketch_image = torch.from_numpy(combined_edges).to(depth_image.device).float() / 255.0
|
| 1209 |
+
sketch_image = sketch_image.unsqueeze(-1)
|
| 1210 |
+
return sketch_image
|
| 1211 |
+
|
| 1212 |
+
def back_project(self, image, elev, azim, camera_distance=None, center=None, method=None):
|
| 1213 |
+
"""
|
| 1214 |
+
Back-project a rendered image onto the mesh's UV texture space.
|
| 1215 |
+
Handles visibility, viewing angle, and boundary detection for texture baking.
|
| 1216 |
+
|
| 1217 |
+
Args:
|
| 1218 |
+
image: Input image to back-project (PIL Image, numpy array, or tensor)
|
| 1219 |
+
elev: Camera elevation angle in degrees used for rendering
|
| 1220 |
+
azim: Camera azimuth angle in degrees used for rendering
|
| 1221 |
+
camera_distance: Camera distance (uses default if None)
|
| 1222 |
+
center: Camera focus center (uses origin if None)
|
| 1223 |
+
method: Back-projection method ("linear", "mip-map", "back_sample", uses default if None)
|
| 1224 |
+
|
| 1225 |
+
Returns:
|
| 1226 |
+
Tuple of (texture, cosine_map, boundary_map) tensors in UV space
|
| 1227 |
+
"""
|
| 1228 |
+
|
| 1229 |
+
if isinstance(image, Image.Image):
|
| 1230 |
+
image = torch.tensor(np.array(image) / 255.0)
|
| 1231 |
+
elif isinstance(image, np.ndarray):
|
| 1232 |
+
image = torch.tensor(image)
|
| 1233 |
+
if image.dim() == 2:
|
| 1234 |
+
image = image.unsqueeze(-1)
|
| 1235 |
+
image = image.float().to(self.device)
|
| 1236 |
+
resolution = image.shape[:2]
|
| 1237 |
+
channel = image.shape[-1]
|
| 1238 |
+
texture = torch.zeros(self.texture_size + (channel,)).to(self.device)
|
| 1239 |
+
cos_map = torch.zeros(self.texture_size + (1,)).to(self.device)
|
| 1240 |
+
|
| 1241 |
+
proj = self.camera_proj_mat
|
| 1242 |
+
r_mv = get_mv_matrix(
|
| 1243 |
+
elev=elev,
|
| 1244 |
+
azim=azim,
|
| 1245 |
+
camera_distance=self.camera_distance if camera_distance is None else camera_distance,
|
| 1246 |
+
center=center,
|
| 1247 |
+
)
|
| 1248 |
+
pos_camera = transform_pos(r_mv, self.vtx_pos, keepdim=True)
|
| 1249 |
+
pos_clip = transform_pos(proj, pos_camera)
|
| 1250 |
+
pos_camera = pos_camera[:, :3] / pos_camera[:, 3:4]
|
| 1251 |
+
|
| 1252 |
+
v0 = pos_camera[self.pos_idx[:, 0], :]
|
| 1253 |
+
v1 = pos_camera[self.pos_idx[:, 1], :]
|
| 1254 |
+
v2 = pos_camera[self.pos_idx[:, 2], :]
|
| 1255 |
+
face_normals = F.normalize(torch.cross(v1 - v0, v2 - v0, dim=-1), dim=-1)
|
| 1256 |
+
|
| 1257 |
+
tex_depth = pos_camera[:, 2].reshape(1, -1, 1).contiguous()
|
| 1258 |
+
rast_out, rast_out_db = self.raster_rasterize(pos_clip, self.pos_idx, resolution=resolution)
|
| 1259 |
+
visible_mask = torch.clamp(rast_out[..., -1:], 0, 1)[0, ...]
|
| 1260 |
+
|
| 1261 |
+
if self.shader_type == "vertex":
|
| 1262 |
+
vertex_normals = trimesh.geometry.mean_vertex_normals(
|
| 1263 |
+
vertex_count=self.vtx_pos.shape[0],
|
| 1264 |
+
faces=self.pos_idx.cpu(),
|
| 1265 |
+
face_normals=face_normals.cpu(),
|
| 1266 |
+
)
|
| 1267 |
+
vertex_normals = torch.from_numpy(vertex_normals).float().to(self.device).contiguous()
|
| 1268 |
+
normal, _ = self.raster_interpolate(vertex_normals[None, ...], rast_out, self.pos_idx)
|
| 1269 |
+
elif self.shader_type == "face":
|
| 1270 |
+
tri_ids = rast_out[..., 3]
|
| 1271 |
+
tri_ids_mask = tri_ids > 0
|
| 1272 |
+
tri_ids = ((tri_ids - 1) * tri_ids_mask).long()
|
| 1273 |
+
normal = torch.zeros(rast_out.shape[0], rast_out.shape[1], rast_out.shape[2], 3).to(rast_out)
|
| 1274 |
+
normal.reshape(-1, 3)[tri_ids_mask.view(-1)] = face_normals.reshape(-1, 3)[tri_ids[tri_ids_mask].view(-1)]
|
| 1275 |
+
|
| 1276 |
+
normal = normal[0, ...]
|
| 1277 |
+
uv, _ = self.raster_interpolate(self.vtx_uv[None, ...], rast_out, self.uv_idx)
|
| 1278 |
+
depth, _ = self.raster_interpolate(tex_depth, rast_out, self.pos_idx)
|
| 1279 |
+
depth = depth[0, ...]
|
| 1280 |
+
|
| 1281 |
+
depth_max, depth_min = depth[visible_mask > 0].max(), depth[visible_mask > 0].min()
|
| 1282 |
+
depth_normalized = (depth - depth_min) / (depth_max - depth_min)
|
| 1283 |
+
depth_image = depth_normalized * visible_mask # Mask out background.
|
| 1284 |
+
|
| 1285 |
+
sketch_image = self.render_sketch_from_depth(depth_image)
|
| 1286 |
+
|
| 1287 |
+
lookat = torch.tensor([[0, 0, -1]], device=self.device)
|
| 1288 |
+
# cos_image = torch.nn.functional.cosine_similarity(lookat, normal.view(-1, 3))
|
| 1289 |
+
# cos_image = cos_image.view(normal.shape[0], normal.shape[1], 1)
|
| 1290 |
+
cos_raw = torch.nn.functional.cosine_similarity(lookat, normal.view(-1, 3)).view(normal.shape[0], normal.shape[1], 1)
|
| 1291 |
+
cos_image = cos_raw.abs() # 양면 처리
|
| 1292 |
+
|
| 1293 |
+
cos_thres = np.cos(self.bake_angle_thres / 180 * np.pi)
|
| 1294 |
+
cos_image[cos_image < cos_thres] = 0
|
| 1295 |
+
|
| 1296 |
+
# shrink
|
| 1297 |
+
if self.bake_unreliable_kernel_size > 0:
|
| 1298 |
+
kernel_size = self.bake_unreliable_kernel_size * 2 + 1
|
| 1299 |
+
kernel = torch.ones((1, 1, kernel_size, kernel_size), dtype=torch.float32).to(sketch_image.device)
|
| 1300 |
+
|
| 1301 |
+
visible_mask = visible_mask.permute(2, 0, 1).unsqueeze(0).float()
|
| 1302 |
+
visible_mask = F.conv2d(1.0 - visible_mask, kernel, padding=kernel_size // 2)
|
| 1303 |
+
visible_mask = 1.0 - (visible_mask > 0).float() # 二值化
|
| 1304 |
+
visible_mask = visible_mask.squeeze(0).permute(1, 2, 0)
|
| 1305 |
+
|
| 1306 |
+
sketch_image = sketch_image.permute(2, 0, 1).unsqueeze(0)
|
| 1307 |
+
sketch_image = F.conv2d(sketch_image, kernel, padding=kernel_size // 2)
|
| 1308 |
+
sketch_image = (sketch_image > 0).float() # 二值化
|
| 1309 |
+
sketch_image = sketch_image.squeeze(0).permute(1, 2, 0)
|
| 1310 |
+
visible_mask = visible_mask * (sketch_image < 0.5)
|
| 1311 |
+
|
| 1312 |
+
cos_image[visible_mask == 0] = 0
|
| 1313 |
+
|
| 1314 |
+
method = self.bake_mode if method is None else method
|
| 1315 |
+
|
| 1316 |
+
if method == "linear":
|
| 1317 |
+
proj_mask = (visible_mask != 0).view(-1)
|
| 1318 |
+
uv = uv.squeeze(0).contiguous().view(-1, 2)[proj_mask]
|
| 1319 |
+
image = image.squeeze(0).contiguous().view(-1, channel)[proj_mask]
|
| 1320 |
+
cos_image = cos_image.contiguous().view(-1, 1)[proj_mask]
|
| 1321 |
+
sketch_image = sketch_image.contiguous().view(-1, 1)[proj_mask]
|
| 1322 |
+
|
| 1323 |
+
texture = linear_grid_put_2d(self.texture_size[1], self.texture_size[0], uv[..., [1, 0]], image)
|
| 1324 |
+
cos_map = linear_grid_put_2d(self.texture_size[1], self.texture_size[0], uv[..., [1, 0]], cos_image)
|
| 1325 |
+
boundary_map = linear_grid_put_2d(self.texture_size[1], self.texture_size[0], uv[..., [1, 0]], sketch_image)
|
| 1326 |
+
elif method == "mip-map":
|
| 1327 |
+
proj_mask = (visible_mask != 0).view(-1)
|
| 1328 |
+
uv = uv.squeeze(0).contiguous().view(-1, 2)[proj_mask]
|
| 1329 |
+
image = image.squeeze(0).contiguous().view(-1, channel)[proj_mask]
|
| 1330 |
+
cos_image = cos_image.contiguous().view(-1, 1)[proj_mask]
|
| 1331 |
+
|
| 1332 |
+
texture = mipmap_linear_grid_put_2d(
|
| 1333 |
+
self.texture_size[1], self.texture_size[0], uv[..., [1, 0]], image, min_resolution=128
|
| 1334 |
+
)
|
| 1335 |
+
cos_map = mipmap_linear_grid_put_2d(
|
| 1336 |
+
self.texture_size[1], self.texture_size[0], uv[..., [1, 0]], cos_image, min_resolution=256
|
| 1337 |
+
)
|
| 1338 |
+
|
| 1339 |
+
if self.vtx_map is not None:
|
| 1340 |
+
vertex_normals = vertex_normals[self.vtx_map, :]
|
| 1341 |
+
normal_map = self.uv_feature_map(vertex_normals)
|
| 1342 |
+
# cos_map_uv = torch.nn.functional.cosine_similarity(lookat, normal_map.view(-1, 3)) # .abs()
|
| 1343 |
+
cos_map_uv = torch.nn.functional.cosine_similarity(lookat, normal_map.view(-1, 3)).abs()
|
| 1344 |
+
cos_map_uv = cos_map_uv.view(1, 1, normal_map.shape[0], normal_map.shape[1])
|
| 1345 |
+
cos_map_uv = torch.nn.functional.max_pool2d(cos_map_uv, kernel_size=3, stride=1, padding=1)
|
| 1346 |
+
cos_map_uv = cos_map_uv.reshape(self.texture_size[0], self.texture_size[1], 1)
|
| 1347 |
+
cos_map_uv[cos_map_uv < cos_thres] = 0
|
| 1348 |
+
# cos_map = torch.min(cos_map, cos_map_uv)
|
| 1349 |
+
cos_map[cos_map_uv < cos_thres] = 0
|
| 1350 |
+
elif method == "back_sample":
|
| 1351 |
+
|
| 1352 |
+
img_proj = torch.from_numpy(
|
| 1353 |
+
np.array(((proj[0, 0], 0, 0, 0), (0, proj[1, 1], 0, 0), (0, 0, 1, 0), (0, 0, 0, 1)))
|
| 1354 |
+
).to(self.tex_position)
|
| 1355 |
+
w2c = torch.from_numpy(r_mv).to(self.tex_position)
|
| 1356 |
+
v_proj = self.tex_position @ w2c.T @ img_proj
|
| 1357 |
+
inner_mask = (v_proj[:, 0] <= 1.0) & (v_proj[:, 0] >= -1.0) & (v_proj[:, 1] <= 1.0) & (v_proj[:, 1] >= -1.0)
|
| 1358 |
+
inner_valid_idx = torch.where(inner_mask)[0].long()
|
| 1359 |
+
img_x = torch.clamp(
|
| 1360 |
+
((v_proj[:, 0].clamp(-1, 1) * 0.5 + 0.5) * (resolution[0])).long(), 0, resolution[0] - 1
|
| 1361 |
+
)
|
| 1362 |
+
img_y = torch.clamp(
|
| 1363 |
+
((v_proj[:, 1].clamp(-1, 1) * 0.5 + 0.5) * (resolution[1])).long(), 0, resolution[1] - 1
|
| 1364 |
+
)
|
| 1365 |
+
|
| 1366 |
+
indices = img_y * resolution[0] + img_x
|
| 1367 |
+
sampled_z = depth.reshape(-1)[indices]
|
| 1368 |
+
sampled_m = visible_mask.reshape(-1)[indices]
|
| 1369 |
+
v_z = v_proj[:, 2]
|
| 1370 |
+
|
| 1371 |
+
sampled_w = cos_image.reshape(-1)[indices]
|
| 1372 |
+
depth_thres = 3e-3
|
| 1373 |
+
|
| 1374 |
+
# valid_idx = torch.where((torch.abs(v_z - sampled_z) < depth_thres) * (sampled_m*sampled_w>0))[0]
|
| 1375 |
+
valid_idx = torch.where((torch.abs(v_z - sampled_z) < depth_thres) & (sampled_m * sampled_w > 0))[0]
|
| 1376 |
+
|
| 1377 |
+
intersection_mask = torch.isin(valid_idx, inner_valid_idx)
|
| 1378 |
+
valid_idx = valid_idx[intersection_mask].to(inner_valid_idx)
|
| 1379 |
+
|
| 1380 |
+
indices = indices[valid_idx]
|
| 1381 |
+
sampled_b = sketch_image.reshape(-1)[indices]
|
| 1382 |
+
sampled_w = sampled_w[valid_idx]
|
| 1383 |
+
|
| 1384 |
+
# bilinear sampling rgb
|
| 1385 |
+
wx = ((v_proj[:, 0] * 0.5 + 0.5) * resolution[0] - img_x)[valid_idx].reshape(-1, 1)
|
| 1386 |
+
wy = ((v_proj[:, 1] * 0.5 + 0.5) * resolution[1] - img_y)[valid_idx].reshape(-1, 1)
|
| 1387 |
+
img_x = img_x[valid_idx]
|
| 1388 |
+
img_y = img_y[valid_idx]
|
| 1389 |
+
img_x_r = torch.clamp(img_x + 1, 0, resolution[0] - 1)
|
| 1390 |
+
img_y_r = torch.clamp(img_y + 1, 0, resolution[1] - 1)
|
| 1391 |
+
indices_lr = img_y * resolution[0] + img_x_r
|
| 1392 |
+
indices_rl = img_y_r * resolution[0] + img_x
|
| 1393 |
+
indices_rr = img_y_r * resolution[0] + img_x_r
|
| 1394 |
+
rgb = image.reshape(-1, channel)
|
| 1395 |
+
sampled_rgb = (rgb[indices] * (1 - wx) + rgb[indices_lr] * wx) * (1 - wy) + (
|
| 1396 |
+
rgb[indices_rl] * (1 - wx) + rgb[indices_rr] * wx
|
| 1397 |
+
) * wy
|
| 1398 |
+
|
| 1399 |
+
# return sampled_rgb, sampled_w, sampled_b, valid_idx
|
| 1400 |
+
texture = torch.zeros(self.texture_size[0], self.texture_size[1], channel, device=self.device).reshape(
|
| 1401 |
+
-1, channel
|
| 1402 |
+
)
|
| 1403 |
+
cos_map = torch.zeros(self.texture_size[0], self.texture_size[1], 1, device=self.device).reshape(-1)
|
| 1404 |
+
boundary_map = torch.zeros(self.texture_size[0], self.texture_size[1], 1, device=self.device).reshape(-1)
|
| 1405 |
+
|
| 1406 |
+
valid_tex_indices = self.tex_grid[valid_idx, 0] * self.texture_size[1] + self.tex_grid[valid_idx, 1]
|
| 1407 |
+
texture[valid_tex_indices, :] = sampled_rgb
|
| 1408 |
+
cos_map[valid_tex_indices] = sampled_w
|
| 1409 |
+
boundary_map[valid_tex_indices] = sampled_b
|
| 1410 |
+
|
| 1411 |
+
texture = texture.view(self.texture_size[0], self.texture_size[1], channel)
|
| 1412 |
+
cos_map = cos_map.view(self.texture_size[0], self.texture_size[1], 1)
|
| 1413 |
+
# texture = torch.clamp(texture,0,1)
|
| 1414 |
+
|
| 1415 |
+
else:
|
| 1416 |
+
raise f"No bake mode {method}"
|
| 1417 |
+
return texture, cos_map, boundary_map
|
| 1418 |
+
|
| 1419 |
+
def bake_texture(self, colors, elevs, azims, camera_distance=None, center=None, exp=6, weights=None):
|
| 1420 |
+
"""
|
| 1421 |
+
Bake multiple view images into a single UV texture using weighted blending.
|
| 1422 |
+
|
| 1423 |
+
Args:
|
| 1424 |
+
colors: List of input images (tensors, numpy arrays, or PIL Images)
|
| 1425 |
+
elevs: List of elevation angles for each view
|
| 1426 |
+
azims: List of azimuth angles for each view
|
| 1427 |
+
camera_distance: Camera distance (uses default if None)
|
| 1428 |
+
center: Camera focus center (uses origin if None)
|
| 1429 |
+
exp: Exponent for cosine weighting (higher values favor front-facing views)
|
| 1430 |
+
weights: Optional per-view weights (defaults to 1.0 for all views)
|
| 1431 |
+
|
| 1432 |
+
Returns:
|
| 1433 |
+
Tuple of (merged_texture, trust_map) tensors in UV space
|
| 1434 |
+
"""
|
| 1435 |
+
if isinstance(colors, torch.Tensor):
|
| 1436 |
+
colors = [colors[i, ...].float().permute(1, 2, 0) for i in range(colors.shape[0])]
|
| 1437 |
+
else:
|
| 1438 |
+
for i in range(len(colors)):
|
| 1439 |
+
if isinstance(colors[i], Image.Image):
|
| 1440 |
+
colors[i] = torch.tensor(np.array(colors[i]) / 255.0, device=self.device).float()
|
| 1441 |
+
if weights is None:
|
| 1442 |
+
weights = [1.0 for _ in range(len(colors))]
|
| 1443 |
+
textures = []
|
| 1444 |
+
cos_maps = []
|
| 1445 |
+
for color, elev, azim, weight in zip(colors, elevs, azims, weights):
|
| 1446 |
+
texture, cos_map, _ = self.back_project(color, elev, azim, camera_distance, center)
|
| 1447 |
+
cos_map = weight * (cos_map**exp)
|
| 1448 |
+
textures.append(texture)
|
| 1449 |
+
cos_maps.append(cos_map)
|
| 1450 |
+
|
| 1451 |
+
texture_merge, trust_map_merge = self.fast_bake_texture(textures, cos_maps)
|
| 1452 |
+
return texture_merge, trust_map_merge
|
| 1453 |
+
|
| 1454 |
+
@torch.no_grad()
|
| 1455 |
+
def fast_bake_texture(self, textures, cos_maps):
|
| 1456 |
+
"""
|
| 1457 |
+
Efficiently merge multiple textures using cosine-weighted blending.
|
| 1458 |
+
Optimizes by skipping views that don't contribute new information.
|
| 1459 |
+
|
| 1460 |
+
Args:
|
| 1461 |
+
textures: List of texture tensors to merge
|
| 1462 |
+
cos_maps: List of corresponding cosine weight maps
|
| 1463 |
+
|
| 1464 |
+
Returns:
|
| 1465 |
+
Tuple of (merged_texture, valid_mask) tensors
|
| 1466 |
+
"""
|
| 1467 |
+
|
| 1468 |
+
channel = textures[0].shape[-1]
|
| 1469 |
+
texture_merge = torch.zeros(self.texture_size + (channel,)).to(self.device)
|
| 1470 |
+
trust_map_merge = torch.zeros(self.texture_size + (1,)).to(self.device)
|
| 1471 |
+
for texture, cos_map in zip(textures, cos_maps):
|
| 1472 |
+
view_sum = (cos_map > 0).sum()
|
| 1473 |
+
painted_sum = ((cos_map > 0) * (trust_map_merge > 0)).sum()
|
| 1474 |
+
if painted_sum / view_sum > 0.99:
|
| 1475 |
+
continue
|
| 1476 |
+
texture_merge += texture * cos_map
|
| 1477 |
+
trust_map_merge += cos_map
|
| 1478 |
+
texture_merge = texture_merge / torch.clamp(trust_map_merge, min=1e-8)
|
| 1479 |
+
|
| 1480 |
+
return texture_merge, trust_map_merge > 1e-8
|
| 1481 |
+
|
| 1482 |
+
@torch.no_grad()
|
| 1483 |
+
def uv_inpaint(self, texture, mask, vertex_inpaint=True, method="NS", return_float=False):
|
| 1484 |
+
"""
|
| 1485 |
+
Inpaint missing regions in UV texture using mesh-aware and traditional methods.
|
| 1486 |
+
|
| 1487 |
+
Args:
|
| 1488 |
+
texture: Input texture as tensor, numpy array, or PIL Image
|
| 1489 |
+
mask: Binary mask indicating regions to inpaint (1 = keep, 0 = inpaint)
|
| 1490 |
+
vertex_inpaint: Whether to use mesh vertex connectivity for inpainting
|
| 1491 |
+
method: Inpainting method ("NS" for Navier-Stokes)
|
| 1492 |
+
return_float: Whether to return float values (False returns uint8)
|
| 1493 |
+
|
| 1494 |
+
Returns:
|
| 1495 |
+
Inpainted texture as numpy array
|
| 1496 |
+
"""
|
| 1497 |
+
|
| 1498 |
+
if isinstance(texture, torch.Tensor):
|
| 1499 |
+
texture_np = texture.cpu().numpy()
|
| 1500 |
+
elif isinstance(texture, np.ndarray):
|
| 1501 |
+
texture_np = texture
|
| 1502 |
+
elif isinstance(texture, Image.Image):
|
| 1503 |
+
texture_np = np.array(texture) / 255.0
|
| 1504 |
+
|
| 1505 |
+
if isinstance(mask, torch.Tensor):
|
| 1506 |
+
mask = (mask.squeeze(-1).cpu().numpy() * 255).astype(np.uint8)
|
| 1507 |
+
|
| 1508 |
+
if vertex_inpaint:
|
| 1509 |
+
vtx_pos, pos_idx, vtx_uv, uv_idx = self.get_mesh()
|
| 1510 |
+
texture_np, mask = meshVerticeInpaint(texture_np, mask, vtx_pos, vtx_uv, pos_idx, uv_idx)
|
| 1511 |
+
# save the mask image for debug
|
| 1512 |
+
mask_image = Image.fromarray(255 - mask)
|
| 1513 |
+
mask_image.save("debug_uv_mask.png")
|
| 1514 |
+
|
| 1515 |
+
if method == "NS":
|
| 1516 |
+
texture_np = cv2.inpaint((texture_np * 255).astype(np.uint8), 255 - mask, 3, cv2.INPAINT_NS)
|
| 1517 |
+
assert return_float == False
|
| 1518 |
+
|
| 1519 |
+
return texture_np
|
home/ubuntu/aaaaa/data/rgbmr/DifferentiableRenderer/__init__.py
ADDED
|
File without changes
|
home/ubuntu/aaaaa/data/rgbmr/DifferentiableRenderer/camera_utils.py
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import math
|
| 2 |
+
|
| 3 |
+
import numpy as np
|
| 4 |
+
import torch
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
def transform_pos(mtx, pos, keepdim=False):
|
| 8 |
+
t_mtx = torch.from_numpy(mtx).to(pos.device) if isinstance(mtx, np.ndarray) else mtx
|
| 9 |
+
if pos.shape[-1] == 3:
|
| 10 |
+
posw = torch.cat([pos, torch.ones([pos.shape[0], 1]).to(pos.device)], axis=1)
|
| 11 |
+
else:
|
| 12 |
+
posw = pos
|
| 13 |
+
|
| 14 |
+
if keepdim:
|
| 15 |
+
return torch.matmul(posw, t_mtx.t())[...]
|
| 16 |
+
else:
|
| 17 |
+
return torch.matmul(posw, t_mtx.t())[None, ...]
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def get_mv_matrix(elev, azim, camera_distance, center=None):
|
| 21 |
+
elev = -elev
|
| 22 |
+
azim += 90
|
| 23 |
+
|
| 24 |
+
elev_rad = math.radians(elev)
|
| 25 |
+
azim_rad = math.radians(azim)
|
| 26 |
+
|
| 27 |
+
camera_position = np.array(
|
| 28 |
+
[
|
| 29 |
+
camera_distance * math.cos(elev_rad) * math.cos(azim_rad),
|
| 30 |
+
camera_distance * math.cos(elev_rad) * math.sin(azim_rad),
|
| 31 |
+
camera_distance * math.sin(elev_rad),
|
| 32 |
+
]
|
| 33 |
+
)
|
| 34 |
+
|
| 35 |
+
if center is None:
|
| 36 |
+
center = np.array([0, 0, 0])
|
| 37 |
+
else:
|
| 38 |
+
center = np.array(center)
|
| 39 |
+
|
| 40 |
+
lookat = center - camera_position
|
| 41 |
+
lookat = lookat / np.linalg.norm(lookat)
|
| 42 |
+
|
| 43 |
+
up = np.array([0, 0, 1.0])
|
| 44 |
+
right = np.cross(lookat, up)
|
| 45 |
+
right = right / np.linalg.norm(right)
|
| 46 |
+
up = np.cross(right, lookat)
|
| 47 |
+
up = up / np.linalg.norm(up)
|
| 48 |
+
|
| 49 |
+
c2w = np.concatenate([np.stack([right, up, -lookat], axis=-1), camera_position[:, None]], axis=-1)
|
| 50 |
+
|
| 51 |
+
w2c = np.zeros((4, 4))
|
| 52 |
+
w2c[:3, :3] = np.transpose(c2w[:3, :3], (1, 0))
|
| 53 |
+
w2c[:3, 3:] = -np.matmul(np.transpose(c2w[:3, :3], (1, 0)), c2w[:3, 3:])
|
| 54 |
+
w2c[3, 3] = 1.0
|
| 55 |
+
|
| 56 |
+
return w2c.astype(np.float32)
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
def get_orthographic_projection_matrix(left=-1, right=1, bottom=-1, top=1, near=0, far=2):
|
| 60 |
+
"""
|
| 61 |
+
计算正交投影矩阵。
|
| 62 |
+
|
| 63 |
+
参数:
|
| 64 |
+
left (float): 投影区域左侧边界。
|
| 65 |
+
right (float): 投影区域右侧边界。
|
| 66 |
+
bottom (float): 投影区域底部边界。
|
| 67 |
+
top (float): 投影区域顶部边界。
|
| 68 |
+
near (float): 投影区域近裁剪面距离。
|
| 69 |
+
far (float): 投影区域远裁剪面距离。
|
| 70 |
+
|
| 71 |
+
返回:
|
| 72 |
+
numpy.ndarray: 正交投影矩阵。
|
| 73 |
+
"""
|
| 74 |
+
ortho_matrix = np.eye(4, dtype=np.float32)
|
| 75 |
+
ortho_matrix[0, 0] = 2 / (right - left)
|
| 76 |
+
ortho_matrix[1, 1] = 2 / (top - bottom)
|
| 77 |
+
ortho_matrix[2, 2] = -2 / (far - near)
|
| 78 |
+
ortho_matrix[0, 3] = -(right + left) / (right - left)
|
| 79 |
+
ortho_matrix[1, 3] = -(top + bottom) / (top - bottom)
|
| 80 |
+
ortho_matrix[2, 3] = -(far + near) / (far - near)
|
| 81 |
+
return ortho_matrix
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
def get_perspective_projection_matrix(fovy, aspect_wh, near, far):
|
| 85 |
+
fovy_rad = math.radians(fovy)
|
| 86 |
+
return np.array(
|
| 87 |
+
[
|
| 88 |
+
[1.0 / (math.tan(fovy_rad / 2.0) * aspect_wh), 0, 0, 0],
|
| 89 |
+
[0, 1.0 / math.tan(fovy_rad / 2.0), 0, 0],
|
| 90 |
+
[0, 0, -(far + near) / (far - near), -2.0 * far * near / (far - near)],
|
| 91 |
+
[0, 0, -1, 0],
|
| 92 |
+
]
|
| 93 |
+
).astype(np.float32)
|
home/ubuntu/aaaaa/data/rgbmr/DifferentiableRenderer/compile_mesh_painter.sh
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
c++ -O3 -Wall -shared -std=c++11 -fPIC `python -m pybind11 --includes` mesh_inpaint_processor.cpp -o mesh_inpaint_processor`python3-config --extension-suffix`
|
home/ubuntu/aaaaa/data/rgbmr/DifferentiableRenderer/mesh_inpaint_processor.cpp
ADDED
|
@@ -0,0 +1,550 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#include <pybind11/numpy.h>
|
| 2 |
+
#include <pybind11/pybind11.h>
|
| 3 |
+
#include <pybind11/stl.h>
|
| 4 |
+
|
| 5 |
+
#include <algorithm>
|
| 6 |
+
#include <array>
|
| 7 |
+
#include <cmath>
|
| 8 |
+
#include <queue>
|
| 9 |
+
#include <vector>
|
| 10 |
+
#include <functional>
|
| 11 |
+
|
| 12 |
+
namespace py = pybind11;
|
| 13 |
+
using namespace std;
|
| 14 |
+
|
| 15 |
+
namespace {
|
| 16 |
+
|
| 17 |
+
// =========================
|
| 18 |
+
// Core mesh data container
|
| 19 |
+
// =========================
|
| 20 |
+
struct MeshData {
|
| 21 |
+
int texture_height, texture_width, texture_channel;
|
| 22 |
+
int vtx_num;
|
| 23 |
+
float* texture_ptr;
|
| 24 |
+
uint8_t* mask_ptr;
|
| 25 |
+
float* vtx_pos_ptr;
|
| 26 |
+
float* vtx_uv_ptr;
|
| 27 |
+
int* pos_idx_ptr;
|
| 28 |
+
int* uv_idx_ptr;
|
| 29 |
+
|
| 30 |
+
// Keep buffers alive
|
| 31 |
+
py::buffer_info texture_buf, mask_buf, vtx_pos_buf, vtx_uv_buf, pos_idx_buf, uv_idx_buf;
|
| 32 |
+
|
| 33 |
+
MeshData(py::array_t<float>& texture, py::array_t<uint8_t>& mask,
|
| 34 |
+
py::array_t<float>& vtx_pos, py::array_t<float>& vtx_uv,
|
| 35 |
+
py::array_t<int>& pos_idx, py::array_t<int>& uv_idx) {
|
| 36 |
+
texture_buf = texture.request();
|
| 37 |
+
mask_buf = mask.request();
|
| 38 |
+
vtx_pos_buf = vtx_pos.request();
|
| 39 |
+
vtx_uv_buf = vtx_uv.request();
|
| 40 |
+
pos_idx_buf = pos_idx.request();
|
| 41 |
+
uv_idx_buf = uv_idx.request();
|
| 42 |
+
|
| 43 |
+
texture_height = static_cast<int>(texture_buf.shape[0]);
|
| 44 |
+
texture_width = static_cast<int>(texture_buf.shape[1]);
|
| 45 |
+
texture_channel= static_cast<int>(texture_buf.shape[2]);
|
| 46 |
+
texture_ptr = static_cast<float*>(texture_buf.ptr);
|
| 47 |
+
mask_ptr = static_cast<uint8_t*>(mask_buf.ptr);
|
| 48 |
+
|
| 49 |
+
vtx_num = static_cast<int>(vtx_pos_buf.shape[0]);
|
| 50 |
+
vtx_pos_ptr = static_cast<float*>(vtx_pos_buf.ptr);
|
| 51 |
+
vtx_uv_ptr = static_cast<float*>(vtx_uv_buf.ptr);
|
| 52 |
+
pos_idx_ptr = static_cast<int*>(pos_idx_buf.ptr);
|
| 53 |
+
uv_idx_ptr = static_cast<int*>(uv_idx_buf.ptr);
|
| 54 |
+
}
|
| 55 |
+
};
|
| 56 |
+
|
| 57 |
+
// =========================
|
| 58 |
+
// Helpers
|
| 59 |
+
// =========================
|
| 60 |
+
|
| 61 |
+
// Integer pixel (y, x) from UV with vertical flip (V -> image y)
|
| 62 |
+
pair<int,int> calculateUVCoordinates(int vtx_uv_idx, const MeshData& data) {
|
| 63 |
+
int x = static_cast<int>(std::round(data.vtx_uv_ptr[vtx_uv_idx * 2] * (data.texture_width - 1)));
|
| 64 |
+
int y = static_cast<int>(std::round((1.0f - data.vtx_uv_ptr[vtx_uv_idx * 2 + 1]) * (data.texture_height - 1)));
|
| 65 |
+
return {y, x}; // (y, x)
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
// Float pixel (x, y) from UV with vertical flip
|
| 69 |
+
inline pair<float,float> uvIdxToXYFloat(int vtx_uv_idx, const MeshData& data) {
|
| 70 |
+
float x = data.vtx_uv_ptr[vtx_uv_idx * 2] * (data.texture_width - 1);
|
| 71 |
+
float y = (1.0f - data.vtx_uv_ptr[vtx_uv_idx * 2 + 1]) * (data.texture_height - 1);
|
| 72 |
+
return {x, y}; // (x, y)
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
float calculateDistanceWeight(const array<float,3>& v0, const array<float,3>& v1) {
|
| 76 |
+
float dx = v0[0] - v1[0];
|
| 77 |
+
float dy = v0[1] - v1[1];
|
| 78 |
+
float dz = v0[2] - v1[2];
|
| 79 |
+
float d = std::sqrt(dx*dx + dy*dy + dz*dz);
|
| 80 |
+
float inv = 1.0f / std::max(d, 1e-4f);
|
| 81 |
+
return inv * inv;
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
array<float,3> getVertexPosition(int vtx_idx, const MeshData& data) {
|
| 85 |
+
return { data.vtx_pos_ptr[vtx_idx*3+0],
|
| 86 |
+
data.vtx_pos_ptr[vtx_idx*3+1],
|
| 87 |
+
data.vtx_pos_ptr[vtx_idx*3+2] };
|
| 88 |
+
}
|
| 89 |
+
|
| 90 |
+
// Undirected 1-ring graph from faces
|
| 91 |
+
void buildGraph(vector<vector<int>>& G, const MeshData& data) {
|
| 92 |
+
G.clear();
|
| 93 |
+
G.resize(data.vtx_num);
|
| 94 |
+
const int F = static_cast<int>(data.uv_idx_buf.shape[0]);
|
| 95 |
+
for (int i = 0; i < F; ++i) {
|
| 96 |
+
for (int k = 0; k < 3; ++k) {
|
| 97 |
+
int a = data.pos_idx_ptr[i * 3 + k];
|
| 98 |
+
int b = data.pos_idx_ptr[i * 3 + (k + 1) % 3];
|
| 99 |
+
G[a].push_back(b);
|
| 100 |
+
G[b].push_back(a);
|
| 101 |
+
}
|
| 102 |
+
}
|
| 103 |
+
}
|
| 104 |
+
|
| 105 |
+
// =========================
|
| 106 |
+
// Vertex-aware initialization
|
| 107 |
+
// - For each 3D vertex, aggregate samples from all its UV corners.
|
| 108 |
+
// - If a UV corner pixel is invalid (mask==0), search a small neighborhood.
|
| 109 |
+
// - Average the found samples to get a stable vertex color.
|
| 110 |
+
// =========================
|
| 111 |
+
|
| 112 |
+
// Search nearest valid (mask>0) pixel around (x0,y0). Returns true if found.
|
| 113 |
+
bool sample_color_near_uv(const MeshData& data, int x0, int y0, int r_max, vector<float>& out_color) {
|
| 114 |
+
const int W = data.texture_width;
|
| 115 |
+
const int H = data.texture_height;
|
| 116 |
+
const int C = data.texture_channel;
|
| 117 |
+
out_color.assign(C, 0.0f);
|
| 118 |
+
|
| 119 |
+
// Clamp seed
|
| 120 |
+
x0 = std::min(std::max(x0, 0), W - 1);
|
| 121 |
+
y0 = std::min(std::max(y0, 0), H - 1);
|
| 122 |
+
|
| 123 |
+
// If seed is valid, take it.
|
| 124 |
+
if (data.mask_ptr[y0 * W + x0] > 0) {
|
| 125 |
+
for (int c = 0; c < C; ++c) {
|
| 126 |
+
out_color[c] = data.texture_ptr[(y0 * W + x0) * C + c];
|
| 127 |
+
}
|
| 128 |
+
return true;
|
| 129 |
+
}
|
| 130 |
+
|
| 131 |
+
// Expand square neighborhood up to r_max
|
| 132 |
+
float best_d2 = std::numeric_limits<float>::infinity();
|
| 133 |
+
int best_x = -1, best_y = -1;
|
| 134 |
+
|
| 135 |
+
for (int r = 1; r <= r_max; ++r) {
|
| 136 |
+
int xmin = std::max(0, x0 - r);
|
| 137 |
+
int xmax = std::min(W - 1, x0 + r);
|
| 138 |
+
int ymin = std::max(0, y0 - r);
|
| 139 |
+
int ymax = std::min(H - 1, y0 + r);
|
| 140 |
+
bool found_this_ring = false;
|
| 141 |
+
|
| 142 |
+
for (int y = ymin; y <= ymax; ++y) {
|
| 143 |
+
for (int x = xmin; x <= xmax; ++x) {
|
| 144 |
+
if (data.mask_ptr[y * W + x] == 0) continue;
|
| 145 |
+
float dx = float(x - x0), dy = float(y - y0);
|
| 146 |
+
float d2 = dx*dx + dy*dy;
|
| 147 |
+
if (d2 < best_d2) {
|
| 148 |
+
best_d2 = d2;
|
| 149 |
+
best_x = x;
|
| 150 |
+
best_y = y;
|
| 151 |
+
found_this_ring = true;
|
| 152 |
+
}
|
| 153 |
+
}
|
| 154 |
+
}
|
| 155 |
+
// Early exit when ring finds something (closest for this r)
|
| 156 |
+
if (found_this_ring) break;
|
| 157 |
+
}
|
| 158 |
+
|
| 159 |
+
if (best_x >= 0) {
|
| 160 |
+
for (int c = 0; c < C; ++c) {
|
| 161 |
+
out_color[c] = data.texture_ptr[(best_y * W + best_x) * C + c];
|
| 162 |
+
}
|
| 163 |
+
return true;
|
| 164 |
+
}
|
| 165 |
+
return false;
|
| 166 |
+
}
|
| 167 |
+
|
| 168 |
+
template<typename MaskType>
|
| 169 |
+
void initializeVertexDataVertexAware(const MeshData& data,
|
| 170 |
+
vector<MaskType>& vtx_mask,
|
| 171 |
+
vector<vector<float>>& vtx_color,
|
| 172 |
+
vector<int>* uncolored_vtxs = nullptr,
|
| 173 |
+
MaskType mask_value = static_cast<MaskType>(1),
|
| 174 |
+
int r_max = 5) {
|
| 175 |
+
const int V = data.vtx_num;
|
| 176 |
+
const int C = data.texture_channel;
|
| 177 |
+
const int F = static_cast<int>(data.uv_idx_buf.shape[0]);
|
| 178 |
+
|
| 179 |
+
vtx_mask.assign(V, static_cast<MaskType>(0));
|
| 180 |
+
vtx_color.assign(V, vector<float>(C, 0.0f));
|
| 181 |
+
vector<float> accum_w(V, 0.0f);
|
| 182 |
+
if (uncolored_vtxs) uncolored_vtxs->clear();
|
| 183 |
+
|
| 184 |
+
for (int face_idx = 0; face_idx < F; ++face_idx) {
|
| 185 |
+
for (int k = 0; k < 3; ++k) {
|
| 186 |
+
int vtx_idx = data.pos_idx_ptr[face_idx * 3 + k];
|
| 187 |
+
int vtx_uv_idx = data.uv_idx_ptr[face_idx * 3 + k];
|
| 188 |
+
|
| 189 |
+
// Seed pixel from this UV corner
|
| 190 |
+
auto uv_xy_int = calculateUVCoordinates(vtx_uv_idx, data); // (y, x)
|
| 191 |
+
int sx = uv_xy_int.second;
|
| 192 |
+
int sy = uv_xy_int.first;
|
| 193 |
+
|
| 194 |
+
vector<float> col;
|
| 195 |
+
if (!sample_color_near_uv(data, sx, sy, r_max, col)) {
|
| 196 |
+
continue; // no contribution from this corner
|
| 197 |
+
}
|
| 198 |
+
|
| 199 |
+
// Equal weight per found corner (can be replaced by distance-based if needed)
|
| 200 |
+
float w = 1.0f;
|
| 201 |
+
for (int c = 0; c < C; ++c) {
|
| 202 |
+
vtx_color[vtx_idx][c] += w * col[c];
|
| 203 |
+
}
|
| 204 |
+
accum_w[vtx_idx] += w;
|
| 205 |
+
}
|
| 206 |
+
}
|
| 207 |
+
|
| 208 |
+
for (int v = 0; v < V; ++v) {
|
| 209 |
+
if (accum_w[v] > 0.0f) {
|
| 210 |
+
float inv = 1.0f / accum_w[v];
|
| 211 |
+
for (int c = 0; c < C; ++c) vtx_color[v][c] *= inv;
|
| 212 |
+
vtx_mask[v] = mask_value;
|
| 213 |
+
} else {
|
| 214 |
+
if (uncolored_vtxs) uncolored_vtxs->push_back(v);
|
| 215 |
+
}
|
| 216 |
+
}
|
| 217 |
+
}
|
| 218 |
+
|
| 219 |
+
// =========================
|
| 220 |
+
// Smoothing / Propagation
|
| 221 |
+
// =========================
|
| 222 |
+
template<typename MaskType>
|
| 223 |
+
void performSmoothingAlgorithm(const MeshData& data, const vector<vector<int>>& G,
|
| 224 |
+
vector<MaskType>& vtx_mask, vector<vector<float>>& vtx_color,
|
| 225 |
+
const vector<int>& uncolored_vtxs,
|
| 226 |
+
function<bool(MaskType)> is_colored_func,
|
| 227 |
+
function<void(MaskType&)> set_colored_func) {
|
| 228 |
+
// Limit to a small fixed number of passes for stability
|
| 229 |
+
const int max_pass = 8;
|
| 230 |
+
const int C = data.texture_channel;
|
| 231 |
+
|
| 232 |
+
for (int pass = 0; pass < max_pass; ++pass) {
|
| 233 |
+
int remained = 0;
|
| 234 |
+
|
| 235 |
+
for (int vtx_idx : uncolored_vtxs) {
|
| 236 |
+
if (is_colored_func(vtx_mask[vtx_idx])) continue;
|
| 237 |
+
|
| 238 |
+
vector<float> sum_color(C, 0.0f);
|
| 239 |
+
float total_w = 0.0f;
|
| 240 |
+
|
| 241 |
+
array<float,3> v0 = getVertexPosition(vtx_idx, data);
|
| 242 |
+
for (int nb : G[vtx_idx]) {
|
| 243 |
+
if (!is_colored_func(vtx_mask[nb])) continue;
|
| 244 |
+
array<float,3> v1 = getVertexPosition(nb, data);
|
| 245 |
+
float w = calculateDistanceWeight(v0, v1);
|
| 246 |
+
for (int c = 0; c < C; ++c) sum_color[c] += vtx_color[nb][c] * w;
|
| 247 |
+
total_w += w;
|
| 248 |
+
}
|
| 249 |
+
|
| 250 |
+
if (total_w > 0.0f) {
|
| 251 |
+
float inv = 1.0f / total_w;
|
| 252 |
+
for (int c = 0; c < C; ++c) vtx_color[vtx_idx][c] = sum_color[c] * inv;
|
| 253 |
+
set_colored_func(vtx_mask[vtx_idx]);
|
| 254 |
+
} else {
|
| 255 |
+
remained++;
|
| 256 |
+
}
|
| 257 |
+
}
|
| 258 |
+
|
| 259 |
+
if (remained == 0) break; // converged
|
| 260 |
+
}
|
| 261 |
+
}
|
| 262 |
+
|
| 263 |
+
void performForwardPropagation(const MeshData& data, const vector<vector<int>>& G,
|
| 264 |
+
vector<float>& vtx_mask, vector<vector<float>>& vtx_color,
|
| 265 |
+
queue<int>& active_vtxs) {
|
| 266 |
+
const int C = data.texture_channel;
|
| 267 |
+
|
| 268 |
+
while (!active_vtxs.empty()) {
|
| 269 |
+
queue<int> pending;
|
| 270 |
+
|
| 271 |
+
while (!active_vtxs.empty()) {
|
| 272 |
+
int vtx_idx = active_vtxs.front();
|
| 273 |
+
active_vtxs.pop();
|
| 274 |
+
|
| 275 |
+
array<float,3> v0 = getVertexPosition(vtx_idx, data);
|
| 276 |
+
for (int nb : G[vtx_idx]) {
|
| 277 |
+
if (vtx_mask[nb] > 0) continue;
|
| 278 |
+
|
| 279 |
+
array<float,3> v1 = getVertexPosition(nb, data);
|
| 280 |
+
float w = calculateDistanceWeight(v0, v1);
|
| 281 |
+
for (int c = 0; c < C; ++c) vtx_color[nb][c] += vtx_color[vtx_idx][c] * w;
|
| 282 |
+
|
| 283 |
+
if (vtx_mask[nb] == 0.0f) pending.push(nb);
|
| 284 |
+
vtx_mask[nb] -= w;
|
| 285 |
+
}
|
| 286 |
+
}
|
| 287 |
+
|
| 288 |
+
while (!pending.empty()) {
|
| 289 |
+
int v = pending.front(); pending.pop();
|
| 290 |
+
for (int c = 0; c < C; ++c) vtx_color[v][c] /= -vtx_mask[v];
|
| 291 |
+
vtx_mask[v] = 1.0f;
|
| 292 |
+
active_vtxs.push(v);
|
| 293 |
+
}
|
| 294 |
+
}
|
| 295 |
+
}
|
| 296 |
+
|
| 297 |
+
// =========================
|
| 298 |
+
// Triangle rasterization
|
| 299 |
+
// =========================
|
| 300 |
+
inline float edgeFunc(float x0,float y0,float x1,float y1,float x,float y){
|
| 301 |
+
return (x - x0)*(y1 - y0) - (y - y0)*(x1 - x0);
|
| 302 |
+
}
|
| 303 |
+
|
| 304 |
+
// Create output by rasterizing per-face colors from vertex colors.
|
| 305 |
+
// Only fills pixels where original mask == 0 (keeps existing valid texels).
|
| 306 |
+
pair<py::array_t<float>, py::array_t<uint8_t>> createOutputArrays(
|
| 307 |
+
const MeshData& data, const vector<float>& vtx_mask,
|
| 308 |
+
const vector<vector<float>>& vtx_color) {
|
| 309 |
+
|
| 310 |
+
// Allocate and copy originals
|
| 311 |
+
py::array_t<float> new_texture(data.texture_buf.size);
|
| 312 |
+
py::array_t<uint8_t> new_mask(data.mask_buf.size);
|
| 313 |
+
|
| 314 |
+
auto new_texture_buf = new_texture.request();
|
| 315 |
+
auto new_mask_buf = new_mask.request();
|
| 316 |
+
|
| 317 |
+
float* new_texture_ptr = static_cast<float*>(new_texture_buf.ptr);
|
| 318 |
+
uint8_t* new_mask_ptr = static_cast<uint8_t*>(new_mask_buf.ptr);
|
| 319 |
+
|
| 320 |
+
std::copy(data.texture_ptr, data.texture_ptr + data.texture_buf.size, new_texture_ptr);
|
| 321 |
+
std::copy(data.mask_ptr, data.mask_ptr + data.mask_buf.size, new_mask_ptr);
|
| 322 |
+
|
| 323 |
+
const int W = data.texture_width;
|
| 324 |
+
const int H = data.texture_height;
|
| 325 |
+
const int C = data.texture_channel;
|
| 326 |
+
const int F = static_cast<int>(data.uv_idx_buf.shape[0]);
|
| 327 |
+
|
| 328 |
+
for (int face_idx = 0; face_idx < F; ++face_idx) {
|
| 329 |
+
int v0 = data.pos_idx_ptr[face_idx*3 + 0];
|
| 330 |
+
int v1 = data.pos_idx_ptr[face_idx*3 + 1];
|
| 331 |
+
int v2 = data.pos_idx_ptr[face_idx*3 + 2];
|
| 332 |
+
|
| 333 |
+
// If no vertex has color, skip
|
| 334 |
+
if (!(vtx_mask[v0] > 0.0f || vtx_mask[v1] > 0.0f || vtx_mask[v2] > 0.0f)) continue;
|
| 335 |
+
|
| 336 |
+
int u0 = data.uv_idx_ptr[face_idx*3 + 0];
|
| 337 |
+
int u1 = data.uv_idx_ptr[face_idx*3 + 1];
|
| 338 |
+
int u2 = data.uv_idx_ptr[face_idx*3 + 2];
|
| 339 |
+
|
| 340 |
+
auto xy0 = uvIdxToXYFloat(u0, data); float x0 = xy0.first, y0 = xy0.second;
|
| 341 |
+
auto xy1 = uvIdxToXYFloat(u1, data); float x1 = xy1.first, y1 = xy1.second;
|
| 342 |
+
auto xy2 = uvIdxToXYFloat(u2, data); float x2 = xy2.first, y2 = xy2.second;
|
| 343 |
+
|
| 344 |
+
// Prepare vertex colors; if some are missing, use average of existing ones
|
| 345 |
+
vector<float> col0(C, 0.0f), col1(C, 0.0f), col2(C, 0.0f);
|
| 346 |
+
int cnt = 0;
|
| 347 |
+
if (vtx_mask[v0] > 0.0f) { for (int c = 0; c < C; ++c) col0[c] = vtx_color[v0][c]; cnt++; }
|
| 348 |
+
if (vtx_mask[v1] > 0.0f) { for (int c = 0; c < C; ++c) col1[c] = vtx_color[v1][c]; cnt++; }
|
| 349 |
+
if (vtx_mask[v2] > 0.0f) { for (int c = 0; c < C; ++c) col2[c] = vtx_color[v2][c]; cnt++; }
|
| 350 |
+
|
| 351 |
+
if (cnt > 0 && cnt < 3) {
|
| 352 |
+
// compute average of available
|
| 353 |
+
vector<float> avg(C, 0.0f);
|
| 354 |
+
if (vtx_mask[v0] > 0.0f) for (int c = 0; c < C; ++c) avg[c] += vtx_color[v0][c];
|
| 355 |
+
if (vtx_mask[v1] > 0.0f) for (int c = 0; c < C; ++c) avg[c] += vtx_color[v1][c];
|
| 356 |
+
if (vtx_mask[v2] > 0.0f) for (int c = 0; c < C; ++c) avg[c] += vtx_color[v2][c];
|
| 357 |
+
for (int c = 0; c < C; ++c) avg[c] /= float(cnt);
|
| 358 |
+
if (!(vtx_mask[v0] > 0.0f)) col0 = avg;
|
| 359 |
+
if (!(vtx_mask[v1] > 0.0f)) col1 = avg;
|
| 360 |
+
if (!(vtx_mask[v2] > 0.0f)) col2 = avg;
|
| 361 |
+
}
|
| 362 |
+
|
| 363 |
+
// Bounding box
|
| 364 |
+
int xmin = std::max(0, (int)std::floor(std::min({x0, x1, x2})));
|
| 365 |
+
int xmax = std::min(W-1, (int)std::ceil (std::max({x0, x1, x2})));
|
| 366 |
+
int ymin = std::max(0, (int)std::floor(std::min({y0, y1, y2})));
|
| 367 |
+
int ymax = std::min(H-1, (int)std::ceil (std::max({y0, y1, y2})));
|
| 368 |
+
|
| 369 |
+
float area = edgeFunc(x0,y0, x1,y1, x2,y2);
|
| 370 |
+
if (std::fabs(area) < 1e-6f) continue;
|
| 371 |
+
|
| 372 |
+
for (int y = ymin; y <= ymax; ++y) {
|
| 373 |
+
for (int x = xmin; x <= xmax; ++x) {
|
| 374 |
+
// Respect original valid texels
|
| 375 |
+
if (new_mask_ptr[y * W + x] > 0) continue;
|
| 376 |
+
|
| 377 |
+
float px = x + 0.5f, py = y + 0.5f;
|
| 378 |
+
float w0 = edgeFunc(x1,y1, x2,y2, px,py);
|
| 379 |
+
float w1 = edgeFunc(x2,y2, x0,y0, px,py);
|
| 380 |
+
float w2 = edgeFunc(x0,y0, x1,y1, px,py);
|
| 381 |
+
|
| 382 |
+
if ((w0 >= 0 && w1 >= 0 && w2 >= 0) || (w0 <= 0 && w1 <= 0 && w2 <= 0)) {
|
| 383 |
+
w0 /= area; w1 /= area; w2 /= area;
|
| 384 |
+
for (int c = 0; c < C; ++c) {
|
| 385 |
+
float v = w0*col0[c] + w1*col1[c] + w2*col2[c];
|
| 386 |
+
new_texture_ptr[(y * W + x) * C + c] = v;
|
| 387 |
+
}
|
| 388 |
+
new_mask_ptr[y * W + x] = 255;
|
| 389 |
+
}
|
| 390 |
+
}
|
| 391 |
+
}
|
| 392 |
+
}
|
| 393 |
+
|
| 394 |
+
// Reshape to HxWxC and HxW
|
| 395 |
+
new_texture.resize({ data.texture_height, data.texture_width, data.texture_channel });
|
| 396 |
+
new_mask.resize({ data.texture_height, data.texture_width });
|
| 397 |
+
return { new_texture, new_mask };
|
| 398 |
+
}
|
| 399 |
+
|
| 400 |
+
// Vertex color dump (unchanged interface)
|
| 401 |
+
pair<py::array_t<float>, py::array_t<uint8_t>> createVertexColorOutput(
|
| 402 |
+
const MeshData& data, const vector<int>& vtx_mask,
|
| 403 |
+
const vector<vector<float>>& vtx_color) {
|
| 404 |
+
|
| 405 |
+
py::array_t<float> py_vtx_color({ data.vtx_num, data.texture_channel });
|
| 406 |
+
py::array_t<uint8_t> py_vtx_mask({ data.vtx_num });
|
| 407 |
+
|
| 408 |
+
auto py_vtx_color_buf = py_vtx_color.request();
|
| 409 |
+
auto py_vtx_mask_buf = py_vtx_mask.request();
|
| 410 |
+
|
| 411 |
+
float* py_vtx_color_ptr = static_cast<float*>(py_vtx_color_buf.ptr);
|
| 412 |
+
uint8_t* py_vtx_mask_ptr = static_cast<uint8_t*>(py_vtx_mask_buf.ptr);
|
| 413 |
+
|
| 414 |
+
for (int i = 0; i < data.vtx_num; ++i) {
|
| 415 |
+
py_vtx_mask_ptr[i] = static_cast<uint8_t>(vtx_mask[i]);
|
| 416 |
+
for (int c = 0; c < data.texture_channel; ++c) {
|
| 417 |
+
py_vtx_color_ptr[i * data.texture_channel + c] = vtx_color[i][c];
|
| 418 |
+
}
|
| 419 |
+
}
|
| 420 |
+
return { py_vtx_color, py_vtx_mask };
|
| 421 |
+
}
|
| 422 |
+
|
| 423 |
+
} // anonymous namespace
|
| 424 |
+
|
| 425 |
+
// =========================
|
| 426 |
+
// meshVerticeInpaint - smooth
|
| 427 |
+
// =========================
|
| 428 |
+
pair<py::array_t<float>, py::array_t<uint8_t>> meshVerticeInpaint_smooth(
|
| 429 |
+
py::array_t<float> texture, py::array_t<uint8_t> mask,
|
| 430 |
+
py::array_t<float> vtx_pos, py::array_t<float> vtx_uv,
|
| 431 |
+
py::array_t<int> pos_idx, py::array_t<int> uv_idx) {
|
| 432 |
+
|
| 433 |
+
MeshData data(texture, mask, vtx_pos, vtx_uv, pos_idx, uv_idx);
|
| 434 |
+
|
| 435 |
+
vector<float> vtx_mask;
|
| 436 |
+
vector<vector<float>> vtx_color;
|
| 437 |
+
vector<int> uncolored_vtxs;
|
| 438 |
+
vector<vector<int>> G;
|
| 439 |
+
|
| 440 |
+
// Vertex-aware initialization (multi-corner with neighborhood search)
|
| 441 |
+
initializeVertexDataVertexAware<float>(data, vtx_mask, vtx_color, &uncolored_vtxs, 1.0f, 5);
|
| 442 |
+
buildGraph(G, data);
|
| 443 |
+
|
| 444 |
+
performSmoothingAlgorithm<float>(
|
| 445 |
+
data, G, vtx_mask, vtx_color, uncolored_vtxs,
|
| 446 |
+
[](float m){ return m > 0.0f; },
|
| 447 |
+
[](float& m){ m = 1.0f; }
|
| 448 |
+
);
|
| 449 |
+
|
| 450 |
+
return createOutputArrays(data, vtx_mask, vtx_color);
|
| 451 |
+
}
|
| 452 |
+
|
| 453 |
+
// =========================
|
| 454 |
+
// meshVerticeInpaint - forward
|
| 455 |
+
// =========================
|
| 456 |
+
pair<py::array_t<float>, py::array_t<uint8_t>> meshVerticeInpaint_forward(
|
| 457 |
+
py::array_t<float> texture, py::array_t<uint8_t> mask,
|
| 458 |
+
py::array_t<float> vtx_pos, py::array_t<float> vtx_uv,
|
| 459 |
+
py::array_t<int> pos_idx, py::array_t<int> uv_idx) {
|
| 460 |
+
|
| 461 |
+
MeshData data(texture, mask, vtx_pos, vtx_uv, pos_idx, uv_idx);
|
| 462 |
+
|
| 463 |
+
vector<float> vtx_mask;
|
| 464 |
+
vector<vector<float>> vtx_color;
|
| 465 |
+
vector<vector<int>> G;
|
| 466 |
+
queue<int> active_vtxs;
|
| 467 |
+
|
| 468 |
+
// Vertex-aware seeds
|
| 469 |
+
initializeVertexDataVertexAware<float>(data, vtx_mask, vtx_color, nullptr, 1.0f, 5);
|
| 470 |
+
buildGraph(G, data);
|
| 471 |
+
|
| 472 |
+
for (int i = 0; i < data.vtx_num; ++i) {
|
| 473 |
+
if (vtx_mask[i] == 1.0f) active_vtxs.push(i);
|
| 474 |
+
}
|
| 475 |
+
|
| 476 |
+
performForwardPropagation(data, G, vtx_mask, vtx_color, active_vtxs);
|
| 477 |
+
|
| 478 |
+
return createOutputArrays(data, vtx_mask, vtx_color);
|
| 479 |
+
}
|
| 480 |
+
|
| 481 |
+
// =========================
|
| 482 |
+
// Public inpaint API
|
| 483 |
+
// =========================
|
| 484 |
+
pair<py::array_t<float>, py::array_t<uint8_t>> meshVerticeInpaint(
|
| 485 |
+
py::array_t<float> texture, py::array_t<uint8_t> mask,
|
| 486 |
+
py::array_t<float> vtx_pos, py::array_t<float> vtx_uv,
|
| 487 |
+
py::array_t<int> pos_idx, py::array_t<int> uv_idx,
|
| 488 |
+
const string& method = "smooth") {
|
| 489 |
+
|
| 490 |
+
if (method == "smooth") {
|
| 491 |
+
return meshVerticeInpaint_smooth(texture, mask, vtx_pos, vtx_uv, pos_idx, uv_idx);
|
| 492 |
+
} else if (method == "forward") {
|
| 493 |
+
return meshVerticeInpaint_forward(texture, mask, vtx_pos, vtx_uv, pos_idx, uv_idx);
|
| 494 |
+
} else {
|
| 495 |
+
throw std::invalid_argument("Invalid method. Use 'smooth' or 'forward'.");
|
| 496 |
+
}
|
| 497 |
+
}
|
| 498 |
+
|
| 499 |
+
// =========================
|
| 500 |
+
// meshVerticeColor (vertex dump)
|
| 501 |
+
// =========================
|
| 502 |
+
pair<py::array_t<float>, py::array_t<uint8_t>> meshVerticeColor_smooth(
|
| 503 |
+
py::array_t<float> texture, py::array_t<uint8_t> mask,
|
| 504 |
+
py::array_t<float> vtx_pos, py::array_t<float> vtx_uv,
|
| 505 |
+
py::array_t<int> pos_idx, py::array_t<int> uv_idx) {
|
| 506 |
+
|
| 507 |
+
MeshData data(texture, mask, vtx_pos, vtx_uv, pos_idx, uv_idx);
|
| 508 |
+
|
| 509 |
+
vector<int> vtx_mask;
|
| 510 |
+
vector<vector<float>> vtx_color;
|
| 511 |
+
vector<int> uncolored_vtxs;
|
| 512 |
+
vector<vector<int>> G;
|
| 513 |
+
|
| 514 |
+
// Vertex-aware init for INT mask type
|
| 515 |
+
initializeVertexDataVertexAware<int>(data, vtx_mask, vtx_color, &uncolored_vtxs, 1, 5);
|
| 516 |
+
buildGraph(G, data);
|
| 517 |
+
|
| 518 |
+
performSmoothingAlgorithm<int>(
|
| 519 |
+
data, G, vtx_mask, vtx_color, uncolored_vtxs,
|
| 520 |
+
[](int m){ return m > 0; },
|
| 521 |
+
[](int& m){ m = 2; } // mark as colored
|
| 522 |
+
);
|
| 523 |
+
|
| 524 |
+
return createVertexColorOutput(data, vtx_mask, vtx_color);
|
| 525 |
+
}
|
| 526 |
+
|
| 527 |
+
pair<py::array_t<float>, py::array_t<uint8_t>> meshVerticeColor(
|
| 528 |
+
py::array_t<float> texture, py::array_t<uint8_t> mask,
|
| 529 |
+
py::array_t<float> vtx_pos, py::array_t<float> vtx_uv,
|
| 530 |
+
py::array_t<int> pos_idx, py::array_t<int> uv_idx,
|
| 531 |
+
const string& method = "smooth") {
|
| 532 |
+
|
| 533 |
+
if (method == "smooth") {
|
| 534 |
+
return meshVerticeColor_smooth(texture, mask, vtx_pos, vtx_uv, pos_idx, uv_idx);
|
| 535 |
+
} else {
|
| 536 |
+
throw std::invalid_argument("Invalid method. Use 'smooth' or 'forward'.");
|
| 537 |
+
}
|
| 538 |
+
}
|
| 539 |
+
|
| 540 |
+
// =========================
|
| 541 |
+
// PyBind11 module
|
| 542 |
+
// =========================
|
| 543 |
+
PYBIND11_MODULE(mesh_inpaint_processor, m) {
|
| 544 |
+
m.def("meshVerticeInpaint", &meshVerticeInpaint, "A function to process mesh",
|
| 545 |
+
py::arg("texture"), py::arg("mask"), py::arg("vtx_pos"), py::arg("vtx_uv"),
|
| 546 |
+
py::arg("pos_idx"), py::arg("uv_idx"), py::arg("method") = "smooth");
|
| 547 |
+
m.def("meshVerticeColor", &meshVerticeColor, "A function to process mesh",
|
| 548 |
+
py::arg("texture"), py::arg("mask"), py::arg("vtx_pos"), py::arg("vtx_uv"),
|
| 549 |
+
py::arg("pos_idx"), py::arg("uv_idx"), py::arg("method") = "smooth");
|
| 550 |
+
}
|
home/ubuntu/aaaaa/data/rgbmr/DifferentiableRenderer/mesh_inpaint_processor.cpython-310-x86_64-linux-gnu.so
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:b13b87ac483bc2a86adb0812fa7950eef76092e4d6031d3dc4e01f28ecd92609
|
| 3 |
+
size 242392
|
home/ubuntu/aaaaa/data/rgbmr/DifferentiableRenderer/mesh_utils.py
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import cv2
|
| 3 |
+
# import bpy
|
| 4 |
+
import math
|
| 5 |
+
import numpy as np
|
| 6 |
+
from io import StringIO
|
| 7 |
+
from typing import Optional, Tuple, Dict, Any
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
def _safe_extract_attribute(obj: Any, attr_path: str, default: Any = None) -> Any:
|
| 11 |
+
"""Extract nested attribute safely from object."""
|
| 12 |
+
try:
|
| 13 |
+
for attr in attr_path.split("."):
|
| 14 |
+
obj = getattr(obj, attr)
|
| 15 |
+
return obj
|
| 16 |
+
except AttributeError:
|
| 17 |
+
return default
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def _convert_to_numpy(data: Any, dtype: np.dtype) -> Optional[np.ndarray]:
|
| 21 |
+
"""Convert data to numpy array with specified dtype, handling None values."""
|
| 22 |
+
if data is None:
|
| 23 |
+
return None
|
| 24 |
+
return np.asarray(data, dtype=dtype)
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
def load_mesh(mesh):
|
| 28 |
+
"""Load mesh data including vertices, faces, UV coordinates and texture."""
|
| 29 |
+
# Extract vertex positions and face indices
|
| 30 |
+
vtx_pos = _safe_extract_attribute(mesh, "vertices")
|
| 31 |
+
pos_idx = _safe_extract_attribute(mesh, "faces")
|
| 32 |
+
|
| 33 |
+
# Extract UV coordinates (reusing face indices for UV indices)
|
| 34 |
+
vtx_uv = _safe_extract_attribute(mesh, "visual.uv")
|
| 35 |
+
uv_idx = pos_idx # Reuse face indices for UV mapping
|
| 36 |
+
|
| 37 |
+
# Convert to numpy arrays with appropriate dtypes
|
| 38 |
+
vtx_pos = _convert_to_numpy(vtx_pos, np.float32)
|
| 39 |
+
pos_idx = _convert_to_numpy(pos_idx, np.int32)
|
| 40 |
+
vtx_uv = _convert_to_numpy(vtx_uv, np.float32)
|
| 41 |
+
uv_idx = _convert_to_numpy(uv_idx, np.int32)
|
| 42 |
+
|
| 43 |
+
texture_data = None
|
| 44 |
+
return vtx_pos, pos_idx, vtx_uv, uv_idx, texture_data
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
def _get_base_path_and_name(mesh_path: str) -> Tuple[str, str]:
|
| 48 |
+
"""Get base path without extension and mesh name."""
|
| 49 |
+
base_path = os.path.splitext(mesh_path)[0]
|
| 50 |
+
name = os.path.basename(base_path)
|
| 51 |
+
return base_path, name
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
def _save_texture_map(
|
| 55 |
+
texture: np.ndarray,
|
| 56 |
+
base_path: str,
|
| 57 |
+
suffix: str = "",
|
| 58 |
+
image_format: str = ".jpg",
|
| 59 |
+
color_convert: Optional[int] = None,
|
| 60 |
+
) -> str:
|
| 61 |
+
"""Save texture map with optional color conversion."""
|
| 62 |
+
path = f"{base_path}{suffix}{image_format}"
|
| 63 |
+
processed_texture = (texture * 255).astype(np.uint8)
|
| 64 |
+
|
| 65 |
+
if color_convert is not None:
|
| 66 |
+
processed_texture = cv2.cvtColor(processed_texture, color_convert)
|
| 67 |
+
cv2.imwrite(path, processed_texture)
|
| 68 |
+
else:
|
| 69 |
+
cv2.imwrite(path, processed_texture[..., ::-1]) # RGB to BGR
|
| 70 |
+
|
| 71 |
+
return os.path.basename(path)
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
def _write_mtl_properties(f, properties: Dict[str, Any]):
|
| 75 |
+
"""Write material properties to MTL file."""
|
| 76 |
+
for key, value in properties.items():
|
| 77 |
+
if isinstance(value, (list, tuple)):
|
| 78 |
+
f.write(f"{key} {' '.join(map(str, value))}\n")
|
| 79 |
+
else:
|
| 80 |
+
f.write(f"{key} {value}\n")
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
def _create_obj_content(
|
| 84 |
+
vtx_pos: np.ndarray, vtx_uv: np.ndarray, pos_idx: np.ndarray, uv_idx: np.ndarray, name: str
|
| 85 |
+
) -> str:
|
| 86 |
+
"""Create OBJ file content."""
|
| 87 |
+
buffer = StringIO()
|
| 88 |
+
|
| 89 |
+
# Write header and vertices
|
| 90 |
+
buffer.write(f"mtllib {name}.mtl\no {name}\n")
|
| 91 |
+
np.savetxt(buffer, vtx_pos, fmt="v %.6f %.6f %.6f")
|
| 92 |
+
np.savetxt(buffer, vtx_uv, fmt="vt %.6f %.6f")
|
| 93 |
+
buffer.write("s 0\nusemtl Material\n")
|
| 94 |
+
|
| 95 |
+
# Write faces
|
| 96 |
+
pos_idx_plus1 = pos_idx + 1
|
| 97 |
+
uv_idx_plus1 = uv_idx + 1
|
| 98 |
+
face_format = np.frompyfunc(lambda *x: f"{int(x[0])}/{int(x[1])}", 2, 1)
|
| 99 |
+
faces = face_format(pos_idx_plus1, uv_idx_plus1)
|
| 100 |
+
face_strings = [f"f {' '.join(face)}" for face in faces]
|
| 101 |
+
buffer.write("\n".join(face_strings) + "\n")
|
| 102 |
+
|
| 103 |
+
return buffer.getvalue()
|
| 104 |
+
|
| 105 |
+
|
| 106 |
+
def save_obj_mesh(mesh_path, vtx_pos, pos_idx, vtx_uv, uv_idx, texture, metallic=None, roughness=None, normal=None):
|
| 107 |
+
"""Save mesh as OBJ file with textures and material."""
|
| 108 |
+
# Convert inputs to numpy arrays
|
| 109 |
+
vtx_pos = _convert_to_numpy(vtx_pos, np.float32)
|
| 110 |
+
vtx_uv = _convert_to_numpy(vtx_uv, np.float32)
|
| 111 |
+
pos_idx = _convert_to_numpy(pos_idx, np.int32)
|
| 112 |
+
uv_idx = _convert_to_numpy(uv_idx, np.int32)
|
| 113 |
+
|
| 114 |
+
base_path, name = _get_base_path_and_name(mesh_path)
|
| 115 |
+
|
| 116 |
+
# Create and save OBJ content
|
| 117 |
+
obj_content = _create_obj_content(vtx_pos, vtx_uv, pos_idx, uv_idx, name)
|
| 118 |
+
with open(mesh_path, "w") as obj_file:
|
| 119 |
+
obj_file.write(obj_content)
|
| 120 |
+
|
| 121 |
+
# Save texture maps
|
| 122 |
+
texture_maps = {}
|
| 123 |
+
texture_maps["diffuse"] = _save_texture_map(texture, base_path)
|
| 124 |
+
|
| 125 |
+
if metallic is not None:
|
| 126 |
+
texture_maps["metallic"] = _save_texture_map(metallic, base_path, "_metallic", color_convert=cv2.COLOR_RGB2GRAY)
|
| 127 |
+
if roughness is not None:
|
| 128 |
+
texture_maps["roughness"] = _save_texture_map(
|
| 129 |
+
roughness, base_path, "_roughness", color_convert=cv2.COLOR_RGB2GRAY
|
| 130 |
+
)
|
| 131 |
+
if normal is not None:
|
| 132 |
+
texture_maps["normal"] = _save_texture_map(normal, base_path, "_normal")
|
| 133 |
+
|
| 134 |
+
# Create MTL file
|
| 135 |
+
_create_mtl_file(base_path, texture_maps, metallic is not None)
|
| 136 |
+
|
| 137 |
+
|
| 138 |
+
def _create_mtl_file(base_path: str, texture_maps: Dict[str, str], is_pbr: bool):
|
| 139 |
+
"""Create MTL material file."""
|
| 140 |
+
mtl_path = f"{base_path}.mtl"
|
| 141 |
+
|
| 142 |
+
with open(mtl_path, "w") as f:
|
| 143 |
+
f.write("newmtl Material\n")
|
| 144 |
+
|
| 145 |
+
if is_pbr:
|
| 146 |
+
# PBR material properties
|
| 147 |
+
properties = {
|
| 148 |
+
"Kd": [0.800, 0.800, 0.800],
|
| 149 |
+
"Ke": [0.000, 0.000, 0.000], # 鐜鍏夐伄钄�
|
| 150 |
+
"Ni": 1.500, # 鎶樺皠绯绘暟
|
| 151 |
+
"d": 1.0, # 閫忔槑搴�
|
| 152 |
+
"illum": 2, # 鍏夌収妯″瀷
|
| 153 |
+
"map_Kd": texture_maps["diffuse"],
|
| 154 |
+
}
|
| 155 |
+
_write_mtl_properties(f, properties)
|
| 156 |
+
|
| 157 |
+
# Additional PBR maps
|
| 158 |
+
map_configs = [("metallic", "map_Pm"), ("roughness", "map_Pr"), ("normal", "map_Bump -bm 1.0")]
|
| 159 |
+
|
| 160 |
+
for texture_key, mtl_key in map_configs:
|
| 161 |
+
if texture_key in texture_maps:
|
| 162 |
+
f.write(f"{mtl_key} {texture_maps[texture_key]}\n")
|
| 163 |
+
else:
|
| 164 |
+
# Standard material properties
|
| 165 |
+
properties = {
|
| 166 |
+
"Ns": 250.000000,
|
| 167 |
+
"Ka": [0.200, 0.200, 0.200],
|
| 168 |
+
"Kd": [0.800, 0.800, 0.800],
|
| 169 |
+
"Ks": [0.500, 0.500, 0.500],
|
| 170 |
+
"Ke": [0.000, 0.000, 0.000],
|
| 171 |
+
"Ni": 1.500,
|
| 172 |
+
"d": 1.0,
|
| 173 |
+
"illum": 3,
|
| 174 |
+
"map_Kd": texture_maps["diffuse"],
|
| 175 |
+
}
|
| 176 |
+
_write_mtl_properties(f, properties)
|
| 177 |
+
|
| 178 |
+
|
| 179 |
+
def save_mesh(mesh_path, vtx_pos, pos_idx, vtx_uv, uv_idx, texture, metallic=None, roughness=None, normal=None):
|
| 180 |
+
"""Save mesh using OBJ format."""
|
| 181 |
+
save_obj_mesh(
|
| 182 |
+
mesh_path, vtx_pos, pos_idx, vtx_uv, uv_idx, texture, metallic=metallic, roughness=roughness, normal=normal
|
| 183 |
+
)
|
| 184 |
+
|
| 185 |
+
|
| 186 |
+
# def _setup_blender_scene():
|
| 187 |
+
# """Setup Blender scene for conversion."""
|
| 188 |
+
# if "convert" not in bpy.data.scenes:
|
| 189 |
+
# bpy.data.scenes.new("convert")
|
| 190 |
+
# bpy.context.window.scene = bpy.data.scenes["convert"]
|
| 191 |
+
|
| 192 |
+
|
| 193 |
+
# def _clear_scene_objects():
|
| 194 |
+
# """Clear all objects from current Blender scene."""
|
| 195 |
+
# for obj in bpy.context.scene.objects:
|
| 196 |
+
# obj.select_set(True)
|
| 197 |
+
# bpy.data.objects.remove(obj, do_unlink=True)
|
| 198 |
+
|
| 199 |
+
|
| 200 |
+
# def _select_mesh_objects():
|
| 201 |
+
# """Select all mesh objects in scene."""
|
| 202 |
+
# bpy.ops.object.select_all(action="DESELECT")
|
| 203 |
+
# for obj in bpy.context.scene.objects:
|
| 204 |
+
# if obj.type == "MESH":
|
| 205 |
+
# obj.select_set(True)
|
| 206 |
+
|
| 207 |
+
|
| 208 |
+
# def _merge_vertices_if_needed(merge_vertices: bool):
|
| 209 |
+
# """Merge duplicate vertices if requested."""
|
| 210 |
+
# if not merge_vertices:
|
| 211 |
+
# return
|
| 212 |
+
|
| 213 |
+
# for obj in bpy.context.selected_objects:
|
| 214 |
+
# if obj.type == "MESH":
|
| 215 |
+
# bpy.context.view_layer.objects.active = obj
|
| 216 |
+
# bpy.ops.object.mode_set(mode="EDIT")
|
| 217 |
+
# bpy.ops.mesh.select_all(action="SELECT")
|
| 218 |
+
# bpy.ops.mesh.remove_doubles()
|
| 219 |
+
# bpy.ops.object.mode_set(mode="OBJECT")
|
| 220 |
+
|
| 221 |
+
|
| 222 |
+
# def _apply_shading(shade_type: str, auto_smooth_angle: float):
|
| 223 |
+
# """Apply shading to selected objects."""
|
| 224 |
+
# shading_ops = {
|
| 225 |
+
# "SMOOTH": lambda: bpy.ops.object.shade_smooth(),
|
| 226 |
+
# "FLAT": lambda: bpy.ops.object.shade_flat(),
|
| 227 |
+
# "AUTO_SMOOTH": lambda: _apply_auto_smooth(auto_smooth_angle),
|
| 228 |
+
# }
|
| 229 |
+
|
| 230 |
+
# if shade_type in shading_ops:
|
| 231 |
+
# shading_ops[shade_type]()
|
| 232 |
+
|
| 233 |
+
|
| 234 |
+
# def _apply_auto_smooth(auto_smooth_angle: float):
|
| 235 |
+
# """Apply auto smooth based on Blender version."""
|
| 236 |
+
# angle_rad = math.radians(auto_smooth_angle)
|
| 237 |
+
|
| 238 |
+
# if bpy.app.version < (4, 1, 0):
|
| 239 |
+
# bpy.ops.object.shade_smooth(use_auto_smooth=True, auto_smooth_angle=angle_rad)
|
| 240 |
+
# elif bpy.app.version < (4, 2, 0):
|
| 241 |
+
# bpy.ops.object.shade_smooth_by_angle(angle=angle_rad)
|
| 242 |
+
# else:
|
| 243 |
+
# bpy.ops.object.shade_auto_smooth(angle=angle_rad)
|
| 244 |
+
|
| 245 |
+
|
| 246 |
+
# def convert_obj_to_glb(
|
| 247 |
+
# obj_path: str,
|
| 248 |
+
# glb_path: str,
|
| 249 |
+
# shade_type: str = "SMOOTH",
|
| 250 |
+
# auto_smooth_angle: float = 60,
|
| 251 |
+
# merge_vertices: bool = False,
|
| 252 |
+
# ) -> bool:
|
| 253 |
+
# """Convert OBJ file to GLB format using Blender."""
|
| 254 |
+
# try:
|
| 255 |
+
# _setup_blender_scene()
|
| 256 |
+
# _clear_scene_objects()
|
| 257 |
+
|
| 258 |
+
# # Import OBJ file
|
| 259 |
+
# bpy.ops.wm.obj_import(filepath=obj_path)
|
| 260 |
+
# _select_mesh_objects()
|
| 261 |
+
|
| 262 |
+
# # Process meshes
|
| 263 |
+
# _merge_vertices_if_needed(merge_vertices)
|
| 264 |
+
# _apply_shading(shade_type, auto_smooth_angle)
|
| 265 |
+
|
| 266 |
+
# # Export to GLB
|
| 267 |
+
# bpy.ops.export_scene.gltf(filepath=glb_path, use_active_scene=True)
|
| 268 |
+
# return True
|
| 269 |
+
# except Exception:
|
| 270 |
+
# return False
|
home/ubuntu/aaaaa/data/rgbmr/DifferentiableRenderer/obj_to_glb.py
ADDED
|
@@ -0,0 +1,332 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import io
|
| 3 |
+
from typing import Optional, Tuple, Union
|
| 4 |
+
|
| 5 |
+
import numpy as np
|
| 6 |
+
from PIL import Image
|
| 7 |
+
import trimesh
|
| 8 |
+
|
| 9 |
+
from gltflib import (
|
| 10 |
+
GLTF, GLTFModel, Asset, Scene, Node, Mesh, Primitive, Attributes,
|
| 11 |
+
Buffer, BufferView, Accessor, AccessorType, BufferTarget, ComponentType,
|
| 12 |
+
Material, PBRMetallicRoughness, Texture, Image as GLTFImage, TextureInfo,
|
| 13 |
+
FileResource, Sampler
|
| 14 |
+
)
|
| 15 |
+
|
| 16 |
+
def _mime_from_path(path: str) -> str:
|
| 17 |
+
ext = os.path.splitext(path)[1].lower()
|
| 18 |
+
if ext in [".jpg", ".jpeg"]:
|
| 19 |
+
return "image/jpeg"
|
| 20 |
+
if ext == ".png":
|
| 21 |
+
return "image/png"
|
| 22 |
+
if ext == ".webp":
|
| 23 |
+
return "image/webp"
|
| 24 |
+
# PIL로 최후 판정
|
| 25 |
+
try:
|
| 26 |
+
with Image.open(path) as im:
|
| 27 |
+
fmt = (im.format or "").lower()
|
| 28 |
+
return {"jpeg": "image/jpeg", "png": "image/png", "webp": "image/webp"}.get(fmt, "image/png")
|
| 29 |
+
except Exception:
|
| 30 |
+
return "image/png"
|
| 31 |
+
|
| 32 |
+
def _mime_from_image(img: Image.Image) -> str:
|
| 33 |
+
"""PIL Image로부터 MIME 타입 추론"""
|
| 34 |
+
fmt = (img.format or "").lower()
|
| 35 |
+
return {"jpeg": "image/jpeg", "png": "image/png", "webp": "image/webp"}.get(fmt, "image/png")
|
| 36 |
+
|
| 37 |
+
def _align4(n: int) -> int:
|
| 38 |
+
return (n + 3) & ~3
|
| 39 |
+
|
| 40 |
+
def _pack_f32(arr: np.ndarray) -> bytes:
|
| 41 |
+
return arr.astype("<f4", copy=False).tobytes(order="C")
|
| 42 |
+
|
| 43 |
+
def _pack_indices(arr: np.ndarray) -> Tuple[bytes, int]:
|
| 44 |
+
max_idx = int(arr.max()) if arr.size > 0 else 0
|
| 45 |
+
if max_idx < 65536:
|
| 46 |
+
return arr.astype("<u2", copy=False).tobytes(order="C"), ComponentType.UNSIGNED_SHORT.value
|
| 47 |
+
return arr.astype("<u4", copy=False).tobytes(order="C"), ComponentType.UNSIGNED_INT.value
|
| 48 |
+
|
| 49 |
+
def _mins_maxs(vec: np.ndarray) -> Tuple[list, list]:
|
| 50 |
+
if vec.size == 0:
|
| 51 |
+
return [0.0, 0.0, 0.0], [0.0, 0.0, 0.0]
|
| 52 |
+
mins = [float(vec[:, i].min()) for i in range(vec.shape[1])]
|
| 53 |
+
maxs = [float(vec[:, i].max()) for i in range(vec.shape[1])]
|
| 54 |
+
return mins, maxs
|
| 55 |
+
|
| 56 |
+
def obj_to_pbr_glb(
|
| 57 |
+
obj_path_or_mesh: Union[str, trimesh.Trimesh],
|
| 58 |
+
base_color_path: Union[str, Image.Image],
|
| 59 |
+
orm_path: Optional[Union[str, Image.Image]],
|
| 60 |
+
output_glb_path: str,
|
| 61 |
+
*,
|
| 62 |
+
flip_uv: bool = True,
|
| 63 |
+
center: bool = False,
|
| 64 |
+
scale_to_unit: bool = False,
|
| 65 |
+
generator: str = "obj2pbr-glb (gltflib)",
|
| 66 |
+
material_name: str = "Material",
|
| 67 |
+
node_name: str = "Node",
|
| 68 |
+
mesh_name: str = "Mesh",
|
| 69 |
+
) -> None:
|
| 70 |
+
# ---- 1) OBJ 로드 (단일 메시로 직접 로드) ----
|
| 71 |
+
# OBJ는 단일 메시이므로 force="mesh"로 직접 로드
|
| 72 |
+
if isinstance(obj_path_or_mesh, str):
|
| 73 |
+
mesh = trimesh.load(obj_path_or_mesh, force="mesh", process=True)
|
| 74 |
+
elif isinstance(obj_path_or_mesh, trimesh.Trimesh):
|
| 75 |
+
mesh = obj_path_or_mesh
|
| 76 |
+
else:
|
| 77 |
+
raise ValueError("Invalid input type for obj_path_or_mesh.")
|
| 78 |
+
|
| 79 |
+
if not isinstance(mesh, trimesh.Trimesh):
|
| 80 |
+
raise ValueError("Failed to load OBJ as a Trimesh.")
|
| 81 |
+
|
| 82 |
+
if center:
|
| 83 |
+
mesh.vertices -= mesh.vertices.mean(axis=0)
|
| 84 |
+
if scale_to_unit:
|
| 85 |
+
s = np.max(np.linalg.norm(mesh.vertices, axis=1))
|
| 86 |
+
if s > 0:
|
| 87 |
+
mesh.vertices /= s
|
| 88 |
+
if mesh.faces is None or len(mesh.faces) == 0:
|
| 89 |
+
raise ValueError("Mesh has no faces. Ensure the OBJ is triangulated.")
|
| 90 |
+
|
| 91 |
+
# ---- 2) 속성 수집 ----
|
| 92 |
+
positions = np.asarray(mesh.vertices, dtype=np.float32)
|
| 93 |
+
normals = np.asarray(mesh.vertex_normals, dtype=np.float32)
|
| 94 |
+
|
| 95 |
+
# UV 좌표 추출 (여러 경로 시도)
|
| 96 |
+
texcoords = None
|
| 97 |
+
|
| 98 |
+
# 방법 1: mesh.visual.uv 확인
|
| 99 |
+
if hasattr(mesh.visual, "uv") and mesh.visual.uv is not None and len(mesh.visual.uv) > 0:
|
| 100 |
+
texcoords = np.asarray(mesh.visual.uv, dtype=np.float32)
|
| 101 |
+
print(f"UV loaded from mesh.visual.uv: shape={texcoords.shape}")
|
| 102 |
+
|
| 103 |
+
# 방법 2: TextureVisuals의 uv 속성 확인
|
| 104 |
+
elif hasattr(mesh.visual, "kind") and mesh.visual.kind == "texture":
|
| 105 |
+
if hasattr(mesh.visual, "uv") and mesh.visual.uv is not None:
|
| 106 |
+
texcoords = np.asarray(mesh.visual.uv, dtype=np.float32)
|
| 107 |
+
print(f"UV loaded from TextureVisuals: shape={texcoords.shape}")
|
| 108 |
+
|
| 109 |
+
# 방법 3: metadata에서 UV 확인
|
| 110 |
+
elif hasattr(mesh, "metadata") and mesh.metadata is not None:
|
| 111 |
+
if "uv" in mesh.metadata:
|
| 112 |
+
texcoords = np.asarray(mesh.metadata["uv"], dtype=np.float32)
|
| 113 |
+
print(f"UV loaded from metadata: shape={texcoords.shape}")
|
| 114 |
+
|
| 115 |
+
if texcoords is None or len(texcoords) == 0:
|
| 116 |
+
# 디버깅 정보 출력
|
| 117 |
+
print(f"Debug info:")
|
| 118 |
+
print(f" mesh.visual type: {type(mesh.visual)}")
|
| 119 |
+
print(f" hasattr(mesh.visual, 'uv'): {hasattr(mesh.visual, 'uv')}")
|
| 120 |
+
if hasattr(mesh.visual, 'uv'):
|
| 121 |
+
print(f" mesh.visual.uv: {mesh.visual.uv}")
|
| 122 |
+
print(f" mesh.vertices.shape: {mesh.vertices.shape}")
|
| 123 |
+
print(f" mesh.faces.shape: {mesh.faces.shape}")
|
| 124 |
+
raise ValueError(
|
| 125 |
+
"OBJ has no UVs but textures were provided. "
|
| 126 |
+
"The OBJ file may not have proper UV mapping or the UV data is not being loaded correctly. "
|
| 127 |
+
"Please check if the OBJ file has 'vt' (texture coordinates) entries."
|
| 128 |
+
)
|
| 129 |
+
|
| 130 |
+
# UV 좌표 정규화 및 플립
|
| 131 |
+
texcoords = texcoords - np.floor(texcoords) # wrap to [0, 1]
|
| 132 |
+
if flip_uv:
|
| 133 |
+
texcoords[:, 1] = 1.0 - texcoords[:, 1]
|
| 134 |
+
|
| 135 |
+
faces = np.asarray(mesh.faces, dtype=np.uint32)
|
| 136 |
+
indices = faces.reshape(-1)
|
| 137 |
+
|
| 138 |
+
# ---- 3) 단일 버퍼(bin_data) 구성 (기하 + 이미지) ----
|
| 139 |
+
bin_data = bytearray()
|
| 140 |
+
views = {} # name -> (offset, length, target)
|
| 141 |
+
|
| 142 |
+
def _append_bytes(name: str, payload: bytes, target: Optional[int]) -> int:
|
| 143 |
+
"""payload를 4바이트 정렬로 추가하고 BufferView 인덱스를 반환하지 않고, 오프셋만 기록한다."""
|
| 144 |
+
nonlocal bin_data, views
|
| 145 |
+
off = _align4(len(bin_data))
|
| 146 |
+
if off > len(bin_data):
|
| 147 |
+
bin_data.extend(b"\x00" * (off - len(bin_data)))
|
| 148 |
+
bin_data.extend(payload)
|
| 149 |
+
views[name] = (off, len(payload), target)
|
| 150 |
+
return off
|
| 151 |
+
|
| 152 |
+
# 기하 데이터
|
| 153 |
+
_append_bytes("POSITION", _pack_f32(positions), BufferTarget.ARRAY_BUFFER.value)
|
| 154 |
+
_append_bytes("NORMAL", _pack_f32(normals), BufferTarget.ARRAY_BUFFER.value)
|
| 155 |
+
_append_bytes("TEXCOORD_0", _pack_f32(texcoords), BufferTarget.ARRAY_BUFFER.value)
|
| 156 |
+
idx_bytes, idx_ct = _pack_indices(indices)
|
| 157 |
+
_append_bytes("INDICES", idx_bytes, BufferTarget.ELEMENT_ARRAY_BUFFER.value)
|
| 158 |
+
|
| 159 |
+
# 이미지(알베도 / ORM)를 bufferView로 임베드
|
| 160 |
+
def _embed_image_to_buffer(name: str, path_or_image: Union[str, Image.Image]) -> Tuple[str, str]:
|
| 161 |
+
if isinstance(path_or_image, Image.Image):
|
| 162 |
+
# PIL Image인 경우
|
| 163 |
+
mime = _mime_from_image(path_or_image)
|
| 164 |
+
# Image를 바이트로 변환 (PNG 형식으로 저장)
|
| 165 |
+
img_buffer = io.BytesIO()
|
| 166 |
+
# format이 없거나 알 수 없는 경우 PNG로 저장
|
| 167 |
+
save_format = (path_or_image.format or "PNG").upper()
|
| 168 |
+
if save_format not in ["PNG", "JPEG", "WEBP"]:
|
| 169 |
+
save_format = "PNG"
|
| 170 |
+
mime = "image/png"
|
| 171 |
+
path_or_image.save(img_buffer, format=save_format)
|
| 172 |
+
img_bytes = img_buffer.getvalue()
|
| 173 |
+
else:
|
| 174 |
+
# 파일 경로인 경우
|
| 175 |
+
mime = _mime_from_path(path_or_image)
|
| 176 |
+
with open(path_or_image, "rb") as f:
|
| 177 |
+
img_bytes = f.read()
|
| 178 |
+
_append_bytes(name, img_bytes, None) # images는 target 없음
|
| 179 |
+
return name, mime
|
| 180 |
+
|
| 181 |
+
img_entries = [] # (key, mime)
|
| 182 |
+
base_key, base_mime = _embed_image_to_buffer("IMG_BASE", base_color_path)
|
| 183 |
+
img_entries.append((base_key, base_mime))
|
| 184 |
+
orm_key, orm_mime = (None, None)
|
| 185 |
+
if orm_path is not None:
|
| 186 |
+
orm_key, orm_mime = _embed_image_to_buffer("IMG_ORM", orm_path)
|
| 187 |
+
img_entries.append((orm_key, orm_mime))
|
| 188 |
+
|
| 189 |
+
# 마지막 정렬 패딩
|
| 190 |
+
final_len = _align4(len(bin_data))
|
| 191 |
+
if final_len > len(bin_data):
|
| 192 |
+
bin_data.extend(b"\x00" * (final_len - len(bin_data)))
|
| 193 |
+
|
| 194 |
+
# ---- 4) Buffer / BufferViews / Accessors ----
|
| 195 |
+
# Buffer는 하나, uri는 리소스 이름으로 설정(내보내기 시 GLB에 임베드됨)
|
| 196 |
+
buffer = Buffer(byteLength=final_len, uri="buffer0.bin")
|
| 197 |
+
|
| 198 |
+
# BufferView들(기하)
|
| 199 |
+
bviews = []
|
| 200 |
+
bv_index = {}
|
| 201 |
+
for key in ["POSITION", "NORMAL", "TEXCOORD_0", "INDICES"]:
|
| 202 |
+
off, ln, tgt = views[key]
|
| 203 |
+
bv_idx = len(bviews)
|
| 204 |
+
bviews.append(BufferView(buffer=0, byteOffset=off, byteLength=ln, target=tgt))
|
| 205 |
+
bv_index[key] = bv_idx
|
| 206 |
+
|
| 207 |
+
# BufferView들(이미지) — target 없음
|
| 208 |
+
image_view_indices = {}
|
| 209 |
+
for key, mime in img_entries:
|
| 210 |
+
off, ln, _ = views[key]
|
| 211 |
+
bv_idx = len(bviews)
|
| 212 |
+
bviews.append(BufferView(buffer=0, byteOffset=off, byteLength=ln))
|
| 213 |
+
image_view_indices[key] = (bv_idx, mime)
|
| 214 |
+
|
| 215 |
+
# Accessors
|
| 216 |
+
pos_min, pos_max = _mins_maxs(positions)
|
| 217 |
+
accessors = [
|
| 218 |
+
Accessor( # 0: POSITION
|
| 219 |
+
bufferView=bv_index["POSITION"], byteOffset=0,
|
| 220 |
+
componentType=ComponentType.FLOAT.value, count=positions.shape[0],
|
| 221 |
+
type=AccessorType.VEC3.value, min=pos_min, max=pos_max
|
| 222 |
+
),
|
| 223 |
+
Accessor( # 1: NORMAL
|
| 224 |
+
bufferView=bv_index["NORMAL"], byteOffset=0,
|
| 225 |
+
componentType=ComponentType.FLOAT.value, count=normals.shape[0],
|
| 226 |
+
type=AccessorType.VEC3.value
|
| 227 |
+
),
|
| 228 |
+
Accessor( # 2: TEXCOORD_0
|
| 229 |
+
bufferView=bv_index["TEXCOORD_0"], byteOffset=0,
|
| 230 |
+
componentType=ComponentType.FLOAT.value, count=texcoords.shape[0],
|
| 231 |
+
type=AccessorType.VEC2.value
|
| 232 |
+
),
|
| 233 |
+
Accessor( # 3: INDICES
|
| 234 |
+
bufferView=bv_index["INDICES"], byteOffset=0,
|
| 235 |
+
componentType=idx_ct, count=indices.size,
|
| 236 |
+
type=AccessorType.SCALAR.value
|
| 237 |
+
),
|
| 238 |
+
]
|
| 239 |
+
|
| 240 |
+
# ---- 5) Images / Textures / Material (PBR) ----
|
| 241 |
+
# 이미지: bufferView 기반, uri 없음!
|
| 242 |
+
images = []
|
| 243 |
+
textures = []
|
| 244 |
+
samplers = [Sampler()]
|
| 245 |
+
|
| 246 |
+
# base color
|
| 247 |
+
base_view_idx, base_mime = image_view_indices["IMG_BASE"]
|
| 248 |
+
base_img_idx = len(images)
|
| 249 |
+
images.append(GLTFImage(bufferView=base_view_idx, mimeType=base_mime))
|
| 250 |
+
base_tex_idx = len(textures)
|
| 251 |
+
textures.append(Texture(source=base_img_idx, sampler=0))
|
| 252 |
+
|
| 253 |
+
# orm (선택)
|
| 254 |
+
orm_tex_idx = None
|
| 255 |
+
if orm_key is not None:
|
| 256 |
+
orm_view_idx, orm_mime = image_view_indices["IMG_ORM"]
|
| 257 |
+
orm_img_idx = len(images)
|
| 258 |
+
images.append(GLTFImage(bufferView=orm_view_idx, mimeType=orm_mime))
|
| 259 |
+
orm_tex_idx = len(textures)
|
| 260 |
+
textures.append(Texture(source=orm_img_idx, sampler=0))
|
| 261 |
+
|
| 262 |
+
# PBR 머티리얼
|
| 263 |
+
pbr = PBRMetallicRoughness(
|
| 264 |
+
baseColorTexture=TextureInfo(index=base_tex_idx),
|
| 265 |
+
baseColorFactor=[1.0, 1.0, 1.0, 1.0],
|
| 266 |
+
)
|
| 267 |
+
if orm_tex_idx is not None:
|
| 268 |
+
# glTF 표준: G=roughness, B=metallic
|
| 269 |
+
pbr.metallicRoughnessTexture = TextureInfo(index=orm_tex_idx)
|
| 270 |
+
pbr.metallicFactor = 1.0
|
| 271 |
+
pbr.roughnessFactor = 1.0
|
| 272 |
+
else:
|
| 273 |
+
pbr.metallicFactor = 0.0
|
| 274 |
+
pbr.roughnessFactor = 0.9
|
| 275 |
+
|
| 276 |
+
material = Material(name=material_name, pbrMetallicRoughness=pbr)
|
| 277 |
+
|
| 278 |
+
# ---- 6) Mesh / Node / Scene ----
|
| 279 |
+
primitive = Primitive(
|
| 280 |
+
attributes=Attributes(POSITION=0, NORMAL=1, TEXCOORD_0=2),
|
| 281 |
+
indices=3,
|
| 282 |
+
material=0,
|
| 283 |
+
mode=4 # TRIANGLES
|
| 284 |
+
)
|
| 285 |
+
gltf_mesh = Mesh(name=mesh_name, primitives=[primitive])
|
| 286 |
+
node = Node(name=node_name, mesh=0)
|
| 287 |
+
scene = Scene(nodes=[0])
|
| 288 |
+
|
| 289 |
+
# ---- 7) GLTF 모델 조립 및 GLB 내보내기 ----
|
| 290 |
+
model = GLTFModel(
|
| 291 |
+
asset=Asset(version="2.0", generator=generator),
|
| 292 |
+
scenes=[scene],
|
| 293 |
+
scene=0,
|
| 294 |
+
nodes=[node],
|
| 295 |
+
meshes=[gltf_mesh],
|
| 296 |
+
materials=[material],
|
| 297 |
+
buffers=[buffer],
|
| 298 |
+
bufferViews=bviews,
|
| 299 |
+
accessors=accessors,
|
| 300 |
+
images=images,
|
| 301 |
+
textures=textures,
|
| 302 |
+
samplers=samplers,
|
| 303 |
+
)
|
| 304 |
+
|
| 305 |
+
# 단일 리소스(버퍼)만 GLB에 임베드
|
| 306 |
+
resources = [FileResource("buffer0.bin", data=bytearray(bin_data))]
|
| 307 |
+
gltf = GLTF(model=model, resources=resources)
|
| 308 |
+
gltf.export(output_glb_path)
|
| 309 |
+
|
| 310 |
+
if __name__ == "__main__":
|
| 311 |
+
import argparse
|
| 312 |
+
parser = argparse.ArgumentParser(description="Convert OBJ to GLB")
|
| 313 |
+
parser.add_argument("input_obj", help="Path to the input OBJ file")
|
| 314 |
+
parser.add_argument("--base_color", required=True, help="Path to the base color texture (RGB)")
|
| 315 |
+
parser.add_argument("--orm", required=True, help="Path to the ORM texture (R:unused, G:roughness, B:metallic)")
|
| 316 |
+
parser.add_argument("--output_glb", required=False, help="Path to the output GLB file")
|
| 317 |
+
parser.add_argument("--flip_uv", action="store_true", help="Flip the V coordinate of UVs")
|
| 318 |
+
parser.add_argument("--center", action="store_true", help="Center the model")
|
| 319 |
+
parser.add_argument("--scale_to_unit", action="store_true", help="Scale to unit bounding radius")
|
| 320 |
+
args = parser.parse_args()
|
| 321 |
+
|
| 322 |
+
out = args.output_glb or os.path.splitext(args.input_obj)[0] + ".glb"
|
| 323 |
+
obj_to_pbr_glb(
|
| 324 |
+
obj_path_or_mesh=args.input_obj,
|
| 325 |
+
base_color_path=args.base_color,
|
| 326 |
+
orm_path=args.orm,
|
| 327 |
+
output_glb_path=out,
|
| 328 |
+
flip_uv=args.flip_uv,
|
| 329 |
+
center=args.center,
|
| 330 |
+
scale_to_unit=args.scale_to_unit,
|
| 331 |
+
)
|
| 332 |
+
print(f"Converted '{args.input_obj}' to '{out}' successfully.")
|
home/ubuntu/aaaaa/data/rgbmr/MCVAE_CONFIG_UPGRADE_SUMMARY.md
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# MCVAE Config System Upgrade - Implementation Summary
|
| 2 |
+
|
| 3 |
+
## Overview
|
| 4 |
+
Successfully upgraded MCVAE's config management system to match MCDiff's base-variant inheritance structure. The new system is **fully backward compatible** while adding powerful new configuration management capabilities.
|
| 5 |
+
|
| 6 |
+
## Changes Made
|
| 7 |
+
|
| 8 |
+
### 1. Updated `train_mcvae.py`
|
| 9 |
+
|
| 10 |
+
#### Added Import
|
| 11 |
+
```python
|
| 12 |
+
from mcgen.utils.config import load_config
|
| 13 |
+
```
|
| 14 |
+
|
| 15 |
+
#### Removed Old Simple Config Loader
|
| 16 |
+
Removed the basic `load_config()` function that only supported simple OmegaConf merging:
|
| 17 |
+
```python
|
| 18 |
+
# REMOVED:
|
| 19 |
+
def load_config(path: str, overrides: list[str] | None = None):
|
| 20 |
+
base = OmegaConf.load(path)
|
| 21 |
+
cli = OmegaConf.from_cli(overrides or [])
|
| 22 |
+
cfg = OmegaConf.merge(base, cli)
|
| 23 |
+
OmegaConf.resolve(cfg)
|
| 24 |
+
return cfg
|
| 25 |
+
```
|
| 26 |
+
|
| 27 |
+
#### Updated `main()` Function
|
| 28 |
+
- Enhanced argument parsing with better documentation
|
| 29 |
+
- Now uses `mcgen.utils.config.load_config()` which supports base-variant inheritance
|
| 30 |
+
- Changed `override_tokens` to `cli_overrides` parameter name for consistency
|
| 31 |
+
- Added comprehensive docstring with usage examples
|
| 32 |
+
|
| 33 |
+
### 2. Created Example Variant Config
|
| 34 |
+
Created `configs/mcvae/variant_example.yaml` demonstrating the inheritance pattern:
|
| 35 |
+
```yaml
|
| 36 |
+
base: configs/mcvae/default.yaml
|
| 37 |
+
|
| 38 |
+
# Override specific parameters
|
| 39 |
+
project:
|
| 40 |
+
run_name: MCVAE_v1.1.0_variant_example
|
| 41 |
+
trainer:
|
| 42 |
+
max_steps: 50001
|
| 43 |
+
optim:
|
| 44 |
+
lr: 1.0e-4
|
| 45 |
+
data:
|
| 46 |
+
train:
|
| 47 |
+
batch_size: 6
|
| 48 |
+
```
|
| 49 |
+
|
| 50 |
+
### 3. Created Comprehensive Test Suite
|
| 51 |
+
Created `test_mcvae_config.py` that validates:
|
| 52 |
+
- ✅ Loading `default.yaml` directly (backward compatibility)
|
| 53 |
+
- ✅ Applying CLI overrides to default config
|
| 54 |
+
- ✅ Loading variant configs with base inheritance
|
| 55 |
+
- ✅ Combining variant inheritance with CLI overrides
|
| 56 |
+
- ✅ Verifying all original default.yaml parameters remain unchanged
|
| 57 |
+
|
| 58 |
+
## Features Now Supported
|
| 59 |
+
|
| 60 |
+
### 1. Base-Variant Inheritance
|
| 61 |
+
Variant configs can inherit from `default.yaml`:
|
| 62 |
+
```yaml
|
| 63 |
+
base: configs/mcvae/default.yaml
|
| 64 |
+
|
| 65 |
+
# Only override what you need
|
| 66 |
+
trainer:
|
| 67 |
+
max_steps: 100000
|
| 68 |
+
```
|
| 69 |
+
|
| 70 |
+
### 2. CLI Overrides
|
| 71 |
+
Override any config parameter from command line:
|
| 72 |
+
```bash
|
| 73 |
+
python train_mcvae.py trainer.max_steps=50000 optim.lr=1e-4
|
| 74 |
+
```
|
| 75 |
+
|
| 76 |
+
### 3. Priority System
|
| 77 |
+
Configuration values are merged with proper precedence:
|
| 78 |
+
**default.yaml < variant.yaml < CLI arguments**
|
| 79 |
+
|
| 80 |
+
### 4. Full Backward Compatibility
|
| 81 |
+
Existing workflows continue to work exactly as before:
|
| 82 |
+
```bash
|
| 83 |
+
# Still works identically
|
| 84 |
+
python train_mcvae.py --config configs/mcvae/default.yaml
|
| 85 |
+
|
| 86 |
+
# Also still works
|
| 87 |
+
python train_mcvae.py trainer.max_steps=50000
|
| 88 |
+
```
|
| 89 |
+
|
| 90 |
+
## Usage Examples
|
| 91 |
+
|
| 92 |
+
### Example 1: Use Default Config
|
| 93 |
+
```bash
|
| 94 |
+
python train_mcvae.py
|
| 95 |
+
# or explicitly:
|
| 96 |
+
python train_mcvae.py --config configs/mcvae/default.yaml
|
| 97 |
+
```
|
| 98 |
+
|
| 99 |
+
### Example 2: Quick Parameter Tweaks
|
| 100 |
+
```bash
|
| 101 |
+
python train_mcvae.py trainer.max_steps=100000 optim.lr=1e-4 data.train.batch_size=4
|
| 102 |
+
```
|
| 103 |
+
|
| 104 |
+
### Example 3: Create and Use Variant Config
|
| 105 |
+
Create `configs/mcvae/my_experiment.yaml`:
|
| 106 |
+
```yaml
|
| 107 |
+
base: configs/mcvae/default.yaml
|
| 108 |
+
|
| 109 |
+
project:
|
| 110 |
+
run_name: MCVAE_my_experiment
|
| 111 |
+
|
| 112 |
+
trainer:
|
| 113 |
+
max_steps: 200000
|
| 114 |
+
|
| 115 |
+
optim:
|
| 116 |
+
lr: 5e-5
|
| 117 |
+
```
|
| 118 |
+
|
| 119 |
+
Run it:
|
| 120 |
+
```bash
|
| 121 |
+
python train_mcvae.py --config configs/mcvae/my_experiment.yaml
|
| 122 |
+
```
|
| 123 |
+
|
| 124 |
+
### Example 4: Variant + CLI Override
|
| 125 |
+
```bash
|
| 126 |
+
python train_mcvae.py --config configs/mcvae/my_experiment.yaml seed=999
|
| 127 |
+
```
|
| 128 |
+
|
| 129 |
+
## Validation Results
|
| 130 |
+
|
| 131 |
+
All tests passed successfully:
|
| 132 |
+
|
| 133 |
+
### Test 1: Default Config Loading ✅
|
| 134 |
+
- All 23 key parameters match original `default.yaml`
|
| 135 |
+
- Seed: 43, max_steps: 400001, lr: 3e-05, batch_size: 8, etc.
|
| 136 |
+
|
| 137 |
+
### Test 2: CLI Overrides ✅
|
| 138 |
+
- Successfully overrode: max_steps, lr, batch_size
|
| 139 |
+
- Non-overridden values remain from default
|
| 140 |
+
|
| 141 |
+
### Test 3: Variant Inheritance ✅
|
| 142 |
+
- Inherited values: seed, offset_mode, etc.
|
| 143 |
+
- Overridden values: run_name, max_steps, lr, batch_size
|
| 144 |
+
|
| 145 |
+
### Test 4: Priority Order ✅
|
| 146 |
+
- Confirmed: default < variant < CLI
|
| 147 |
+
|
| 148 |
+
### Test 5: Backward Compatibility ✅
|
| 149 |
+
- Original default.yaml produces **identical** config as before
|
| 150 |
+
- Zero breaking changes
|
| 151 |
+
|
| 152 |
+
## Files Modified
|
| 153 |
+
|
| 154 |
+
1. **train_mcvae.py**: Updated config loading to use centralized system
|
| 155 |
+
2. **configs/mcvae/variant_example.yaml**: New - example variant config
|
| 156 |
+
3. **test_mcvae_config.py**: New - comprehensive test suite
|
| 157 |
+
|
| 158 |
+
## Files Using Centralized Config System
|
| 159 |
+
|
| 160 |
+
Both training scripts now use the same config management:
|
| 161 |
+
- ✅ `train_mcdiff.py` - Already using `mcgen.utils.config.load_config`
|
| 162 |
+
- ✅ `train_mcvae.py` - Now using `mcgen.utils.config.load_config`
|
| 163 |
+
|
| 164 |
+
## Verification Commands
|
| 165 |
+
|
| 166 |
+
```bash
|
| 167 |
+
# Run comprehensive test suite
|
| 168 |
+
python test_mcvae_config.py
|
| 169 |
+
|
| 170 |
+
# Test default config loading
|
| 171 |
+
python train_mcvae.py --config configs/mcvae/default.yaml
|
| 172 |
+
|
| 173 |
+
# Test CLI overrides
|
| 174 |
+
python train_mcvae.py trainer.max_steps=1000
|
| 175 |
+
|
| 176 |
+
# Test variant inheritance
|
| 177 |
+
python train_mcvae.py --config configs/mcvae/variant_example.yaml
|
| 178 |
+
```
|
| 179 |
+
|
| 180 |
+
## Migration Guide for Existing Users
|
| 181 |
+
|
| 182 |
+
**Good news: No migration needed!**
|
| 183 |
+
|
| 184 |
+
Your existing commands will continue to work exactly as before:
|
| 185 |
+
- ✅ `python train_mcvae.py` - works
|
| 186 |
+
- ✅ `python train_mcvae.py --config configs/mcvae/default.yaml` - works
|
| 187 |
+
- ✅ `python train_mcvae.py trainer.max_steps=50000` - works
|
| 188 |
+
|
| 189 |
+
**New capabilities** are available whenever you want to use them:
|
| 190 |
+
- Create variant configs to organize experiments
|
| 191 |
+
- Mix variants with CLI overrides
|
| 192 |
+
- Enjoy better config management
|
| 193 |
+
|
| 194 |
+
## Implementation Quality
|
| 195 |
+
|
| 196 |
+
✅ **Zero Breaking Changes**: All existing functionality preserved
|
| 197 |
+
✅ **Thoroughly Tested**: 5 comprehensive test scenarios
|
| 198 |
+
✅ **Consistent with MCDiff**: Same config system across codebase
|
| 199 |
+
✅ **Well Documented**: Inline docs + examples + this summary
|
| 200 |
+
✅ **Production Ready**: No errors, all tests passing
|
| 201 |
+
|
| 202 |
+
## Conclusion
|
| 203 |
+
|
| 204 |
+
The MCVAE config system has been successfully upgraded to match MCDiff's sophisticated base-variant inheritance pattern. The implementation is:
|
| 205 |
+
|
| 206 |
+
1. **Backward Compatible**: Existing workflows unchanged
|
| 207 |
+
2. **Feature Rich**: Supports inheritance, overrides, and proper precedence
|
| 208 |
+
3. **Well Tested**: Comprehensive test suite validates all scenarios
|
| 209 |
+
4. **Consistent**: Same system used by both training scripts
|
| 210 |
+
5. **Production Ready**: Zero errors, all tests passing
|
| 211 |
+
|
| 212 |
+
Users can continue using MCVAE exactly as before, or take advantage of the new config management features whenever needed.
|
home/ubuntu/aaaaa/data/rgbmr/README.md
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# MCGen: RGBMR Dataset · Rendering · Evaluation
|
| 2 |
+
|
| 3 |
+
## TL;DR (Quick Start)
|
| 4 |
+
|
| 5 |
+
### 1) GLB 생성 – Run Mesh Texturing
|
| 6 |
+
|
| 7 |
+
```bash
|
| 8 |
+
python inference_batch.py \
|
| 9 |
+
--glb_list glbs_eval_ref.txt \
|
| 10 |
+
--prompt_csv prompts_bs.csv \
|
| 11 |
+
--output_dir ./outputs_eval/ours_glbs \
|
| 12 |
+
--devices 0 1 2 3 4 5 6 7
|
| 13 |
+
```
|
| 14 |
+
|
| 15 |
+
### 2) 렌더링 – 무작위 envmap으로 GLB list 전체에 대해 실행
|
| 16 |
+
|
| 17 |
+
```bash
|
| 18 |
+
python tools/render_eval_batch.py \
|
| 19 |
+
--glb_list glbs_eval_ours.txt \
|
| 20 |
+
--hdr_list /home/USER/data/envmaps/hdr_list.txt \
|
| 21 |
+
--output_dir ./outputs_eval/ours \
|
| 22 |
+
--devices 0 1 2 3 4 5 6 7
|
| 23 |
+
```
|
| 24 |
+
|
| 25 |
+
### 3) 평가 – reference에 대한 비교 지표 산출
|
| 26 |
+
|
| 27 |
+
```bash
|
| 28 |
+
python tools/evaluate.py \
|
| 29 |
+
--ref_root outputs_eval/ref/ \
|
| 30 |
+
--method_root outputs_eval/ours/ \
|
| 31 |
+
--prompts_csv prompts_bs.csv \
|
| 32 |
+
--out_csv metrics_summary.csv
|
| 33 |
+
```
|
| 34 |
+
|
| 35 |
+
---
|
| 36 |
+
|
| 37 |
+
## 1. RGBMR Dataset
|
| 38 |
+
|
| 39 |
+
### 1.1 스키마 개요
|
| 40 |
+
|
| 41 |
+
* **SCHEMA_RGBMR** 기반 `views`와 오브젝트 단위 필드로 구성된 WebDataset.
|
| 42 |
+
* 내부 후처리로 아래의 텐서들이 준비됩니다.
|
| 43 |
+
|
| 44 |
+
* `pixel_values` : ((V,5,H,W)) — **rgbRM** = `albedo`(RGB 3채널) + `orm`의 **[roughness, metallic]** 2채널 → 범위 ([-1,1])
|
| 45 |
+
* `cond_values` : ((V,6,H,W)) — **position (x,y,z)** + **normal (nx,ny,nz)** → 범위 ([-1,1])
|
| 46 |
+
* `normal_fullres` : 원본 해상도 노멀(([-1,1]))
|
| 47 |
+
* `depth` : Raw depth map ranging from 0.5 ~ 1.5. Background region has the value **NaN**.
|
| 48 |
+
* `pos_token` : Block-average downsampled position map for token position embedding.
|
| 49 |
+
* `w2c` : `c2w`의 역행렬
|
| 50 |
+
* 모든 키(`albedo`,`orm`,`pos`,`normal`)는 `size×size`로 리사이즈 후 ([-1,1]) 스케일링
|
| 51 |
+
* `prompt_embeds` : preprocessed prompt embeddings for SD3.5.
|
| 52 |
+
* `pooled_prompt_embeds` : preprocessed pooled prompt embeddings for SD3.5.
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
### 1.2 `build_mcdiff_dataset(...)` 인자
|
| 56 |
+
|
| 57 |
+
| 인자 | 타입 | 기본값 | 설명 |
|
| 58 |
+
| -------------------- | --------- | ------- | ---------------------------------------------- |
|
| 59 |
+
| `dataset_path` | str | - | WebDataset Shard(s) 경로 또는 패턴 |
|
| 60 |
+
| `schema` | Dict | - | 반드시 `{"views": {...}}` 포함 |
|
| 61 |
+
| `num_views` | int | - | 샘플당 선택할 뷰 수 (`1 ≤ num_views ≤ meta.num_views`) |
|
| 62 |
+
| `size` | int | 1024 | 최종 해상도. **16의 배수 & ≤1024** 제약 |
|
| 63 |
+
| `shardshuffle` | int | 1000 | 샤드 셔플 윈도우 |
|
| 64 |
+
| `split_by_node` | bool | True | 분산에서 노드별 샤드 분할 |
|
| 65 |
+
| `split_by_worker` | bool | True | 워커별 샤드 분할 |
|
| 66 |
+
| `fixed_view_indices` | List[int] | None | validation 시 고정 뷰 인덱스. **길이 = `num_views`** |
|
| 67 |
+
|
| 68 |
+
### 1.3 DataLoader 예시
|
| 69 |
+
|
| 70 |
+
```python
|
| 71 |
+
from data.rgbmr_dataset import SCHEMA_RGBMR, build_mcdiff_dataset
|
| 72 |
+
from webdataset import WebLoader
|
| 73 |
+
|
| 74 |
+
train_dataset = build_mcdiff_dataset(
|
| 75 |
+
cfg.data.train.root,
|
| 76 |
+
SCHEMA_RGBMR,
|
| 77 |
+
cfg.model.num_views,
|
| 78 |
+
size=cfg.model.resolution,
|
| 79 |
+
).shuffle(8).batched(cfg.data.train.batch_size, partial=False)
|
| 80 |
+
|
| 81 |
+
train_dataloader = WebLoader(
|
| 82 |
+
train_dataset,
|
| 83 |
+
batch_size=None,
|
| 84 |
+
num_workers=cfg.data.train.num_workers,
|
| 85 |
+
pin_memory=True,
|
| 86 |
+
persistent_workers=False,
|
| 87 |
+
)
|
| 88 |
+
|
| 89 |
+
val_dataset = build_mcdiff_dataset(
|
| 90 |
+
cfg.data.val.root,
|
| 91 |
+
SCHEMA_RGBMR,
|
| 92 |
+
cfg.model.num_views,
|
| 93 |
+
size=cfg.model.resolution,
|
| 94 |
+
split_by_node=False,
|
| 95 |
+
split_by_worker=False,
|
| 96 |
+
shardshuffle=False,
|
| 97 |
+
fixed_view_indices=[0, 14, 1, 15, 2, 3],
|
| 98 |
+
)
|
| 99 |
+
|
| 100 |
+
# 배치 사용 예
|
| 101 |
+
pixel_values_mv = batch["pixel_values"].to(device) # (B,V,5,H,W) in [-1,1]
|
| 102 |
+
cond_values_mv = batch["cond_values"].to(device) # (B,V,6,H,W) in [-1,1]
|
| 103 |
+
prompt_embeds_mv = batch["prompt_embeds"] # (B, T, D)
|
| 104 |
+
pooled_prompt_embeds_mv = batch["pooled_prompt_embeds"] # (B, Dp)
|
| 105 |
+
```
|
| 106 |
+
|
| 107 |
+
---
|
| 108 |
+
|
| 109 |
+
## 2. Rendering (for Eval)
|
| 110 |
+
|
| 111 |
+
`tools/render_eval_batch.py`는 다수의 GLB를 **Deterministic**한 방식으로 HDR과 매핑해 Blender로 렌더링을 수행합니다.
|
| 112 |
+
|
| 113 |
+
### 2.1 인자 설명
|
| 114 |
+
|
| 115 |
+
| 인자 | 필수 | 설명 |
|
| 116 |
+
| -------------- | :-: | -------------------------------------------------- |
|
| 117 |
+
| `--glb_list` | ✓ | GLB 경로 목록(.txt, 라인당 1개) |
|
| 118 |
+
| `--hdr_list` | ✓ | HDR/HDRI 경로 목록(.txt) |
|
| 119 |
+
| `--output_dir` | ✓ | 결과 베이스 디렉토리 |
|
| 120 |
+
| `--devices` | ✓ | 사용할 CUDA 디바이스 인덱스들(e.g., `0 1 2`) |
|
| 121 |
+
| `--timeout` | | 각 작업 최대 실행 시간(초), 기본 `999999` |
|
| 122 |
+
| `--blender` | | Blender 실행 파일 경로. 기본은 `blender` 또는 `$BLENDER_EXEC` |
|
| 123 |
+
| `--script` | | 실행할 `render_eval.py` 경로 (기본: runner와 같은 폴더) |
|
| 124 |
+
| `--extra` | | 렌더 스크립트에 그대로 전달할 추가 인자 문자열 |
|
| 125 |
+
|
| 126 |
+
* **GLB→HDR 매핑**: 내부 해시를 사용해 각 GLB에 대해 일관된 HDR이 선택됩니다(재현성 확보).
|
| 127 |
+
* **멀티 GPU 큐잉**: 지정한 `--devices`를 라운드로빈으로 할당하여 병렬 실행.
|
| 128 |
+
|
| 129 |
+
### 2.2 사용 예시
|
| 130 |
+
|
| 131 |
+
```bash
|
| 132 |
+
python tools/render_eval_batch.py \
|
| 133 |
+
--glb_list glbs_eval_ours.txt \
|
| 134 |
+
--hdr_list /home/USER/data/envmaps/hdr_list.txt \
|
| 135 |
+
--output_dir ./outputs_eval/ours \
|
| 136 |
+
--devices 0 1 2 3 4 5 6 7
|
| 137 |
+
```
|
| 138 |
+
|
| 139 |
+
> 환경 변수: `BLENDER_EXEC`를 설정하면 `--blender`를 생략할 수 있습니다.
|
| 140 |
+
|
| 141 |
+
---
|
| 142 |
+
|
| 143 |
+
## 3. Batched Inference (GLB Generation)
|
| 144 |
+
|
| 145 |
+
`inference_batch.py`는 GLB 스템 이름을 **프롬프트 CSV**와 매칭하여 `inference.py`를 병렬 실행합니다.
|
| 146 |
+
|
| 147 |
+
### 3.1 인자 설명
|
| 148 |
+
|
| 149 |
+
| 인자 | 필수 | 설명 |
|
| 150 |
+
| --------------- | :-: | ----------------------------------------- |
|
| 151 |
+
| `--glb_list` | ✓ | GLB 경로 목록(.txt, 라인당 1개) — 스템으로 프롬프트 매칭 |
|
| 152 |
+
| `--prompt_csv` | ✓ | CSV (`gid,prompt` 컬럼) |
|
| 153 |
+
| `--output_dir` | ✓ | `inference.py`의 `--out`으로 전달될 베이스 출력 디렉토리 |
|
| 154 |
+
| `--devices` | ✓ | CUDA 디바이스 인덱스 리스트 |
|
| 155 |
+
| `--timeout` | | 작업 최대 시간(초) |
|
| 156 |
+
| `--python_exec` | | 파이썬 실행 경로. 기본: 현재 파이썬 또는 `$PYTHON_EXEC` |
|
| 157 |
+
| `--script` | | 실행할 `inference.py` 경로 (기본: runner와 같은 폴더) |
|
| 158 |
+
| `--extra` | | `inference.py`에 그대로 전달할 추가 인자 문자열 |
|
| 159 |
+
|
| 160 |
+
### 3.2 사용 예시
|
| 161 |
+
|
| 162 |
+
```bash
|
| 163 |
+
python inference_batch.py \
|
| 164 |
+
--glb_list ../glbs_eval_ref.txt \
|
| 165 |
+
--prompt_csv ../prompts_bs.csv \
|
| 166 |
+
--output_dir ./outputs_eval/ours_glbs \
|
| 167 |
+
--devices 2
|
| 168 |
+
```
|
| 169 |
+
|
| 170 |
+
> 매칭 불가 스템은 스킵되며, 요약 박스에 누락 개수가 표시됩니다.
|
| 171 |
+
|
| 172 |
+
---
|
| 173 |
+
|
| 174 |
+
## 4. Evaluation
|
| 175 |
+
|
| 176 |
+
`tools/evaluate.py`는 동일한 뷰 설정으로 렌더된 **참조(ref)** vs **방법(ours)** 결과를 비교합니다.
|
| 177 |
+
|
| 178 |
+
### 4.1 인자 설명
|
| 179 |
+
|
| 180 |
+
| 인자 | 필수 | 설명 |
|
| 181 |
+
| --------------- | :-: | ------------------------------------------ |
|
| 182 |
+
| `--ref_root` | ✓ | 참조 렌더 폴더 루트 |
|
| 183 |
+
| `--method_root` | ✓ | 방법 렌더 폴더 루트 |
|
| 184 |
+
| `--prompts_csv` | ✓ | `gid,prompt` 컬럼 보유 CSV (텍스트-이미지 CLIP 점수용) |
|
| 185 |
+
| `--out_csv` | | 요약 메트릭 CSV 파일명 (기본: `metrics_summary.csv`) |
|
| 186 |
+
| `--device` | | `auto`/`cpu`/`cuda` (기본: `auto`) |
|
| 187 |
+
| `--batch_size` | | 피처 추출 배치 사이즈 (기본: 64) |
|
| 188 |
+
| `--num_workers` | | DataLoader 워커 수 (기본: 8) |
|
| 189 |
+
|
| 190 |
+
### 4.2 산출 지표
|
| 191 |
+
|
| 192 |
+
* **FID‑CLIP (shaded, albedo)**: CLIP 임베딩 공간에서의 분포 차이 지표
|
| 193 |
+
* **KID (shaded, albedo)**: 커널 기반 분포 차이 지표
|
| 194 |
+
* **CLIP text‑image (shaded, albedo; ours만)**: 프롬프트와 결과 간 유사도
|
| 195 |
+
* **RMSE (roughness, metallic)**: 채널별 픽셀 RMSE
|
| 196 |
+
|
| 197 |
+
### 4.3 출력
|
| 198 |
+
|
| 199 |
+
* 콘솔 요약 + `--out_csv`에 최종 metric이 저장됨.
|
| 200 |
+
|
| 201 |
+
---
|
| 202 |
+
|
| 203 |
+
## 5. 입출력 파일 규약
|
| 204 |
+
|
| 205 |
+
* **`glb_list`**: GLB 경로를 한 줄에 하나씩 기록한 `.txt`
|
| 206 |
+
* **`hdr_list`**: HDR/HDRI 경로를 한 줄에 하나씩 기록한 `.txt`
|
| 207 |
+
* **`prompts_csv`**: 두 컬럼 `gid,prompt`를 보유. **GLB 스템명 = `gid`**로 매칭
|
home/ubuntu/aaaaa/data/rgbmr/complex_object_ids.json
ADDED
|
@@ -0,0 +1,1678 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[
|
| 2 |
+
"002f9bf02cdf4ff695ebbf61190ed89f",
|
| 3 |
+
"00366db8bb464e5c8b26703c70a686bc",
|
| 4 |
+
"00452de0ad4342daa0e139ad70ed2232",
|
| 5 |
+
"005e6a893d984538a7caee0cd403e0c6",
|
| 6 |
+
"00795a3f52d84b5d90fc352f220e6f8e",
|
| 7 |
+
"008be41361a44dfebe0d2003523e8f85",
|
| 8 |
+
"00e1ec5c6754447cbf3ccbf0a6383506",
|
| 9 |
+
"00ff6f491b1c4a74a5f90deb51c9a686",
|
| 10 |
+
"014012ec8eb3434d87adaaecb6fc9592",
|
| 11 |
+
"014d21f3b726425298b1a7a70a92b5a4",
|
| 12 |
+
"014ebfab735341248431da3d6447bbb5",
|
| 13 |
+
"017b9b167c654f18b2fee876dc5d4d25",
|
| 14 |
+
"01893d6934224c90a021ca9f1b592c59",
|
| 15 |
+
"01b077eae2de4aadac1e8de7d1cf3ba9",
|
| 16 |
+
"01d685032bb7401a90a57d2c775d7cd9",
|
| 17 |
+
"01e93883acac478386f049e339d5ae54",
|
| 18 |
+
"01f637d4083443548b382840d61a68a7",
|
| 19 |
+
"02583ecd6d02499daf73a1ba1981014e",
|
| 20 |
+
"02614a69beb24b10a01d34aa27ee7f58",
|
| 21 |
+
"02684df750894322ad1dcdbb9a798135",
|
| 22 |
+
"02873313e26f4543be9650357ee75448",
|
| 23 |
+
"02a5748af09c491780997e4af41fe316",
|
| 24 |
+
"03038d3742d6460db54ec989c8752831",
|
| 25 |
+
"030b98bbffd6481ebc86f6133e42ebcd",
|
| 26 |
+
"0319cbd39597460ba079c4737f856339",
|
| 27 |
+
"031debe8efad46dd9ba362604707ebb1",
|
| 28 |
+
"032ddebdff8b40e0bd2a51495753493a",
|
| 29 |
+
"03685bda9cb843d88f550a1f7f393184",
|
| 30 |
+
"037b2fde967243d883a0ee54b9aa4972",
|
| 31 |
+
"038348f33b27488e9b6ed0efe8415647",
|
| 32 |
+
"0397ff8b6a3148028f6db1e9e77925a8",
|
| 33 |
+
"03f33486821448e7961b111285525503",
|
| 34 |
+
"03f341ca580b47fb89fdc828a3248cd6",
|
| 35 |
+
"043353aec9b7403a8d2be7e075315528",
|
| 36 |
+
"043dbd8f74144e29bcd45ccfd59592b1",
|
| 37 |
+
"04629445e5b14f93bf18dcd211fa3d78",
|
| 38 |
+
"04ca24df177a4d14985a2e23b78def83",
|
| 39 |
+
"04db7c9a9ee9485b82999083b957b1c9",
|
| 40 |
+
"04e2f89dff504a1db0d62c2fc0788d6e",
|
| 41 |
+
"04f1cb7bb04d41e2bb7e291bf92f0744",
|
| 42 |
+
"04f51877ba25493d94d05f6086492f37",
|
| 43 |
+
"0534bc651ac84d0a912666060b8c0db1",
|
| 44 |
+
"0579a70d5a1b4e0fac473bb6861eeac5",
|
| 45 |
+
"059a7936ed89419ba9eae3153753ae86",
|
| 46 |
+
"05a396517a184002a943d309f4a2a317",
|
| 47 |
+
"05ee6c82045a4e3d9144d4cf56215f1f",
|
| 48 |
+
"05fc2a534c524c27ba7cfd74508ac482",
|
| 49 |
+
"0641654877a44c70b3ee5a44e163746b",
|
| 50 |
+
"0642122de7a04c8b8a694ce3735f8a2d",
|
| 51 |
+
"0657a328adde41ef978fa181c09955f5",
|
| 52 |
+
"0680fe1bd60343279824f50ba4c6f57f",
|
| 53 |
+
"068969f41a3245e8a4657e86db500e61",
|
| 54 |
+
"069b86d4b4a2492e92eb3349805479f9",
|
| 55 |
+
"06d400aa4d2343e490d07c23ff661e4c",
|
| 56 |
+
"06d5a80a04fc4c5ab552759e9a97d91a",
|
| 57 |
+
"06f867be028f44ed91f39471108c76de",
|
| 58 |
+
"0702225ca7e449c9ba85f3a142c65bee",
|
| 59 |
+
"07121d29537c4c70bccd4d02db6a6466",
|
| 60 |
+
"072d99792e534c22a163c20a917da4f9",
|
| 61 |
+
"076a510d663d41c380d8937581b68249",
|
| 62 |
+
"077c9dff3d824c8caf0e98df24debf57",
|
| 63 |
+
"07ff66a33eff4a0a93252317708d9cff",
|
| 64 |
+
"081f2783a8e44b559d37c08f7a402843",
|
| 65 |
+
"0846167f9302450cba1926c6fc5d6d42",
|
| 66 |
+
"084c7bc72c934da09d5aa2e00543a6b3",
|
| 67 |
+
"0853e9a4988b4953aae535dd23440e60",
|
| 68 |
+
"087d5869e9c34ac380a96ac0f892e05a",
|
| 69 |
+
"089327b8a40c48ec9d5b5946caf528df",
|
| 70 |
+
"089aa02943704e1aba9c953a89611d2c",
|
| 71 |
+
"08d237ffee3d4898b000ae0878a15a5a",
|
| 72 |
+
"08e02f5b80c34cb9b09c0a6a48deba9d",
|
| 73 |
+
"08eabca8888a41609cbc0043048dbd80",
|
| 74 |
+
"09146af7b959498482a73e717747ccd0",
|
| 75 |
+
"09415b04c271474a9757c53a7e27c414",
|
| 76 |
+
"0972ddcebed24084b47bab5eee38748c",
|
| 77 |
+
"09740836b5f04e8397a3b826ced11ab0",
|
| 78 |
+
"097fceeb1d85448882f5f9e385dbb144",
|
| 79 |
+
"09862077f68b4cb3a0c9c2ef82e359f5",
|
| 80 |
+
"09da8c6710654aa1b1ec8d6fb785b9b9",
|
| 81 |
+
"09fb79ac0c7c43ae9b1a1bd461619d9f",
|
| 82 |
+
"0a3e122170504d1dbddbde0a49f549e7",
|
| 83 |
+
"0a4c078d52e64241ad4da67e2f5ae3a0",
|
| 84 |
+
"0a4f3c74e1ee4ed5b9a5e36c7374aa37",
|
| 85 |
+
"0a5a267ffc814be49592cab5bdcf5d92",
|
| 86 |
+
"0a8293cd82b141d09b480a3f80c17b7e",
|
| 87 |
+
"0aa08cfba5bc422198828d1b720c53fb",
|
| 88 |
+
"0ad8b6a0fe5b40328bea1581000b9c9a",
|
| 89 |
+
"0ae52abcca054a57b58a0e9265b89b17",
|
| 90 |
+
"0af3d81c117e4e8aa84757b8f0df2ea8",
|
| 91 |
+
"0b04cb995fcc4c8cac5bfc8fc1aa4f3f",
|
| 92 |
+
"0b1e10b981194005b9fd842e45665fbd",
|
| 93 |
+
"0b73b0c1550746f38e4018f53e7bb56f",
|
| 94 |
+
"0b90478f3e1a4a1ba99fe0bfad77b96d",
|
| 95 |
+
"0b98efe575c446beb105a8dfd321f3e9",
|
| 96 |
+
"0ba8079c88fe44a49b84c051359a64e8",
|
| 97 |
+
"0bb43ca370104656959b91a27acfb6ab",
|
| 98 |
+
"0bc8be3751fd4af6b9171273a82592fc",
|
| 99 |
+
"0be09d151cfe4db5a6812306e9234ff3",
|
| 100 |
+
"0be84630c8e84508926c5922c541e1ce",
|
| 101 |
+
"0c1f1e1bc488444c8c5931d8c51ffedc",
|
| 102 |
+
"0c2d347c89b44323bc989ca85b6334a4",
|
| 103 |
+
"0c34e7efe79f4672ae5472fbe89e466d",
|
| 104 |
+
"0c9286b463384164877261b6ad6cd48c",
|
| 105 |
+
"0d322eeac13543c3b02ddf5a9dc5d66d",
|
| 106 |
+
"0d63a37b476a4338aa82084ba8d5e82e",
|
| 107 |
+
"0db114d7753344d6825aa4f21ec56db9",
|
| 108 |
+
"0ddefb81adf94e44a00ec5fa2db02630",
|
| 109 |
+
"0de0219a9d114947ae5626bd8a2abc75",
|
| 110 |
+
"0df3d7b24cba42ebbe2b6d542df0b2eb",
|
| 111 |
+
"0dfa5395abcb446c8779c1e369fc3727",
|
| 112 |
+
"0e09d9cd2efb473394e8977c57fba0f3",
|
| 113 |
+
"0e4cd5dff04143dda558b7b52d61f616",
|
| 114 |
+
"0e57497e42d74c76b5bd05021250dac0",
|
| 115 |
+
"0e5cd116d92c4100951056487260e026",
|
| 116 |
+
"0e80fb1e983943e78c64018efa7d8837",
|
| 117 |
+
"0eb317b09e5c44beb0e4681b06c15ae6",
|
| 118 |
+
"0eecf555488e4adfb57cbb0afe7836d6",
|
| 119 |
+
"0f03d940e6184871a0f38d429bfc006f",
|
| 120 |
+
"0f1a699145f441299d09ac3c0cf20e12",
|
| 121 |
+
"0f64e9fd042e4036a806cb1a4beb3690",
|
| 122 |
+
"0f88101d09d1408bb882fce581d9ec0c",
|
| 123 |
+
"0f9ebd68d56b443bae52cfebdbed0f24",
|
| 124 |
+
"0fdcb7df01d34aeea01ed1758d629f34",
|
| 125 |
+
"0feb0ffcfa9a47ba84ea7d3ac0ce5d70",
|
| 126 |
+
"1002c84db1ca45dc9d77c9e8e12a6437",
|
| 127 |
+
"1019140d9e354b489eaaf32895f8a7a3",
|
| 128 |
+
"1034844d4d564d1d9127b920c0f182da",
|
| 129 |
+
"1059e80a5f414388b2f240313acc5533",
|
| 130 |
+
"108635ed9e1c48b7a94f77e2405c4608",
|
| 131 |
+
"109ee24f61fe4c9bac7e4853dacca608",
|
| 132 |
+
"109f4aa661a24ffcb44758de9fdbff20",
|
| 133 |
+
"10b46ec4c10944faba5c00aa38a8c79c",
|
| 134 |
+
"10bff3bb4b474689968752ef9e2c0647",
|
| 135 |
+
"10d03a06c3474517b94a8d09f760aa41",
|
| 136 |
+
"10e1cceb4f1d4ab8bdb0c768afafad2c",
|
| 137 |
+
"11884dc5030840efa444dcfcc446389c",
|
| 138 |
+
"118ad6a688f24b75aa1751bbd05dd89d",
|
| 139 |
+
"119d5abe285545559f2a8b25760f7354",
|
| 140 |
+
"11aa844a77384a7cac91f1e7d0e39544",
|
| 141 |
+
"11aab5e450e640f5834da7c968b8d31c",
|
| 142 |
+
"11cc190943df4e69bf3b59c49defe013",
|
| 143 |
+
"11cdd06d68de4e42a1acf23093efddad",
|
| 144 |
+
"11ff1777e9254a5f8f510ff56108247c",
|
| 145 |
+
"122f100ebdec482eb5c81d01156246e1",
|
| 146 |
+
"1234ef64b5bd42be8f0e32725144790b",
|
| 147 |
+
"124ad78379bd4d259d9a253fb1a977a7",
|
| 148 |
+
"125c54f0b85746d496291418ab5607c5",
|
| 149 |
+
"12846c4c93d048feb07e96fb65f82ad1",
|
| 150 |
+
"12b5e7216c28431da88b63502faa7c6b",
|
| 151 |
+
"12d2ded2734a49aeabec371415409a18",
|
| 152 |
+
"12d76407cbd047d2a23a86c73e556c9e",
|
| 153 |
+
"12f2ba3da13c47f0a4a0e6c1521cf01a",
|
| 154 |
+
"13289bcc70ee4bd8a2f573b642aa1133",
|
| 155 |
+
"136494ed0f6c410caf53530bbe14ca64",
|
| 156 |
+
"138125d59ade4d328cc14cea5ff6f143",
|
| 157 |
+
"13987fff4df94d298c69027b98000516",
|
| 158 |
+
"13a1a1f76ec741e7abf4b4483ad36605",
|
| 159 |
+
"13aaa9a0462249f199ac808682fd57e8",
|
| 160 |
+
"13caef3c8cd344d6b696222deafa8515",
|
| 161 |
+
"14142eb541474335945c06a3630cafe1",
|
| 162 |
+
"141c59f3250c430d9e40c67887ba39bc",
|
| 163 |
+
"143f0bc1ebef48a4906597acbea6ea71",
|
| 164 |
+
"144051ab17b948359dc413c5a74968b6",
|
| 165 |
+
"147255b123c4419cb745c9a688f5a460",
|
| 166 |
+
"147a9dd1ec4d49e18ea793368ce4404b",
|
| 167 |
+
"14800755f6df45bcb07d71bee1464ab2",
|
| 168 |
+
"14c7c5d33a264a9c8de921aa77487941",
|
| 169 |
+
"1502810bac424f1fa91e3512dff3c59e",
|
| 170 |
+
"151f9f9b09a7455e9368d1cca370f545",
|
| 171 |
+
"15d86ea3b13444229dc8a9d3ca612369",
|
| 172 |
+
"15dfa9a9920d4508a5011a2d34a7ee9e",
|
| 173 |
+
"15edc8bf5aeb4185b6c0fb3753520c88",
|
| 174 |
+
"15fda9e154434cbc8a66bee0ccf71c65",
|
| 175 |
+
"1605c3b534de4de496c25012f8425dc2",
|
| 176 |
+
"162270d1e1f54c26b60cb691f5951f22",
|
| 177 |
+
"1633021c9ec94986b8f84ea703e1c979",
|
| 178 |
+
"16583a12813d41f1afe021389c6e8b7e",
|
| 179 |
+
"167151b4ff1842959d86ce589619b21f",
|
| 180 |
+
"16a421af043c438ca4cca51eb6ebe220",
|
| 181 |
+
"16ba45d18fd2443db23c858d3819952e",
|
| 182 |
+
"16d9b596dd384dbea7b88e5b3145b8f6",
|
| 183 |
+
"16e87b9d1c1c416db183baeb5aae8a50",
|
| 184 |
+
"16fa6474f4e6432fbfd33af10134d31f",
|
| 185 |
+
"1706b681036f41eab7156a461679420b",
|
| 186 |
+
"172615267260434f99da009ac53c22a0",
|
| 187 |
+
"17529cf13c1c4f7dac42b3a84e7915dc",
|
| 188 |
+
"176f8676f5214fdbbd2a5f780d062ff5",
|
| 189 |
+
"17f0b9d773fc4e968b162069fa5232d5",
|
| 190 |
+
"1852b23b73a94d4da46992c54e629da3",
|
| 191 |
+
"18c081f765854d249bb8dc580a1e9f7c",
|
| 192 |
+
"18d603a1f4b149d1b232563eba466a8a",
|
| 193 |
+
"18e60c14f46a4c3c9b361b80bf02beb2",
|
| 194 |
+
"18f67de6592a4cd4b8c31ff30cf4eaab",
|
| 195 |
+
"18f85fec08084a5d82a97347dc2b10be",
|
| 196 |
+
"193036f84cf34d489d49d0b8275e589e",
|
| 197 |
+
"193a7d0218b8400a9a7de60cbeae26df",
|
| 198 |
+
"1969eafcb62049e4b513aee276987ac7",
|
| 199 |
+
"19c02044ccf444038ac784dc88709a01",
|
| 200 |
+
"19c1558f1bbb403b943f338a9a70da89",
|
| 201 |
+
"19e4f6e71dba45ac8a3f027f33c4c3f1",
|
| 202 |
+
"1a0e4275e04442679edc4a8f09fff71f",
|
| 203 |
+
"1a35afde483f49f69a4a3c1da2df63e3",
|
| 204 |
+
"1a4a3013741e4fa5a70f5548958c21f0",
|
| 205 |
+
"1a5f0ad5860742c7be1da6757359230c",
|
| 206 |
+
"1a735bd0fcfc4e87ae3b89a7cdcecf4a",
|
| 207 |
+
"1a7aa42ca78c4e10ae98c1e77b0087be",
|
| 208 |
+
"1a7e9fe5bc284ea8ae54bbdb0fa8e87c",
|
| 209 |
+
"1ac4581e09b04ea6968a50e404a781ce",
|
| 210 |
+
"1ae3b14b5d5c4d0fb21abbf94655158b",
|
| 211 |
+
"1b03759aca22488a801662ed266068df",
|
| 212 |
+
"1b24bd7f2ca84cc4b9713065c211c5be",
|
| 213 |
+
"1b347387cd8741b5846534742ebc476b",
|
| 214 |
+
"1b3c4bcf091840b986c88c2a1740739a",
|
| 215 |
+
"1b5443261efb4c99be19ec82e3fcbecb",
|
| 216 |
+
"1b5ae52c77524c57b15f4a4c6a546087",
|
| 217 |
+
"1b834999835249228d4afb25cbc9b9ef",
|
| 218 |
+
"1b996dd59072468da9fcad2c8f0a8ef5",
|
| 219 |
+
"1b9c5e3b31fb4613b7be4ba640d70f0c",
|
| 220 |
+
"1be961388f3f4a858f9413232656c957",
|
| 221 |
+
"1beba9b24a9143df8559410f1522aad6",
|
| 222 |
+
"1c45284044f545d0888036ad34ac3657",
|
| 223 |
+
"1c670c8f241043d793dae59e1af693e1",
|
| 224 |
+
"1ca4dfff5736410fa3de378891b04eae",
|
| 225 |
+
"1cc0466a1cf14992b3329b13676e1295",
|
| 226 |
+
"1cc3e5baf8b54a358fc41062f74d97bd",
|
| 227 |
+
"1d0819976773469183e100765aa17244",
|
| 228 |
+
"1d0ceb703f704cd4ad3d0bf8e35f6b0d",
|
| 229 |
+
"1d133a74071c40bb9a49eb2bdb612827",
|
| 230 |
+
"1d13dbcab1224d54adef7dcf760ede5f",
|
| 231 |
+
"1d26bb676f044b6f81488ff7b43fd408",
|
| 232 |
+
"1d7537713c3c444fada9c650b84116c1",
|
| 233 |
+
"1db0bf90fad9413f9c70bd9afb8dd252",
|
| 234 |
+
"1dceb198db1d4c7db0777b85a57454ab",
|
| 235 |
+
"1ded9f27d0ae46e9a7f4c871eef7ed8e",
|
| 236 |
+
"1dfdb8070a8a42e1aab9fd38fa5630ac",
|
| 237 |
+
"1e025ab1bf234858ba48db14a54f26fc",
|
| 238 |
+
"1e61211efb1c49939ba6e45022e42873",
|
| 239 |
+
"1e704ff821fb4df5acb42117855e0370",
|
| 240 |
+
"1f2a9fc9bc0043328a40064dbe3fcd28",
|
| 241 |
+
"1f2ead206be24675bfbc63c3aec9aab7",
|
| 242 |
+
"1f5516c73928421cbc0ba1d7e1ff24d6",
|
| 243 |
+
"1f75786d2bf047a38f3971d7758aa990",
|
| 244 |
+
"1f8aabdf44424d72818bfcdc600bcad7",
|
| 245 |
+
"200ebda19ef44aeba975cf35e8d78cd6",
|
| 246 |
+
"204f47380bfc4d83901e19112c116bb9",
|
| 247 |
+
"2051d1d1a15b4d03bf3424fdd7717858",
|
| 248 |
+
"205a9efa37a54e8d8c3a1248de4c804a",
|
| 249 |
+
"20703acffbba47ce8b3755faa966ae22",
|
| 250 |
+
"2099b1f909a345d0be4ec934186ba866",
|
| 251 |
+
"20b77794b58d4f91b257676482fdf28d",
|
| 252 |
+
"20dd7f7bdc9a4c36aef491f12afa14d8",
|
| 253 |
+
"20ed29af326943a690ed71cee3efb5e5",
|
| 254 |
+
"20f9af9b8a404d5cb022ac6fe87f21f5",
|
| 255 |
+
"212980eff27d4d45abb2d72421a7ed9e",
|
| 256 |
+
"2144e2292abd45a58b75332a920fde98",
|
| 257 |
+
"2145f8891d0446b2ac4c25c285ff338d",
|
| 258 |
+
"215641b9f8e04a5585be12f050c29e45",
|
| 259 |
+
"216abeb437454c53bb556148bec71404",
|
| 260 |
+
"21705607294b4c1691e3770757f969fb",
|
| 261 |
+
"2177902e39d24548b3b1bbeb290a13ca",
|
| 262 |
+
"21851bfa7ee94bd49ae8161874e0b669",
|
| 263 |
+
"21ec37b286474fedb43307f6f289269e",
|
| 264 |
+
"22071b002d96499396a447b9235cec79",
|
| 265 |
+
"2221a5081f6d4ef9a1977d8d09d72941",
|
| 266 |
+
"22ac998056374bc3bd82d78b921debbd",
|
| 267 |
+
"22eb777ecde1455c9accc98b05fe1140",
|
| 268 |
+
"22f3cadbccad4ac68cec0a22a95a6d9b",
|
| 269 |
+
"232a246915e34a0cb7fd0485c5344401",
|
| 270 |
+
"2330d6dddc0d4a2b9fa9a644d249b766",
|
| 271 |
+
"2333799431754d42aab6712986a312c7",
|
| 272 |
+
"23466a346e9b4755a392d9cf69606a07",
|
| 273 |
+
"23b89235f11247e5a1dc1f55ba69f09c",
|
| 274 |
+
"23eb94cac33d4099b7185a81bdc038e4",
|
| 275 |
+
"23ede16550d84e81a4c255183510825f",
|
| 276 |
+
"23fa151346304c8bb8c58f58a76e6407",
|
| 277 |
+
"240d0bb449a949ca8b64cf1e776065c0",
|
| 278 |
+
"244b511ee2ca4f3794d118b872ee229c",
|
| 279 |
+
"254815aada9e445e927fea6a25a6d22e",
|
| 280 |
+
"254eb565be1b490bb64ef97e1274057f",
|
| 281 |
+
"2566ee4337b2410081cedc901b932618",
|
| 282 |
+
"256f723c10be45c0a2b662b3ca656026",
|
| 283 |
+
"2597f49cb567419f838c7df39c45d5ce",
|
| 284 |
+
"25ea2f17700e4f1ab9dc38b3fcaaba0b",
|
| 285 |
+
"26349afbeb094d1684e90884d98e38d9",
|
| 286 |
+
"264d5fe5038c408b87a7d2e62eaf0e35",
|
| 287 |
+
"266271bf84724e91ba76807e9d84e204",
|
| 288 |
+
"26847febe5af4c8e8615e1052ff69fac",
|
| 289 |
+
"269997245d764743a648d3ee4208e4a0",
|
| 290 |
+
"26f48e21a7f7487cada9cd59d840a9c4",
|
| 291 |
+
"2738b65016284ea3bdf041ea9135a121",
|
| 292 |
+
"273b53c4bd464b4daaba1fa94a9b8722",
|
| 293 |
+
"27c6ac2f6db648618186d4c00f3acfff",
|
| 294 |
+
"27da060494084e12a41403eec5a96498",
|
| 295 |
+
"27e541911b994ce0a0b546f0a861145e",
|
| 296 |
+
"27f6af85b7e9485da35e1bdbb5da680e",
|
| 297 |
+
"280a65bca20b425e96617b0dd78cce21",
|
| 298 |
+
"2819271389e1451eab2ef7e1333b45de",
|
| 299 |
+
"28215c6ceef6454cacebb92b5f2d4784",
|
| 300 |
+
"2834f838915b42f89f7dc299e6c39a0e",
|
| 301 |
+
"285420b2b0474a1199030d820f641d66",
|
| 302 |
+
"2871ff97b5d5478f9aa80a431a0049f1",
|
| 303 |
+
"2884eeef3d324910b50d10061dd884ae",
|
| 304 |
+
"28b76708813b402eaa71877738954df5",
|
| 305 |
+
"28dff4ee44a6444f98243982b6363b6a",
|
| 306 |
+
"28e7eedb0c4849ff8b0d744a74e9e424",
|
| 307 |
+
"28ec724bf8c141848b7910fa7ad25a89",
|
| 308 |
+
"290a75f5b21f4fc88c2a10849b4eb21d",
|
| 309 |
+
"292d33c6f0bb40c2b229cf8ba480f7b5",
|
| 310 |
+
"29360182779942f39104f227a161b987",
|
| 311 |
+
"293a20a6a9284de9a1c4d40092dd7bcf",
|
| 312 |
+
"293fc6beecd44a8aaf20df205822e742",
|
| 313 |
+
"2967048d83614382a43e1e9817b89573",
|
| 314 |
+
"296a53dcaca64681819a43b8a7ff7db9",
|
| 315 |
+
"29f78283c38d487aa8f34497d1233015",
|
| 316 |
+
"29fac935b6984498977832bcce0b1ffe",
|
| 317 |
+
"2a2927434cd14316a445044b69c6cf3e",
|
| 318 |
+
"2a6667ef214b4596b5dc407f4b073fdb",
|
| 319 |
+
"2a86f45c248d4b06a8ef86213916e089",
|
| 320 |
+
"2a8d375f2aef42259c6b6814a3ab72a6",
|
| 321 |
+
"2ac7e46b2ee6451e877c8f2a7d203859",
|
| 322 |
+
"2acda3ed2f9046129897f0f1e36092bd",
|
| 323 |
+
"2ad22ad416e74cc0a2a99567fd9c8f8e",
|
| 324 |
+
"2b1eab63ef4a48ad8e62be8d6b42d83b",
|
| 325 |
+
"2b273f7abb484ed29ace055984eb26b9",
|
| 326 |
+
"2b32f215f41d447c9fc1cbc75b64cd51",
|
| 327 |
+
"2b3e6f8e37344325b98a6165030f3340",
|
| 328 |
+
"2b4a5d2057824f44bf35056f531f8b69",
|
| 329 |
+
"2b4c4ce5ffef4646adf1c6b78d29b47a",
|
| 330 |
+
"2b4d6590fa6a4226a1366e2f636df7f6",
|
| 331 |
+
"2b9de21987c6419ebe72df19e9c4163a",
|
| 332 |
+
"2bb06b2f975545e0bf74b0be3c522d6e",
|
| 333 |
+
"2bd82aa8ca92482882241a3762684509",
|
| 334 |
+
"2ca8d4fbb25d49cb9da094549ed4ffaa",
|
| 335 |
+
"2cd2108f386f4b19aee80c782c3ee20d",
|
| 336 |
+
"2d1d858c4bca475b9eb5d6d95f7b6ea8",
|
| 337 |
+
"2d4f0a9502a346bba2718b7c43dd43a6",
|
| 338 |
+
"2d69c8441654486c872e7ef70ee913c6",
|
| 339 |
+
"2d76588c822942259b579e15640d7511",
|
| 340 |
+
"2d8b169f416f43df84fb679420e0a148",
|
| 341 |
+
"2da2098bab544c688fd106ed05ead14c",
|
| 342 |
+
"2dbd978c84da4270aa19091ed599007e",
|
| 343 |
+
"2dd385cde1c142deb8e3ae4b12e85dd1",
|
| 344 |
+
"2e09121c47cb493bb9fe754ee92653f9",
|
| 345 |
+
"2e0c2c37a55f48bba314c894ca5e5f0b",
|
| 346 |
+
"2e1fcfe8fd3b4ae4b41f79dfb2fcfc4b",
|
| 347 |
+
"2e283153e26146fc876895570d1ef74f",
|
| 348 |
+
"2e2bb2aa721748e785adbb86c98b7a23",
|
| 349 |
+
"2e352e9c26cd4109bb67132c40df8146",
|
| 350 |
+
"2e3fd6aa4e6b424391c1a13dbdffbfdc",
|
| 351 |
+
"2ea97968269c45e3abe17f84e625e609",
|
| 352 |
+
"2ed70b1329684b56a83d0490660cc066",
|
| 353 |
+
"2f09bba881d34aa1885f3b0476d94e61",
|
| 354 |
+
"2f1706233a3248cf9a74586fc2e7120c",
|
| 355 |
+
"2f1f291be93b4e09898a17f99e99d5d4",
|
| 356 |
+
"2f3b88cc0211484992b393a381a97fb4",
|
| 357 |
+
"2f6ab901c5a84ed6bbdf85a67b22a2ee",
|
| 358 |
+
"2f8b1430ff634501aa3a19717bed0319",
|
| 359 |
+
"2faa80cfd86a4af785c25ef632dc4309",
|
| 360 |
+
"2fb0edd32d1f4b1dac176f5d8352725d",
|
| 361 |
+
"2fbb8cea1ee740008ae5882e48fed8bb",
|
| 362 |
+
"2fcd9c9f248a46e5a2ea5b27379dcdf3",
|
| 363 |
+
"2fe47279d65d429f8101f97e7c89de7f",
|
| 364 |
+
"2ff892fcc6334a3d97d5bbf023b149a1",
|
| 365 |
+
"30017d8129674ed0b5d38dedf3f5686f",
|
| 366 |
+
"3030f6abd4854140ac3c278c3cdba25b",
|
| 367 |
+
"3086fd6d56e54573bdb00844ebca3c77",
|
| 368 |
+
"30cf4a33eaca4e53804f5abc0b8d00a7",
|
| 369 |
+
"30e22b51951945168006423813c26885",
|
| 370 |
+
"30fa4d77375349a49fcd60c4b2039a3c",
|
| 371 |
+
"31e0899e65f74cb9bfdc9df8e71288ee",
|
| 372 |
+
"31fd6b50eb5845d7b0e47d71639a07fe",
|
| 373 |
+
"3218791489fd47c59a33ee6ca38f0c72",
|
| 374 |
+
"32285d0de78b43b18defffcb3f647f37",
|
| 375 |
+
"324e3febdbc74b29a506760d6edb85dd",
|
| 376 |
+
"3269b8399110482cbf6bab889e70da25",
|
| 377 |
+
"3285d5f705604a74835c2b51df373d04",
|
| 378 |
+
"328840dc30c846d6833b57e7a33a41f0",
|
| 379 |
+
"328eb54341cc4b3f9c7a07fa1cb71a02",
|
| 380 |
+
"32c521425f124f24b86c64924d7fddd8",
|
| 381 |
+
"32f46385978241eaaae7ea692367d4e0",
|
| 382 |
+
"32fd9fd8240a4c49a1ed45f9d9c1e828",
|
| 383 |
+
"330b94a411bb465698c5eaf984c46bf1",
|
| 384 |
+
"331c237cc2044d87977cd3d3e9deabbd",
|
| 385 |
+
"3356fd1647c7403f90de64805545c77e",
|
| 386 |
+
"3366c19e79694be0896d569459dde186",
|
| 387 |
+
"3426ec60832b4f7c867ba29dc6d0e808",
|
| 388 |
+
"345fe9f4ebdd47b587f03cd3b81cd9db",
|
| 389 |
+
"3484a4e0862b4205aaeb1ef2c62e483b",
|
| 390 |
+
"349d730859c948b88f172fc5158e32ce",
|
| 391 |
+
"349e97902e844189b17cd86e01d5e2f8",
|
| 392 |
+
"34a383c7dab64f2a9267bd19acd0e8a0",
|
| 393 |
+
"34c2c5615fcc47b0a34de9fc9f77db8c",
|
| 394 |
+
"34ece7dccc4a42cd995a099f3c69e9ba",
|
| 395 |
+
"34eebb66d54b467888d446206bfe6ddf",
|
| 396 |
+
"35002d28d3fe4359be492bd444569744",
|
| 397 |
+
"351f5d4bfb0f416b8cffeb957f88d4c4",
|
| 398 |
+
"3529e97a616241f480a0115d74f97e80",
|
| 399 |
+
"356d3e62399b466fb6578fb1302bffa9",
|
| 400 |
+
"356d6ae5584f40aab1aced6bd6571a74",
|
| 401 |
+
"358be2d78ce8457b8fd37187d9f70976",
|
| 402 |
+
"3598d4508c8c4568a35e2ed00e4cd193",
|
| 403 |
+
"35bd8acad6f64c7eb7a9ff026868410e",
|
| 404 |
+
"3630b9ef86124610932a328aaf2ab023",
|
| 405 |
+
"36342df2dae44dd4894ad4658720c2a4",
|
| 406 |
+
"366d20b241a5446b85e0cd6fc399afaa",
|
| 407 |
+
"3687a268a444492bae2c81d83efde675",
|
| 408 |
+
"36d5b9c904ee4eea9a55ab0eccf7dee8",
|
| 409 |
+
"36d90cc8de3847379c8a636f548ffacf",
|
| 410 |
+
"36e269a8f74143a3b567f582781acaa2",
|
| 411 |
+
"36f06f5bc599499aa7a127c68668edc4",
|
| 412 |
+
"36f768d11a6342b7b36a121e014f521e",
|
| 413 |
+
"37071678dc284350b5268796adffb6d2",
|
| 414 |
+
"3708b0bedf8d47efafe22bea1c00a951",
|
| 415 |
+
"372200b925ef4dc69cf3364daadf786f",
|
| 416 |
+
"3724de1e56ba4f4bb0be2ca3170e39f2",
|
| 417 |
+
"374ed6172d2c405fabaef72b2b644149",
|
| 418 |
+
"3750fba31c2f445a864584cea5e2d78b",
|
| 419 |
+
"376b733d888c41d9ad3ac39eec4498d6",
|
| 420 |
+
"378988e0236c444daea672330e2686df",
|
| 421 |
+
"37b041d8521c4179b3c8679e2ff8dd17",
|
| 422 |
+
"383e4b856aed493a9ef70365cc3685de",
|
| 423 |
+
"387cd4307d1c472493254a659325fbd4",
|
| 424 |
+
"3886c1ca9a0942e19ef288493e1643cf",
|
| 425 |
+
"3893793770594f83a059317f587af8b8",
|
| 426 |
+
"38d1079a273f46349203fa2c3db35d29",
|
| 427 |
+
"38f04a4243f64e808fc7a6866c08dfcc",
|
| 428 |
+
"38f24c1eb8ae44d5bf2a4f9b940c96ec",
|
| 429 |
+
"392a59d222a647828cf3ee4c505edc38",
|
| 430 |
+
"394d1e8122554e00876789fafa68a03a",
|
| 431 |
+
"3953a48277d8460990162d60123c57dc",
|
| 432 |
+
"3959cdc6c8ca475eaa04f9a8d2dbf055",
|
| 433 |
+
"39a5dc35a05d4ac59f22c7d9ea050299",
|
| 434 |
+
"39a61e9a65994ebda5806039ef1afac6",
|
| 435 |
+
"39d799f20f4041228704749c854b60af",
|
| 436 |
+
"39db3486652b45548ab4dbb63a86911e",
|
| 437 |
+
"3a0d055986d74a9598a7a54c3fd05b9d",
|
| 438 |
+
"3a135daf87db490d9d9afb4b19a5a9d7",
|
| 439 |
+
"3a2954f4e11842f69482d1d6ff4eaf23",
|
| 440 |
+
"3a3afdfd1d2b41f68ed7d98c90b69d5e",
|
| 441 |
+
"3a4df884aff945eaa44e31d1b3a16f7f",
|
| 442 |
+
"3a5572342c5c4bdabaf0ac27b6577cb7",
|
| 443 |
+
"3a6019602c234c13a376c55a6c52c0b8",
|
| 444 |
+
"3a7fb4375e8941caae37767f0e30cf45",
|
| 445 |
+
"3a7ff8c34baa4caaa6098ee8ad447679",
|
| 446 |
+
"3a9a470a1d324a49bd362c580d046721",
|
| 447 |
+
"3af2417fa730468b81bcfcac715469b8",
|
| 448 |
+
"3af37c2c89d14ccdbc99563f969ce7c5",
|
| 449 |
+
"3af6861d9c604482a3a4354731c52c1a",
|
| 450 |
+
"3b1393cb24a74ab9a4a25003ea26ca6b",
|
| 451 |
+
"3b44c656b90341108cf27d3dbb88c4dc",
|
| 452 |
+
"3b6d35b6b4f44cb3832a8c0b7e124748",
|
| 453 |
+
"3b7d058289cc433da8338acccb4a59e0",
|
| 454 |
+
"3b84b153520c40048a1f2fa3fda8b866",
|
| 455 |
+
"3bae6520516e4d6d9f67c51837855144",
|
| 456 |
+
"3be2b07c8dab46dca538d21dae5b678e",
|
| 457 |
+
"3beec67e1ee84ed4b8d9e454d682e1f7",
|
| 458 |
+
"3bf8f2214b7043d3bdc543b8136d8e17",
|
| 459 |
+
"3c0cec271575485c83518d710384d084",
|
| 460 |
+
"3c1bd2de915941118890332de06d5df2",
|
| 461 |
+
"3c3ffeccb5be42ed8280d1e65ac2d200",
|
| 462 |
+
"3c53f38f89434099b973c2016e7d6535",
|
| 463 |
+
"3c7d73032bbf4b9eafb58bb943c2d859",
|
| 464 |
+
"3c8ecf379cde41bc8203945e00cf4487",
|
| 465 |
+
"3c8fabdb9e4c4bcd832e2c0b6913a17f",
|
| 466 |
+
"3cc81831328d405880c3fb5df62755a2",
|
| 467 |
+
"3cf7343ec2714fefaa21d4a9005e2f28",
|
| 468 |
+
"3d1429899c464501ad23cbf13449fde2",
|
| 469 |
+
"3d49477c17e9405683601bf9107b7513",
|
| 470 |
+
"3d5487df45fe47cca585e5c786abfde3",
|
| 471 |
+
"3d64b354413e48fca9fef934f25a882f",
|
| 472 |
+
"3d6f9421a9b54fe18b8cf894b17b3bff",
|
| 473 |
+
"3dc5d502413c41ac97eaa871deb32e9b",
|
| 474 |
+
"3de0ba7fb9ac41d6b438501487d2bd83",
|
| 475 |
+
"3e18491fe92c47e8bfae9f4064699e1c",
|
| 476 |
+
"3e3b24b28cde4650b71dbca053056995",
|
| 477 |
+
"3e83a01ee91b4e029f634b54439dc78b",
|
| 478 |
+
"3e8c37f3d701413897c6333446a76a6b",
|
| 479 |
+
"3e9b9f7841f54577829c0a9167b167d7",
|
| 480 |
+
"3f46bca9feaf43338e8956d227163738",
|
| 481 |
+
"3f49492bbed14d89b43cdc3efd5993c2",
|
| 482 |
+
"3f72a9830c2249e5ab397f30de1af84a",
|
| 483 |
+
"3f741c7f7d94427aa413683b71f2d31f",
|
| 484 |
+
"3fc51a48f58f42d28f325db7688be17c",
|
| 485 |
+
"3fda15638cd84814bda4f97327984e42",
|
| 486 |
+
"4047c9ac5e224f738bd67f6a409975bb",
|
| 487 |
+
"405351166c1d4f5d88162d941d44cd60",
|
| 488 |
+
"407bb4001ac746039d654f1332a3e40e",
|
| 489 |
+
"40b27a1c061242a89f8b313c7905533c",
|
| 490 |
+
"40db95838f9f4258bb4767a34fcf48e6",
|
| 491 |
+
"40dc15941e0b456692c83a97bfcd2474",
|
| 492 |
+
"40f09323341f436aa2814c3391e04223",
|
| 493 |
+
"410026f520dc4e6999006b38cfaf5eb5",
|
| 494 |
+
"41075d1b3e714637a16b4ed88bafe1c5",
|
| 495 |
+
"413d197f7c0f40f9a0aaafc323dae2da",
|
| 496 |
+
"41657917b9064ce6b4715e8ba282e100",
|
| 497 |
+
"417e3873fcb34f7ab9744506d7bcc838",
|
| 498 |
+
"41842c8d2ebd402da04def3c53c41633",
|
| 499 |
+
"418553c4a96347bfa4f9b349fb8d89b5",
|
| 500 |
+
"41873763c2c641599a3220b675a5fca7",
|
| 501 |
+
"41f03702289940b29831dcb771fb5f3a",
|
| 502 |
+
"42141d069d8148e3b6eb2d1d803f3596",
|
| 503 |
+
"421de5857e8c43f1b865d1f4d69fe708",
|
| 504 |
+
"423b8807f56147d68ee764c7ba7aa440",
|
| 505 |
+
"424df49ef1334360a9490da34c72b522",
|
| 506 |
+
"4282341a050f4fa9ac9d79c72948e042",
|
| 507 |
+
"4288f5d3a1554b90ade37894c40936a1",
|
| 508 |
+
"429e6e5cf5014e24b5e0c8dd8cb9259b",
|
| 509 |
+
"42ddfe3afe55443ea7ffd7e3e806a631",
|
| 510 |
+
"42fe263b572e4402bba90a41a3110a3d",
|
| 511 |
+
"430f3f8741d54753beb8b238d75c28b8",
|
| 512 |
+
"430f5651a39c46258abb30a22f7b5634",
|
| 513 |
+
"430f90b413c0491b8b3af457236449e4",
|
| 514 |
+
"435326b23a6240f48c2a48140522aa08",
|
| 515 |
+
"4390e3589d8a42e6838c001d697c79d5",
|
| 516 |
+
"43b44305830748d08e9a807006df124e",
|
| 517 |
+
"44112469dea546f396dbb6d35539e8e0",
|
| 518 |
+
"44358bf4519d4815b037cec09e92b7f7",
|
| 519 |
+
"4450069fee444ae0920aa5babd5d9239",
|
| 520 |
+
"4460c58b184244d8906ae6b822ff1ccd",
|
| 521 |
+
"449c75ac880f401eaf0fbc36fd17526b",
|
| 522 |
+
"44aeb49fd30549f08fe28a9c495c3ed6",
|
| 523 |
+
"44b7df77727143169c545e39956a77e2",
|
| 524 |
+
"4544f87c79d647bb985524155829d0ee",
|
| 525 |
+
"455c1b96e8214b45ac06bb2742d8b50d",
|
| 526 |
+
"459c229bb92a4f9481ea42b167082ff8",
|
| 527 |
+
"45a730f934154d0d94c99d1744c92564",
|
| 528 |
+
"45b619b9080a40968af0874e4ca30879",
|
| 529 |
+
"45e0eb9afc694a808fc847332b759bc0",
|
| 530 |
+
"46026de3201245fba3f64f42e502f32c",
|
| 531 |
+
"4608272844e143f3abce6473fd80d3de",
|
| 532 |
+
"462d3296495944f890e24749d251c573",
|
| 533 |
+
"4663bb43c0504c59bea8227084945502",
|
| 534 |
+
"4665bdec11204bfa8e1edc00020c63aa",
|
| 535 |
+
"46b3d3077d304c5695ef4ceef7685970",
|
| 536 |
+
"46e59cb1580e4de4884cc8bb93e91b6a",
|
| 537 |
+
"4713c91e35684df08700e4f75f4f6f60",
|
| 538 |
+
"472037a7db9a444094b92b55dfeca564",
|
| 539 |
+
"47267dcf929946c1816932b6961f83a2",
|
| 540 |
+
"4752dddced01498dbac8cfc0cb469b24",
|
| 541 |
+
"47ace76546294deb85503e7b5f8d8b55",
|
| 542 |
+
"47b7026b98d54cd7bbbada1ba1eb755b",
|
| 543 |
+
"47bf3947748f4d24afdddfe680734ab6",
|
| 544 |
+
"47d4b60361e747edb1f88deb29f26e14",
|
| 545 |
+
"47ec876aa4034f8f8af6f3856513faa4",
|
| 546 |
+
"48016ceb77654dd7af9cd699148afecd",
|
| 547 |
+
"48567d6a1702493080293ea6c21ebf94",
|
| 548 |
+
"48814a3c57f74772a9fcb68e6d720611",
|
| 549 |
+
"489d6b65fc484a25a3c0e26180630719",
|
| 550 |
+
"48ae4ca557d842c3aa3b5e5652dcdab0",
|
| 551 |
+
"48b4f5e4b6f04fa9a13d9df0b7a82309",
|
| 552 |
+
"48c4cfee42f548ecb86b46bf187fa222",
|
| 553 |
+
"49024fa002a14b6ea7b5b45b3087c7db",
|
| 554 |
+
"4903ff98262a43a99bba6184678d5103",
|
| 555 |
+
"492f2d0058104ec2ad5ca7ff09751930",
|
| 556 |
+
"494bb58e60984098afcbf2f095f92830",
|
| 557 |
+
"497228f579654318bd6a371880995a35",
|
| 558 |
+
"49bb763a0f804641ac6101872fb56422",
|
| 559 |
+
"49c59c03ea424fc496c82aa2e3143b85",
|
| 560 |
+
"49ed32b8617e49ec9f580b5f4a753b71",
|
| 561 |
+
"4a31f6063c3d4183b5faaf926540996c",
|
| 562 |
+
"4a3435ce074c4f35a28ff3d1bd7623a4",
|
| 563 |
+
"4abbe8f3478f408aa2c9a4f772976cda",
|
| 564 |
+
"4b18b0caed204f2d8be554b0336749d4",
|
| 565 |
+
"4b33cf70ffca4b95829bec5548eb00f1",
|
| 566 |
+
"4b78a721ae3b4e3aa4587153fed3f4c7",
|
| 567 |
+
"4b883942ae254cdbb32bfe9ff5420455",
|
| 568 |
+
"4bb4247ccc5740b8a5e1db834a62d1a1",
|
| 569 |
+
"4bc0da4c9df0406b85f03a08aeb42091",
|
| 570 |
+
"4c33ad28bbbb4c07bcb765bf7cd5327e",
|
| 571 |
+
"4c3d66f810e74a4bb0d16b41ba93204d",
|
| 572 |
+
"4c593d53f1e64f909de169125c977cbb",
|
| 573 |
+
"4c7033acf418436992d85319cb995851",
|
| 574 |
+
"4c7f765936cf4abdabdd6f48c1b2e4cf",
|
| 575 |
+
"4ce5c565a567461fa1d3fa124d2bf862",
|
| 576 |
+
"4cea6c63123149278f1f4c14a11986ef",
|
| 577 |
+
"4cf149f5d6ee4b8e98620568aa3947aa",
|
| 578 |
+
"4d0bd5f9eaee4bdba2360496fa83a3ce",
|
| 579 |
+
"4d18aabb12cd45e691a5eeae60feaa2f",
|
| 580 |
+
"4d31a5f2c68d4e41bd324e2b23767469",
|
| 581 |
+
"4d41a273fe4c42a59777ac80c50e7f65",
|
| 582 |
+
"4d490543088d43e9bf7e3817a4adddb4",
|
| 583 |
+
"4d5418cedfb9476ebf9093d7ed67133d",
|
| 584 |
+
"4d6ce2d72fd74a66b43d866e8e5f7530",
|
| 585 |
+
"4dea0b20b2fa4a699410274fca4d4947",
|
| 586 |
+
"4df51ea0c3974840b029a515ca975709",
|
| 587 |
+
"4e1634929caf4f85a608fa17b55a1ea3",
|
| 588 |
+
"4e22aca5a799466aa194b173ff53b815",
|
| 589 |
+
"4e3e9de83ea74dae9598b4dd47ada072",
|
| 590 |
+
"4e719b26038f4011a59f4b901c10596f",
|
| 591 |
+
"4e94a564395b4b148eba76f85a5068a2",
|
| 592 |
+
"4e9c3cccbaaa4adc947ad8e8ab351d0d",
|
| 593 |
+
"4eaadfaa977d43ed9afd8064a9932a20",
|
| 594 |
+
"4eb495cdd69e439591557481b7cb408f",
|
| 595 |
+
"4ef030bb0eda45648b34633e61fd5e99",
|
| 596 |
+
"4f0ddf5603b94885aa69279381c3c411",
|
| 597 |
+
"4f1478dc330f4da2a728795b729991dc",
|
| 598 |
+
"4f1ed455fb764463adb0ed1cb779b4c4",
|
| 599 |
+
"4f21c24a1f47436787798485138b6c2d",
|
| 600 |
+
"4f260979c88e4865bebfdd707cb7edbf",
|
| 601 |
+
"4f33982b99d6487084676ed5f2fb0f71",
|
| 602 |
+
"4f3882be7e184b129946d25a75df3b76",
|
| 603 |
+
"4f38ab83cacc477b9f5b5f6d0230f1c9",
|
| 604 |
+
"4f682b0b32204a2b88f1cec535e58242",
|
| 605 |
+
"4fb5fcff117a400ebf5514e69a654a9d",
|
| 606 |
+
"4fcf8b855af7464a98f26f11ccd4ade8",
|
| 607 |
+
"4fd48175b15b4047a2deba77ca22d44a",
|
| 608 |
+
"4febddbd35e64d4a946ece70ce564f82",
|
| 609 |
+
"4fef9ff01ba14955962d05be6e3205a1",
|
| 610 |
+
"4ff2fe86bf0a4f8c84f7894e151c39e4",
|
| 611 |
+
"4ff545286db441c1ad23a49cc0439262",
|
| 612 |
+
"502ff900e8ec4612acdde695650dd3cc",
|
| 613 |
+
"50b0c61bdcc7488f8a5c9fa40f1a04ed",
|
| 614 |
+
"50cfdb6dfc2d4bb399f84603eb9807ca",
|
| 615 |
+
"51341f3ba62a4d76a9216b4c36d673c5",
|
| 616 |
+
"518fe53890f5439bba014f47c5c1b272",
|
| 617 |
+
"5191cdca98ca4b4ba961f00747029b03",
|
| 618 |
+
"5191fd63062340de9173dc3157731917",
|
| 619 |
+
"51a661d02b804960bedb89ae0e753288",
|
| 620 |
+
"51acfb9670c34e18ad4678c8c0c2a9d9",
|
| 621 |
+
"51c73ff41be14fbb88f70f7a5e78e142",
|
| 622 |
+
"51d59c2e12044e90bed75ea1154dcbaf",
|
| 623 |
+
"520dd7d5295a48ebacfada13df7548fa",
|
| 624 |
+
"5227b0c53a794a5d8507d722e9deef03",
|
| 625 |
+
"5285e04a70d543c3a2d88cfb7b55665a",
|
| 626 |
+
"52b2283f5ad14f248dccf7a139662993",
|
| 627 |
+
"531b7f9d88f244729068fa024c28d7e8",
|
| 628 |
+
"535f1c0e90a64a1997d1e0a7fe972d07",
|
| 629 |
+
"536c5d02d1e94d02bcbcb17883cfe85d",
|
| 630 |
+
"537de3ffbd2649da865bbc842126f828",
|
| 631 |
+
"53880a84c7214c3486125b10803aa664",
|
| 632 |
+
"539fe5e6624341f3a0139322423eec85",
|
| 633 |
+
"53c53a112c674d29a2afdbddbe3cecb5",
|
| 634 |
+
"53ca491de67147539b60ea971cceb415",
|
| 635 |
+
"540606ed90d4449ca14d713cc6386d5b",
|
| 636 |
+
"5406fedb5aa6425f87ad0328a3313d56",
|
| 637 |
+
"540c1e3114a244d0b684f3aa9f8adf87",
|
| 638 |
+
"541b53467ad045d59a81fe79d740ea66",
|
| 639 |
+
"542e27eef15b41bc93105e4f1d847942",
|
| 640 |
+
"543b4973b8e246879712903e3f580219",
|
| 641 |
+
"54712cc9bbfe4937acbc0fe252336f54",
|
| 642 |
+
"54926ff096ac4b5686211685e834d908",
|
| 643 |
+
"54b4d912eead40c49c03ed90d07db8e4",
|
| 644 |
+
"54c43cf57ab34f74b349577ba28d6705",
|
| 645 |
+
"550402c03f5544d5b2a9824ceb7eacc7",
|
| 646 |
+
"5527751e780541658d1443693c3b02e4",
|
| 647 |
+
"5532b05f53504abeb8fb8646a6a68e16",
|
| 648 |
+
"553b1d7ee9a7455a8ca0aa71cfe1f289",
|
| 649 |
+
"5564981a8ee24599a780553fa2b88c90",
|
| 650 |
+
"557963f94d0043d9a991dc9306cc7e73",
|
| 651 |
+
"55de8d707c8a4bd6a92122c51bc00fa1",
|
| 652 |
+
"55ee397cf40c4f86af1ee31fb2096d82",
|
| 653 |
+
"560055d1f3294a0ba6586957521cae11",
|
| 654 |
+
"56280d1b819d47d081646a509b4ea50c",
|
| 655 |
+
"562aa156220a4c88b34ee564657c1119",
|
| 656 |
+
"563ab2826c5548d1878213506475cb5f",
|
| 657 |
+
"56591b27807d43d5ab96704449412667",
|
| 658 |
+
"5684c00652674451be036fc5d7dd7217",
|
| 659 |
+
"56c1e1a540a8451bb84c759a753050ff",
|
| 660 |
+
"56c2dc07f2674b80b1cc42b5cf9b4c89",
|
| 661 |
+
"571125aedf354c60a9efb91ef5c789ca",
|
| 662 |
+
"574728d8f8c248e59932f314704503a5",
|
| 663 |
+
"57477e790a354b07bbb319588992f06d",
|
| 664 |
+
"5780da17fceb40edb810cec008acada0",
|
| 665 |
+
"579082d20a7c4b37a9764c84f602c59a",
|
| 666 |
+
"57a9862778bd4a5c95f4d2cd2819763d",
|
| 667 |
+
"57deddbbaf4445c888b229660cea5e28",
|
| 668 |
+
"582f50faf1734840a0d2694a5f869157",
|
| 669 |
+
"58baf3bd5f2b453aae930394725fc228",
|
| 670 |
+
"58cd53de211e4a97b6172c43b82aafca",
|
| 671 |
+
"58cd73335e334ee5a7382b018f9285b4",
|
| 672 |
+
"58d223c211994c8b9f691c69a3742250",
|
| 673 |
+
"59037103a2664590af810ccdd0669c0a",
|
| 674 |
+
"593975b416ec47a8b9d04925bce78690",
|
| 675 |
+
"599261e8b6fd43c280da33b32cf6925d",
|
| 676 |
+
"599a25f9afa54718b686fbb5069747a3",
|
| 677 |
+
"59b1014a0d8c48519d210230045b7c1d",
|
| 678 |
+
"59c577503fcf43a992d061f76fd0e4b2",
|
| 679 |
+
"59cb77de6a8c417a90db182c30a60135",
|
| 680 |
+
"59ed5e0f87c349909aa621ddf27c0036",
|
| 681 |
+
"5a05dc5c2f8c4f0ab67fecd33ff08249",
|
| 682 |
+
"5a561e2ad060465fb9452eba307a76b1",
|
| 683 |
+
"5a8243d749264f319b2c9a9a985db956",
|
| 684 |
+
"5aa35c1999b34c21b97491698128775b",
|
| 685 |
+
"5aae6d93c5cd48c8a39bd561f0e411a3",
|
| 686 |
+
"5aba0956e8f048dead78bda3bf8b2ce1",
|
| 687 |
+
"5ac666618edd491b830f5993a1a361a8",
|
| 688 |
+
"5b313936cf1b4412b7ee0c418745308e",
|
| 689 |
+
"5b316b26d0f844fc987825b975adcad5",
|
| 690 |
+
"5b6f1f3e3d444e1bbf4d737fd262743b",
|
| 691 |
+
"5b7a57bede754e16adacaae90f9dcfd0",
|
| 692 |
+
"5babcc376eb744d894cecedda6a22e4b",
|
| 693 |
+
"5bb9ff97fb9f4e93aaa1f41ba7a47be0",
|
| 694 |
+
"5bf4fcbbc6ea40b69c734f54180fa38c",
|
| 695 |
+
"5c10edda4dc44aa482827753be742d82",
|
| 696 |
+
"5c231d0b680d4f04ae334907b4d7dc18",
|
| 697 |
+
"5c45848fbc8d4184bedbec4c3162d1ff",
|
| 698 |
+
"5c88598e4d65455796c32745d1172865",
|
| 699 |
+
"5c92fe5c54e44f9f95da2cf2e7a3e700",
|
| 700 |
+
"5ca514218156463f922e7b80ec1ed5fa",
|
| 701 |
+
"5ccc35586cb843c48b0b4fdbe16c46d9",
|
| 702 |
+
"5d1544f61cc74047a2453d55791882ba",
|
| 703 |
+
"5d6b77a9180043f0b2dcabac21bc27a7",
|
| 704 |
+
"5d74e5db6e554e0cbca82346602928ed",
|
| 705 |
+
"5d92d3a2be7640a190efee26d69d1347",
|
| 706 |
+
"5db5890c5fe7473aaad2b7507969ac66",
|
| 707 |
+
"5dd9ae47211c476f8d4226147fb1ed68",
|
| 708 |
+
"5e579948ac0b427da1b88306fc65f581",
|
| 709 |
+
"5e972cfb6d42405b8ac32477711bb22c",
|
| 710 |
+
"5eee9d9172dd4738adc6a1916f53eb1f",
|
| 711 |
+
"5f05a5cf07e6423ba546642af5debcab",
|
| 712 |
+
"5fc5ea17fa3043e196d3febae7802329",
|
| 713 |
+
"5fca91a6280d438a9512c21ba8d27e63",
|
| 714 |
+
"5ff9f5f3cbed4312b8db212c25457050",
|
| 715 |
+
"604a8cc0467d47e8b473d8aeb85f5c3a",
|
| 716 |
+
"60586aa3584d4f2d9f2a717d5f728c84",
|
| 717 |
+
"607d8d582d3545a5a11f1209863e25e2",
|
| 718 |
+
"60d5ff8cf56747fba0864be02022ef84",
|
| 719 |
+
"6119e54641db4c1bb3110186d66dd21d",
|
| 720 |
+
"615fd1374bea4eb6bc1689de109f3b23",
|
| 721 |
+
"6161c738388641b5b0803bc23111650e",
|
| 722 |
+
"617015b472d641a9b57269f1136337f5",
|
| 723 |
+
"6175a1681d0648de97b2d62730b2317a",
|
| 724 |
+
"6178e02461f9419a9d8fab1a28ff5c95",
|
| 725 |
+
"618758dd359e415eaa9dacdc212d00a2",
|
| 726 |
+
"61a66adce11d4093a269f92ee615b3e4",
|
| 727 |
+
"61acbe77d630470496e51d0c932ae5b3",
|
| 728 |
+
"61ba551451894a19855a6e9ac1c8ffeb",
|
| 729 |
+
"61e13992d9ff4af7bfbce8f08c23f0b2",
|
| 730 |
+
"6211a8e24151428a9ebef3678fd4b6ed",
|
| 731 |
+
"621948ce0a564a30857c3716ac7ebd0b",
|
| 732 |
+
"62260f2abbff4b1b82e982b515fcc652",
|
| 733 |
+
"6254e180378b416b8aa18fb1b5284c39",
|
| 734 |
+
"6267ab80b28745a68d6d1357e1e0543a",
|
| 735 |
+
"62a4c16ac8084bf88aef8daba8f15249",
|
| 736 |
+
"62c30a8ddb7c41c0a627e602c23fdd4e",
|
| 737 |
+
"62d3322948f849099efd16281a1079aa",
|
| 738 |
+
"62ef67ed44bc49caa8428116257f64f2",
|
| 739 |
+
"62f162c31a404e8d88c7c691c886b6de",
|
| 740 |
+
"634e34ce3c0b4d75addce41c96e7b5ba",
|
| 741 |
+
"635453e91f0f47b5bd81a2f2fdfa91b6",
|
| 742 |
+
"63c36c9b479c48dd9df620d4bef2a5e9",
|
| 743 |
+
"63ca6b766c004b2d917e871dd863b8c9",
|
| 744 |
+
"63e0c9e309df49f8a209229321153742",
|
| 745 |
+
"63ea7be8390a40938613da78ec736038",
|
| 746 |
+
"640b33ee7d784da68c25208c843b5419",
|
| 747 |
+
"6466a4a5cff4418a9d7f4e4f2415d282",
|
| 748 |
+
"6470a004a99b40ef859df84f045c6654",
|
| 749 |
+
"648b81c7b0ba432a865131844b749971",
|
| 750 |
+
"64998ee900d641d2b5096caaa5cdf006",
|
| 751 |
+
"64cc765c1dad479a8b19844775a2aef6",
|
| 752 |
+
"6592c60e5caf48d0b92458bb9e086bc7",
|
| 753 |
+
"65972fba83074d5581abbcc66260999b",
|
| 754 |
+
"659ff4224a2748bfa49ebd4791471530",
|
| 755 |
+
"65b130cabf4446d292880a00b4363555",
|
| 756 |
+
"65ce677e4d8142f89db7c10bbe0f16be",
|
| 757 |
+
"65cfc4b3a40c487e94254f2844eb86f6",
|
| 758 |
+
"65d2c1ff090c44ec8178ff7d14e9d7ef",
|
| 759 |
+
"65ee1e9da62d42609c533716bb98a411",
|
| 760 |
+
"6602fa4a62774841991c5c35783d884b",
|
| 761 |
+
"6613e638925247d6b90e31761dd04e00",
|
| 762 |
+
"661a23e81a5342d1a8225bf078d5c028",
|
| 763 |
+
"664b09353cc342159eb5039c0e8c5131",
|
| 764 |
+
"66cb2ab19a1e4320aae97049c19ef521",
|
| 765 |
+
"66e1041a107248f9a6a154c886b0583d",
|
| 766 |
+
"67175ce64e6b47838ec0eb90bbf4233e",
|
| 767 |
+
"671af27feac14c39826b6d6704806a4c",
|
| 768 |
+
"67a7bfd33fd04a7d91c127ca468bf047",
|
| 769 |
+
"67b08bb5755643c6b4a8e0aa409eed04",
|
| 770 |
+
"67b2d6983ae0405daa199b5ff2d06c5a",
|
| 771 |
+
"6802cd54a7c44ca586a89435e317af5d",
|
| 772 |
+
"68055038a07e4819bd38bc3ff3ee39be",
|
| 773 |
+
"68097329eafd41a4bd1d5f02c1926a72",
|
| 774 |
+
"681fff8a91354f71ad73e8622a28c588",
|
| 775 |
+
"68268b376b174123bf753f0beaf195f6",
|
| 776 |
+
"684b3d859f924c229ef67d0bd8aaf4b8",
|
| 777 |
+
"686496a328f04a42a54ca48c8c1e8c0b",
|
| 778 |
+
"686d33ba777740baa87ec2583a076dcf",
|
| 779 |
+
"6879bd0426894258b7118bfa74b612a9",
|
| 780 |
+
"688c0525bd564087a24e55a19b45e8d0",
|
| 781 |
+
"68b6a8afb6da4e829cbede4e50235db2",
|
| 782 |
+
"68dd5c8e5c7245548edcb533087bdbad",
|
| 783 |
+
"68f783c1fd6c49edb6ebc3478a30fb85",
|
| 784 |
+
"690832b29fd74f80adfb9c70a72aac8a",
|
| 785 |
+
"690afb5cd5114368939b5fb4728ba516",
|
| 786 |
+
"691c480c4e564fac90d64efbdc6a1a8c",
|
| 787 |
+
"69368f6373104f7dbd1fc3b9e5fde801",
|
| 788 |
+
"6941f28a27b742b08dadbf48b12ddf4c",
|
| 789 |
+
"695c62069ac44614acbfc6effcf7499d",
|
| 790 |
+
"6984a2f59b2642e2b021c1f1883d0c6c",
|
| 791 |
+
"69ae6c5ba6574ba1b9cf67ffd3052a79",
|
| 792 |
+
"69c164a0131f4170a6d747eaad66b458",
|
| 793 |
+
"69e273358c994685885a5cf43cbc2a87",
|
| 794 |
+
"69ee8a7b246948d3a8c4d00f5fe99548",
|
| 795 |
+
"6a0987a9fc1b43518a651d77a362fef0",
|
| 796 |
+
"6a1ac7dc4a72440f8aab912cd7c8c639",
|
| 797 |
+
"6a1c75c5fd984767ba49d3ae3ddd5fdb",
|
| 798 |
+
"6a35f92b163d4a54a4c227180262725b",
|
| 799 |
+
"6a65365b39a94b8f87bef73242a8cd4c",
|
| 800 |
+
"6acaac9c73214edeb4145bb3d4d75906",
|
| 801 |
+
"6afefa97b3e14024a0d498310f8fa3b2",
|
| 802 |
+
"6b00a054ee2e4cafbf126d6e7aceb935",
|
| 803 |
+
"6b023e2439a8440e8fe1f263ccbe489d",
|
| 804 |
+
"6b33d7c1a28c4459a1058b829b1d8022",
|
| 805 |
+
"6b37230169d44874998e0ddcb106ca8e",
|
| 806 |
+
"6baf3fa1245e4039baca7109bf602dba",
|
| 807 |
+
"6bb4b73d2daf4ca39f9044171c9c474c",
|
| 808 |
+
"6bcaee578c8e4dbbbaf5fdb746e5ff8c",
|
| 809 |
+
"6bd2f94a87ca45209520b431749b282c",
|
| 810 |
+
"6c04b2c2c8004dc18817e522e5543589",
|
| 811 |
+
"6c44adc2475b4710817508ef565f5cbf",
|
| 812 |
+
"6c574b913a84456a87dc631a0dfd69c4",
|
| 813 |
+
"6cda14331c9c401f8586ed9e93626594",
|
| 814 |
+
"6cdb44a5385047f48b2dcdf1c9edb771",
|
| 815 |
+
"6cdd6964383e41778e517abd6d11b3a7",
|
| 816 |
+
"6cf216e7c4b44eeeb8c32286c2ad3508",
|
| 817 |
+
"6cfe3dcd556d49e0b953042ec327b22b",
|
| 818 |
+
"6d38c30873c542eb984deea1be3a5fef",
|
| 819 |
+
"6d4f5b7a603b43ec80dd8689ebf092ec",
|
| 820 |
+
"6d6c3b8cfa54457a8ffaeb520744c0d9",
|
| 821 |
+
"6d6dc89a87554b58b1cdd477e2c5bee8",
|
| 822 |
+
"6d8bd1fa499b4815b234ab46244cdf83",
|
| 823 |
+
"6d946bfd1ab44200b97f63a39416eaf7",
|
| 824 |
+
"6dabbf3037f841a2a41cf7a6d4639324",
|
| 825 |
+
"6daf0cd669934d1cae88e31fedfa183f",
|
| 826 |
+
"6db9e7aad9d94e5bbf375ad5e23be4ab",
|
| 827 |
+
"6df9eea2a5be4f8fbec679cf1b34c059",
|
| 828 |
+
"6e444e52e656441d82394022f27053c4",
|
| 829 |
+
"6e68d8a8a909449991623605a46ff62c",
|
| 830 |
+
"6e9a71be6fb64785b09f2a4bbf2abcdf",
|
| 831 |
+
"6f29a99af529414a8c98f208dd9ef90e",
|
| 832 |
+
"6f2a81b4544c4518b705f6cad6244597",
|
| 833 |
+
"6f47d38de28b4a879481850b68bca501",
|
| 834 |
+
"6f5a60845785415eb34b556c65e290c5",
|
| 835 |
+
"6fde18e184154bfcb1c662333f54fb03",
|
| 836 |
+
"6fe0e1ee53eb4045b6d1a37f39221ad4",
|
| 837 |
+
"7054d2d8710b4213ad3857c1e37e57ec",
|
| 838 |
+
"7071b9cf40654ca68a618cc3aa3e8731",
|
| 839 |
+
"7095ad6338ec49ab86f026c5aabd1a2e",
|
| 840 |
+
"70a9556f1f2848eea9c1b4999c1d1d98",
|
| 841 |
+
"70c2c16114654e7e924f6254d4143c80",
|
| 842 |
+
"70c42fc642394764822e47f490c26471",
|
| 843 |
+
"70df779f759a471386ca79f735d0ccf8",
|
| 844 |
+
"711acde9db7149b79c6b90c8f9a736e7",
|
| 845 |
+
"711e1a7e980342c79428ef608d67653e",
|
| 846 |
+
"71416aa96c174625b4fd7b4fe5ed203f",
|
| 847 |
+
"715b5bd2832145c7b86ea7f785a2b73e",
|
| 848 |
+
"71893dc577d2466298b045f965482fbc",
|
| 849 |
+
"71a32216dea14f259a04745594bc1a69",
|
| 850 |
+
"71d34d3c50c84e9f8a7a22dfc6bce68d",
|
| 851 |
+
"724432b2187b496dabd74c8ffd119c1a",
|
| 852 |
+
"7291e2fd86b84a05844d23c4ed75d1f4",
|
| 853 |
+
"729f690692a5421592e8ab3217426de0",
|
| 854 |
+
"72d16ce9a267457680325b448853ff73",
|
| 855 |
+
"72dfd7ef4b564a55b5e1ab13da8d6a55",
|
| 856 |
+
"7328466df47b4c2c88c5194a26de281a",
|
| 857 |
+
"736bafbf63a045f080528e4f0b8cb552",
|
| 858 |
+
"739415ddd194434f81777eef8b041bc3",
|
| 859 |
+
"73f80ae78964472bbda19328b354fa35",
|
| 860 |
+
"742fbdd5bf314a488494a4afdba92b25",
|
| 861 |
+
"744715eecfa4452b8624e955679c3068",
|
| 862 |
+
"749ba33db8c243d986c864afe3955731",
|
| 863 |
+
"74e2f89c66c94d788a840ef7dc3cfc4e",
|
| 864 |
+
"750d9b866e5645ba9709bcb1dc9121ec",
|
| 865 |
+
"753427678aa74a248ec40c1a35d2c05e",
|
| 866 |
+
"7574c2f8e93a4628a00ca6fd58255e6d",
|
| 867 |
+
"75c60cb24bfa4ec4836f81a6ce6e6103",
|
| 868 |
+
"75ebaecaeebd4ccabd71729b296dc6e2",
|
| 869 |
+
"76069f879e974bdeb43af713082f2a21",
|
| 870 |
+
"761d9a3648eb4139aeb4c47cf93cfd1c",
|
| 871 |
+
"761dd9c19dbc4bfeae9d08c06722710a",
|
| 872 |
+
"761e5907082a4044a8417bfdf7ee4c66",
|
| 873 |
+
"762dbae73b7d46688d8b3657b7196423",
|
| 874 |
+
"7641ca70948247a48f57a8abb70db602",
|
| 875 |
+
"7684d220b9cf49c9ba1bd6256aa0416f",
|
| 876 |
+
"7689b79133234e5c9ccc5e881f8b8a7d",
|
| 877 |
+
"76a5864ca4f74c83a125041af4daa586",
|
| 878 |
+
"76fa02bee7e34413bd1f2af04a706107",
|
| 879 |
+
"7722d01005314531a1551a890127fc6b",
|
| 880 |
+
"7730b7282b254cd0a4f6163790e0bff2",
|
| 881 |
+
"7746283c634c4855b7e2b08351ab44dd",
|
| 882 |
+
"7758742ed2dd49f49c8a107c9845f597",
|
| 883 |
+
"775975bc98294259b76c44b0d75e9d58",
|
| 884 |
+
"776c21864f8341de9ee7454aa5b220fe",
|
| 885 |
+
"776d4b1c05ff463593d46fe3aad1f1a5",
|
| 886 |
+
"7777f9563f734375abf37aec8c4ff55e",
|
| 887 |
+
"77a20b0c965f4422885f558e94f69f87",
|
| 888 |
+
"77b1336e9c2042fabb5a7175427d5ded",
|
| 889 |
+
"77bc75c7ecbf435da1724062eced10c2",
|
| 890 |
+
"77bd325607974c4c8e3db77a0814842d",
|
| 891 |
+
"77ca17defa6845f8bc09fa350fd8c422",
|
| 892 |
+
"77e19e91f00e40a983d4d41ccb524ec7",
|
| 893 |
+
"7833f24887b14d1b9d2f98e9fd2e744b",
|
| 894 |
+
"78506b95bd5d4132ae00344e4c147d60",
|
| 895 |
+
"78514023844c4401a61b259630a39b88",
|
| 896 |
+
"789e28c00c72440d93b494bbcabf6bd5",
|
| 897 |
+
"78d89086a6f64b89b85158987ac366c0",
|
| 898 |
+
"78db1c0163b74cc8947edeb42dd79edd",
|
| 899 |
+
"78dbcac5eb3d4c9dafb9226cd78ce9ed",
|
| 900 |
+
"78fc5eaf6edd4c7f80fad7ed953822d6",
|
| 901 |
+
"790c40ccff6843eab0b7b4bd18421ff8",
|
| 902 |
+
"793d83afac59488cbfeee6ec4b0c0c18",
|
| 903 |
+
"794712e7d8a14367bafbeb2864f2695e",
|
| 904 |
+
"79477474139945a5a5a0164c8fa1b465",
|
| 905 |
+
"7973d98b789b44deb93c76aacbb8e66f",
|
| 906 |
+
"799b7ba3c0f242ba8d78dea38a54b916",
|
| 907 |
+
"79bc0a1e5ad2453b8ca761b7a12063e7",
|
| 908 |
+
"79c293346e5247ca84b31c4a6ea23131",
|
| 909 |
+
"79ce537a22044a3494b477739a650889",
|
| 910 |
+
"79ddf53a0a8b42b59540b29c42458620",
|
| 911 |
+
"79ef8744e7974cd69ba6c992f0ebaf1b",
|
| 912 |
+
"7a0496b2bfc5419e8d2e0b9b6cbb500b",
|
| 913 |
+
"7a6c27097bcc43859f146eacf34945c3",
|
| 914 |
+
"7a9f728fbf37420f9bd68cb3be8a2e5f",
|
| 915 |
+
"7aa8750fd1aa4dcd89a5c039caab5940",
|
| 916 |
+
"7ac460ae0e2d44fe977db67f4e90ec27",
|
| 917 |
+
"7b090422e3b745d2ac7632838adc2793",
|
| 918 |
+
"7b48292340ed466d831ef99b17df227d",
|
| 919 |
+
"7b863e9c3fda4147a35a2b5e4421dee2",
|
| 920 |
+
"7b8e7485eb0a4e9fb1cff992765f5be7",
|
| 921 |
+
"7c05c683425740febabd29a937de624f",
|
| 922 |
+
"7c1582b839fd4b318bfcc976e94e6c25",
|
| 923 |
+
"7c2652a35dbb48deb82bd6e87693a12e",
|
| 924 |
+
"7c65246246f74b7e83fb720fe062444c",
|
| 925 |
+
"7c66faf7b6104fa1b29035e3b5eb7102",
|
| 926 |
+
"7c86cf1acb4f4f7c9751c16101cc62ea",
|
| 927 |
+
"7c954751f56d4715ae7ce99aa0158b78",
|
| 928 |
+
"7cec9c91c93e48b185adb045404e8f2f",
|
| 929 |
+
"7cf2a742340647608e9ef7af049b6ac0",
|
| 930 |
+
"7d34a706b55d4acebea87abbe2e67d79",
|
| 931 |
+
"7d4dc0e32cd545109ed8a00bd85a7840",
|
| 932 |
+
"7d996ae0f69945e9a87fc3c542ce2683",
|
| 933 |
+
"7d9f4c78ef6f486fa94e3581548ed5bf",
|
| 934 |
+
"7dc5903c241c48d9989e10396e82f90e",
|
| 935 |
+
"7de0fde2ea434e378c0c948abcf5a91b",
|
| 936 |
+
"7e0ea5afa2464e3190f141d983382b6a",
|
| 937 |
+
"7e1fae1510be4ebeb41d0e7fca9b2fe9",
|
| 938 |
+
"7e45ad634364474798ba58c0db46ecde",
|
| 939 |
+
"7e464462d6fd463e860f91f70db01848",
|
| 940 |
+
"7e6f3be701224306be49375af2e39025",
|
| 941 |
+
"7e7703b966f44564a93b54cf5d61d2ab",
|
| 942 |
+
"7eab76e784054c7fb23afb9bf93feab2",
|
| 943 |
+
"7ec3859752304e1497fcad1dbc8fd27f",
|
| 944 |
+
"7edddfba4b51475f9438cfd8043ded86",
|
| 945 |
+
"7eee9870d8664dee9966228052d249ab",
|
| 946 |
+
"7f74cf149f434c378816bbc17084323e",
|
| 947 |
+
"7f7d9831de524660b6e2428ac19a674f",
|
| 948 |
+
"7fbd63691be243e996af064ba8e196fe",
|
| 949 |
+
"7fbfd3bef2d043729900839e6ca21355",
|
| 950 |
+
"7fc0b31e990c435ea4029ad2a1670cec",
|
| 951 |
+
"7ffeaf23240141f5b0974abb74c0e452",
|
| 952 |
+
"801da8da9e244c06a5af7b48e052b365",
|
| 953 |
+
"8026dc5f17254c9dbaeae2ee6e4f2779",
|
| 954 |
+
"8034f1419ad5453a8d79d9d641b3ef38",
|
| 955 |
+
"803cf6b3a1ac4de9b3306e9c119516c3",
|
| 956 |
+
"808cfda3601a4d978b2591133204c844",
|
| 957 |
+
"8094f24d861044c3a03b0fad4e8445f6",
|
| 958 |
+
"809fd40376ce47deaf0cba856ec2785d",
|
| 959 |
+
"81e14da144654169938f316d9962a27d",
|
| 960 |
+
"81ea8c1314fc4517995b7ce5218c676d",
|
| 961 |
+
"822dd26fa82143c98eb769729c245843",
|
| 962 |
+
"822e9386244749c98992fb5c09029c17",
|
| 963 |
+
"826d09ed54ab425a83ba09e82f082c70",
|
| 964 |
+
"82919f5c92a04c7cbabaed43ad02c33d",
|
| 965 |
+
"82de9078a05444e7b24bdca8f45b08a1",
|
| 966 |
+
"832e6fddc6104248bdc37545c7b506b6",
|
| 967 |
+
"8337f24d481b4b62a528b25ce544158c",
|
| 968 |
+
"83479a56bcfb45078bf65cfdccaf895b",
|
| 969 |
+
"834858034a794ce59d428379dd9b0f1f",
|
| 970 |
+
"83781a711aa9491f96bdeee95c819715",
|
| 971 |
+
"83ba11956b8c4c2ba48f00b27d3e076f",
|
| 972 |
+
"83e0f5401e15427a90cae22d0ec8fd02",
|
| 973 |
+
"840a2c9386c242d88bd1a6f8a3bf491b",
|
| 974 |
+
"840f27f03b464cb88423df7afb28c9ca",
|
| 975 |
+
"84a6575ac1d04589b7379f3b73944e2c",
|
| 976 |
+
"84a997ff428b4afb87d5e9ac3213eb09",
|
| 977 |
+
"84acd48c7e17498ea1d486556e635ba3",
|
| 978 |
+
"84b15921c86b4f7ba1bdbec7c854854b",
|
| 979 |
+
"84c113fd7225428c8aa8db4f28563b2c",
|
| 980 |
+
"854dbbb2ab994ec8a1b46289a333a9b4",
|
| 981 |
+
"85b31e7e54b74bcc92fdf9bf5dddd4cf",
|
| 982 |
+
"8606f632251943fab5c37de86ce86e44",
|
| 983 |
+
"8616350c05974bc28c6ae44ac788d8f3",
|
| 984 |
+
"8657d2c5e7734a1aba0b71abcb0e04ab",
|
| 985 |
+
"865ef12fcce64e61b97b83b04794d07d",
|
| 986 |
+
"867410393151465c8b48b4333602358d",
|
| 987 |
+
"86a5abfa102948119febdec47db00706",
|
| 988 |
+
"86c0e546489a426c941f26d522621c93",
|
| 989 |
+
"8700abc1bd4c4a90b97e6c462503cf88",
|
| 990 |
+
"8700d16e247d4a51956ef904643b95fa",
|
| 991 |
+
"8708af3768a743e7bc0c0b2c3bd2c5c7",
|
| 992 |
+
"872d01ee121c4d1ebfd38c5c2616e181",
|
| 993 |
+
"873144f3192a4b4490675151af5529a6",
|
| 994 |
+
"8772d82deffe4b8c868641618049bd3f",
|
| 995 |
+
"878781513c784e8c915a4df019faf22a",
|
| 996 |
+
"8795471e399040f6bf10813221f6dc22",
|
| 997 |
+
"880fa188c1704773b40aa222dcf485d2",
|
| 998 |
+
"881a9a5f16f04cb68ad1dfb5e3bec34e",
|
| 999 |
+
"8846701f077241e6bf4090b5abb19919",
|
| 1000 |
+
"8859ecb3dafd4bacb977839427e43317",
|
| 1001 |
+
"887796ef013c42c9837320edaa316d07",
|
| 1002 |
+
"888947939fb743fd9cea71a2a5c07684",
|
| 1003 |
+
"88e140a5035f4ab093fb62e494675b8a",
|
| 1004 |
+
"88eec6b796df4444a7b24383369f350c",
|
| 1005 |
+
"8926de526bdb487a94e3c3ce3dd49935",
|
| 1006 |
+
"89301025dd73470c89fd328407a12011",
|
| 1007 |
+
"895a7a7d01f64785b03ee102c0344371",
|
| 1008 |
+
"896222fc3f2c454ab9cbe701bdeb7b33",
|
| 1009 |
+
"897be755cbd04f1a8f33ea0ec852a280",
|
| 1010 |
+
"8983e5d47f12435bbd1189168b75abeb",
|
| 1011 |
+
"898feb7c028c4763b9627543038f5ea8",
|
| 1012 |
+
"899d758d99294a019ca24fb848d0a60c",
|
| 1013 |
+
"89f6779a95804832be2b52d2b1efd273",
|
| 1014 |
+
"8a20e56e69e14851bb157a3d0ce2eb9f",
|
| 1015 |
+
"8a4b9859fd6b4ce1a9619cee2dcd3e42",
|
| 1016 |
+
"8a547261211b45ca96a852ca1dc5ba61",
|
| 1017 |
+
"8a7d086e0a8949cbb9341f0763c98265",
|
| 1018 |
+
"8a9edc74f8444057a0a17d4a42ad3aad",
|
| 1019 |
+
"8ab0fec878924fd295e705ae080ce9a3",
|
| 1020 |
+
"8abdc0b307634afaa099b9e38132c151",
|
| 1021 |
+
"8acc1b8ae90c4ed78f9fc91285dedbe4",
|
| 1022 |
+
"8acd766e66b6478a8843f03ebb085ba2",
|
| 1023 |
+
"8b224a3b0b8d46c98e8cb572453d0eca",
|
| 1024 |
+
"8b297620c9664c09bd216395868e3fc2",
|
| 1025 |
+
"8b34d4b347154db5adad199575cb8fec",
|
| 1026 |
+
"8b39b8957231420d919b60febb627bd5",
|
| 1027 |
+
"8b7a073c112c48fbbcef3e02a14eb619",
|
| 1028 |
+
"8b8b6a5ba71741e39934826d268a09af",
|
| 1029 |
+
"8ba53f9043e64e89a7b7abb45651c932",
|
| 1030 |
+
"8c07ef9c69b3482a86904cf6f6d12457",
|
| 1031 |
+
"8c10d8b64f1d40afad35ebd9dc414177",
|
| 1032 |
+
"8c564dccf3184669880b729e36541c3b",
|
| 1033 |
+
"8ca5fcbf41734109928e08c3ba03b96d",
|
| 1034 |
+
"8cb42925a5c5487596ef76b27d3d373e",
|
| 1035 |
+
"8ccecf650a4a47d68f7d2ab27b87850f",
|
| 1036 |
+
"8cd40f962f2c440c9caf1db1a505357f",
|
| 1037 |
+
"8ce3d9559a134bb1895654770f721956",
|
| 1038 |
+
"8d7f958e192b454fbeacd2f789dd4cd0",
|
| 1039 |
+
"8d96c299671c4116bde407991e2ff04c",
|
| 1040 |
+
"8db2af4b6a474097b946b767dfff8a8f",
|
| 1041 |
+
"8db69d80a04e4afd82be3f95a66c9dde",
|
| 1042 |
+
"8de9c1b24cc741b19eed5e6ff572383f",
|
| 1043 |
+
"8df86390e8c34a42ae64100f03d2384e",
|
| 1044 |
+
"8dfbf3c06ea4426d9bb9ff7ec125393a",
|
| 1045 |
+
"8e46cd880daa4cb6ae7e18897cf98180",
|
| 1046 |
+
"8e65882e534245c5b283577c5521acaf",
|
| 1047 |
+
"8ea24adb9e9e4cd2b429b132ef049052",
|
| 1048 |
+
"8efa4f8b34d042d78b1512fc5135eb1f",
|
| 1049 |
+
"8f0d6e7582b44bb9b4a81e2f18b2584e",
|
| 1050 |
+
"8f1cbc19dc414ad89097eabe063ada88",
|
| 1051 |
+
"8f285bed98a641b383a45e7fe970175a",
|
| 1052 |
+
"8f31a85c22d14760974c23535d32c3f2",
|
| 1053 |
+
"8f33734764b64263b998610438addfe4",
|
| 1054 |
+
"8f485e3c4d7f4cf291869b53d4bfb04a",
|
| 1055 |
+
"8f79577525dd4c81bff24b86848785e8",
|
| 1056 |
+
"8f8a6439e1c64106afa27b28936d5ef8",
|
| 1057 |
+
"8fa283778cf84b6faf26114a40289e60",
|
| 1058 |
+
"8fb4d6efb8c94cfb8c5b6f986a009726",
|
| 1059 |
+
"8fc9e1a422c24deebbc955e871693709",
|
| 1060 |
+
"9055288483244d228a5d183b206ab9ec",
|
| 1061 |
+
"906df3a351564d2aa34e2fb6a85b71ba",
|
| 1062 |
+
"90789439e18c4f3cbdb5c51b6c43b531",
|
| 1063 |
+
"90acd51924864030ad8e019b332716e4",
|
| 1064 |
+
"90c364a220514d20b9be9e06c1a0a184",
|
| 1065 |
+
"90e943ba104e45f5bf8edb878d99ab95",
|
| 1066 |
+
"91277c9935a745ac83507bf6a1f1ce0a",
|
| 1067 |
+
"91413c86c92d47c4a61afc7e1875b1ac",
|
| 1068 |
+
"9228178641df4a4cac543f129d526b16",
|
| 1069 |
+
"92384abd65874b8eaecee88cacc299ae",
|
| 1070 |
+
"924d926858e84e6cbe124cc5e4d3d797",
|
| 1071 |
+
"92acb79620954d7faafe1b33f4ffadb9",
|
| 1072 |
+
"92ba44c964614eccb41c7b0b2fe9ac5f",
|
| 1073 |
+
"92cb6892fd2e46059a9fef031aa3ac11",
|
| 1074 |
+
"92fc926cadae41468a3976b931c58ba4",
|
| 1075 |
+
"9310ddb8efff44b3b59e1d553ce18a2c",
|
| 1076 |
+
"933435c0db8640ffb17191b029727048",
|
| 1077 |
+
"9338582032f348a49da8be159a650699",
|
| 1078 |
+
"937718e4bfb040d08ecfb57f88028cd9",
|
| 1079 |
+
"939c65e55bec4a27978f1480462d78de",
|
| 1080 |
+
"93a6d86f97ff49e18220c8d5af6f2410",
|
| 1081 |
+
"93d57f0c4ae04a89b698b84a5cc28620",
|
| 1082 |
+
"93e9d54392d34f45ab3bb9183c9e721e",
|
| 1083 |
+
"94007567ad674848baaa184dd69316c7",
|
| 1084 |
+
"9415b5a3d80646a6a04c95021eead04c",
|
| 1085 |
+
"9417265e5b1a44c9b96e4ea1c3133356",
|
| 1086 |
+
"9436fc1799344a0a95133338978cb575",
|
| 1087 |
+
"9442cb6575b3424fb608b1ef883f8ae4",
|
| 1088 |
+
"9458727177e44b6194f518712be17d61",
|
| 1089 |
+
"94c25097256547498d3119e1d3442aca",
|
| 1090 |
+
"94c3787a948349f288a83a9a7accc76d",
|
| 1091 |
+
"95028bb2100d48418161062f2d84e637",
|
| 1092 |
+
"952bcd70171d4c4b965192cb284e2fb1",
|
| 1093 |
+
"956cef1ca1af49f5953fc202b20dcfb9",
|
| 1094 |
+
"957da1be796943dc988d10f5b087d8c8",
|
| 1095 |
+
"95b6ed35955e4d13af13584f79fd96dd",
|
| 1096 |
+
"95b6fbc975624eb1b88afb92e15658e6",
|
| 1097 |
+
"95c53161769f4c259afbb41bfc94f5f3",
|
| 1098 |
+
"95ef0d8332c44c8cb2892900d6cb27d9",
|
| 1099 |
+
"95f6f7d8904f4a719eabff7d38e022ee",
|
| 1100 |
+
"960bdb8733104752a1327d89d9fc8046",
|
| 1101 |
+
"963c49228f73402ab93c59c8d8af8380",
|
| 1102 |
+
"964b92911b4e4fbfbe82360ea1d476d7",
|
| 1103 |
+
"967b58b39a244dea8ccf934d6973054f",
|
| 1104 |
+
"96d5e95a6627478e9a9b7ce59738b1fc",
|
| 1105 |
+
"9701c9240e5b40e7b5da140716a78d0e",
|
| 1106 |
+
"9706a8b7365945ecb40fb57693c9b23f",
|
| 1107 |
+
"9747c1a7c1e54e079fc5b8ecff22b96c",
|
| 1108 |
+
"975bf43d39fd4440aafa511aa85c4693",
|
| 1109 |
+
"976564c01fb341e3b75fc4dcc4e96ba8",
|
| 1110 |
+
"978b2d75c8964f969c6513d140d15f0b",
|
| 1111 |
+
"98161ac528d8469da7f1e98d05c8b4dd",
|
| 1112 |
+
"9833113cb50c4e96b931e1500996d12f",
|
| 1113 |
+
"9855438bf7d14f9b8a83e779c03e937f",
|
| 1114 |
+
"9865812391e1410ebf320ba485cdc2f7",
|
| 1115 |
+
"98cafbdff0c54ddb9f4b26d6a79768fa",
|
| 1116 |
+
"98f31e11891d4b92b03ba96745fae9f2",
|
| 1117 |
+
"991635b829a944768c92b65587279051",
|
| 1118 |
+
"993a9c9ec519406ba3957046b8ed07f0",
|
| 1119 |
+
"996d7f11a517464eaca8ac6fced3b1c8",
|
| 1120 |
+
"9992ec94247644ec8f86e75159749f8c",
|
| 1121 |
+
"9994a32ac7f041258ce42db6927109df",
|
| 1122 |
+
"99b9da73ba7b42f19de8c3008224eb72",
|
| 1123 |
+
"99c8aa6c73f7482ca9008a97393f8a3c",
|
| 1124 |
+
"9a1f55860bb44de1bb536cd01cabd155",
|
| 1125 |
+
"9a42e5c2337d43a69b7def22736cb369",
|
| 1126 |
+
"9a49e336092d4e5aacc01aba739f601a",
|
| 1127 |
+
"9a4c584a1e444676960846d8bc4cbbce",
|
| 1128 |
+
"9a78cc1efe5148af93df636a777019c4",
|
| 1129 |
+
"9a7d317fea29496ba0b2c5cd83be473f",
|
| 1130 |
+
"9a89d1c9bda24ca1b777f5f0b4637bd2",
|
| 1131 |
+
"9a93c60936614cb7bebc4791f8c7b318",
|
| 1132 |
+
"9ad64e4cc1de4b719a669ef7caf6e329",
|
| 1133 |
+
"9ad868279fe746cdbf64afe5668d7b02",
|
| 1134 |
+
"9b12c91d3fad415392a3956cff795bb6",
|
| 1135 |
+
"9b693a5b97c54adcb815701889f7fe1b",
|
| 1136 |
+
"9b7ae513a2294711a9e083de1a4dcae5",
|
| 1137 |
+
"9b82427e59aa44509713f4d5b2ae5b3c",
|
| 1138 |
+
"9b8a6c99aec9431b811121675bba0f08",
|
| 1139 |
+
"9bab8b2b13094066a36af285c2e20a9c",
|
| 1140 |
+
"9bc7394303a34558b0c6324e56f47d54",
|
| 1141 |
+
"9bf986e0ca924aca92462c5bf271e3d3",
|
| 1142 |
+
"9c41cd8aa389471f9e95b6ed5367fb35",
|
| 1143 |
+
"9c4b477d7d834ecf8b15cae2d2883438",
|
| 1144 |
+
"9c8a9a46e86e4bdfb084c658eee53dcb",
|
| 1145 |
+
"9c90baefd9444fd0a8d71e789e22d548",
|
| 1146 |
+
"9c9c46a8146443deb081230ef202e987",
|
| 1147 |
+
"9ccadc2945c34826a0ce1c37de13675e",
|
| 1148 |
+
"9cd02abf1e3f43e388bdbee8562d5687",
|
| 1149 |
+
"9d5502124c954756a347d7ab07557b93",
|
| 1150 |
+
"9d7456f25298471aa7b736f2e1c3ffd9",
|
| 1151 |
+
"9db3ddd3c9d549f3a35b92288bbd6a1d",
|
| 1152 |
+
"9dcb7e0e87194b0a9d0bc81fb3685920",
|
| 1153 |
+
"9dd25a357ed642b1b2607ce2b1cfc0f3",
|
| 1154 |
+
"9dee38c8c7d544018dd60410c8dd7059",
|
| 1155 |
+
"9df9e5041a48418ca9691045b4e59730",
|
| 1156 |
+
"9e0bdadfc8c24565b1160497fb0f4f27",
|
| 1157 |
+
"9e1fd2f3d79348b9a45350c644fb2e29",
|
| 1158 |
+
"9e4011bc059c4a1db5f8bea8bb96484c",
|
| 1159 |
+
"9e4b555e7d4a4e9d896b284d8faa03c1",
|
| 1160 |
+
"9e6b76fab17a4b40ad79cb72a46f3899",
|
| 1161 |
+
"9e6db7d9a00a417c978a71b371c68a5f",
|
| 1162 |
+
"9e74bafc9d0e4d739b33a08e5361e7db",
|
| 1163 |
+
"9ee0770e069448f6ab316ef8300bd5cb",
|
| 1164 |
+
"9eedfb865cf84cd59a15e65f7f2d393b",
|
| 1165 |
+
"9f0a3283cbca4454bf04c8d43494247a",
|
| 1166 |
+
"9f24973fc9c344539a6105a1e7357fb4",
|
| 1167 |
+
"9f4c5635597e4edea48b6f069f12a154",
|
| 1168 |
+
"9f691379311f40289c6b661973eb2633",
|
| 1169 |
+
"9fa2da2c42234b58896e8d23393cac24",
|
| 1170 |
+
"a022864ae7b642a39469652c3eda2b9a",
|
| 1171 |
+
"a0426fb42d044ce386d55ed4594a328f",
|
| 1172 |
+
"a067690ca5ba4734a6cecb51816c534f",
|
| 1173 |
+
"a0ae47edf24b4e8ea3b4d66afd70a95e",
|
| 1174 |
+
"a0bf19176b5245bfa4704242fb16aee4",
|
| 1175 |
+
"a0d0ea06fcea41fd843af40691e85f34",
|
| 1176 |
+
"a0fd7b70748f469e8a984b84ae570d6f",
|
| 1177 |
+
"a117781bb5724467bdb321aa7ac02d1e",
|
| 1178 |
+
"a11b00033ce344aba75918fea71b1863",
|
| 1179 |
+
"a14e7cc8ae6643d69395276456d813eb",
|
| 1180 |
+
"a1613311507a4fc8b6a4f3d859a447e6",
|
| 1181 |
+
"a1754867f6094ba28b32cfd72dce0ff3",
|
| 1182 |
+
"a193d18c46084d60b2500cf235a41a89",
|
| 1183 |
+
"a19f6e77e5f244188ae31c800e17fc1c",
|
| 1184 |
+
"a1bce4d33d3d4d47a32745b785de6003",
|
| 1185 |
+
"a1c46bc271bf44b3afcc7320de94797c",
|
| 1186 |
+
"a1caf2c91b9745949ab91cdc9902fea4",
|
| 1187 |
+
"a1f6743d821c41028f095731fabf54da",
|
| 1188 |
+
"a21b111d6bf34c6b8e3e553f653f83fd",
|
| 1189 |
+
"a2296d354281424188ab006d3ccc8b0f",
|
| 1190 |
+
"a268989869814587add9bce658d79ea6",
|
| 1191 |
+
"a28a424008f04771865712e5e3054f04",
|
| 1192 |
+
"a28f97adf9ad47d6806a4d6d85706b81",
|
| 1193 |
+
"a29496f73923481fb509e002671e6a62",
|
| 1194 |
+
"a2d8cd051fe04166909a8eb3fd2af233",
|
| 1195 |
+
"a2f31b5a37fc44c49c11026cf2a97f74",
|
| 1196 |
+
"a304a6be438241c5bf8eb89f574e3d7c",
|
| 1197 |
+
"a30cdc34303c42cd953dc935e1bf2110",
|
| 1198 |
+
"a35c9d88cc9b48d099a69dd1d3aa69d7",
|
| 1199 |
+
"a3911e5dafc44f9fafde9e8d424f4a4e",
|
| 1200 |
+
"a391c14193234bb9b654848a2b386c22",
|
| 1201 |
+
"a3a5829c7e5141f3a4ac46740bb440f3",
|
| 1202 |
+
"a3d79ffe82f94c1daa45b8ffe4933851",
|
| 1203 |
+
"a3f3028abd154ce3804f8711c30d4f6c",
|
| 1204 |
+
"a4070f7a072043ba86a590b2cb48a45c",
|
| 1205 |
+
"a4197833548745c2b13ef53fb2cc4d38",
|
| 1206 |
+
"a4909ba08ad540c289d03c3c67b9d856",
|
| 1207 |
+
"a4b0fa883f0649f4968c94f3c421d493",
|
| 1208 |
+
"a4efba8b24f0499c8c248b0ea366db88",
|
| 1209 |
+
"a5023a5400274762ae9c128c07c82443",
|
| 1210 |
+
"a52bc5f0be6746888734e7b6277bfabf",
|
| 1211 |
+
"a55418a161a14c369d01fefbf7f4c442",
|
| 1212 |
+
"a5cf428b41374afdac04c62137c87fb7",
|
| 1213 |
+
"a5ed5a1f630b4d689b2d65e5ae8b56fa",
|
| 1214 |
+
"a6004ae37b2247208e80b8b8412f3b61",
|
| 1215 |
+
"a63b41bf6b364c9f9177c9b1e3da049d",
|
| 1216 |
+
"a63b931003a44d45aa22e9bad9dc8b1a",
|
| 1217 |
+
"a645ef2489244f52bf3ccf6836b60984",
|
| 1218 |
+
"a646038eb9ae434f80dc6dc726c1dda0",
|
| 1219 |
+
"a654dcfdd5744259a88885c20d14cd9b",
|
| 1220 |
+
"a65ec17538c3429ea50ceda913fa565e",
|
| 1221 |
+
"a6b9bca28b9f44429e41b815976584ad",
|
| 1222 |
+
"a6d34a86d9444ebd9690ea1ae8ba594e",
|
| 1223 |
+
"a700978deeb14f67b2710afaedc2a807",
|
| 1224 |
+
"a707582f74e74ddb86bb109bb956e750",
|
| 1225 |
+
"a71b6d54f71f43e28c019cdb42be55b9",
|
| 1226 |
+
"a7229ddcde4043f6a0e34ef3f6df19ff",
|
| 1227 |
+
"a734f7a8acfc431cb19993dd98a2998a",
|
| 1228 |
+
"a7352824623f4629976067a996dbc37e",
|
| 1229 |
+
"a739fbc9d88c4daf9f730dd9f6778c68",
|
| 1230 |
+
"a73d63f44fd54f209bb6bac37fe247b9",
|
| 1231 |
+
"a7780e3667954bbf97bb619cb8a3e272",
|
| 1232 |
+
"a828fe9dc5a9415ba3d7f977a9825ea5",
|
| 1233 |
+
"a82cb8c7613a47a693e084fa52d0d9bb",
|
| 1234 |
+
"a87a76e7544a41de971814dc9a19eb11",
|
| 1235 |
+
"a893146b76cb470b9056e6b88ba8afb0",
|
| 1236 |
+
"a8ad20910503482db578f9298afd81c6",
|
| 1237 |
+
"a8c375384be9427b91a4f68125c3942a",
|
| 1238 |
+
"a9161e6c2ec14d7aa9fe10194468f8d6",
|
| 1239 |
+
"a93e93e3e41e49feac04747c6f540b05",
|
| 1240 |
+
"a977c54c25c74a0c9fc0fc4de99ecd45",
|
| 1241 |
+
"a9a6555b9cf949d3a01003c3bed6dac8",
|
| 1242 |
+
"a9cd031ef92f459995d4d2e8595583bd",
|
| 1243 |
+
"a9ce75a690c84f06ba0934c2a27e7fcb",
|
| 1244 |
+
"a9ddc86165ba4b819360231d29b9defa",
|
| 1245 |
+
"a9e576e05442468a94e07d5c6a652b3b",
|
| 1246 |
+
"a9fa5d325b1f4b2b84c212faf1254ccf",
|
| 1247 |
+
"aa0183f6bf584115a7d3edeec4d7dac9",
|
| 1248 |
+
"aa2be996b12f431cbb9a214519b0923c",
|
| 1249 |
+
"aa5f73e3d7774a9c9e72b25db2b89da9",
|
| 1250 |
+
"aa7f2187820c49d886e40c69c24c10e3",
|
| 1251 |
+
"aab20be23bde45cbbf26a46f51a09e91",
|
| 1252 |
+
"aac11115274f4fc78265b0f04df2e520",
|
| 1253 |
+
"aaeb0e31501e4f6d8724b6065f7a62be",
|
| 1254 |
+
"aaf795d26d2045a2ad366a62f661ef6e",
|
| 1255 |
+
"aaff1dffbcc44f2e8ba35516509185ed",
|
| 1256 |
+
"abbf5216c6a44b70a7a26d35ced8e1c1",
|
| 1257 |
+
"abcb0be28b944a49887d49935464be9e",
|
| 1258 |
+
"abe0b76027f6445ea496499cab9bf2d0",
|
| 1259 |
+
"abef4ff1da264d88b10f99e910759ddc",
|
| 1260 |
+
"abf756cea6e34b14835b7c0dcbf07c3a",
|
| 1261 |
+
"ac3483cf825249dfab4fdc00fe8e21ac",
|
| 1262 |
+
"ac37aa4de6394799bd3c1143f0a0742f",
|
| 1263 |
+
"ac6ae15a135a4e848036c0b4eef86d09",
|
| 1264 |
+
"ac723edacfe94c9a9233de5e82a227b2",
|
| 1265 |
+
"ac967d0e52d04e87a2e9accc69260636",
|
| 1266 |
+
"ac9f145a25fc4eacb6fe3904a7e2c9f6",
|
| 1267 |
+
"ad0a47e65810464e9d4ba6ef65208367",
|
| 1268 |
+
"ad224585dc804988b812821a51a573e4",
|
| 1269 |
+
"ad22be23ff8d40b9928500b13180b47d",
|
| 1270 |
+
"ad5f24c0c0544c12934a1e8be286f5e8",
|
| 1271 |
+
"ad82c1a7ee664dc5a260ff947c693fd8",
|
| 1272 |
+
"ad837c6229fe4a82a1a60dda23c8a1ef",
|
| 1273 |
+
"ad984cb5ced945d7a5c53a7ed2232c7b",
|
| 1274 |
+
"adb078c42d0746798efaa3e4686afd33",
|
| 1275 |
+
"adb24e49311b48e69a95c3a5324817ec",
|
| 1276 |
+
"adc46168b8c54bc4ab01343638c42e10",
|
| 1277 |
+
"adcc143d66344019bf695d63299486b3",
|
| 1278 |
+
"adea146fdc9c4fa5a9a68a257e1f7060",
|
| 1279 |
+
"ae0411f1f2b44d8189028448ab49b2d3",
|
| 1280 |
+
"ae07b87675b24418b6763cd18cab83e2",
|
| 1281 |
+
"ae1fc814ccd5461aa243c53fcd0d6c83",
|
| 1282 |
+
"ae314dc3de994d2a8d7ea9dc052b28e0",
|
| 1283 |
+
"ae3ce147938d47788cf9d5f29d32fa66",
|
| 1284 |
+
"ae5a5ef80af440daba1b97464429c519",
|
| 1285 |
+
"ae5e5283d56647beaaeb029d4fb92faa",
|
| 1286 |
+
"ae61592e73224a6c90897dfe4e51e0fe",
|
| 1287 |
+
"ae707476ba784da2bfa83406cbfc4f3d",
|
| 1288 |
+
"ae87e445e97e48648b073dee0abe72ef",
|
| 1289 |
+
"ae90aff7b8834654a6f5cabff7d35437",
|
| 1290 |
+
"ae9d7a4529134cb0b3601c0ca699ffe6",
|
| 1291 |
+
"aeef6d53b5ce4f2eb391281a2c04e89e",
|
| 1292 |
+
"aef67d7de76247969056225468ac1764",
|
| 1293 |
+
"af57904d39954ce09375f94134105ee1",
|
| 1294 |
+
"afe31faf1d5541d3bfed9f2cd86f29b5",
|
| 1295 |
+
"b0264b10cab6494f94b4073b7dd92357",
|
| 1296 |
+
"b02d58f07e7243a0ad4077e5f778abef",
|
| 1297 |
+
"b089ed83b2974a81a00b5ac6330c07d0",
|
| 1298 |
+
"b0b69dac8c0748578c5a9a948ca7c2a0",
|
| 1299 |
+
"b0c00b0f224c4b04b4ecf0468445d0d8",
|
| 1300 |
+
"b0cf6517af894b59a25ff95aa6e83aa4",
|
| 1301 |
+
"b0e026d0c08448bdb38fc60ce71e488a",
|
| 1302 |
+
"b0e695e8483f46f88943173a70be9709",
|
| 1303 |
+
"b0f83707ec294bb2813e67c8c3e5db31",
|
| 1304 |
+
"b101fd45b31c4719b7caca788619716a",
|
| 1305 |
+
"b13e35727e404b2b9fd3ea64c083ff2e",
|
| 1306 |
+
"b15c6bc6090f40009ed8e8511820827a",
|
| 1307 |
+
"b19b27b5bf0d498289c662177b465350",
|
| 1308 |
+
"b1a1aae682f241b280eab323cff8014a",
|
| 1309 |
+
"b1f89ce951194147bce1bc55f9248c28",
|
| 1310 |
+
"b207d9d282484037b072daecbf96c2a8",
|
| 1311 |
+
"b21b62f911e54b29863d37b2912105f7",
|
| 1312 |
+
"b228a29fa84544c2be501c295653ffe7",
|
| 1313 |
+
"b266a2134d9945f697123fc6c8e23522",
|
| 1314 |
+
"b2686595ad1d4907bc46e87e9131df1e",
|
| 1315 |
+
"b2698fff89454bd5ac826ac762fd0a32",
|
| 1316 |
+
"b28364e02a294f9bbd8b3fdfeb33cb6a",
|
| 1317 |
+
"b2935e579f4141dc96e16f77151997b1",
|
| 1318 |
+
"b2a9701db8b84d3a82f99d1f719af0d4",
|
| 1319 |
+
"b2f80a34e1cb428f9f0139870a18f1a3",
|
| 1320 |
+
"b2fe519bf8ca4aeabdb5f489c260294a",
|
| 1321 |
+
"b333b3e6066443debac98c0ab1897e13",
|
| 1322 |
+
"b3389be723684644b731483f31ccfc15",
|
| 1323 |
+
"b342af82a6ba4d3f94f546611782b963",
|
| 1324 |
+
"b34f1c1ef15b4a07a160b1792eb2ebca",
|
| 1325 |
+
"b369627b8a8f47c297a2a4e67929d3af",
|
| 1326 |
+
"b379e058279844a7b6bc4e7e16476171",
|
| 1327 |
+
"b3fc468b4c4c4e94816cf878f01fb0a4",
|
| 1328 |
+
"b43d9c7d138f49ab9f03ceec47d636b5",
|
| 1329 |
+
"b4621c3f42c04db3ac22ee782a77abdb",
|
| 1330 |
+
"b47899d1db7c4d068c9abb6f78c5f147",
|
| 1331 |
+
"b4dcf459298b43f19ccef6a999ca88ac",
|
| 1332 |
+
"b4e4c397f4f045adbe9999d414fae54c",
|
| 1333 |
+
"b522897d579b4520be476c586a5f14ab",
|
| 1334 |
+
"b587a8c1714c4dca8810ba06b6f65475",
|
| 1335 |
+
"b5ac89e44bd3413495ed6b6ec7fb6837",
|
| 1336 |
+
"b652c476dfeb46f283afb1b471bff652",
|
| 1337 |
+
"b6695c4c642a473ebc870cf303a6b78a",
|
| 1338 |
+
"b66b45a9f46045f7914dc2a5472a7e50",
|
| 1339 |
+
"b67ae7fa7aad4fe0865d0fce24a5ee07",
|
| 1340 |
+
"b6d4f852379f4504b2e7abc28a7dc740",
|
| 1341 |
+
"b6eb96e28f184cf5a8098f68570f2314",
|
| 1342 |
+
"b6fe8b5f49d04d1788829597bb65d410",
|
| 1343 |
+
"b7074ce8f0b34ebb8e11eb1af7a857f3",
|
| 1344 |
+
"b72b882899824e1aa4b7c361654948c9",
|
| 1345 |
+
"b73508fc71c045e7b8895e5b5e13c4a5",
|
| 1346 |
+
"b75ecd7e5126418cb1988a91efb324fa",
|
| 1347 |
+
"b7a85dc6d37a4f06a2275c70f045ce6e",
|
| 1348 |
+
"b7c2b3de4e6a48dda3909a6db64c4ac4",
|
| 1349 |
+
"b7d498a138f343c7910ea169ed1c2a3f",
|
| 1350 |
+
"b836c6e9ffac44f1bba88d57bf225f16",
|
| 1351 |
+
"b8c35388ecd443178c24a5e5cb4c972c",
|
| 1352 |
+
"b8ea5f41413f4c9baba3cbd38bfbc899",
|
| 1353 |
+
"b9057fa941aa41d0be9f20514c1bed24",
|
| 1354 |
+
"b920ba1ce9a14e41a222749c24e5abed",
|
| 1355 |
+
"b93effd9393b44e08b4b89911ff56c0a",
|
| 1356 |
+
"b961b284a61d4347bdbc63399cdcf8cd",
|
| 1357 |
+
"b969d3c8b4fb4222901c3de1ace9cabc",
|
| 1358 |
+
"b96cd6917b974bfe99116ecc288a98cb",
|
| 1359 |
+
"b97e39d020364038aec5809c10729c20",
|
| 1360 |
+
"b9a66b51a6ca4262a6b3d5c8e5f963ec",
|
| 1361 |
+
"b9f15edeb58647c08a9adab01c91683b",
|
| 1362 |
+
"b9fd273288e445bbafe8c149434214ea",
|
| 1363 |
+
"ba09a16bc68e47f28d9900ca2ee70cf8",
|
| 1364 |
+
"ba197f0c7dc147a78005c100a8c67cde",
|
| 1365 |
+
"ba3d56e0c6b843bdacabfcb4b4f90801",
|
| 1366 |
+
"ba5c799fb68b4ac8a1eedfa0586f04f8",
|
| 1367 |
+
"ba6c4234114f4378bf852a5ba12bb7c5",
|
| 1368 |
+
"ba7fbe198ac8410b8b27f81243cacc8a",
|
| 1369 |
+
"ba81bdfa19d54db2a15422341ca8ab87",
|
| 1370 |
+
"baabe69ab1c44cf0b29c147ff0dcedaf",
|
| 1371 |
+
"baad87b0ba9448399d7f72d0fbbc97ff",
|
| 1372 |
+
"bab447d504d94e9691c43ab851e37221",
|
| 1373 |
+
"badc51f2098a4f40a7d166fec9812f4f",
|
| 1374 |
+
"badee623199445cab579b25c535311e0",
|
| 1375 |
+
"bb3d01e7dfd941ecaa8757d59aa560b1",
|
| 1376 |
+
"bb5452b2711c444290834a31996537e1",
|
| 1377 |
+
"bb57ddbb1c2140358b027dfd16b7154b",
|
| 1378 |
+
"bb7483cfa54a47dd856bd529818673c3",
|
| 1379 |
+
"bb8ac243ce464df1af1922599fb3e901",
|
| 1380 |
+
"bb9468613b2f419788f74f94a0820878",
|
| 1381 |
+
"bb9f64ffbff74a0ab4db3d3fe2667388",
|
| 1382 |
+
"bbb693d729264b76a667c2db6d35b2e7",
|
| 1383 |
+
"bbddc0cc8d6a4c9a96a0fc4170594336",
|
| 1384 |
+
"bbe6eb2ff68e46c19f1bd22c1b24dc07",
|
| 1385 |
+
"bc013f0ea12d426d9338d3b437dd5593",
|
| 1386 |
+
"bc0cbd2413a148658db983de5dd1f80b",
|
| 1387 |
+
"bc2ef0701e464672ad8ce2bc3e78f8b3",
|
| 1388 |
+
"bc32d3d924774b3caab10356c1177505",
|
| 1389 |
+
"bc445514ea3c4fd9a316053928cbba12",
|
| 1390 |
+
"bc5030885a5548e595b0a3bea9599354",
|
| 1391 |
+
"bc63ebaf707f4185888bce9c54b6d59c",
|
| 1392 |
+
"bc872d79faa34693adfe6436bb806c39",
|
| 1393 |
+
"bc9c1ec4957b4e32bdbdaf8e70ce9966",
|
| 1394 |
+
"bcdb24f0968c4e1fb7f82154f8adc4ee",
|
| 1395 |
+
"bcfae6acaa254778921933e9ca0f52b9",
|
| 1396 |
+
"bd0065fbb3e745a3907a42b65ef74e23",
|
| 1397 |
+
"bd1b88e868a74e07b2da0512e5041820",
|
| 1398 |
+
"bd5889e7b8ce4c3693e7b58f7de60660",
|
| 1399 |
+
"bd5d5a1fdbc7470fab575738d1741a3d",
|
| 1400 |
+
"bd82374391904aea903d68e22c3b98d8",
|
| 1401 |
+
"bdbcf20dc4e44c34bd6783a025a25dbf",
|
| 1402 |
+
"bdc57a0bf55742edb3a819adb23f52ac",
|
| 1403 |
+
"bdcf5dad9bd4495ea32975f36bb536dd",
|
| 1404 |
+
"bdd3558ddd854a24a090e88595814508",
|
| 1405 |
+
"bdd8a24ad40c4b1facd2d463d2a8dadf",
|
| 1406 |
+
"be162fbb800a41188853844ff0779fc6",
|
| 1407 |
+
"be182bab94004469a07fb9283cd0966f",
|
| 1408 |
+
"be52a68f4f2a409486cf6c0bbceb7f4c",
|
| 1409 |
+
"beaa427961df495c970495d81cc684c3",
|
| 1410 |
+
"becfc582462b432cbd158ada35106a17",
|
| 1411 |
+
"befdd451a67e40698ce02773ca146e13",
|
| 1412 |
+
"bf1cadd21a704739bc19d535ae741728",
|
| 1413 |
+
"bf3e162cdda84afc8c1057bd8ca59b12",
|
| 1414 |
+
"bf429028039d40b1b6da66b27e5cec80",
|
| 1415 |
+
"bf4a3f32b0af40e58653049bdf2265c6",
|
| 1416 |
+
"bf530a7f485c42a2aa650aaa6a3b38f2",
|
| 1417 |
+
"bf859e10b7e74b13b1f76a90b33c13d8",
|
| 1418 |
+
"bfc22926b2f842ab83dc31ce1e95de8f",
|
| 1419 |
+
"bfdb7491cbe04dfd84a1f60bbac3f77e",
|
| 1420 |
+
"c0156044d4834a18a44eb9f01d406bcc",
|
| 1421 |
+
"c0618ebd49724b838060fb70859a23d2",
|
| 1422 |
+
"c063d1a11b0848a5ab12ce20e6f4fb51",
|
| 1423 |
+
"c06cba99d6cd47c6a5b0ad90545f211f",
|
| 1424 |
+
"c072b4097f894f34a2144c6d53c13ade",
|
| 1425 |
+
"c0951700653e4e978ef6199c38d07012",
|
| 1426 |
+
"c098dab6131b4e41a3aff26ab21fb72b",
|
| 1427 |
+
"c0e2a0ad0cf34e4cb8c8695c1470ad47",
|
| 1428 |
+
"c0e3e1f810c446ba943b7ae635846362",
|
| 1429 |
+
"c0f8d797244541948e916984c5b4b79d",
|
| 1430 |
+
"c1130e0746cd41a5b108c5c7a8bbe321",
|
| 1431 |
+
"c11c343241d54c1fbbf2bbc5f0f4f3c4",
|
| 1432 |
+
"c1322815d25c4e2aa50fcb93cd3417f2",
|
| 1433 |
+
"c1338e44401949c1be64e6668d38c100",
|
| 1434 |
+
"c15bff78b50442a39c821330cdd02fd1",
|
| 1435 |
+
"c17e8692f8d141b39ef30ff643163c42",
|
| 1436 |
+
"c1ffea27ec114116951a1d99bdf68aa7",
|
| 1437 |
+
"c205ed51b2554c9ba9fa2c3ccefb8473",
|
| 1438 |
+
"c23e4e9a003a4c1a8e7a12897cb2989b",
|
| 1439 |
+
"c2a66c76348b493196e765361b30b82f",
|
| 1440 |
+
"c2d9dfa3c9f74a54805b6266dffdcac6",
|
| 1441 |
+
"c301fe7e7b5f4ffd9984888ea676ac18",
|
| 1442 |
+
"c3505b11077b4b2d824c33b573fc6e08",
|
| 1443 |
+
"c35eac0ca1274ef7954f80dccf7c7fde",
|
| 1444 |
+
"c38973bee1784c6eb644e28057b1b207",
|
| 1445 |
+
"c3b896134b184ad9a52f542b0ad539da",
|
| 1446 |
+
"c3e41b2565094c56ba594cb9697d0c98",
|
| 1447 |
+
"c3e92af004704cc2b8f2ac99c706208c",
|
| 1448 |
+
"c3edeb36f0ca4dd698b4262abe128bc1",
|
| 1449 |
+
"c3f58532473d4796a9b0de9c0136a0bd",
|
| 1450 |
+
"c433d675d27d489b99db4683f092fda0",
|
| 1451 |
+
"c445e6ac66764779b262014cc97e9192",
|
| 1452 |
+
"c45475ead1024e42b6665339544791cf",
|
| 1453 |
+
"c46a254e08384b23883a570f483ededf",
|
| 1454 |
+
"c4d94ba01c1d44a5a998e09b7dbd52a5",
|
| 1455 |
+
"c4dd2ae8d8ee463a8eda2a8ed770ce34",
|
| 1456 |
+
"c4e9a94ae8d14822a9ec49778868a1d8",
|
| 1457 |
+
"c51100de7bb84e6882f92c9b6ab4b62f",
|
| 1458 |
+
"c51feb09710645bd9df7617f7655f065",
|
| 1459 |
+
"c547abcd2b594a449964b0123e96b2f4",
|
| 1460 |
+
"c54b4c5e6cb64da58d2bc48c7f8a2115",
|
| 1461 |
+
"c56c8aae879b43c292a0f2516b8d3715",
|
| 1462 |
+
"c573303be1f04e0c94cfa245c2f2ddcf",
|
| 1463 |
+
"c5740d905b0143a5831cac15c887bd8a",
|
| 1464 |
+
"c5fdf4ef354b4b6ead18dbfb6e07805c",
|
| 1465 |
+
"c60aa3efb391448faa955426411d485a",
|
| 1466 |
+
"c612eeac87ae4b19a7ade75418a3b42a",
|
| 1467 |
+
"c61a7acb3f7649c786cce53aa80df1fe",
|
| 1468 |
+
"c624b290938c411880ec5254091ab572",
|
| 1469 |
+
"c65995ebe77347ff8a782b3f22cd92b2",
|
| 1470 |
+
"c68f8b1a585a4de6ac0bb133e822b8e5",
|
| 1471 |
+
"c69887e12a164e20bfdc40c5a57c9d77",
|
| 1472 |
+
"c6b5a0a815174bd8bb7410668af0f67d",
|
| 1473 |
+
"c6cf38c5fcf446c6ad9c68f97ea9d924",
|
| 1474 |
+
"c7255f3e4e9d47dca1e66c42c3abf00b",
|
| 1475 |
+
"c7314a1d76c541df8d6723d772ad38c1",
|
| 1476 |
+
"c737dcaa92a74959a6dbf73ce9f6f80c",
|
| 1477 |
+
"c73a0aeaf5184b8d920e26209a381cd6",
|
| 1478 |
+
"c7cc79f24c3046958bb2e3b62fcba583",
|
| 1479 |
+
"c80234658ea34ea4b0fdbcf53f997651",
|
| 1480 |
+
"c84221e0bc74453f96c27c6440081f56",
|
| 1481 |
+
"c885e000d9964d3b89c72d1034414d40",
|
| 1482 |
+
"c8c39f135f324c188448cfa37d63b1a4",
|
| 1483 |
+
"c8d2ae976cec4fcabe1837f1caa40b8e",
|
| 1484 |
+
"c8f071466cd149968a5304b2267f270a",
|
| 1485 |
+
"c99e544549a244ce87938a9e3ab4e0e5",
|
| 1486 |
+
"c9b9c316434547c892e7ac7bde27a74b",
|
| 1487 |
+
"c9dc675a3b9e419da43020b005f2daf8",
|
| 1488 |
+
"c9e8e4f732304503a2f1dab5bd3575a8",
|
| 1489 |
+
"ca4c5fb30d4f4e33a145e0ec21004538",
|
| 1490 |
+
"ca788d3707e74a8eb2a11b4ba3c663fc",
|
| 1491 |
+
"ca98ed893c6e4279a27678a28bf12bca",
|
| 1492 |
+
"ca9ab0992cfa4704a7369bb8a1e354ce",
|
| 1493 |
+
"ca9ba816376147c39bbe86a0e963c2a6",
|
| 1494 |
+
"cae4483da950451dad042eaaa0ec3f71",
|
| 1495 |
+
"caef776ade4249beb2df98a19e640922",
|
| 1496 |
+
"caf3e1a20fec4d85b2b82b28b7c9748a",
|
| 1497 |
+
"cb0e7bf9c2c34681a1c46510298eca2e",
|
| 1498 |
+
"cb4868a7428e4c868112600d4258d467",
|
| 1499 |
+
"cb55690638a24306b5af6829fbf2fd3b",
|
| 1500 |
+
"cbaea52a561a485985187467cc4cebba",
|
| 1501 |
+
"cbdf402976724a82949f655eb2769db2",
|
| 1502 |
+
"cbea02f813454fdb88ddf695dad0ba95",
|
| 1503 |
+
"cc084f03563d4fc8a842cd1b144af160",
|
| 1504 |
+
"cc1b43705a4e41fb93278436dfd19aa7",
|
| 1505 |
+
"cc474a048f2d406fae7ddccc6e33b5e4",
|
| 1506 |
+
"cca59986dfb24be5bb3ef76ef03e6446",
|
| 1507 |
+
"ccb5addc6298440b9387eba77cd96b3f",
|
| 1508 |
+
"ccc764da89ab48f089ec24646b099fd2",
|
| 1509 |
+
"cd75d662c61e47be9b7eed198bbc4653",
|
| 1510 |
+
"cde1c503e1384e45a897ad33621c039c",
|
| 1511 |
+
"ce27aef8f565405ab97345111f56cbcf",
|
| 1512 |
+
"ce35cd13734c47f8b0dbfb1a7ce7d737",
|
| 1513 |
+
"ce4059f8cee64886b4900bae6081a412",
|
| 1514 |
+
"cef43d0b4ee54de191d8d6d1c607c3b4",
|
| 1515 |
+
"cefa7ff3c50d4b599dee919173771f10",
|
| 1516 |
+
"cefe8210e84b41efa52f932dd5bdbbf8",
|
| 1517 |
+
"cf09960a2b76439eac0dad00e36e321d",
|
| 1518 |
+
"cf15f213e497421abf1b0c559b9a7bf1",
|
| 1519 |
+
"cf32f764ec5a43ad9243aec1b7981817",
|
| 1520 |
+
"cf47293c05f1485eaba4437965ec7cb6",
|
| 1521 |
+
"cf4c2238567c42eb8bf5f3a1cf269f26",
|
| 1522 |
+
"cf7dc5fb55214706b2725e900515c9ea",
|
| 1523 |
+
"cf7f3eb8fdbe43b29e4f6ee5a17fe913",
|
| 1524 |
+
"cf96e6948340491c97370f2f75d6a715",
|
| 1525 |
+
"cfa8ae9850724ecc9137297a6cf48661",
|
| 1526 |
+
"cfb21be3208142e58302a954bb258638",
|
| 1527 |
+
"cfc48976f7474878bd2236b2e9fcbcfa",
|
| 1528 |
+
"cfd1a14262c9452a98265ff51df738da",
|
| 1529 |
+
"d01a7b47c5634beca150febdc9eb3804",
|
| 1530 |
+
"d023021664b14066ba2091b46796d48a",
|
| 1531 |
+
"d0296bd8998b4d64a156ec3614194cf8",
|
| 1532 |
+
"d02ba46d170143e58ec7f9e4c7edb09c",
|
| 1533 |
+
"d03d0eddd62b49e39b6916825fe2cc4a",
|
| 1534 |
+
"d081cded74804470bc3b0874cbeae248",
|
| 1535 |
+
"d0a9268f7fb24dcd8f77c66f2fee33d9",
|
| 1536 |
+
"d0e93f7b8717493b959d797cad7a7051",
|
| 1537 |
+
"d124e83b3bb243488241b8ac07f6ba3b",
|
| 1538 |
+
"d143036cc7cd4810aa1b135ce4b75186",
|
| 1539 |
+
"d1563c5e2d29424fb1ce03e03a4f0e2c",
|
| 1540 |
+
"d16736e243904210b938590a42c8ea88",
|
| 1541 |
+
"d18a91b5578c4e6fa9e8fa0f4f892655",
|
| 1542 |
+
"d18c53e5716941a79385dc1b7a20aa33",
|
| 1543 |
+
"d19600dfb01044819f0e2ad57c5b144f",
|
| 1544 |
+
"d1b8af21f2a7463a800dd21828143d73",
|
| 1545 |
+
"d1feaa811a43422eb9918abc60b6d03e",
|
| 1546 |
+
"d2134da7512c4a4ea3acb5efc038939c",
|
| 1547 |
+
"d21cf60625aa40aa984bc52a5c1d28b6",
|
| 1548 |
+
"d25283e6dc7c4af9858db4b33b2cd898",
|
| 1549 |
+
"d263b96494e046c3a7721039d52c70bd",
|
| 1550 |
+
"d281b292903d422aa0cef4e97f47c516",
|
| 1551 |
+
"d2840479d3764edaaec419baa528b686",
|
| 1552 |
+
"d29681d43409491ca48a1a4daa08c9c0",
|
| 1553 |
+
"d2ad89ca538043b5a705578f7d63e26b",
|
| 1554 |
+
"d2bca193811341a9b6626fb9440e1483",
|
| 1555 |
+
"d2c42715bc8f4ac793365a9e018a9154",
|
| 1556 |
+
"d2ec14c4188e4cbe9bf8f649d54e9026",
|
| 1557 |
+
"d341a70ad72d4aef890642bf4b351164",
|
| 1558 |
+
"d370a3eb0a164dc19ba4547a89956599",
|
| 1559 |
+
"d3df4b71522a4af492642fd8b4188bb2",
|
| 1560 |
+
"d446483c0dc44723a520d1dc5019ef41",
|
| 1561 |
+
"d44d57497d0e49378dc6cc030a3a795c",
|
| 1562 |
+
"d468bad5f0da414b80a7a10e0ce06963",
|
| 1563 |
+
"d4879dcacecd4e31841b6f85853059e3",
|
| 1564 |
+
"d4afdbf2942b4d028c8e5ae678748618",
|
| 1565 |
+
"d50988a3159f488193d45c33e1bc42d2",
|
| 1566 |
+
"d54709facd344eb68b60778618ee30b1",
|
| 1567 |
+
"d54b3349d9454dce842ba7cb29d2f55d",
|
| 1568 |
+
"d557afd3c789449a81deea08cf8b38e6",
|
| 1569 |
+
"d5658d7c19b54a7eb651fb90f49a1eb1",
|
| 1570 |
+
"d576f209de3448d9992b1e3475bef5ff",
|
| 1571 |
+
"d5879bee6d804e06b91eac1ab17c0865",
|
| 1572 |
+
"d5af027ff24a49478cfdb02ae813636e",
|
| 1573 |
+
"d5d862bcfd4441c98daeb64b81f478e5",
|
| 1574 |
+
"d604adba8218419984863c94d55e179a",
|
| 1575 |
+
"d645572faab04171a80b508787ce2e94",
|
| 1576 |
+
"d665b19e9b27446abffefed954dc08bd",
|
| 1577 |
+
"d67c689abb49402dbd5531e658e1228a",
|
| 1578 |
+
"d68520a7d7fd457da9abafec6b55bce6",
|
| 1579 |
+
"d6993387c8fc49c49ef427a5e43556dd",
|
| 1580 |
+
"d6c3ff1f15344a31b1120f48fd2718c5",
|
| 1581 |
+
"d6c53cc6b38d47b4bd44c1355151fa8a",
|
| 1582 |
+
"d6ee29b78ed54a51b99069dfb50f23ae",
|
| 1583 |
+
"d739778fc14a4edcafaa62e0d8722f2e",
|
| 1584 |
+
"d74022bfc72f452bbed363059bbb79d6",
|
| 1585 |
+
"d76d9d2174a14235af9ec89b6fbe2f37",
|
| 1586 |
+
"d7d6e3e059e2492188d7bb7b17626a45",
|
| 1587 |
+
"d7df7692001a427caee08f0997f52316",
|
| 1588 |
+
"d7ff32605636455a813c60c77e7817ba",
|
| 1589 |
+
"d831ace59eea441f971547b991d0b6ce",
|
| 1590 |
+
"d85e6f53c445408bab71e9c68eb59140",
|
| 1591 |
+
"d86759c3aa944793bd8e79a6339a7342",
|
| 1592 |
+
"d892496c52524e0ebfe1db7482c3c3af",
|
| 1593 |
+
"d896c972fc894772b47748fbbbf4b5eb",
|
| 1594 |
+
"d89e46167ef045bb8c2d279dc2b51ff7",
|
| 1595 |
+
"d8bc73eb1ead46fb8594b8caf926bda7",
|
| 1596 |
+
"d961bc05652f41b1b90f9358f69eb23a",
|
| 1597 |
+
"d9b5a2c032244ccf8ee63bd26ed8bb2b",
|
| 1598 |
+
"d9e3f302b8ef4b0297cbe4a042b1887c",
|
| 1599 |
+
"d9ecc99108a643ab887296cb986be937",
|
| 1600 |
+
"da256042b24f41469b605ec24f027301",
|
| 1601 |
+
"da26fbe1b21340cca9d2e31f4c70571f",
|
| 1602 |
+
"da4e172fddde4d2d8b2072aa6560ed6d",
|
| 1603 |
+
"da6ab3a8f5554d6ca485bfb5698fabe9",
|
| 1604 |
+
"da8b73929a5843e185c627bc675c730a",
|
| 1605 |
+
"da8bbcb226fc4b2a862e94a4c5ce1930",
|
| 1606 |
+
"da8fcc845fdf4227839784102dc182e7",
|
| 1607 |
+
"da97043fbee64b1d92c5406ef80a5ea9",
|
| 1608 |
+
"db49d1d472704b19836ab1a8aeb09d58",
|
| 1609 |
+
"db80c36bf818475bb43f84cc015d7803",
|
| 1610 |
+
"db9bc936d89d4f63aaf2f58c8048eec4",
|
| 1611 |
+
"dba737640da74746afa8189dfc867d98",
|
| 1612 |
+
"dbf9c15333814afa88345312b521ddab",
|
| 1613 |
+
"dbfd139a36ef42e59392c8a9cd878986",
|
| 1614 |
+
"dc436f07d85146a49747ee01e4259049",
|
| 1615 |
+
"dc4c91abf45342b4bb8822f50fa162b2",
|
| 1616 |
+
"dc5edb6be1c14fb1b5484541ae3aa673",
|
| 1617 |
+
"dd6ccc2389454afda4a5d34c8b6ce4d5",
|
| 1618 |
+
"ddabe7f3825643048541f8d570560c36",
|
| 1619 |
+
"ddf2459697d54117a2d0ac499f7fdb6b",
|
| 1620 |
+
"de522f0520794e8ba79361b235a08bd1",
|
| 1621 |
+
"de5b614cdb2042e29fcf0f848b26af21",
|
| 1622 |
+
"de8616a79cb74d9d8618c91ce254c9a6",
|
| 1623 |
+
"dec9e92b1a86422c8df32c835704387c",
|
| 1624 |
+
"df0511efc31b431daebe5825eefcc12d",
|
| 1625 |
+
"df14fec21675475c871568b7b89bac22",
|
| 1626 |
+
"df224818be2946f69e1cac929504b07b",
|
| 1627 |
+
"df232d48ce254cb8bf156b20dd096e16",
|
| 1628 |
+
"df261dc53ac1410f9cd462d6de578f5d",
|
| 1629 |
+
"df9f511762f44544a37799e18dce3f83",
|
| 1630 |
+
"dfa463836cdd48b4bb4caa3f868ed07a",
|
| 1631 |
+
"dfd3fab648834cd586230ecde5d80498",
|
| 1632 |
+
"dfd734a9ae4343fc8f572b11fe09ead3",
|
| 1633 |
+
"dfffd398b6cc4e14bb76aa73fc1bda33",
|
| 1634 |
+
"e02091a7f05d4a78a8a7ee07054c68f0",
|
| 1635 |
+
"e05d63a78f2c44c5a64fbc419998e603",
|
| 1636 |
+
"e06a0ddd061b4919a33868e84e0bb59c",
|
| 1637 |
+
"e06d06101cc8434e91034b97211d5dd5",
|
| 1638 |
+
"e088a1fc5a67448589f15e78517ebd39",
|
| 1639 |
+
"e0c7ddea147e4843a3f69d78362be91d",
|
| 1640 |
+
"e0ea25464d2442a183fd1d16b31d0d6d",
|
| 1641 |
+
"e1083bbb88ac4e55b31613f98bc39aee",
|
| 1642 |
+
"e108c545e1194c36841e43a32b007312",
|
| 1643 |
+
"e13434a196e842b3980aef22835e4ecb",
|
| 1644 |
+
"e16778a84a054a68a93c0a843ceaca9c",
|
| 1645 |
+
"e16ed5283fd1443da96d30b14fbe26a3",
|
| 1646 |
+
"e1773e5177454abea4410b442cd51bc3",
|
| 1647 |
+
"e17e25c330704fb9b75a14470d1602b2",
|
| 1648 |
+
"e19e12c46c5849feb4559bf12fb1a1d0",
|
| 1649 |
+
"e1afbb66a7974c2ca15efa9186af729c",
|
| 1650 |
+
"e1b76ff0ea3046dfbe81a05c61f28ef2",
|
| 1651 |
+
"e1bc7fd65d7b4a5b85624fc9cb483993",
|
| 1652 |
+
"e1e2705f28eb468f8995be58cc59e180",
|
| 1653 |
+
"e1eeec54327545438376e773dfbb95a3",
|
| 1654 |
+
"e204b769571444e5916c3d7ca67a4219",
|
| 1655 |
+
"e21fdbef2bca4ef5a46d2f0a523b530a",
|
| 1656 |
+
"e24ee911ddac4ab9b2869913c6a8e969",
|
| 1657 |
+
"e2e4bb23e9214105879b033692377580",
|
| 1658 |
+
"e311b9100c564c599b64c98d0ceb879b",
|
| 1659 |
+
"e317dd0031794072a86e1e9fb7fb0f3d",
|
| 1660 |
+
"e33e35f643334b8fa5861c14fde1edc1",
|
| 1661 |
+
"e35dab813fe34035b66ac15989b1e2be",
|
| 1662 |
+
"e378396052674803a8990bd1d8c0b04f",
|
| 1663 |
+
"e38c85b66b974f769446c8dbb0e15c2e",
|
| 1664 |
+
"e39ec3c45acc40b79db8bf133e1dfc77",
|
| 1665 |
+
"e3a23079ea72443f9b469806d4ddb002",
|
| 1666 |
+
"e3aca5ecbbdf4d3585c3e241faf97206",
|
| 1667 |
+
"e3c0b2df930b48f5bc1fa5d38155e54d",
|
| 1668 |
+
"e3df35afea5d4f3fa8ec2c0b9574bcd8",
|
| 1669 |
+
"e3fa8eda5cd648f8b3b2a88189fe3e2f",
|
| 1670 |
+
"e406259aefca4cb8844e55a42ea45c87",
|
| 1671 |
+
"e4087350c4d84d8391de09b405c96488",
|
| 1672 |
+
"e4158753fe574a86ac7f457179db6ecf",
|
| 1673 |
+
"e43ed4c1b7254bedb8ec34ea8bbf341a",
|
| 1674 |
+
"e4656569bcb44476b5d8676724c22b6f",
|
| 1675 |
+
"e47be5f609ef415f97b51b264cd16259",
|
| 1676 |
+
"e4cfa0c54f474f918f40277fe2bbacfb",
|
| 1677 |
+
"e4ec75222bb44422bf0b7c90453b7f94"
|
| 1678 |
+
]
|
home/ubuntu/aaaaa/data/rgbmr/configs/mcdiff/default.yaml
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ===== MC-diffusion Training Config (SD3.5 + mcVAE) =====
|
| 2 |
+
|
| 3 |
+
seed: 43
|
| 4 |
+
|
| 5 |
+
project:
|
| 6 |
+
output_dir: outputs/mcdiff
|
| 7 |
+
logging_dir: logs
|
| 8 |
+
null_pe: precomputed_tensors/null_prompt_embeds.pt
|
| 9 |
+
null_ppe: precomputed_tensors/null_pooled_prompt_embeds.pt
|
| 10 |
+
|
| 11 |
+
data:
|
| 12 |
+
train:
|
| 13 |
+
root: /home/aaaaa/data/rgbmr_mv_web2/merged
|
| 14 |
+
num_workers: 2
|
| 15 |
+
batch_size: 4
|
| 16 |
+
val:
|
| 17 |
+
root: /home/aaaaa/data/rgbmr_mv_web2/validation
|
| 18 |
+
|
| 19 |
+
model:
|
| 20 |
+
pretrained_model_name_or_path: stabilityai/stable-diffusion-3.5-medium
|
| 21 |
+
init_transformer_weights: null
|
| 22 |
+
sd_vae_path: vae_sd35
|
| 23 |
+
mcvae_config: configs/mcvae/config.json
|
| 24 |
+
mcvae_ckpt: null
|
| 25 |
+
mcvae_offset_mode: true # If true, mcVAE encoder outputs offsets added to base mean/logvar. If false, directly predicts mean/logvar.
|
| 26 |
+
num_views: 6
|
| 27 |
+
use_dual_branch: false
|
| 28 |
+
use_caa: true
|
| 29 |
+
use_rope: true
|
| 30 |
+
use_global_token: false
|
| 31 |
+
use_global_pos: false # If true, use global frame (identity) instead of local query frame (w2c) for delta position
|
| 32 |
+
resolution: 512
|
| 33 |
+
condition_channels: 32 # 0 | 16 | 32
|
| 34 |
+
corr_dilate_iterations: 2 # Number of times to apply dilate_f2l to correspondence tensors
|
| 35 |
+
|
| 36 |
+
attn_lora:
|
| 37 |
+
enabled: true
|
| 38 |
+
r_q: 32
|
| 39 |
+
r_k: 32
|
| 40 |
+
r_v: 32
|
| 41 |
+
alpha_q: 32
|
| 42 |
+
alpha_k: 32
|
| 43 |
+
alpha_v: 32
|
| 44 |
+
apply_to_self: true
|
| 45 |
+
limit_joint: null
|
| 46 |
+
limit_self: null
|
| 47 |
+
|
| 48 |
+
lora:
|
| 49 |
+
rank: 0
|
| 50 |
+
lora_layers: null
|
| 51 |
+
lora_blocks: null
|
| 52 |
+
lora_dropout: 0.0
|
| 53 |
+
|
| 54 |
+
train:
|
| 55 |
+
max_train_steps: 40001
|
| 56 |
+
gradient_accumulation_steps: 1
|
| 57 |
+
gradient_checkpointing: false
|
| 58 |
+
allow_tf32: true
|
| 59 |
+
mixed_precision: bf16
|
| 60 |
+
upcast_before_saving: false
|
| 61 |
+
local_rank: -1
|
| 62 |
+
resume_from_checkpoint: null
|
| 63 |
+
checkpointing_steps: 1000
|
| 64 |
+
checkpoints_total_limit: 40
|
| 65 |
+
validation_steps: 1000
|
| 66 |
+
proportion_empty_prompts: 0.1
|
| 67 |
+
max_grad_norm: 1.0
|
| 68 |
+
|
| 69 |
+
optim:
|
| 70 |
+
optimizer: AdamW
|
| 71 |
+
use_8bit_adam: true
|
| 72 |
+
learning_rate: 5e-5
|
| 73 |
+
scale_lr: false
|
| 74 |
+
adam_beta1: 0.9
|
| 75 |
+
adam_beta2: 0.999
|
| 76 |
+
adam_weight_decay: 1e-4
|
| 77 |
+
adam_epsilon: 1e-8
|
| 78 |
+
prodigy_beta3: null
|
| 79 |
+
prodigy_decouple: true
|
| 80 |
+
prodigy_use_bias_correction: true
|
| 81 |
+
prodigy_safeguard_warmup: true
|
| 82 |
+
|
| 83 |
+
scheduler:
|
| 84 |
+
lr_scheduler: constant
|
| 85 |
+
lr_warmup_steps: 500
|
| 86 |
+
lr_num_cycles: 5
|
| 87 |
+
lr_power: 1.0
|
| 88 |
+
|
| 89 |
+
weighting:
|
| 90 |
+
weighting_scheme: logit_normal
|
| 91 |
+
logit_mean: 0.0
|
| 92 |
+
logit_std: 1.0
|
| 93 |
+
mode_scale: 1.29
|
| 94 |
+
precondition_outputs: false
|
| 95 |
+
|
| 96 |
+
|
| 97 |
+
# For Single-Full: (42hr)
|
| 98 |
+
# accelerate launch --num_processes 8 --main_process_port 0 train_mcdiff.py \
|
| 99 |
+
# train.max_train_steps=40001 data.train.batch_size=4 \
|
| 100 |
+
# model.mcvae_ckpt="outputs/mcvae_v1.8.1.pt" \
|
| 101 |
+
# project.output_dir=outputs/mcdiff_v.single_full \
|
| 102 |
+
# model.use_dual_branch=false model.use_caa=false
|
| 103 |
+
|
| 104 |
+
# For Single-CAA(5x5): (42hr)
|
| 105 |
+
# accelerate launch --num_processes 8 --main_process_port 0 train_mcdiff.py \
|
| 106 |
+
# train.max_train_steps=40001 data.train.batch_size=4 \
|
| 107 |
+
# model.mcvae_ckpt="outputs/mcvae_v1.8.1.pt" \
|
| 108 |
+
# project.output_dir=outputs/mcdiff_v.single_caa \
|
| 109 |
+
# model.use_dual_branch=false model.use_caa=true
|
| 110 |
+
|
| 111 |
+
# For Single-Full-Global(3x3): (36hr)
|
| 112 |
+
# accelerate launch --num_processes 8 --main_process_port 0 train_mcdiff.py \
|
| 113 |
+
# train.max_train_steps=40001 data.train.batch_size=4 \
|
| 114 |
+
# model.mcvae_ckpt="outputs/mcvae_v1.8.1.pt" \
|
| 115 |
+
# project.output_dir=outputs/mcdiff_v.single_caa_global \
|
| 116 |
+
# model.use_dual_branch=false model.use_caa=true model.use_global_token=true
|
| 117 |
+
|
| 118 |
+
# For Dual-Full: (72hr)
|
| 119 |
+
# accelerate launch --num_processes 8 --main_process_port 0 train_mcdiff.py \
|
| 120 |
+
# train.max_train_steps=40001 data.train.batch_size=1 train.gradient_accumulation_steps=4 \
|
| 121 |
+
# model.mcvae_ckpt="outputs/mcvae_v1.8.1.pt" \
|
| 122 |
+
# project.output_dir=outputs/mcdiff_v.dual_full \
|
| 123 |
+
# model.use_dual_branch=true model.use_caa=false
|
| 124 |
+
|
| 125 |
+
# For Dual-CAA: (72hr)
|
| 126 |
+
# accelerate launch --num_processes 8 --main_process_port 0 train_mcdiff.py \
|
| 127 |
+
# train.max_train_steps=40001 data.train.batch_size=1 train.gradient_accumulation_steps=4 \
|
| 128 |
+
# model.mcvae_ckpt="outputs/mcvae_v1.8.1.pt" \
|
| 129 |
+
# project.output_dir=outputs/mcdiff_v.dual_caa \
|
| 130 |
+
# model.use_dual_branch=true model.use_caa=true
|
home/ubuntu/aaaaa/data/rgbmr/configs/mcdiff/dual_caa.yaml
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
base: configs/mcdiff/default.yaml
|
| 2 |
+
|
| 3 |
+
project:
|
| 4 |
+
output_dir: outputs/mcdiff_v.dual_caa
|
| 5 |
+
|
| 6 |
+
data:
|
| 7 |
+
train:
|
| 8 |
+
num_workers: 4
|
| 9 |
+
batch_size: 1
|
| 10 |
+
|
| 11 |
+
model:
|
| 12 |
+
mcvae_ckpt: "outputs/mcvae_v1.8.1.pt"
|
| 13 |
+
use_dual_branch: true
|
| 14 |
+
use_caa: true
|
| 15 |
+
use_rope: true
|
| 16 |
+
use_global_token: false
|
| 17 |
+
use_global_pos: false
|
| 18 |
+
corr_dilate_iterations: 2
|
| 19 |
+
|
| 20 |
+
train:
|
| 21 |
+
max_train_steps: 80001
|
| 22 |
+
gradient_accumulation_steps: 4
|
home/ubuntu/aaaaa/data/rgbmr/configs/mcdiff/dual_full.yaml
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
base: configs/mcdiff/default.yaml
|
| 2 |
+
|
| 3 |
+
project:
|
| 4 |
+
output_dir: outputs/mcdiff_v.dual_full
|
| 5 |
+
|
| 6 |
+
data:
|
| 7 |
+
train:
|
| 8 |
+
num_workers: 4
|
| 9 |
+
batch_size: 1
|
| 10 |
+
|
| 11 |
+
model:
|
| 12 |
+
mcvae_ckpt: "outputs/mcvae_v1.8.1.pt"
|
| 13 |
+
use_dual_branch: true
|
| 14 |
+
use_caa: false
|
| 15 |
+
use_rope: true
|
| 16 |
+
use_global_token: false
|
| 17 |
+
use_global_pos: false
|
| 18 |
+
corr_dilate_iterations: 2
|
| 19 |
+
|
| 20 |
+
train:
|
| 21 |
+
gradient_accumulation_steps: 4
|
home/ubuntu/aaaaa/data/rgbmr/configs/mcdiff/single_caa.yaml
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
base: configs/mcdiff/default.yaml
|
| 2 |
+
|
| 3 |
+
project:
|
| 4 |
+
output_dir: outputs/mcdiff_v.single_caa
|
| 5 |
+
|
| 6 |
+
data:
|
| 7 |
+
train:
|
| 8 |
+
num_workers: 4
|
| 9 |
+
batch_size: 4
|
| 10 |
+
|
| 11 |
+
model:
|
| 12 |
+
mcvae_ckpt: "outputs/mcvae_v1.8.1.pt"
|
| 13 |
+
use_dual_branch: false
|
| 14 |
+
use_caa: true
|
| 15 |
+
use_rope: true
|
| 16 |
+
use_global_token: false
|
| 17 |
+
use_global_pos: false
|
| 18 |
+
corr_dilate_iterations: 2
|
| 19 |
+
|
| 20 |
+
train:
|
| 21 |
+
gradient_accumulation_steps: 1
|
home/ubuntu/aaaaa/data/rgbmr/configs/mcdiff/single_caa_100k_vae.yaml
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
base: configs/mcdiff/default.yaml
|
| 2 |
+
|
| 3 |
+
project:
|
| 4 |
+
output_dir: outputs/mcdiff_v.single_caa_100k_vae
|
| 5 |
+
|
| 6 |
+
data:
|
| 7 |
+
train:
|
| 8 |
+
num_workers: 4
|
| 9 |
+
batch_size: 4
|
| 10 |
+
|
| 11 |
+
model:
|
| 12 |
+
mcvae_ckpt: "outputs/mcvae_v1.8.1_100k.pt"
|
| 13 |
+
use_dual_branch: false
|
| 14 |
+
use_caa: true
|
| 15 |
+
use_rope: true
|
| 16 |
+
use_global_token: false
|
| 17 |
+
use_global_pos: false
|
| 18 |
+
corr_dilate_iterations: 2
|
| 19 |
+
|
| 20 |
+
train:
|
| 21 |
+
gradient_accumulation_steps: 1
|
home/ubuntu/aaaaa/data/rgbmr/configs/mcdiff/single_caa_global_pos.yaml
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
base: configs/mcdiff/default.yaml
|
| 2 |
+
|
| 3 |
+
project:
|
| 4 |
+
output_dir: outputs/mcdiff_v.single_caa_global_pos
|
| 5 |
+
|
| 6 |
+
data:
|
| 7 |
+
train:
|
| 8 |
+
num_workers: 4
|
| 9 |
+
batch_size: 4
|
| 10 |
+
|
| 11 |
+
model:
|
| 12 |
+
mcvae_ckpt: "outputs/mcvae_v1.8.1.pt"
|
| 13 |
+
use_dual_branch: false
|
| 14 |
+
use_caa: true
|
| 15 |
+
use_rope: true
|
| 16 |
+
use_global_token: false
|
| 17 |
+
use_global_pos: true
|
| 18 |
+
corr_dilate_iterations: 2
|
| 19 |
+
|
| 20 |
+
train:
|
| 21 |
+
gradient_accumulation_steps: 1
|
home/ubuntu/aaaaa/data/rgbmr/configs/mcdiff/single_caa_global_token.yaml
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
base: configs/mcdiff/default.yaml
|
| 2 |
+
|
| 3 |
+
project:
|
| 4 |
+
output_dir: outputs/mcdiff_v.single_caa_global_token
|
| 5 |
+
|
| 6 |
+
data:
|
| 7 |
+
train:
|
| 8 |
+
num_workers: 4
|
| 9 |
+
batch_size: 4
|
| 10 |
+
|
| 11 |
+
model:
|
| 12 |
+
mcvae_ckpt: "outputs/mcvae_v1.8.1.pt"
|
| 13 |
+
use_dual_branch: false
|
| 14 |
+
use_caa: true
|
| 15 |
+
use_rope: true
|
| 16 |
+
use_global_token: true
|
| 17 |
+
use_global_pos: false
|
| 18 |
+
corr_dilate_iterations: 2
|
| 19 |
+
|
| 20 |
+
train:
|
| 21 |
+
gradient_accumulation_steps: 1
|
home/ubuntu/aaaaa/data/rgbmr/configs/mcdiff/single_caa_tmp.yaml
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
base: configs/mcdiff/default.yaml
|
| 2 |
+
|
| 3 |
+
project:
|
| 4 |
+
output_dir: outputs/mcdiff_v.single_caa_tmp
|
| 5 |
+
|
| 6 |
+
data:
|
| 7 |
+
train:
|
| 8 |
+
num_workers: 4
|
| 9 |
+
batch_size: 4
|
| 10 |
+
|
| 11 |
+
model:
|
| 12 |
+
mcvae_ckpt: "outputs/mcvae_v1.8.1.pt"
|
| 13 |
+
use_dual_branch: false
|
| 14 |
+
use_caa: true
|
| 15 |
+
use_rope: true
|
| 16 |
+
use_global_token: false
|
| 17 |
+
use_global_pos: false
|
| 18 |
+
corr_dilate_iterations: 2
|
| 19 |
+
|
| 20 |
+
train:
|
| 21 |
+
gradient_accumulation_steps: 1
|
home/ubuntu/aaaaa/data/rgbmr/configs/mcdiff/single_caaa.yaml
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
base: configs/mcdiff/default.yaml
|
| 2 |
+
|
| 3 |
+
project:
|
| 4 |
+
output_dir: outputs/mcdiff_v.single_caaa
|
| 5 |
+
|
| 6 |
+
data:
|
| 7 |
+
train:
|
| 8 |
+
num_workers: 4
|
| 9 |
+
batch_size: 4
|
| 10 |
+
|
| 11 |
+
model:
|
| 12 |
+
mcvae_ckpt: "outputs/mcvae_v1.8.1.pt"
|
| 13 |
+
use_dual_branch: false
|
| 14 |
+
use_caa: true
|
| 15 |
+
use_rope: true
|
| 16 |
+
use_global_token: false
|
| 17 |
+
use_global_pos: false
|
| 18 |
+
corr_dilate_iterations: 2
|
| 19 |
+
|
| 20 |
+
train:
|
| 21 |
+
gradient_accumulation_steps: 1
|
home/ubuntu/aaaaa/data/rgbmr/configs/mcdiff/single_full.yaml
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
base: configs/mcdiff/default.yaml
|
| 2 |
+
|
| 3 |
+
project:
|
| 4 |
+
output_dir: outputs/mcdiff_v.single_full
|
| 5 |
+
|
| 6 |
+
data:
|
| 7 |
+
train:
|
| 8 |
+
num_workers: 4
|
| 9 |
+
batch_size: 2
|
| 10 |
+
|
| 11 |
+
model:
|
| 12 |
+
mcvae_ckpt: "outputs/mcvae_v1.8.1.pt"
|
| 13 |
+
use_dual_branch: false
|
| 14 |
+
use_caa: false
|
| 15 |
+
use_rope: true
|
| 16 |
+
use_global_token: false
|
| 17 |
+
use_global_pos: false
|
| 18 |
+
corr_dilate_iterations: 2
|
| 19 |
+
|
| 20 |
+
train:
|
| 21 |
+
gradient_accumulation_steps: 2
|
home/ubuntu/aaaaa/data/rgbmr/configs/mcdiff/single_fulll.yaml
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
base: configs/mcdiff/default.yaml
|
| 2 |
+
|
| 3 |
+
project:
|
| 4 |
+
output_dir: outputs/mcdiff_v.single_fulll
|
| 5 |
+
|
| 6 |
+
data:
|
| 7 |
+
train:
|
| 8 |
+
num_workers: 4
|
| 9 |
+
batch_size: 2
|
| 10 |
+
|
| 11 |
+
model:
|
| 12 |
+
mcvae_ckpt: "outputs/mcvae_v1.8.1.pt"
|
| 13 |
+
use_dual_branch: false
|
| 14 |
+
use_caa: false
|
| 15 |
+
use_rope: true
|
| 16 |
+
use_global_token: false
|
| 17 |
+
use_global_pos: false
|
| 18 |
+
corr_dilate_iterations: 2
|
| 19 |
+
|
| 20 |
+
train:
|
| 21 |
+
gradient_accumulation_steps: 2
|
home/ubuntu/aaaaa/data/rgbmr/configs/mcvae/config.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"_class_name": "AutoencoderKL",
|
| 3 |
+
"_diffusers_version": "0.31.0.dev0",
|
| 4 |
+
"_name_or_path": "../sdxl-vae/",
|
| 5 |
+
"act_fn": "silu",
|
| 6 |
+
"block_out_channels": [
|
| 7 |
+
128,
|
| 8 |
+
256,
|
| 9 |
+
512,
|
| 10 |
+
512
|
| 11 |
+
],
|
| 12 |
+
"down_block_types": [
|
| 13 |
+
"DownEncoderBlock2D",
|
| 14 |
+
"DownEncoderBlock2D",
|
| 15 |
+
"DownEncoderBlock2D",
|
| 16 |
+
"DownEncoderBlock2D"
|
| 17 |
+
],
|
| 18 |
+
"force_upcast": false,
|
| 19 |
+
"in_channels": 5,
|
| 20 |
+
"latent_channels": 16,
|
| 21 |
+
"latents_mean": null,
|
| 22 |
+
"latents_std": null,
|
| 23 |
+
"layers_per_block": 2,
|
| 24 |
+
"mid_block_add_attention": true,
|
| 25 |
+
"norm_num_groups": 32,
|
| 26 |
+
"out_channels": 5,
|
| 27 |
+
"sample_size": 1024,
|
| 28 |
+
"scaling_factor": null,
|
| 29 |
+
"shift_factor": null,
|
| 30 |
+
"up_block_types": [
|
| 31 |
+
"UpDecoderBlock2D",
|
| 32 |
+
"UpDecoderBlock2D",
|
| 33 |
+
"UpDecoderBlock2D",
|
| 34 |
+
"UpDecoderBlock2D"
|
| 35 |
+
],
|
| 36 |
+
"use_post_quant_conv": false,
|
| 37 |
+
"use_quant_conv": false
|
| 38 |
+
}
|
home/ubuntu/aaaaa/data/rgbmr/configs/mcvae/default.yaml
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
# ===== Multi-Channel VAE Training Config (SD3.5 VAE, z=16) =====
|
| 3 |
+
|
| 4 |
+
seed: 43
|
| 5 |
+
device: cuda
|
| 6 |
+
|
| 7 |
+
project:
|
| 8 |
+
name: MCVAE_revised
|
| 9 |
+
run_name: MCVAE_v1.1.0
|
| 10 |
+
notes: "SD3.5-like (z=16)"
|
| 11 |
+
save_dir: ./outputs/${project.run_name}
|
| 12 |
+
|
| 13 |
+
data:
|
| 14 |
+
train:
|
| 15 |
+
root: /scratch/aaaaa/data/rgbmr_mv_web2/merged
|
| 16 |
+
num_workers: 2
|
| 17 |
+
batch_size: 8
|
| 18 |
+
val:
|
| 19 |
+
root: /scratch/aaaaa/data/rgbmr_mv_web2/validation_mcvae
|
| 20 |
+
num_workers: 1
|
| 21 |
+
batch_size: 6
|
| 22 |
+
|
| 23 |
+
model:
|
| 24 |
+
resolution: 512
|
| 25 |
+
image_range: [-1.0, 1.0]
|
| 26 |
+
mcvae_config: ./configs/mcvae/config.json
|
| 27 |
+
|
| 28 |
+
init_layers_from: null # path to checkpoint to init layers from, or null to train from sd vae
|
| 29 |
+
init_layers_strict: false
|
| 30 |
+
|
| 31 |
+
offset_mode: true # If true, encoder outputs offsets added to base mean/logvar. If false, directly predicts mean/logvar.
|
| 32 |
+
deterministic_mode: false # If true, encoder outputs deterministic latents (logvar=-30, Dirac delta). KL terms auto-disabled.
|
| 33 |
+
|
| 34 |
+
sd_vae:
|
| 35 |
+
from_pretrained: ./vae_sd35
|
| 36 |
+
dtype: bfloat16
|
| 37 |
+
|
| 38 |
+
latent_transform:
|
| 39 |
+
type: scaling+crop+rotation # identity | scaling | crop | rotation | ...
|
| 40 |
+
identity_prob: 0.5 # Probability of NOT applying augmentation
|
| 41 |
+
tiebreaking_prob: 0.3 # When both scaling and crop are enabled, prob of choosing scaling
|
| 42 |
+
scaling_range: [16, 64] # Min and max for 64-based scaling
|
| 43 |
+
crop_range: [3, 32] # Min and max for 64-based crop
|
| 44 |
+
|
| 45 |
+
loss:
|
| 46 |
+
disc_start: 40001
|
| 47 |
+
disc_num_layers: 5
|
| 48 |
+
disc_in_channels: 5
|
| 49 |
+
disc_loss: hinge
|
| 50 |
+
disc_factor: 1.0
|
| 51 |
+
base_recon_weight: 3.0
|
| 52 |
+
disc_weight: 0.02
|
| 53 |
+
recon_loss_type: mse
|
| 54 |
+
perceptual_rgb_weight: 0.24 # should be 3/5 * 0.4
|
| 55 |
+
perceptual_orm_weight: 0.16 # should be 2/5 * 0.4
|
| 56 |
+
orm_loss_type: lpips_per_channel
|
| 57 |
+
orm_loss_params: {}
|
| 58 |
+
use_identity_loss: false # If true, adds identity loss: L2(vae.decode(mcvae.encode(x)), x[:,:3])
|
| 59 |
+
|
| 60 |
+
optim:
|
| 61 |
+
lr: 3.0e-5
|
| 62 |
+
betas: [0.9, 0.99]
|
| 63 |
+
weight_decay: 0.0
|
| 64 |
+
use_8bit_adam: true
|
| 65 |
+
|
| 66 |
+
trainer:
|
| 67 |
+
max_steps: 400001 # double counted by lightning, so 200k = 400001
|
| 68 |
+
val_check_interval: 750
|
| 69 |
+
log_every_n_steps: 50
|
| 70 |
+
limit_val_batches: 128
|
| 71 |
+
precision: bf16-mixed
|
| 72 |
+
accumulate_grad_batches: 1
|
| 73 |
+
gradient_clip_val: 0.0
|
| 74 |
+
devices: -1
|
| 75 |
+
accelerator: gpu
|
| 76 |
+
num_sanity_val_steps: 1
|
| 77 |
+
|
| 78 |
+
logging:
|
| 79 |
+
image_log_every_n_val_epochs: 1
|
| 80 |
+
n_log_images: 6
|
| 81 |
+
save_top_k: 5
|
| 82 |
+
monitor: val/recon_loss
|
| 83 |
+
mode: min
|
| 84 |
+
layers_save_every_n_steps: 10000
|
| 85 |
+
layers_weights_filename: layer_weights.pt
|
| 86 |
+
train_image_every_n_steps: 400001
|
| 87 |
+
|
| 88 |
+
lambdas:
|
| 89 |
+
lambda_recon: 1.0
|
| 90 |
+
lambda_kl: 1e-6
|
| 91 |
+
lambda_kl_base: 3e-9
|
| 92 |
+
lambda_identity: 3.0 # Weight for identity loss (only used if loss.use_identity_loss=true)
|
home/ubuntu/aaaaa/data/rgbmr/configs/mcvae/layerdiffuse.yaml
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
base: configs/mcvae/default.yaml
|
| 3 |
+
|
| 4 |
+
project:
|
| 5 |
+
name: MCVAE_FINAL
|
| 6 |
+
run_name: MCVAE_v.layerdiffuse
|
| 7 |
+
|
| 8 |
+
data:
|
| 9 |
+
train:
|
| 10 |
+
root: /home/ubuntu/aaaaa/data/rgbmr/merged
|
| 11 |
+
batch_size: 8
|
| 12 |
+
val:
|
| 13 |
+
root: /home/ubuntu/aaaaa/data/rgbmr/validation_mcvae
|
| 14 |
+
|
| 15 |
+
model:
|
| 16 |
+
offset_mode: true
|
| 17 |
+
deterministic_mode: true # Enable deterministic mode (logvar=-30, Dirac delta, KL auto-disabled)
|
| 18 |
+
|
| 19 |
+
latent_transform:
|
| 20 |
+
type: scaling+crop+rotation # identity | scaling | crop | rotation | ...
|
| 21 |
+
identity_prob: 0.5
|
| 22 |
+
|
| 23 |
+
loss:
|
| 24 |
+
use_identity_loss: true # If true, adds identity loss: L2(vae.decode(mcvae.encode(x)), x[:,:3])
|
| 25 |
+
|
| 26 |
+
trainer:
|
| 27 |
+
max_steps: 400001 # double counted by lightning, so 200k = 400001
|
| 28 |
+
accumulate_grad_batches: 1
|
| 29 |
+
|
| 30 |
+
lambdas:
|
| 31 |
+
lambda_recon: 1.0
|
| 32 |
+
lambda_kl: 0.0 # Auto-disabled by deterministic_mode
|
| 33 |
+
lambda_kl_base: 0.0 # Auto-disabled by deterministic_mode
|
| 34 |
+
lambda_identity: 3.0
|
home/ubuntu/aaaaa/data/rgbmr/configs/mcvae/no_crop.yaml
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
base: configs/mcvae/default.yaml
|
| 3 |
+
|
| 4 |
+
project:
|
| 5 |
+
name: MCVAE_FINAL
|
| 6 |
+
run_name: MCVAE_v.no_crop
|
| 7 |
+
|
| 8 |
+
data:
|
| 9 |
+
train:
|
| 10 |
+
root: /home/aaaaa/data/rgbmr_mv_web2/merged
|
| 11 |
+
batch_size: 8
|
| 12 |
+
val:
|
| 13 |
+
root: /home/aaaaa/data/rgbmr_mv_web2/validation_mcvae
|
| 14 |
+
|
| 15 |
+
model:
|
| 16 |
+
offset_mode: true
|
| 17 |
+
|
| 18 |
+
latent_transform:
|
| 19 |
+
type: identity # identity | scaling | crop | rotation | ...
|
| 20 |
+
identity_prob: 1.0
|
| 21 |
+
|
| 22 |
+
trainer:
|
| 23 |
+
max_steps: 400001 # double counted by lightning, so 200k = 400001
|
| 24 |
+
accumulate_grad_batches: 1
|
| 25 |
+
|
| 26 |
+
lambdas:
|
| 27 |
+
lambda_recon: 1.0
|
| 28 |
+
lambda_kl: 1e-6
|
| 29 |
+
lambda_kl_base: 3e-9
|
home/ubuntu/aaaaa/data/rgbmr/configs/mcvae/orchid.yaml
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
base: configs/mcvae/default.yaml
|
| 3 |
+
|
| 4 |
+
project:
|
| 5 |
+
name: MCVAE_FINAL
|
| 6 |
+
run_name: MCVAE_v.orchid
|
| 7 |
+
|
| 8 |
+
data:
|
| 9 |
+
train:
|
| 10 |
+
root: /home/ubuntu/aaaaa/data/rgbmr/merged
|
| 11 |
+
batch_size: 8
|
| 12 |
+
val:
|
| 13 |
+
root: /home/ubuntu/aaaaa/data/rgbmr/validation_mcvae
|
| 14 |
+
|
| 15 |
+
model:
|
| 16 |
+
offset_mode: false
|
| 17 |
+
|
| 18 |
+
latent_transform:
|
| 19 |
+
type: scaling+crop+rotation # identity | scaling | crop | rotation | ...
|
| 20 |
+
identity_prob: 0.5
|
| 21 |
+
|
| 22 |
+
trainer:
|
| 23 |
+
max_steps: 400001 # double counted by lightning, so 200k = 400001
|
| 24 |
+
accumulate_grad_batches: 1
|
| 25 |
+
|
| 26 |
+
lambdas:
|
| 27 |
+
lambda_recon: 1.0
|
| 28 |
+
lambda_kl: 1e-6
|
| 29 |
+
lambda_kl_base: 3e-9
|
home/ubuntu/aaaaa/data/rgbmr/configs/mcvae/ours.yaml
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
base: configs/mcvae/default.yaml
|
| 3 |
+
|
| 4 |
+
project:
|
| 5 |
+
name: MCVAE_FINAL
|
| 6 |
+
run_name: MCVAE_v.ours
|
| 7 |
+
|
| 8 |
+
data:
|
| 9 |
+
train:
|
| 10 |
+
root: /home/aaaaa/data/rgbmr_mv_web2/merged
|
| 11 |
+
batch_size: 8
|
| 12 |
+
val:
|
| 13 |
+
root: /home/aaaaa/data/rgbmr_mv_web2/validation_mcvae
|
| 14 |
+
|
| 15 |
+
model:
|
| 16 |
+
offset_mode: true
|
| 17 |
+
|
| 18 |
+
latent_transform:
|
| 19 |
+
type: scaling+crop+rotation # identity | scaling | crop | rotation | ...
|
| 20 |
+
identity_prob: 0.5
|
| 21 |
+
|
| 22 |
+
trainer:
|
| 23 |
+
max_steps: 400001 # double counted by lightning, so 200k = 400001
|
| 24 |
+
accumulate_grad_batches: 1
|
| 25 |
+
|
| 26 |
+
lambdas:
|
| 27 |
+
lambda_recon: 1.0
|
| 28 |
+
lambda_kl: 1e-6
|
| 29 |
+
lambda_kl_base: 3e-9
|
home/ubuntu/aaaaa/data/rgbmr/configs/mcvae/variant_example.yaml
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Example variant config that inherits from default.yaml
|
| 2 |
+
# This demonstrates the base-variant inheritance structure
|
| 3 |
+
|
| 4 |
+
base: configs/mcvae/default.yaml
|
| 5 |
+
|
| 6 |
+
# Override specific parameters
|
| 7 |
+
project:
|
| 8 |
+
run_name: MCVAE_v1.1.0_variant_example
|
| 9 |
+
notes: "Example variant configuration"
|
| 10 |
+
|
| 11 |
+
# Override training parameters
|
| 12 |
+
trainer:
|
| 13 |
+
max_steps: 50001
|
| 14 |
+
val_check_interval: 500
|
| 15 |
+
|
| 16 |
+
# Override optimizer parameters
|
| 17 |
+
optim:
|
| 18 |
+
lr: 1.0e-4
|
| 19 |
+
|
| 20 |
+
# Override data parameters
|
| 21 |
+
data:
|
| 22 |
+
train:
|
| 23 |
+
batch_size: 6
|
| 24 |
+
num_workers: 4
|
home/ubuntu/aaaaa/data/rgbmr/custom_rasterizer/custom_rasterizer/__init__.py
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
from .render import rasterize, interpolate
|
| 3 |
+
"""
|
| 4 |
+
from .render import *
|
home/ubuntu/aaaaa/data/rgbmr/custom_rasterizer/custom_rasterizer/render.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import custom_rasterizer_kernel
|
| 2 |
+
import torch
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
def rasterize(pos, tri, resolution, clamp_depth=torch.zeros(0), use_depth_prior=0):
|
| 6 |
+
assert pos.device == tri.device
|
| 7 |
+
findices, barycentric = custom_rasterizer_kernel.rasterize_image(
|
| 8 |
+
pos[0], tri, clamp_depth, resolution[1], resolution[0], 1e-6, use_depth_prior
|
| 9 |
+
)
|
| 10 |
+
return findices, barycentric
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
def interpolate(col, findices, barycentric, tri):
|
| 14 |
+
f = findices - 1 + (findices == 0)
|
| 15 |
+
vcol = col[0, tri.long()[f.long()]]
|
| 16 |
+
result = barycentric.view(*barycentric.shape, 1) * vcol
|
| 17 |
+
result = torch.sum(result, axis=-2)
|
| 18 |
+
return result.view(1, *result.shape)
|
home/ubuntu/aaaaa/data/rgbmr/custom_rasterizer/lib/custom_rasterizer_kernel/__init__.py
ADDED
|
File without changes
|
home/ubuntu/aaaaa/data/rgbmr/custom_rasterizer/lib/custom_rasterizer_kernel/grid_neighbor.cpp
ADDED
|
@@ -0,0 +1,574 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#include "rasterizer.h"
|
| 2 |
+
#include <fstream>
|
| 3 |
+
|
| 4 |
+
inline int pos2key(float* p, int resolution) {
|
| 5 |
+
int x = (p[0] * 0.5 + 0.5) * resolution;
|
| 6 |
+
int y = (p[1] * 0.5 + 0.5) * resolution;
|
| 7 |
+
int z = (p[2] * 0.5 + 0.5) * resolution;
|
| 8 |
+
return (x * resolution + y) * resolution + z;
|
| 9 |
+
}
|
| 10 |
+
|
| 11 |
+
inline void key2pos(int key, int resolution, float* p) {
|
| 12 |
+
int x = key / resolution / resolution;
|
| 13 |
+
int y = key / resolution % resolution;
|
| 14 |
+
int z = key % resolution;
|
| 15 |
+
p[0] = ((x + 0.5) / resolution - 0.5) * 2;
|
| 16 |
+
p[1] = ((y + 0.5) / resolution - 0.5) * 2;
|
| 17 |
+
p[2] = ((z + 0.5) / resolution - 0.5) * 2;
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
inline void key2cornerpos(int key, int resolution, float* p) {
|
| 21 |
+
int x = key / resolution / resolution;
|
| 22 |
+
int y = key / resolution % resolution;
|
| 23 |
+
int z = key % resolution;
|
| 24 |
+
p[0] = ((x + 0.75) / resolution - 0.5) * 2;
|
| 25 |
+
p[1] = ((y + 0.25) / resolution - 0.5) * 2;
|
| 26 |
+
p[2] = ((z + 0.75) / resolution - 0.5) * 2;
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
inline float* pos_ptr(int l, int i, int j, torch::Tensor t) {
|
| 30 |
+
float* pdata = t.data_ptr<float>();
|
| 31 |
+
int height = t.size(1);
|
| 32 |
+
int width = t.size(2);
|
| 33 |
+
return &pdata[((l * height + i) * width + j) * 4];
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
struct Grid
|
| 37 |
+
{
|
| 38 |
+
std::vector<int> seq2oddcorner;
|
| 39 |
+
std::vector<int> seq2evencorner;
|
| 40 |
+
std::vector<int> seq2grid;
|
| 41 |
+
std::vector<int> seq2normal;
|
| 42 |
+
std::vector<int> seq2neighbor;
|
| 43 |
+
std::unordered_map<int, int> grid2seq;
|
| 44 |
+
std::vector<int> downsample_seq;
|
| 45 |
+
int num_origin_seq;
|
| 46 |
+
int resolution;
|
| 47 |
+
int stride;
|
| 48 |
+
};
|
| 49 |
+
|
| 50 |
+
inline void pos_from_seq(Grid& grid, int seq, float* p) {
|
| 51 |
+
auto k = grid.seq2grid[seq];
|
| 52 |
+
key2pos(k, grid.resolution, p);
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
inline int fetch_seq(Grid& grid, int l, int i, int j, torch::Tensor pdata) {
|
| 56 |
+
float* p = pos_ptr(l, i, j, pdata);
|
| 57 |
+
if (p[3] == 0)
|
| 58 |
+
return -1;
|
| 59 |
+
auto key = pos2key(p, grid.resolution);
|
| 60 |
+
int seq = grid.grid2seq[key];
|
| 61 |
+
return seq;
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
inline int fetch_last_seq(Grid& grid, int i, int j, torch::Tensor pdata) {
|
| 65 |
+
int num_layers = pdata.size(0);
|
| 66 |
+
int l = 0;
|
| 67 |
+
int idx = fetch_seq(grid, l, i, j, pdata);
|
| 68 |
+
while (l < num_layers - 1) {
|
| 69 |
+
l += 1;
|
| 70 |
+
int new_idx = fetch_seq(grid, l, i, j, pdata);
|
| 71 |
+
if (new_idx == -1)
|
| 72 |
+
break;
|
| 73 |
+
idx = new_idx;
|
| 74 |
+
}
|
| 75 |
+
return idx;
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
inline int fetch_nearest_seq(Grid& grid, int i, int j, int dim, float d, torch::Tensor pdata) {
|
| 79 |
+
float p[3];
|
| 80 |
+
float max_dist = 1e10;
|
| 81 |
+
int best_idx = -1;
|
| 82 |
+
int num_layers = pdata.size(0);
|
| 83 |
+
for (int l = 0; l < num_layers; ++l) {
|
| 84 |
+
int idx = fetch_seq(grid, l, i, j, pdata);
|
| 85 |
+
if (idx == -1)
|
| 86 |
+
break;
|
| 87 |
+
pos_from_seq(grid, idx, p);
|
| 88 |
+
float dist = std::abs(d - p[(dim + 2) % 3]);
|
| 89 |
+
if (dist < max_dist) {
|
| 90 |
+
max_dist = dist;
|
| 91 |
+
best_idx = idx;
|
| 92 |
+
}
|
| 93 |
+
}
|
| 94 |
+
return best_idx;
|
| 95 |
+
}
|
| 96 |
+
|
| 97 |
+
inline int fetch_nearest_seq_layer(Grid& grid, int i, int j, int dim, float d, torch::Tensor pdata) {
|
| 98 |
+
float p[3];
|
| 99 |
+
float max_dist = 1e10;
|
| 100 |
+
int best_layer = -1;
|
| 101 |
+
int num_layers = pdata.size(0);
|
| 102 |
+
for (int l = 0; l < num_layers; ++l) {
|
| 103 |
+
int idx = fetch_seq(grid, l, i, j, pdata);
|
| 104 |
+
if (idx == -1)
|
| 105 |
+
break;
|
| 106 |
+
pos_from_seq(grid, idx, p);
|
| 107 |
+
float dist = std::abs(d - p[(dim + 2) % 3]);
|
| 108 |
+
if (dist < max_dist) {
|
| 109 |
+
max_dist = dist;
|
| 110 |
+
best_layer = l;
|
| 111 |
+
}
|
| 112 |
+
}
|
| 113 |
+
return best_layer;
|
| 114 |
+
}
|
| 115 |
+
|
| 116 |
+
void FetchNeighbor(Grid& grid, int seq, float* pos, int dim, int boundary_info, std::vector<torch::Tensor>& view_layer_positions,
|
| 117 |
+
int* output_indices)
|
| 118 |
+
{
|
| 119 |
+
auto t = view_layer_positions[dim];
|
| 120 |
+
int height = t.size(1);
|
| 121 |
+
int width = t.size(2);
|
| 122 |
+
int top = 0;
|
| 123 |
+
int ci = 0;
|
| 124 |
+
int cj = 0;
|
| 125 |
+
if (dim == 0) {
|
| 126 |
+
ci = (pos[1]/2+0.5)*height;
|
| 127 |
+
cj = (pos[0]/2+0.5)*width;
|
| 128 |
+
}
|
| 129 |
+
else if (dim == 1) {
|
| 130 |
+
ci = (pos[1]/2+0.5)*height;
|
| 131 |
+
cj = (pos[2]/2+0.5)*width;
|
| 132 |
+
}
|
| 133 |
+
else {
|
| 134 |
+
ci = (-pos[2]/2+0.5)*height;
|
| 135 |
+
cj = (pos[0]/2+0.5)*width;
|
| 136 |
+
}
|
| 137 |
+
int stride = grid.stride;
|
| 138 |
+
for (int ni = ci + stride; ni >= ci - stride; ni -= stride) {
|
| 139 |
+
for (int nj = cj - stride; nj <= cj + stride; nj += stride) {
|
| 140 |
+
int idx = -1;
|
| 141 |
+
if (ni == ci && nj == cj)
|
| 142 |
+
idx = seq;
|
| 143 |
+
else if (!(ni < 0 || ni >= height || nj < 0 || nj >= width)) {
|
| 144 |
+
if (boundary_info == -1)
|
| 145 |
+
idx = fetch_seq(grid, 0, ni, nj, t);
|
| 146 |
+
else if (boundary_info == 1)
|
| 147 |
+
idx = fetch_last_seq(grid, ni, nj, t);
|
| 148 |
+
else
|
| 149 |
+
idx = fetch_nearest_seq(grid, ni, nj, dim, pos[(dim + 2) % 3], t);
|
| 150 |
+
}
|
| 151 |
+
output_indices[top] = idx;
|
| 152 |
+
top += 1;
|
| 153 |
+
}
|
| 154 |
+
}
|
| 155 |
+
}
|
| 156 |
+
|
| 157 |
+
void DownsampleGrid(Grid& src, Grid& tar)
|
| 158 |
+
{
|
| 159 |
+
src.downsample_seq.resize(src.seq2grid.size(), -1);
|
| 160 |
+
tar.resolution = src.resolution / 2;
|
| 161 |
+
tar.stride = src.stride * 2;
|
| 162 |
+
float pos[3];
|
| 163 |
+
std::vector<int> seq2normal_count;
|
| 164 |
+
for (int i = 0; i < src.seq2grid.size(); ++i) {
|
| 165 |
+
key2pos(src.seq2grid[i], src.resolution, pos);
|
| 166 |
+
int k = pos2key(pos, tar.resolution);
|
| 167 |
+
int s = seq2normal_count.size();
|
| 168 |
+
if (!tar.grid2seq.count(k)) {
|
| 169 |
+
tar.grid2seq[k] = tar.seq2grid.size();
|
| 170 |
+
tar.seq2grid.emplace_back(k);
|
| 171 |
+
seq2normal_count.emplace_back(0);
|
| 172 |
+
seq2normal_count.emplace_back(0);
|
| 173 |
+
seq2normal_count.emplace_back(0);
|
| 174 |
+
//tar.seq2normal.emplace_back(src.seq2normal[i]);
|
| 175 |
+
} else {
|
| 176 |
+
s = tar.grid2seq[k] * 3;
|
| 177 |
+
}
|
| 178 |
+
seq2normal_count[s + src.seq2normal[i]] += 1;
|
| 179 |
+
src.downsample_seq[i] = tar.grid2seq[k];
|
| 180 |
+
}
|
| 181 |
+
tar.seq2normal.resize(seq2normal_count.size() / 3);
|
| 182 |
+
for (int i = 0; i < seq2normal_count.size(); i += 3) {
|
| 183 |
+
int t = 0;
|
| 184 |
+
for (int j = 1; j < 3; ++j) {
|
| 185 |
+
if (seq2normal_count[i + j] > seq2normal_count[i + t])
|
| 186 |
+
t = j;
|
| 187 |
+
}
|
| 188 |
+
tar.seq2normal[i / 3] = t;
|
| 189 |
+
}
|
| 190 |
+
}
|
| 191 |
+
|
| 192 |
+
void NeighborGrid(Grid& grid, std::vector<torch::Tensor> view_layer_positions, int v)
|
| 193 |
+
{
|
| 194 |
+
grid.seq2evencorner.resize(grid.seq2grid.size(), 0);
|
| 195 |
+
grid.seq2oddcorner.resize(grid.seq2grid.size(), 0);
|
| 196 |
+
std::unordered_set<int> visited_seq;
|
| 197 |
+
for (int vd = 0; vd < 3; ++vd) {
|
| 198 |
+
auto t = view_layer_positions[vd];
|
| 199 |
+
auto t0 = view_layer_positions[v];
|
| 200 |
+
int height = t.size(1);
|
| 201 |
+
int width = t.size(2);
|
| 202 |
+
int num_layers = t.size(0);
|
| 203 |
+
int num_view_layers = t0.size(0);
|
| 204 |
+
for (int i = 0; i < height; ++i) {
|
| 205 |
+
for (int j = 0; j < width; ++j) {
|
| 206 |
+
for (int l = 0; l < num_layers; ++l) {
|
| 207 |
+
int seq = fetch_seq(grid, l, i, j, t);
|
| 208 |
+
if (seq == -1)
|
| 209 |
+
break;
|
| 210 |
+
int dim = grid.seq2normal[seq];
|
| 211 |
+
if (dim != v)
|
| 212 |
+
continue;
|
| 213 |
+
|
| 214 |
+
float pos[3];
|
| 215 |
+
pos_from_seq(grid, seq, pos);
|
| 216 |
+
|
| 217 |
+
int ci = 0;
|
| 218 |
+
int cj = 0;
|
| 219 |
+
if (dim == 0) {
|
| 220 |
+
ci = (pos[1]/2+0.5)*height;
|
| 221 |
+
cj = (pos[0]/2+0.5)*width;
|
| 222 |
+
}
|
| 223 |
+
else if (dim == 1) {
|
| 224 |
+
ci = (pos[1]/2+0.5)*height;
|
| 225 |
+
cj = (pos[2]/2+0.5)*width;
|
| 226 |
+
}
|
| 227 |
+
else {
|
| 228 |
+
ci = (-pos[2]/2+0.5)*height;
|
| 229 |
+
cj = (pos[0]/2+0.5)*width;
|
| 230 |
+
}
|
| 231 |
+
|
| 232 |
+
if ((ci % (grid.stride * 2) < grid.stride) && (cj % (grid.stride * 2) >= grid.stride))
|
| 233 |
+
grid.seq2evencorner[seq] = 1;
|
| 234 |
+
|
| 235 |
+
if ((ci % (grid.stride * 2) >= grid.stride) && (cj % (grid.stride * 2) < grid.stride))
|
| 236 |
+
grid.seq2oddcorner[seq] = 1;
|
| 237 |
+
|
| 238 |
+
bool is_boundary = false;
|
| 239 |
+
if (vd == v) {
|
| 240 |
+
if (l == 0 || l == num_layers - 1)
|
| 241 |
+
is_boundary = true;
|
| 242 |
+
else {
|
| 243 |
+
int seq_new = fetch_seq(grid, l + 1, i, j, t);
|
| 244 |
+
if (seq_new == -1)
|
| 245 |
+
is_boundary = true;
|
| 246 |
+
}
|
| 247 |
+
}
|
| 248 |
+
int boundary_info = 0;
|
| 249 |
+
if (is_boundary && (l == 0))
|
| 250 |
+
boundary_info = -1;
|
| 251 |
+
else if (is_boundary)
|
| 252 |
+
boundary_info = 1;
|
| 253 |
+
if (visited_seq.count(seq))
|
| 254 |
+
continue;
|
| 255 |
+
visited_seq.insert(seq);
|
| 256 |
+
|
| 257 |
+
FetchNeighbor(grid, seq, pos, dim, boundary_info, view_layer_positions, &grid.seq2neighbor[seq * 9]);
|
| 258 |
+
}
|
| 259 |
+
}
|
| 260 |
+
}
|
| 261 |
+
}
|
| 262 |
+
}
|
| 263 |
+
|
| 264 |
+
void PadGrid(Grid& src, Grid& tar, std::vector<torch::Tensor>& view_layer_positions) {
|
| 265 |
+
auto& downsample_seq = src.downsample_seq;
|
| 266 |
+
auto& seq2evencorner = src.seq2evencorner;
|
| 267 |
+
auto& seq2oddcorner = src.seq2oddcorner;
|
| 268 |
+
int indices[9];
|
| 269 |
+
std::vector<int> mapped_even_corners(tar.seq2grid.size(), 0);
|
| 270 |
+
std::vector<int> mapped_odd_corners(tar.seq2grid.size(), 0);
|
| 271 |
+
for (int i = 0; i < downsample_seq.size(); ++i) {
|
| 272 |
+
if (seq2evencorner[i] > 0) {
|
| 273 |
+
mapped_even_corners[downsample_seq[i]] = 1;
|
| 274 |
+
}
|
| 275 |
+
if (seq2oddcorner[i] > 0) {
|
| 276 |
+
mapped_odd_corners[downsample_seq[i]] = 1;
|
| 277 |
+
}
|
| 278 |
+
}
|
| 279 |
+
auto& tar_seq2normal = tar.seq2normal;
|
| 280 |
+
auto& tar_seq2grid = tar.seq2grid;
|
| 281 |
+
for (int i = 0; i < tar_seq2grid.size(); ++i) {
|
| 282 |
+
if (mapped_even_corners[i] == 1 && mapped_odd_corners[i] == 1)
|
| 283 |
+
continue;
|
| 284 |
+
auto k = tar_seq2grid[i];
|
| 285 |
+
float p[3];
|
| 286 |
+
key2cornerpos(k, tar.resolution, p);
|
| 287 |
+
|
| 288 |
+
int src_key = pos2key(p, src.resolution);
|
| 289 |
+
if (!src.grid2seq.count(src_key)) {
|
| 290 |
+
int seq = src.seq2grid.size();
|
| 291 |
+
src.grid2seq[src_key] = seq;
|
| 292 |
+
src.seq2evencorner.emplace_back((mapped_even_corners[i] == 0));
|
| 293 |
+
src.seq2oddcorner.emplace_back((mapped_odd_corners[i] == 0));
|
| 294 |
+
src.seq2grid.emplace_back(src_key);
|
| 295 |
+
src.seq2normal.emplace_back(tar_seq2normal[i]);
|
| 296 |
+
FetchNeighbor(src, seq, p, tar_seq2normal[i], 0, view_layer_positions, indices);
|
| 297 |
+
for (int j = 0; j < 9; ++j) {
|
| 298 |
+
src.seq2neighbor.emplace_back(indices[j]);
|
| 299 |
+
}
|
| 300 |
+
src.downsample_seq.emplace_back(i);
|
| 301 |
+
} else {
|
| 302 |
+
int seq = src.grid2seq[src_key];
|
| 303 |
+
if (mapped_even_corners[i] == 0)
|
| 304 |
+
src.seq2evencorner[seq] = 1;
|
| 305 |
+
if (mapped_odd_corners[i] == 0)
|
| 306 |
+
src.seq2oddcorner[seq] = 1;
|
| 307 |
+
}
|
| 308 |
+
}
|
| 309 |
+
}
|
| 310 |
+
|
| 311 |
+
std::vector<std::vector<torch::Tensor>> build_hierarchy(std::vector<torch::Tensor> view_layer_positions,
|
| 312 |
+
std::vector<torch::Tensor> view_layer_normals, int num_level, int resolution)
|
| 313 |
+
{
|
| 314 |
+
if (view_layer_positions.size() != 3 || num_level < 1) {
|
| 315 |
+
printf("Alert! We require 3 layers and at least 1 level! (%d %d)\n", view_layer_positions.size(), num_level);
|
| 316 |
+
return {{},{},{},{}};
|
| 317 |
+
}
|
| 318 |
+
|
| 319 |
+
std::vector<Grid> grids;
|
| 320 |
+
grids.resize(num_level);
|
| 321 |
+
|
| 322 |
+
std::vector<float> seq2pos;
|
| 323 |
+
auto& seq2grid = grids[0].seq2grid;
|
| 324 |
+
auto& seq2normal = grids[0].seq2normal;
|
| 325 |
+
auto& grid2seq = grids[0].grid2seq;
|
| 326 |
+
grids[0].resolution = resolution;
|
| 327 |
+
grids[0].stride = 1;
|
| 328 |
+
|
| 329 |
+
auto int64_options = torch::TensorOptions().dtype(torch::kInt64).requires_grad(false);
|
| 330 |
+
auto float_options = torch::TensorOptions().dtype(torch::kFloat32).requires_grad(false);
|
| 331 |
+
|
| 332 |
+
for (int v = 0; v < 3; ++v) {
|
| 333 |
+
int num_layers = view_layer_positions[v].size(0);
|
| 334 |
+
int height = view_layer_positions[v].size(1);
|
| 335 |
+
int width = view_layer_positions[v].size(2);
|
| 336 |
+
float* data = view_layer_positions[v].data_ptr<float>();
|
| 337 |
+
float* data_normal = view_layer_normals[v].data_ptr<float>();
|
| 338 |
+
for (int l = 0; l < num_layers; ++l) {
|
| 339 |
+
for (int i = 0; i < height; ++i) {
|
| 340 |
+
for (int j = 0; j < width; ++j) {
|
| 341 |
+
float* p = &data[(i * width + j) * 4];
|
| 342 |
+
float* n = &data_normal[(i * width + j) * 3];
|
| 343 |
+
if (p[3] == 0)
|
| 344 |
+
continue;
|
| 345 |
+
auto k = pos2key(p, resolution);
|
| 346 |
+
if (!grid2seq.count(k)) {
|
| 347 |
+
int dim = 0;
|
| 348 |
+
for (int d = 0; d < 3; ++d) {
|
| 349 |
+
if (std::abs(n[d]) > std::abs(n[dim]))
|
| 350 |
+
dim = d;
|
| 351 |
+
}
|
| 352 |
+
dim = (dim + 1) % 3;
|
| 353 |
+
grid2seq[k] = seq2grid.size();
|
| 354 |
+
seq2grid.emplace_back(k);
|
| 355 |
+
seq2pos.push_back(p[0]);
|
| 356 |
+
seq2pos.push_back(p[1]);
|
| 357 |
+
seq2pos.push_back(p[2]);
|
| 358 |
+
seq2normal.emplace_back(dim);
|
| 359 |
+
}
|
| 360 |
+
}
|
| 361 |
+
}
|
| 362 |
+
data += (height * width * 4);
|
| 363 |
+
data_normal += (height * width * 3);
|
| 364 |
+
}
|
| 365 |
+
}
|
| 366 |
+
|
| 367 |
+
for (int i = 0; i < num_level - 1; ++i) {
|
| 368 |
+
DownsampleGrid(grids[i], grids[i + 1]);
|
| 369 |
+
}
|
| 370 |
+
|
| 371 |
+
for (int l = 0; l < num_level; ++l) {
|
| 372 |
+
grids[l].seq2neighbor.resize(grids[l].seq2grid.size() * 9, -1);
|
| 373 |
+
grids[l].num_origin_seq = grids[l].seq2grid.size();
|
| 374 |
+
for (int d = 0; d < 3; ++d) {
|
| 375 |
+
NeighborGrid(grids[l], view_layer_positions, d);
|
| 376 |
+
}
|
| 377 |
+
}
|
| 378 |
+
|
| 379 |
+
for (int i = num_level - 2; i >= 0; --i) {
|
| 380 |
+
PadGrid(grids[i], grids[i + 1], view_layer_positions);
|
| 381 |
+
}
|
| 382 |
+
for (int i = grids[0].num_origin_seq; i < grids[0].seq2grid.size(); ++i) {
|
| 383 |
+
int k = grids[0].seq2grid[i];
|
| 384 |
+
float p[3];
|
| 385 |
+
key2pos(k, grids[0].resolution, p);
|
| 386 |
+
seq2pos.push_back(p[0]);
|
| 387 |
+
seq2pos.push_back(p[1]);
|
| 388 |
+
seq2pos.push_back(p[2]);
|
| 389 |
+
}
|
| 390 |
+
|
| 391 |
+
std::vector<torch::Tensor> texture_positions(2);
|
| 392 |
+
std::vector<torch::Tensor> grid_neighbors(grids.size());
|
| 393 |
+
std::vector<torch::Tensor> grid_downsamples(grids.size() - 1);
|
| 394 |
+
std::vector<torch::Tensor> grid_evencorners(grids.size());
|
| 395 |
+
std::vector<torch::Tensor> grid_oddcorners(grids.size());
|
| 396 |
+
|
| 397 |
+
texture_positions[0] = torch::zeros({seq2pos.size() / 3, 3}, float_options);
|
| 398 |
+
texture_positions[1] = torch::zeros({seq2pos.size() / 3}, float_options);
|
| 399 |
+
float* positions_out_ptr = texture_positions[0].data_ptr<float>();
|
| 400 |
+
memcpy(positions_out_ptr, seq2pos.data(), sizeof(float) * seq2pos.size());
|
| 401 |
+
positions_out_ptr = texture_positions[1].data_ptr<float>();
|
| 402 |
+
for (int i = 0; i < grids[0].seq2grid.size(); ++i) {
|
| 403 |
+
positions_out_ptr[i] = (i < grids[0].num_origin_seq);
|
| 404 |
+
}
|
| 405 |
+
|
| 406 |
+
for (int i = 0; i < grids.size(); ++i) {
|
| 407 |
+
grid_neighbors[i] = torch::zeros({grids[i].seq2grid.size(), 9}, int64_options);
|
| 408 |
+
long* nptr = grid_neighbors[i].data_ptr<long>();
|
| 409 |
+
for (int j = 0; j < grids[i].seq2neighbor.size(); ++j) {
|
| 410 |
+
nptr[j] = grids[i].seq2neighbor[j];
|
| 411 |
+
}
|
| 412 |
+
|
| 413 |
+
grid_evencorners[i] = torch::zeros({grids[i].seq2evencorner.size()}, int64_options);
|
| 414 |
+
grid_oddcorners[i] = torch::zeros({grids[i].seq2oddcorner.size()}, int64_options);
|
| 415 |
+
long* dptr = grid_evencorners[i].data_ptr<long>();
|
| 416 |
+
for (int j = 0; j < grids[i].seq2evencorner.size(); ++j) {
|
| 417 |
+
dptr[j] = grids[i].seq2evencorner[j];
|
| 418 |
+
}
|
| 419 |
+
dptr = grid_oddcorners[i].data_ptr<long>();
|
| 420 |
+
for (int j = 0; j < grids[i].seq2oddcorner.size(); ++j) {
|
| 421 |
+
dptr[j] = grids[i].seq2oddcorner[j];
|
| 422 |
+
}
|
| 423 |
+
if (i + 1 < grids.size()) {
|
| 424 |
+
grid_downsamples[i] = torch::zeros({grids[i].downsample_seq.size()}, int64_options);
|
| 425 |
+
long* dptr = grid_downsamples[i].data_ptr<long>();
|
| 426 |
+
for (int j = 0; j < grids[i].downsample_seq.size(); ++j) {
|
| 427 |
+
dptr[j] = grids[i].downsample_seq[j];
|
| 428 |
+
}
|
| 429 |
+
}
|
| 430 |
+
|
| 431 |
+
}
|
| 432 |
+
return {texture_positions, grid_neighbors, grid_downsamples, grid_evencorners, grid_oddcorners};
|
| 433 |
+
}
|
| 434 |
+
|
| 435 |
+
std::vector<std::vector<torch::Tensor>> build_hierarchy_with_feat(
|
| 436 |
+
std::vector<torch::Tensor> view_layer_positions,
|
| 437 |
+
std::vector<torch::Tensor> view_layer_normals,
|
| 438 |
+
std::vector<torch::Tensor> view_layer_feats,
|
| 439 |
+
int num_level, int resolution)
|
| 440 |
+
{
|
| 441 |
+
if (view_layer_positions.size() != 3 || num_level < 1) {
|
| 442 |
+
printf("Alert! We require 3 layers and at least 1 level! (%d %d)\n", view_layer_positions.size(), num_level);
|
| 443 |
+
return {{},{},{},{}};
|
| 444 |
+
}
|
| 445 |
+
|
| 446 |
+
std::vector<Grid> grids;
|
| 447 |
+
grids.resize(num_level);
|
| 448 |
+
|
| 449 |
+
std::vector<float> seq2pos;
|
| 450 |
+
std::vector<float> seq2feat;
|
| 451 |
+
auto& seq2grid = grids[0].seq2grid;
|
| 452 |
+
auto& seq2normal = grids[0].seq2normal;
|
| 453 |
+
auto& grid2seq = grids[0].grid2seq;
|
| 454 |
+
grids[0].resolution = resolution;
|
| 455 |
+
grids[0].stride = 1;
|
| 456 |
+
|
| 457 |
+
auto int64_options = torch::TensorOptions().dtype(torch::kInt64).requires_grad(false);
|
| 458 |
+
auto float_options = torch::TensorOptions().dtype(torch::kFloat32).requires_grad(false);
|
| 459 |
+
|
| 460 |
+
int feat_channel = 3;
|
| 461 |
+
for (int v = 0; v < 3; ++v) {
|
| 462 |
+
int num_layers = view_layer_positions[v].size(0);
|
| 463 |
+
int height = view_layer_positions[v].size(1);
|
| 464 |
+
int width = view_layer_positions[v].size(2);
|
| 465 |
+
float* data = view_layer_positions[v].data_ptr<float>();
|
| 466 |
+
float* data_normal = view_layer_normals[v].data_ptr<float>();
|
| 467 |
+
float* data_feat = view_layer_feats[v].data_ptr<float>();
|
| 468 |
+
feat_channel = view_layer_feats[v].size(3);
|
| 469 |
+
for (int l = 0; l < num_layers; ++l) {
|
| 470 |
+
for (int i = 0; i < height; ++i) {
|
| 471 |
+
for (int j = 0; j < width; ++j) {
|
| 472 |
+
float* p = &data[(i * width + j) * 4];
|
| 473 |
+
float* n = &data_normal[(i * width + j) * 3];
|
| 474 |
+
float* f = &data_feat[(i * width + j) * feat_channel];
|
| 475 |
+
if (p[3] == 0)
|
| 476 |
+
continue;
|
| 477 |
+
auto k = pos2key(p, resolution);
|
| 478 |
+
if (!grid2seq.count(k)) {
|
| 479 |
+
int dim = 0;
|
| 480 |
+
for (int d = 0; d < 3; ++d) {
|
| 481 |
+
if (std::abs(n[d]) > std::abs(n[dim]))
|
| 482 |
+
dim = d;
|
| 483 |
+
}
|
| 484 |
+
dim = (dim + 1) % 3;
|
| 485 |
+
grid2seq[k] = seq2grid.size();
|
| 486 |
+
seq2grid.emplace_back(k);
|
| 487 |
+
seq2pos.push_back(p[0]);
|
| 488 |
+
seq2pos.push_back(p[1]);
|
| 489 |
+
seq2pos.push_back(p[2]);
|
| 490 |
+
for (int c = 0; c < feat_channel; ++c) {
|
| 491 |
+
seq2feat.emplace_back(f[c]);
|
| 492 |
+
}
|
| 493 |
+
seq2normal.emplace_back(dim);
|
| 494 |
+
}
|
| 495 |
+
}
|
| 496 |
+
}
|
| 497 |
+
data += (height * width * 4);
|
| 498 |
+
data_normal += (height * width * 3);
|
| 499 |
+
data_feat += (height * width * feat_channel);
|
| 500 |
+
}
|
| 501 |
+
}
|
| 502 |
+
|
| 503 |
+
for (int i = 0; i < num_level - 1; ++i) {
|
| 504 |
+
DownsampleGrid(grids[i], grids[i + 1]);
|
| 505 |
+
}
|
| 506 |
+
|
| 507 |
+
for (int l = 0; l < num_level; ++l) {
|
| 508 |
+
grids[l].seq2neighbor.resize(grids[l].seq2grid.size() * 9, -1);
|
| 509 |
+
grids[l].num_origin_seq = grids[l].seq2grid.size();
|
| 510 |
+
for (int d = 0; d < 3; ++d) {
|
| 511 |
+
NeighborGrid(grids[l], view_layer_positions, d);
|
| 512 |
+
}
|
| 513 |
+
}
|
| 514 |
+
|
| 515 |
+
for (int i = num_level - 2; i >= 0; --i) {
|
| 516 |
+
PadGrid(grids[i], grids[i + 1], view_layer_positions);
|
| 517 |
+
}
|
| 518 |
+
for (int i = grids[0].num_origin_seq; i < grids[0].seq2grid.size(); ++i) {
|
| 519 |
+
int k = grids[0].seq2grid[i];
|
| 520 |
+
float p[3];
|
| 521 |
+
key2pos(k, grids[0].resolution, p);
|
| 522 |
+
seq2pos.push_back(p[0]);
|
| 523 |
+
seq2pos.push_back(p[1]);
|
| 524 |
+
seq2pos.push_back(p[2]);
|
| 525 |
+
for (int c = 0; c < feat_channel; ++c) {
|
| 526 |
+
seq2feat.emplace_back(0.5);
|
| 527 |
+
}
|
| 528 |
+
}
|
| 529 |
+
|
| 530 |
+
std::vector<torch::Tensor> texture_positions(2);
|
| 531 |
+
std::vector<torch::Tensor> texture_feats(1);
|
| 532 |
+
std::vector<torch::Tensor> grid_neighbors(grids.size());
|
| 533 |
+
std::vector<torch::Tensor> grid_downsamples(grids.size() - 1);
|
| 534 |
+
std::vector<torch::Tensor> grid_evencorners(grids.size());
|
| 535 |
+
std::vector<torch::Tensor> grid_oddcorners(grids.size());
|
| 536 |
+
|
| 537 |
+
texture_positions[0] = torch::zeros({seq2pos.size() / 3, 3}, float_options);
|
| 538 |
+
texture_positions[1] = torch::zeros({seq2pos.size() / 3}, float_options);
|
| 539 |
+
texture_feats[0] = torch::zeros({seq2feat.size() / feat_channel, feat_channel}, float_options);
|
| 540 |
+
float* positions_out_ptr = texture_positions[0].data_ptr<float>();
|
| 541 |
+
memcpy(positions_out_ptr, seq2pos.data(), sizeof(float) * seq2pos.size());
|
| 542 |
+
positions_out_ptr = texture_positions[1].data_ptr<float>();
|
| 543 |
+
for (int i = 0; i < grids[0].seq2grid.size(); ++i) {
|
| 544 |
+
positions_out_ptr[i] = (i < grids[0].num_origin_seq);
|
| 545 |
+
}
|
| 546 |
+
float* feats_out_ptr = texture_feats[0].data_ptr<float>();
|
| 547 |
+
memcpy(feats_out_ptr, seq2feat.data(), sizeof(float) * seq2feat.size());
|
| 548 |
+
|
| 549 |
+
for (int i = 0; i < grids.size(); ++i) {
|
| 550 |
+
grid_neighbors[i] = torch::zeros({grids[i].seq2grid.size(), 9}, int64_options);
|
| 551 |
+
long* nptr = grid_neighbors[i].data_ptr<long>();
|
| 552 |
+
for (int j = 0; j < grids[i].seq2neighbor.size(); ++j) {
|
| 553 |
+
nptr[j] = grids[i].seq2neighbor[j];
|
| 554 |
+
}
|
| 555 |
+
grid_evencorners[i] = torch::zeros({grids[i].seq2evencorner.size()}, int64_options);
|
| 556 |
+
grid_oddcorners[i] = torch::zeros({grids[i].seq2oddcorner.size()}, int64_options);
|
| 557 |
+
long* dptr = grid_evencorners[i].data_ptr<long>();
|
| 558 |
+
for (int j = 0; j < grids[i].seq2evencorner.size(); ++j) {
|
| 559 |
+
dptr[j] = grids[i].seq2evencorner[j];
|
| 560 |
+
}
|
| 561 |
+
dptr = grid_oddcorners[i].data_ptr<long>();
|
| 562 |
+
for (int j = 0; j < grids[i].seq2oddcorner.size(); ++j) {
|
| 563 |
+
dptr[j] = grids[i].seq2oddcorner[j];
|
| 564 |
+
}
|
| 565 |
+
if (i + 1 < grids.size()) {
|
| 566 |
+
grid_downsamples[i] = torch::zeros({grids[i].downsample_seq.size()}, int64_options);
|
| 567 |
+
long* dptr = grid_downsamples[i].data_ptr<long>();
|
| 568 |
+
for (int j = 0; j < grids[i].downsample_seq.size(); ++j) {
|
| 569 |
+
dptr[j] = grids[i].downsample_seq[j];
|
| 570 |
+
}
|
| 571 |
+
}
|
| 572 |
+
}
|
| 573 |
+
return {texture_positions, texture_feats, grid_neighbors, grid_downsamples, grid_evencorners, grid_oddcorners};
|
| 574 |
+
}
|
home/ubuntu/aaaaa/data/rgbmr/custom_rasterizer/lib/custom_rasterizer_kernel/rasterizer.cpp
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#include "rasterizer.h"
|
| 2 |
+
|
| 3 |
+
void rasterizeTriangleCPU(int idx, float* vt0, float* vt1, float* vt2, int width, int height, INT64* zbuffer, float* d, float occlusion_truncation) {
|
| 4 |
+
float x_min = std::min(vt0[0], std::min(vt1[0],vt2[0]));
|
| 5 |
+
float x_max = std::max(vt0[0], std::max(vt1[0],vt2[0]));
|
| 6 |
+
float y_min = std::min(vt0[1], std::min(vt1[1],vt2[1]));
|
| 7 |
+
float y_max = std::max(vt0[1], std::max(vt1[1],vt2[1]));
|
| 8 |
+
|
| 9 |
+
for (int px = x_min; px < x_max + 1; ++px) {
|
| 10 |
+
if (px < 0 || px >= width)
|
| 11 |
+
continue;
|
| 12 |
+
for (int py = y_min; py < y_max + 1; ++py) {
|
| 13 |
+
if (py < 0 || py >= height)
|
| 14 |
+
continue;
|
| 15 |
+
float vt[2] = {px + 0.5, py + 0.5};
|
| 16 |
+
float baryCentricCoordinate[3];
|
| 17 |
+
calculateBarycentricCoordinate(vt0, vt1, vt2, vt, baryCentricCoordinate);
|
| 18 |
+
if (isBarycentricCoordInBounds(baryCentricCoordinate)) {
|
| 19 |
+
int pixel = py * width + px;
|
| 20 |
+
if (zbuffer == 0) {
|
| 21 |
+
zbuffer[pixel] = (INT64)(idx + 1);
|
| 22 |
+
continue;
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
float depth = baryCentricCoordinate[0] * vt0[2] + baryCentricCoordinate[1] * vt1[2] + baryCentricCoordinate[2] * vt2[2];
|
| 26 |
+
float depth_thres = 0;
|
| 27 |
+
if (d) {
|
| 28 |
+
depth_thres = d[pixel] * 0.49999f + 0.5f + occlusion_truncation;
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
int z_quantize = depth * (2<<17);
|
| 32 |
+
INT64 token = (INT64)z_quantize * MAXINT + (INT64)(idx + 1);
|
| 33 |
+
if (depth < depth_thres)
|
| 34 |
+
continue;
|
| 35 |
+
zbuffer[pixel] = std::min(zbuffer[pixel], token);
|
| 36 |
+
}
|
| 37 |
+
}
|
| 38 |
+
}
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
void barycentricFromImgcoordCPU(float* V, int* F, int* findices, INT64* zbuffer, int width, int height, int num_vertices, int num_faces,
|
| 42 |
+
float* barycentric_map, int pix)
|
| 43 |
+
{
|
| 44 |
+
INT64 f = zbuffer[pix] % MAXINT;
|
| 45 |
+
if (f == (MAXINT-1)) {
|
| 46 |
+
findices[pix] = 0;
|
| 47 |
+
barycentric_map[pix * 3] = 0;
|
| 48 |
+
barycentric_map[pix * 3 + 1] = 0;
|
| 49 |
+
barycentric_map[pix * 3 + 2] = 0;
|
| 50 |
+
return;
|
| 51 |
+
}
|
| 52 |
+
findices[pix] = f;
|
| 53 |
+
f -= 1;
|
| 54 |
+
float barycentric[3] = {0, 0, 0};
|
| 55 |
+
if (f >= 0) {
|
| 56 |
+
float vt[2] = {float(pix % width) + 0.5f, float(pix / width) + 0.5f};
|
| 57 |
+
float* vt0_ptr = V + (F[f * 3] * 4);
|
| 58 |
+
float* vt1_ptr = V + (F[f * 3 + 1] * 4);
|
| 59 |
+
float* vt2_ptr = V + (F[f * 3 + 2] * 4);
|
| 60 |
+
|
| 61 |
+
float vt0[2] = {(vt0_ptr[0] / vt0_ptr[3] * 0.5f + 0.5f) * (width - 1) + 0.5f, (0.5f + 0.5f * vt0_ptr[1] / vt0_ptr[3]) * (height - 1) + 0.5f};
|
| 62 |
+
float vt1[2] = {(vt1_ptr[0] / vt1_ptr[3] * 0.5f + 0.5f) * (width - 1) + 0.5f, (0.5f + 0.5f * vt1_ptr[1] / vt1_ptr[3]) * (height - 1) + 0.5f};
|
| 63 |
+
float vt2[2] = {(vt2_ptr[0] / vt2_ptr[3] * 0.5f + 0.5f) * (width - 1) + 0.5f, (0.5f + 0.5f * vt2_ptr[1] / vt2_ptr[3]) * (height - 1) + 0.5f};
|
| 64 |
+
|
| 65 |
+
calculateBarycentricCoordinate(vt0, vt1, vt2, vt, barycentric);
|
| 66 |
+
|
| 67 |
+
barycentric[0] = barycentric[0] / vt0_ptr[3];
|
| 68 |
+
barycentric[1] = barycentric[1] / vt1_ptr[3];
|
| 69 |
+
barycentric[2] = barycentric[2] / vt2_ptr[3];
|
| 70 |
+
float w = 1.0f / (barycentric[0] + barycentric[1] + barycentric[2]);
|
| 71 |
+
barycentric[0] *= w;
|
| 72 |
+
barycentric[1] *= w;
|
| 73 |
+
barycentric[2] *= w;
|
| 74 |
+
|
| 75 |
+
}
|
| 76 |
+
barycentric_map[pix * 3] = barycentric[0];
|
| 77 |
+
barycentric_map[pix * 3 + 1] = barycentric[1];
|
| 78 |
+
barycentric_map[pix * 3 + 2] = barycentric[2];
|
| 79 |
+
}
|
| 80 |
+
|
| 81 |
+
void rasterizeImagecoordsKernelCPU(float* V, int* F, float* d, INT64* zbuffer, float occlusion_trunc, int width, int height, int num_vertices, int num_faces, int f)
|
| 82 |
+
{
|
| 83 |
+
float* vt0_ptr = V + (F[f * 3] * 4);
|
| 84 |
+
float* vt1_ptr = V + (F[f * 3 + 1] * 4);
|
| 85 |
+
float* vt2_ptr = V + (F[f * 3 + 2] * 4);
|
| 86 |
+
|
| 87 |
+
float vt0[3] = {(vt0_ptr[0] / vt0_ptr[3] * 0.5f + 0.5f) * (width - 1) + 0.5f, (0.5f + 0.5f * vt0_ptr[1] / vt0_ptr[3]) * (height - 1) + 0.5f, vt0_ptr[2] / vt0_ptr[3] * 0.49999f + 0.5f};
|
| 88 |
+
float vt1[3] = {(vt1_ptr[0] / vt1_ptr[3] * 0.5f + 0.5f) * (width - 1) + 0.5f, (0.5f + 0.5f * vt1_ptr[1] / vt1_ptr[3]) * (height - 1) + 0.5f, vt1_ptr[2] / vt1_ptr[3] * 0.49999f + 0.5f};
|
| 89 |
+
float vt2[3] = {(vt2_ptr[0] / vt2_ptr[3] * 0.5f + 0.5f) * (width - 1) + 0.5f, (0.5f + 0.5f * vt2_ptr[1] / vt2_ptr[3]) * (height - 1) + 0.5f, vt2_ptr[2] / vt2_ptr[3] * 0.49999f + 0.5f};
|
| 90 |
+
|
| 91 |
+
rasterizeTriangleCPU(f, vt0, vt1, vt2, width, height, zbuffer, d, occlusion_trunc);
|
| 92 |
+
}
|
| 93 |
+
|
| 94 |
+
std::vector<torch::Tensor> rasterize_image_cpu(torch::Tensor V, torch::Tensor F, torch::Tensor D,
|
| 95 |
+
int width, int height, float occlusion_truncation, int use_depth_prior)
|
| 96 |
+
{
|
| 97 |
+
int num_faces = F.size(0);
|
| 98 |
+
int num_vertices = V.size(0);
|
| 99 |
+
auto options = torch::TensorOptions().dtype(torch::kInt32).requires_grad(false);
|
| 100 |
+
auto INT64_options = torch::TensorOptions().dtype(torch::kInt64).requires_grad(false);
|
| 101 |
+
auto findices = torch::zeros({height, width}, options);
|
| 102 |
+
INT64 maxint = (INT64)MAXINT * (INT64)MAXINT + (MAXINT - 1);
|
| 103 |
+
auto z_min = torch::ones({height, width}, INT64_options) * (long)maxint;
|
| 104 |
+
|
| 105 |
+
if (!use_depth_prior) {
|
| 106 |
+
for (int i = 0; i < num_faces; ++i) {
|
| 107 |
+
rasterizeImagecoordsKernelCPU(V.data_ptr<float>(), F.data_ptr<int>(), 0,
|
| 108 |
+
(INT64*)z_min.data_ptr<long>(), occlusion_truncation, width, height, num_vertices, num_faces, i);
|
| 109 |
+
}
|
| 110 |
+
} else {
|
| 111 |
+
for (int i = 0; i < num_faces; ++i)
|
| 112 |
+
rasterizeImagecoordsKernelCPU(V.data_ptr<float>(), F.data_ptr<int>(), D.data_ptr<float>(),
|
| 113 |
+
(INT64*)z_min.data_ptr<long>(), occlusion_truncation, width, height, num_vertices, num_faces, i);
|
| 114 |
+
}
|
| 115 |
+
|
| 116 |
+
auto float_options = torch::TensorOptions().dtype(torch::kFloat32).requires_grad(false);
|
| 117 |
+
auto barycentric = torch::zeros({height, width, 3}, float_options);
|
| 118 |
+
for (int i = 0; i < width * height; ++i)
|
| 119 |
+
barycentricFromImgcoordCPU(V.data_ptr<float>(), F.data_ptr<int>(),
|
| 120 |
+
findices.data_ptr<int>(), (INT64*)z_min.data_ptr<long>(), width, height, num_vertices, num_faces, barycentric.data_ptr<float>(), i);
|
| 121 |
+
|
| 122 |
+
return {findices, barycentric};
|
| 123 |
+
}
|
| 124 |
+
|
| 125 |
+
std::vector<torch::Tensor> rasterize_image(torch::Tensor V, torch::Tensor F, torch::Tensor D,
|
| 126 |
+
int width, int height, float occlusion_truncation, int use_depth_prior)
|
| 127 |
+
{
|
| 128 |
+
int device_id = V.get_device();
|
| 129 |
+
if (device_id == -1)
|
| 130 |
+
return rasterize_image_cpu(V, F, D, width, height, occlusion_truncation, use_depth_prior);
|
| 131 |
+
else
|
| 132 |
+
return rasterize_image_gpu(V, F, D, width, height, occlusion_truncation, use_depth_prior);
|
| 133 |
+
}
|
| 134 |
+
|
| 135 |
+
PYBIND11_MODULE(TORCH_EXTENSION_NAME, m) {
|
| 136 |
+
m.def("rasterize_image", &rasterize_image, "Custom image rasterization");
|
| 137 |
+
m.def("build_hierarchy", &build_hierarchy, "Custom image rasterization");
|
| 138 |
+
m.def("build_hierarchy_with_feat", &build_hierarchy_with_feat, "Custom image rasterization");
|
| 139 |
+
}
|
home/ubuntu/aaaaa/data/rgbmr/custom_rasterizer/lib/custom_rasterizer_kernel/rasterizer.h
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#ifndef RASTERIZER_H_
|
| 2 |
+
#define RASTERIZER_H_
|
| 3 |
+
|
| 4 |
+
#include <torch/extension.h>
|
| 5 |
+
#include <vector>
|
| 6 |
+
#include <ATen/ATen.h>
|
| 7 |
+
#include <ATen/cuda/CUDAContext.h> // For CUDA context
|
| 8 |
+
|
| 9 |
+
#define INT64 unsigned long long
|
| 10 |
+
#define MAXINT 2147483647
|
| 11 |
+
|
| 12 |
+
__host__ __device__ inline float calculateSignedArea2(float* a, float* b, float* c) {
|
| 13 |
+
return ((c[0] - a[0]) * (b[1] - a[1]) - (b[0] - a[0]) * (c[1] - a[1]));
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
+
__host__ __device__ inline void calculateBarycentricCoordinate(float* a, float* b, float* c, float* p,
|
| 17 |
+
float* barycentric)
|
| 18 |
+
{
|
| 19 |
+
float beta_tri = calculateSignedArea2(a, p, c);
|
| 20 |
+
float gamma_tri = calculateSignedArea2(a, b, p);
|
| 21 |
+
float area = calculateSignedArea2(a, b, c);
|
| 22 |
+
if (area == 0) {
|
| 23 |
+
barycentric[0] = -1.0;
|
| 24 |
+
barycentric[1] = -1.0;
|
| 25 |
+
barycentric[2] = -1.0;
|
| 26 |
+
return;
|
| 27 |
+
}
|
| 28 |
+
float tri_inv = 1.0 / area;
|
| 29 |
+
float beta = beta_tri * tri_inv;
|
| 30 |
+
float gamma = gamma_tri * tri_inv;
|
| 31 |
+
float alpha = 1.0 - beta - gamma;
|
| 32 |
+
barycentric[0] = alpha;
|
| 33 |
+
barycentric[1] = beta;
|
| 34 |
+
barycentric[2] = gamma;
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
__host__ __device__ inline bool isBarycentricCoordInBounds(float* barycentricCoord) {
|
| 38 |
+
return barycentricCoord[0] >= 0.0 && barycentricCoord[0] <= 1.0 &&
|
| 39 |
+
barycentricCoord[1] >= 0.0 && barycentricCoord[1] <= 1.0 &&
|
| 40 |
+
barycentricCoord[2] >= 0.0 && barycentricCoord[2] <= 1.0;
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
std::vector<torch::Tensor> rasterize_image_gpu(torch::Tensor V, torch::Tensor F, torch::Tensor D,
|
| 44 |
+
int width, int height, float occlusion_truncation, int use_depth_prior);
|
| 45 |
+
|
| 46 |
+
std::vector<std::vector<torch::Tensor>> build_hierarchy(std::vector<torch::Tensor> view_layer_positions, std::vector<torch::Tensor> view_layer_normals, int num_level, int resolution);
|
| 47 |
+
|
| 48 |
+
std::vector<std::vector<torch::Tensor>> build_hierarchy_with_feat(
|
| 49 |
+
std::vector<torch::Tensor> view_layer_positions,
|
| 50 |
+
std::vector<torch::Tensor> view_layer_normals,
|
| 51 |
+
std::vector<torch::Tensor> view_layer_feats,
|
| 52 |
+
int num_level, int resolution);
|
| 53 |
+
|
| 54 |
+
#endif
|
home/ubuntu/aaaaa/data/rgbmr/custom_rasterizer/lib/custom_rasterizer_kernel/rasterizer_gpu.cu
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#include "rasterizer.h"
|
| 2 |
+
|
| 3 |
+
__device__ void rasterizeTriangleGPU(int idx, float* vt0, float* vt1, float* vt2, int width, int height, INT64* zbuffer, float* d, float occlusion_truncation) {
|
| 4 |
+
float x_min = std::min(vt0[0], std::min(vt1[0],vt2[0]));
|
| 5 |
+
float x_max = std::max(vt0[0], std::max(vt1[0],vt2[0]));
|
| 6 |
+
float y_min = std::min(vt0[1], std::min(vt1[1],vt2[1]));
|
| 7 |
+
float y_max = std::max(vt0[1], std::max(vt1[1],vt2[1]));
|
| 8 |
+
|
| 9 |
+
for (int px = x_min; px < x_max + 1; ++px) {
|
| 10 |
+
if (px < 0 || px >= width)
|
| 11 |
+
continue;
|
| 12 |
+
for (int py = y_min; py < y_max + 1; ++py) {
|
| 13 |
+
if (py < 0 || py >= height)
|
| 14 |
+
continue;
|
| 15 |
+
float vt[2] = {px + 0.5f, py + 0.5f};
|
| 16 |
+
float baryCentricCoordinate[3];
|
| 17 |
+
calculateBarycentricCoordinate(vt0, vt1, vt2, vt, baryCentricCoordinate);
|
| 18 |
+
if (isBarycentricCoordInBounds(baryCentricCoordinate)) {
|
| 19 |
+
int pixel = py * width + px;
|
| 20 |
+
if (zbuffer == 0) {
|
| 21 |
+
atomicExch(&zbuffer[pixel], (INT64)(idx + 1));
|
| 22 |
+
continue;
|
| 23 |
+
}
|
| 24 |
+
float depth = baryCentricCoordinate[0] * vt0[2] + baryCentricCoordinate[1] * vt1[2] + baryCentricCoordinate[2] * vt2[2];
|
| 25 |
+
float depth_thres = 0;
|
| 26 |
+
if (d) {
|
| 27 |
+
depth_thres = d[pixel] * 0.49999f + 0.5f + occlusion_truncation;
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
int z_quantize = depth * (2<<17);
|
| 31 |
+
INT64 token = (INT64)z_quantize * MAXINT + (INT64)(idx + 1);
|
| 32 |
+
if (depth < depth_thres)
|
| 33 |
+
continue;
|
| 34 |
+
atomicMin(&zbuffer[pixel], token);
|
| 35 |
+
}
|
| 36 |
+
}
|
| 37 |
+
}
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
__global__ void barycentricFromImgcoordGPU(float* V, int* F, int* findices, INT64* zbuffer, int width, int height, int num_vertices, int num_faces,
|
| 41 |
+
float* barycentric_map)
|
| 42 |
+
{
|
| 43 |
+
int pix = blockIdx.x * blockDim.x + threadIdx.x;
|
| 44 |
+
if (pix >= width * height)
|
| 45 |
+
return;
|
| 46 |
+
INT64 f = zbuffer[pix] % MAXINT;
|
| 47 |
+
if (f == (MAXINT-1)) {
|
| 48 |
+
findices[pix] = 0;
|
| 49 |
+
barycentric_map[pix * 3] = 0;
|
| 50 |
+
barycentric_map[pix * 3 + 1] = 0;
|
| 51 |
+
barycentric_map[pix * 3 + 2] = 0;
|
| 52 |
+
return;
|
| 53 |
+
}
|
| 54 |
+
findices[pix] = f;
|
| 55 |
+
f -= 1;
|
| 56 |
+
float barycentric[3] = {0, 0, 0};
|
| 57 |
+
if (f >= 0) {
|
| 58 |
+
float vt[2] = {float(pix % width) + 0.5f, float(pix / width) + 0.5f};
|
| 59 |
+
float* vt0_ptr = V + (F[f * 3] * 4);
|
| 60 |
+
float* vt1_ptr = V + (F[f * 3 + 1] * 4);
|
| 61 |
+
float* vt2_ptr = V + (F[f * 3 + 2] * 4);
|
| 62 |
+
|
| 63 |
+
float vt0[2] = {(vt0_ptr[0] / vt0_ptr[3] * 0.5f + 0.5f) * (width - 1) + 0.5f, (0.5f + 0.5f * vt0_ptr[1] / vt0_ptr[3]) * (height - 1) + 0.5f};
|
| 64 |
+
float vt1[2] = {(vt1_ptr[0] / vt1_ptr[3] * 0.5f + 0.5f) * (width - 1) + 0.5f, (0.5f + 0.5f * vt1_ptr[1] / vt1_ptr[3]) * (height - 1) + 0.5f};
|
| 65 |
+
float vt2[2] = {(vt2_ptr[0] / vt2_ptr[3] * 0.5f + 0.5f) * (width - 1) + 0.5f, (0.5f + 0.5f * vt2_ptr[1] / vt2_ptr[3]) * (height - 1) + 0.5f};
|
| 66 |
+
|
| 67 |
+
calculateBarycentricCoordinate(vt0, vt1, vt2, vt, barycentric);
|
| 68 |
+
|
| 69 |
+
barycentric[0] = barycentric[0] / vt0_ptr[3];
|
| 70 |
+
barycentric[1] = barycentric[1] / vt1_ptr[3];
|
| 71 |
+
barycentric[2] = barycentric[2] / vt2_ptr[3];
|
| 72 |
+
float w = 1.0f / (barycentric[0] + barycentric[1] + barycentric[2]);
|
| 73 |
+
barycentric[0] *= w;
|
| 74 |
+
barycentric[1] *= w;
|
| 75 |
+
barycentric[2] *= w;
|
| 76 |
+
|
| 77 |
+
}
|
| 78 |
+
barycentric_map[pix * 3] = barycentric[0];
|
| 79 |
+
barycentric_map[pix * 3 + 1] = barycentric[1];
|
| 80 |
+
barycentric_map[pix * 3 + 2] = barycentric[2];
|
| 81 |
+
}
|
| 82 |
+
|
| 83 |
+
__global__ void rasterizeImagecoordsKernelGPU(float* V, int* F, float* d, INT64* zbuffer, float occlusion_trunc, int width, int height, int num_vertices, int num_faces)
|
| 84 |
+
{
|
| 85 |
+
int f = blockIdx.x * blockDim.x + threadIdx.x;
|
| 86 |
+
if (f >= num_faces)
|
| 87 |
+
return;
|
| 88 |
+
|
| 89 |
+
float* vt0_ptr = V + (F[f * 3] * 4);
|
| 90 |
+
float* vt1_ptr = V + (F[f * 3 + 1] * 4);
|
| 91 |
+
float* vt2_ptr = V + (F[f * 3 + 2] * 4);
|
| 92 |
+
|
| 93 |
+
float vt0[3] = {(vt0_ptr[0] / vt0_ptr[3] * 0.5f + 0.5f) * (width - 1) + 0.5f, (0.5f + 0.5f * vt0_ptr[1] / vt0_ptr[3]) * (height - 1) + 0.5f, vt0_ptr[2] / vt0_ptr[3] * 0.49999f + 0.5f};
|
| 94 |
+
float vt1[3] = {(vt1_ptr[0] / vt1_ptr[3] * 0.5f + 0.5f) * (width - 1) + 0.5f, (0.5f + 0.5f * vt1_ptr[1] / vt1_ptr[3]) * (height - 1) + 0.5f, vt1_ptr[2] / vt1_ptr[3] * 0.49999f + 0.5f};
|
| 95 |
+
float vt2[3] = {(vt2_ptr[0] / vt2_ptr[3] * 0.5f + 0.5f) * (width - 1) + 0.5f, (0.5f + 0.5f * vt2_ptr[1] / vt2_ptr[3]) * (height - 1) + 0.5f, vt2_ptr[2] / vt2_ptr[3] * 0.49999f + 0.5f};
|
| 96 |
+
|
| 97 |
+
rasterizeTriangleGPU(f, vt0, vt1, vt2, width, height, zbuffer, d, occlusion_trunc);
|
| 98 |
+
}
|
| 99 |
+
|
| 100 |
+
std::vector<torch::Tensor> rasterize_image_gpu(torch::Tensor V, torch::Tensor F, torch::Tensor D,
|
| 101 |
+
int width, int height, float occlusion_truncation, int use_depth_prior)
|
| 102 |
+
{
|
| 103 |
+
int device_id = V.get_device();
|
| 104 |
+
cudaSetDevice(device_id);
|
| 105 |
+
int num_faces = F.size(0);
|
| 106 |
+
int num_vertices = V.size(0);
|
| 107 |
+
auto options = torch::TensorOptions().dtype(torch::kInt32).device(torch::kCUDA, device_id).requires_grad(false);
|
| 108 |
+
auto INT64_options = torch::TensorOptions().dtype(torch::kInt64).device(torch::kCUDA, device_id).requires_grad(false);
|
| 109 |
+
auto findices = torch::zeros({height, width}, options);
|
| 110 |
+
INT64 maxint = (INT64)MAXINT * (INT64)MAXINT + (MAXINT - 1);
|
| 111 |
+
auto z_min = torch::ones({height, width}, INT64_options) * (long)maxint;
|
| 112 |
+
|
| 113 |
+
if (!use_depth_prior) {
|
| 114 |
+
rasterizeImagecoordsKernelGPU<<<(num_faces+255)/256,256,0,at::cuda::getCurrentCUDAStream()>>>(V.data_ptr<float>(), F.data_ptr<int>(), 0,
|
| 115 |
+
(INT64*)z_min.data_ptr<long>(), occlusion_truncation, width, height, num_vertices, num_faces);
|
| 116 |
+
} else {
|
| 117 |
+
rasterizeImagecoordsKernelGPU<<<(num_faces+255)/256,256,0,at::cuda::getCurrentCUDAStream()>>>(V.data_ptr<float>(), F.data_ptr<int>(), D.data_ptr<float>(),
|
| 118 |
+
(INT64*)z_min.data_ptr<long>(), occlusion_truncation, width, height, num_vertices, num_faces);
|
| 119 |
+
}
|
| 120 |
+
|
| 121 |
+
auto float_options = torch::TensorOptions().dtype(torch::kFloat32).device(torch::kCUDA, device_id).requires_grad(false);
|
| 122 |
+
auto barycentric = torch::zeros({height, width, 3}, float_options);
|
| 123 |
+
barycentricFromImgcoordGPU<<<(width * height + 255)/256, 256>>>(V.data_ptr<float>(), F.data_ptr<int>(),
|
| 124 |
+
findices.data_ptr<int>(), (INT64*)z_min.data_ptr<long>(), width, height, num_vertices, num_faces, barycentric.data_ptr<float>());
|
| 125 |
+
|
| 126 |
+
return {findices, barycentric};
|
| 127 |
+
}
|
home/ubuntu/aaaaa/data/rgbmr/custom_rasterizer/setup.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from setuptools import setup, find_packages
|
| 2 |
+
import torch
|
| 3 |
+
from torch.utils.cpp_extension import BuildExtension, CUDAExtension, CppExtension
|
| 4 |
+
|
| 5 |
+
# build custom rasterizer
|
| 6 |
+
|
| 7 |
+
custom_rasterizer_module = CUDAExtension(
|
| 8 |
+
"custom_rasterizer_kernel",
|
| 9 |
+
[
|
| 10 |
+
"lib/custom_rasterizer_kernel/rasterizer.cpp",
|
| 11 |
+
"lib/custom_rasterizer_kernel/grid_neighbor.cpp",
|
| 12 |
+
"lib/custom_rasterizer_kernel/rasterizer_gpu.cu",
|
| 13 |
+
],
|
| 14 |
+
)
|
| 15 |
+
|
| 16 |
+
setup(
|
| 17 |
+
packages=find_packages(),
|
| 18 |
+
version="0.1",
|
| 19 |
+
name="custom_rasterizer",
|
| 20 |
+
include_package_data=True,
|
| 21 |
+
package_dir={"": "."},
|
| 22 |
+
ext_modules=[
|
| 23 |
+
custom_rasterizer_module,
|
| 24 |
+
],
|
| 25 |
+
cmdclass={"build_ext": BuildExtension},
|
| 26 |
+
)
|
home/ubuntu/aaaaa/data/rgbmr/data/__pycache__/rgbmr_dataset.cpython-310.pyc
ADDED
|
Binary file (21.5 kB). View file
|
|
|
home/ubuntu/aaaaa/data/rgbmr/data/generate_rgbmr_dataset.py
ADDED
|
@@ -0,0 +1,389 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import argparse
|
| 2 |
+
import os, sys
|
| 3 |
+
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) # repo root append
|
| 4 |
+
|
| 5 |
+
import math
|
| 6 |
+
from typing import Any, Callable, Dict, List, Set, Tuple, Optional, Union
|
| 7 |
+
from pathlib import Path
|
| 8 |
+
import glob
|
| 9 |
+
|
| 10 |
+
import numpy as np
|
| 11 |
+
from PIL import Image
|
| 12 |
+
import torch
|
| 13 |
+
|
| 14 |
+
from tools.utils.mesh_utils import (
|
| 15 |
+
load_mesh,
|
| 16 |
+
get_orthogonal_camera,
|
| 17 |
+
NVDiffRastContextWrapper,
|
| 18 |
+
render,
|
| 19 |
+
)
|
| 20 |
+
|
| 21 |
+
from data.rgbmr_dataset import build_partial_dataset, DT, SCHEMA_RGBMR
|
| 22 |
+
from mcgen.utils.text_encoder_utils import load_text_ctx, encode_prompt
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
# -------------------------
|
| 26 |
+
# Helpers: find glbs, curated ids
|
| 27 |
+
# -------------------------
|
| 28 |
+
|
| 29 |
+
def load_curated_ids(path: Path) -> List[str]:
|
| 30 |
+
ids = []
|
| 31 |
+
with open(path, "r", encoding="utf-8") as f:
|
| 32 |
+
for line in f:
|
| 33 |
+
s = line.strip()
|
| 34 |
+
if not s or s.startswith("#"):
|
| 35 |
+
continue
|
| 36 |
+
ids.append(s)
|
| 37 |
+
return ids
|
| 38 |
+
|
| 39 |
+
def find_glb_index(root, curated_ids):
|
| 40 |
+
glbs = {}
|
| 41 |
+
for p in glob.glob(str(root / "**/*.glb"), recursive=True):
|
| 42 |
+
p = Path(p)
|
| 43 |
+
if p.stem in curated_ids:
|
| 44 |
+
glbs[p.stem] = p
|
| 45 |
+
return glbs
|
| 46 |
+
|
| 47 |
+
def shard_list(full_list, num_shards: int, shard_id: int):
|
| 48 |
+
n = len(full_list)
|
| 49 |
+
if num_shards <= 1:
|
| 50 |
+
return full_list
|
| 51 |
+
sz = (n + num_shards - 1) // num_shards
|
| 52 |
+
start = shard_id * sz
|
| 53 |
+
end = min(start + sz, n)
|
| 54 |
+
return full_list[start:end]
|
| 55 |
+
|
| 56 |
+
# -------------------------
|
| 57 |
+
# Camera/view sampling
|
| 58 |
+
# -------------------------
|
| 59 |
+
def default_view_sampler(num_elev: int = 1, num_azim: int = 24,
|
| 60 |
+
elev_deg: float = 20.0) -> Callable[[Any], List[Tuple[float, float]]]:
|
| 61 |
+
"""
|
| 62 |
+
Returns a function gid -> list[(elevation_deg, azimuth_deg)].
|
| 63 |
+
By default: a single elevation ring with uniformly spaced azimuths.
|
| 64 |
+
"""
|
| 65 |
+
azims = [i * (360.0 / num_azim) for i in range(num_azim)]
|
| 66 |
+
elevs = [elev_deg for _ in range(num_elev)]
|
| 67 |
+
views = [(e, a) for e in elevs for a in azims]
|
| 68 |
+
|
| 69 |
+
def sampler(_gid: Any) -> List[Tuple[float, float]]:
|
| 70 |
+
return views
|
| 71 |
+
|
| 72 |
+
return sampler
|
| 73 |
+
|
| 74 |
+
def random_view_sampler(num_views: int = 4,
|
| 75 |
+
elev_range: Tuple[float, float] = (-30.0, 60.0)) -> Callable[[Any], List[Tuple[float, float]]]:
|
| 76 |
+
"""
|
| 77 |
+
Returns a function gid -> list[(elevation_deg, azimuth_deg)].
|
| 78 |
+
Each object gets num_views random views.
|
| 79 |
+
"""
|
| 80 |
+
def sampler(_gid: Any) -> List[Tuple[float, float]]:
|
| 81 |
+
views = []
|
| 82 |
+
for _ in range(num_views):
|
| 83 |
+
az = np.random.uniform(0.0, 360.0)
|
| 84 |
+
el = np.random.uniform(elev_range[0], elev_range[1])
|
| 85 |
+
views.append((el, az))
|
| 86 |
+
return views
|
| 87 |
+
return sampler
|
| 88 |
+
|
| 89 |
+
def view_sampler_from_list(azim_list: List[float], elev_list: List[float]) -> Callable[[Any], List[Tuple[float, float]]]:
|
| 90 |
+
"""
|
| 91 |
+
azim_list: list of azimuth angles in degrees
|
| 92 |
+
elev_list: list of elevation angles in degrees
|
| 93 |
+
Returns a function gid -> list[(elevation_deg, azimuth_deg)].
|
| 94 |
+
"""
|
| 95 |
+
if len(azim_list) != len(elev_list):
|
| 96 |
+
raise ValueError("azim_list and elev_list must have the same length")
|
| 97 |
+
views = list(zip(elev_list, azim_list))
|
| 98 |
+
|
| 99 |
+
def sampler(_gid: Any) -> List[Tuple[float, float]]:
|
| 100 |
+
return views
|
| 101 |
+
|
| 102 |
+
return sampler
|
| 103 |
+
|
| 104 |
+
|
| 105 |
+
# -------------------------
|
| 106 |
+
# Backend interface
|
| 107 |
+
# -------------------------
|
| 108 |
+
class RenderBackend:
|
| 109 |
+
"""
|
| 110 |
+
Implement this with your renderer. The contract is:
|
| 111 |
+
render(mesh_path, elevation, azimuth, image_size) -> dict with:
|
| 112 |
+
- 'albedo': BxHxWx3 uint8 or float[0,1]
|
| 113 |
+
- 'normal': BxHxWx3 float[-1,1] or uint8 in [0,255] (we'll convert)
|
| 114 |
+
- 'depth': BxHxW float meters
|
| 115 |
+
- 'orm': BxHxWx3 (O,R,M) either float[0,1] or uint8[0,255]
|
| 116 |
+
"""
|
| 117 |
+
|
| 118 |
+
@torch.no_grad()
|
| 119 |
+
def render(
|
| 120 |
+
self,
|
| 121 |
+
ctx: NVDiffRastContextWrapper,
|
| 122 |
+
mesh_path: Any,
|
| 123 |
+
elevation: Union[List[float], float],
|
| 124 |
+
azimuth: Union[List[float], float],
|
| 125 |
+
image_size: Tuple[int, int],
|
| 126 |
+
device: Union[str, torch.device] = "cuda",
|
| 127 |
+
) -> Dict[str, Any]:
|
| 128 |
+
# 로딩
|
| 129 |
+
mesh = load_mesh(
|
| 130 |
+
str(mesh_path),
|
| 131 |
+
rescale=True,
|
| 132 |
+
move_to_center=True,
|
| 133 |
+
flip_uv=True,
|
| 134 |
+
device=device,
|
| 135 |
+
)
|
| 136 |
+
elevation = elevation if isinstance(elevation, list) else [elevation]
|
| 137 |
+
azimuth = azimuth if isinstance(azimuth, list) else [azimuth]
|
| 138 |
+
|
| 139 |
+
# 카메라 배치(정사각형 ortho box; rescale=0.5 기준 안전 여유)
|
| 140 |
+
cams = get_orthogonal_camera(
|
| 141 |
+
elevation_deg=elevation,
|
| 142 |
+
distance=[1.0] * len(azimuth),
|
| 143 |
+
left=-0.55,
|
| 144 |
+
right=0.55,
|
| 145 |
+
bottom=-0.55,
|
| 146 |
+
top=0.55,
|
| 147 |
+
azimuth_deg=azimuth,
|
| 148 |
+
device=device,
|
| 149 |
+
dtype=torch.float32,
|
| 150 |
+
)
|
| 151 |
+
|
| 152 |
+
# 렌더 속성: rgb + roughness + metallic (5채널)
|
| 153 |
+
tex_ovr = torch.concatenate(
|
| 154 |
+
[mesh.texture, torch.zeros_like(mesh.roughness), mesh.roughness, mesh.metallic],
|
| 155 |
+
dim=-1,
|
| 156 |
+
)
|
| 157 |
+
out = render(
|
| 158 |
+
ctx, mesh, cams, height=image_size[0], width=image_size[1], render_attr=True, texture_override=tex_ovr
|
| 159 |
+
)
|
| 160 |
+
|
| 161 |
+
attr = out.attr # [B,H,W,5] float in [0,1]
|
| 162 |
+
pos_batch = out.pos
|
| 163 |
+
normal_batch = out.normal
|
| 164 |
+
depth_batch = out.raw_depth # [B,H,W] float in [0,inf]
|
| 165 |
+
|
| 166 |
+
rgb_batch = attr[..., :3].contiguous()
|
| 167 |
+
orm_batch = attr[..., 3:6].contiguous()
|
| 168 |
+
orm_batch[..., 0] = (orm_batch[..., 0] < 0.25).float() # binarize alpha to get foreground mask
|
| 169 |
+
|
| 170 |
+
pos_batch = pos_batch + 0.5 # [-0.5,0.5] → [0,1]
|
| 171 |
+
normal_batch = (normal_batch + 1.0) * 0.5 # [-1,1] → [0,1]
|
| 172 |
+
|
| 173 |
+
result: Dict[str, Any] = {
|
| 174 |
+
"albedo": rgb_batch,
|
| 175 |
+
"orm": orm_batch,
|
| 176 |
+
"depth": depth_batch,
|
| 177 |
+
"pos": pos_batch,
|
| 178 |
+
"normal": normal_batch,
|
| 179 |
+
"c2w": cams.c2w,
|
| 180 |
+
"scale": 1.1,
|
| 181 |
+
}
|
| 182 |
+
return result
|
| 183 |
+
|
| 184 |
+
|
| 185 |
+
# -------------------------
|
| 186 |
+
# Mesh resolver
|
| 187 |
+
# -------------------------
|
| 188 |
+
def default_mesh_resolver(mesh_index: Dict[str, Any]) -> Callable[[Any], Any]:
|
| 189 |
+
"""
|
| 190 |
+
mesh_index: e.g., {'obj001': '/path/obj001.obj', ...} or preloaded mesh objects.
|
| 191 |
+
"""
|
| 192 |
+
def resolve(gid: Any) -> Any:
|
| 193 |
+
key = str(gid)
|
| 194 |
+
if key not in mesh_index:
|
| 195 |
+
raise KeyError(f"Mesh for gid='{gid}' not found")
|
| 196 |
+
return mesh_index[key]
|
| 197 |
+
return resolve
|
| 198 |
+
|
| 199 |
+
def default_prompt_resolver(prompt_index: Dict[str, str]) -> Callable[[Any], str]:
|
| 200 |
+
"""
|
| 201 |
+
prompt_index: e.g., {'obj001': 'a red apple', ...}
|
| 202 |
+
"""
|
| 203 |
+
def resolve(gid: Any) -> str:
|
| 204 |
+
key = str(gid)
|
| 205 |
+
if key not in prompt_index:
|
| 206 |
+
raise KeyError(f"Prompt for gid='{gid}' not found")
|
| 207 |
+
return prompt_index[key]
|
| 208 |
+
return resolve
|
| 209 |
+
|
| 210 |
+
|
| 211 |
+
# -------------------------
|
| 212 |
+
# Factory: make generate_data(gid)
|
| 213 |
+
# -------------------------
|
| 214 |
+
def make_generate_data(
|
| 215 |
+
render_ctx,
|
| 216 |
+
text_ctx,
|
| 217 |
+
mesh_resolver: Callable[[Any], Any],
|
| 218 |
+
prompt_resolver: Callable[[Any], str],
|
| 219 |
+
backend: RenderBackend,
|
| 220 |
+
view_sampler: Callable[[Any], List[Tuple[float, float]]] = default_view_sampler(),
|
| 221 |
+
image_size: Tuple[int, int] = (512, 512),
|
| 222 |
+
max_seq_length: int = 77,
|
| 223 |
+
):
|
| 224 |
+
"""
|
| 225 |
+
Returns a single-argument function: generate_data(gid) -> dict
|
| 226 |
+
Output structure matches your schema requirements.
|
| 227 |
+
Per-object:
|
| 228 |
+
- 'gid' (str)
|
| 229 |
+
- 'prompt' (str) -> 'a sample text'
|
| 230 |
+
- 'prompt_embeds' (torch.Tensor) -> (L, D)
|
| 231 |
+
- 'pooled_prompt_embeds' (torch.Tensor) -> (D,)
|
| 232 |
+
Per-view (list under 'views'):
|
| 233 |
+
- 'albedo' (PNG-ready RGB)
|
| 234 |
+
- 'orm' (PNG-ready RGB)
|
| 235 |
+
- 'depth' (EXR-ready float32)
|
| 236 |
+
- 'pos' (PNG-ready RGB)
|
| 237 |
+
- 'normal' (PNG-ready RGB)
|
| 238 |
+
- 'elevation' (float, degrees)
|
| 239 |
+
- 'azimuth' (float, degrees)
|
| 240 |
+
"""
|
| 241 |
+
H, W = image_size
|
| 242 |
+
|
| 243 |
+
def generate_data(gid: Any) -> Dict[str, Any]:
|
| 244 |
+
mesh_path = mesh_resolver(gid)
|
| 245 |
+
prompt = prompt_resolver(gid)
|
| 246 |
+
views_out: List[Dict[str, Any]] = []
|
| 247 |
+
|
| 248 |
+
# Collect all views and render in a single batched call
|
| 249 |
+
views = view_sampler(gid)
|
| 250 |
+
if len(views) > 0:
|
| 251 |
+
elev_list = [e for e, _ in views]
|
| 252 |
+
azim_list = [a for _, a in views]
|
| 253 |
+
|
| 254 |
+
out = backend.render(render_ctx, mesh_path, elev_list, azim_list, (H, W))
|
| 255 |
+
|
| 256 |
+
albedo_batch = out['albedo'] # [B,H,W,3] float in [0,1]
|
| 257 |
+
orm_batch = out['orm'] # [B,H,W,3] float in [0,1]
|
| 258 |
+
depth_batch = out['depth'] # [B,H,W] float in [0,inf]
|
| 259 |
+
pos_batch = out['pos'] # [B,H,W,3] float in [0,1]
|
| 260 |
+
normal_batch = out['normal'] # [B,H,W,3] float in [0,1]
|
| 261 |
+
c2w_batch = out['c2w'] # [B,4,4]
|
| 262 |
+
scale = out['scale'] # float scalar
|
| 263 |
+
|
| 264 |
+
for i, (elev_deg, azim_deg) in enumerate(views):
|
| 265 |
+
# ---- albedo ----
|
| 266 |
+
albedo = albedo_batch[i]
|
| 267 |
+
orm = orm_batch[i]
|
| 268 |
+
pos = pos_batch[i]
|
| 269 |
+
normal = normal_batch[i]
|
| 270 |
+
|
| 271 |
+
# ---- depth ----
|
| 272 |
+
depth = depth_batch[i]
|
| 273 |
+
|
| 274 |
+
views_out.append({
|
| 275 |
+
"albedo": albedo, # [H,W,3] float in [0,1], will be converted to PNG
|
| 276 |
+
"orm": orm, # [H,W,3] float in [0,1], will be converted to PNG
|
| 277 |
+
"depth": depth, # [H,W] float, will be saved as EXR
|
| 278 |
+
"pos": pos, # [H,W,3] float in [0,1], will be converted to PNG
|
| 279 |
+
"normal": normal, # [H,W,3] float in [0,1], will be converted to PNG
|
| 280 |
+
"elevation": float(elev_deg), # metadata (store as JSON via schema)
|
| 281 |
+
"azimuth": float(azim_deg),
|
| 282 |
+
"c2w": c2w_batch[i],
|
| 283 |
+
"scale": scale,
|
| 284 |
+
})
|
| 285 |
+
|
| 286 |
+
# encode prompt
|
| 287 |
+
prompt_embeds, pooled_prompt_embeds = encode_prompt(
|
| 288 |
+
text_ctx["encoders"], text_ctx["tokenizers"], prompt,
|
| 289 |
+
max_sequence_length=max_seq_length,
|
| 290 |
+
) # (1, L, D), (1, D)
|
| 291 |
+
|
| 292 |
+
result: Dict[str, Any] = {
|
| 293 |
+
"prompt": prompt,
|
| 294 |
+
"prompt_embeds": prompt_embeds.squeeze(0).cpu(), # (L, D)
|
| 295 |
+
"pooled_prompt_embeds": pooled_prompt_embeds.squeeze(0).cpu(), # (D,)
|
| 296 |
+
"views": views_out,
|
| 297 |
+
}
|
| 298 |
+
return result
|
| 299 |
+
|
| 300 |
+
return generate_data
|
| 301 |
+
|
| 302 |
+
|
| 303 |
+
# -----------------------------------------
|
| 304 |
+
# Example schema to pair with this generator
|
| 305 |
+
# -----------------------------------------
|
| 306 |
+
|
| 307 |
+
if __name__ == "__main__":
|
| 308 |
+
ap = argparse.ArgumentParser()
|
| 309 |
+
ap.add_argument("--out-dir", default="/home/ubuntu/aaaaa/data/rgbmr", help="Dataset root directory")
|
| 310 |
+
ap.add_argument("--gid-path", default="../obj_keys.txt", help="Curated obj ID list path")
|
| 311 |
+
ap.add_argument("--prompt-path", default="../prompts_bs.csv", help="Prompt CSV path (id,prompt)")
|
| 312 |
+
ap.add_argument("--sd-model", default="stabilityai/stable-diffusion-3.5-medium", help="SD3.5 model name")
|
| 313 |
+
ap.add_argument("--dtype", default="float16", choices=["float16", "float32", "bfloat16"], help="Model dtype")
|
| 314 |
+
ap.add_argument("--device", default="cuda", help="Device")
|
| 315 |
+
ap.add_argument("--max_seq", type=int, default=77, help="T5 max token length")
|
| 316 |
+
ap.add_argument("--num_shards", type=int, default=1)
|
| 317 |
+
ap.add_argument("--shard_id", type=int, default=0)
|
| 318 |
+
ap.add_argument("--random-single-view", action="store_true", help="Use random single view per object")
|
| 319 |
+
ap.add_argument("--part_dir", default=None, help="If given, overrides run_name and uses this dir directly")
|
| 320 |
+
args = ap.parse_args()
|
| 321 |
+
# -------------------- schema example --------------------
|
| 322 |
+
|
| 323 |
+
dtype_map = {"float16": torch.float16, "float32": torch.float32, "bfloat16": torch.bfloat16}
|
| 324 |
+
text_ctx = load_text_ctx(args.device, dtype=dtype_map[args.dtype], sd_model_name=args.sd_model)
|
| 325 |
+
render_ctx = NVDiffRastContextWrapper(device=args.device, context_type="cuda")
|
| 326 |
+
|
| 327 |
+
gid_path = Path(args.gid_path)
|
| 328 |
+
out_dir = Path(args.out_dir)
|
| 329 |
+
glb_root = Path("/home/ubuntu/.objaverse/hf-objaverse-v1/glbs")
|
| 330 |
+
|
| 331 |
+
curated_ids = load_curated_ids(gid_path)
|
| 332 |
+
glbs_filtered = find_glb_index(glb_root, set(curated_ids))
|
| 333 |
+
print(f"Found {len(glbs_filtered)} / {len(curated_ids)} curated GLBs")
|
| 334 |
+
|
| 335 |
+
mesh_resolver = default_mesh_resolver(glbs_filtered)
|
| 336 |
+
|
| 337 |
+
# make prompt resolver
|
| 338 |
+
import csv
|
| 339 |
+
prompt_index = {}
|
| 340 |
+
with open(args.prompt_path, "r", encoding="utf-8") as f:
|
| 341 |
+
reader = csv.reader(f)
|
| 342 |
+
for row in reader:
|
| 343 |
+
if len(row) != 2:
|
| 344 |
+
raise ValueError(f"Expected 2 columns per row, got {len(row)}: {row}")
|
| 345 |
+
obj_id, prompt = row
|
| 346 |
+
prompt_index[obj_id] = prompt
|
| 347 |
+
prompt_resolver = default_prompt_resolver(prompt_index)
|
| 348 |
+
|
| 349 |
+
|
| 350 |
+
az = [
|
| 351 |
+
0.0, 90.0, 180.0, 270.0, 0.0, 0.0, # face
|
| 352 |
+
45.0, 135.0, 225.0, 315.0, 45.0, 135.0, 225.0, 315.0, # corner
|
| 353 |
+
45.0, 135.0, 225.0, 315.0, 0.0, 90.0, 180.0, 270.0, 0.0, 90.0, 180.0, 270.0 # edge
|
| 354 |
+
]
|
| 355 |
+
el = [
|
| 356 |
+
0.0, 0.0, 0.0, 0.0, 89.99, -89.99, # face
|
| 357 |
+
45.0, 45.0, 45.0, 45.0, -45.0, -45.0, -45.0, -45.0, # corner
|
| 358 |
+
0.0, 0.0, 0.0, 0.0, 45.0, 45.0, 45.0, 45.0, -45.0, -45.0, -45.0, -45.0 # edge
|
| 359 |
+
]
|
| 360 |
+
view_sampler = None
|
| 361 |
+
if args.random_single_view:
|
| 362 |
+
view_sampler = random_view_sampler(num_views=1, elev_range=(-30.0, 60.0))
|
| 363 |
+
else:
|
| 364 |
+
view_sampler = view_sampler_from_list(azim_list=az, elev_list=el)
|
| 365 |
+
|
| 366 |
+
generate_data = make_generate_data(
|
| 367 |
+
render_ctx=render_ctx,
|
| 368 |
+
text_ctx=text_ctx,
|
| 369 |
+
prompt_resolver=prompt_resolver,
|
| 370 |
+
mesh_resolver=mesh_resolver,
|
| 371 |
+
backend=RenderBackend(),
|
| 372 |
+
view_sampler=view_sampler,
|
| 373 |
+
image_size=(512, 512),
|
| 374 |
+
max_seq_length=args.max_seq,
|
| 375 |
+
)
|
| 376 |
+
|
| 377 |
+
glbs_list = curated_ids
|
| 378 |
+
# glbs_list.sort()
|
| 379 |
+
glbs_sharded = shard_list(glbs_list, args.num_shards, args.shard_id)
|
| 380 |
+
|
| 381 |
+
build_partial_dataset(
|
| 382 |
+
generate_data=generate_data,
|
| 383 |
+
gid_list=glbs_sharded,
|
| 384 |
+
schema=SCHEMA_RGBMR,
|
| 385 |
+
root_dir=out_dir,
|
| 386 |
+
run_name=f"render_{args.shard_id:02d}_of_{args.num_shards:02d}",
|
| 387 |
+
part_dir=args.part_dir,
|
| 388 |
+
progress=True,
|
| 389 |
+
)
|
home/ubuntu/aaaaa/data/rgbmr/data/rgbmr_dataset.py
ADDED
|
@@ -0,0 +1,845 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# rgbmr_dataset.py
|
| 2 |
+
import os
|
| 3 |
+
import io
|
| 4 |
+
import json
|
| 5 |
+
import glob
|
| 6 |
+
import uuid
|
| 7 |
+
import time
|
| 8 |
+
import shutil
|
| 9 |
+
import pathlib
|
| 10 |
+
import random
|
| 11 |
+
from enum import Enum
|
| 12 |
+
from typing import Any, Dict, List, Optional, Union
|
| 13 |
+
|
| 14 |
+
import numpy as np
|
| 15 |
+
from PIL import Image
|
| 16 |
+
import torch
|
| 17 |
+
import torch.nn.functional as F
|
| 18 |
+
import webdataset as wds
|
| 19 |
+
from tqdm import tqdm
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
# =========================
|
| 23 |
+
# Datatypes and schema utils
|
| 24 |
+
# =========================
|
| 25 |
+
class DT(Enum):
|
| 26 |
+
"""Dataset field datatypes."""
|
| 27 |
+
|
| 28 |
+
STR = "str" # utf-8 text
|
| 29 |
+
TENSOR = "tensor" # arbitrary tensor/ndarray -> .npy
|
| 30 |
+
PNG = "png" # 3-channel PNG (uint8); float in [0,1] allowed
|
| 31 |
+
EXR = "exr" # single-channel EXR (depth as float), lossless PIZ by default
|
| 32 |
+
JSON = "json" # json-serializable python object
|
| 33 |
+
BYTES = "bytes" # raw bytes
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
SCHEMA_RGBMR = {
|
| 37 |
+
"prompt": DT.STR,
|
| 38 |
+
"prompt_embeds": DT.TENSOR,
|
| 39 |
+
"pooled_prompt_embeds": DT.TENSOR,
|
| 40 |
+
"views": {
|
| 41 |
+
"albedo": DT.PNG,
|
| 42 |
+
"orm": DT.PNG,
|
| 43 |
+
"depth": DT.EXR, # float32 depth
|
| 44 |
+
"pos": DT.PNG,
|
| 45 |
+
"normal": DT.PNG,
|
| 46 |
+
"elevation": DT.JSON, # float degree
|
| 47 |
+
"azimuth": DT.JSON, # float degree
|
| 48 |
+
"c2w": DT.TENSOR, # camera-to-world matrix (4x4)
|
| 49 |
+
"scale": DT.JSON, # float scale
|
| 50 |
+
},
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
def _ensure_dir(path: str) -> None:
|
| 55 |
+
os.makedirs(path, exist_ok=True)
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
def _timestamp() -> str:
|
| 59 |
+
return time.strftime("%Y-%m-%d %H:%M:%S")
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
def _sanitize_key(name: str) -> str:
|
| 63 |
+
safe = []
|
| 64 |
+
for ch in name:
|
| 65 |
+
if ch.isalnum() or ch in "._-":
|
| 66 |
+
safe.append(ch)
|
| 67 |
+
else:
|
| 68 |
+
safe.append("_")
|
| 69 |
+
return "".join(safe)
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
# Added: canonicalize DT specs (accept Enum or string)
|
| 73 |
+
def _dt_value(dt: Any) -> str:
|
| 74 |
+
# direct enum from this module
|
| 75 |
+
if isinstance(dt, DT):
|
| 76 |
+
return dt.value
|
| 77 |
+
# enum-like objects (possibly from reloaded module)
|
| 78 |
+
if hasattr(dt, "value") and isinstance(getattr(dt, "value"), str):
|
| 79 |
+
return getattr(dt, "value")
|
| 80 |
+
# already a string value
|
| 81 |
+
if isinstance(dt, str):
|
| 82 |
+
return dt
|
| 83 |
+
raise ValueError(f"Unsupported DT spec: {dt} (type: {type(dt)})")
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
def _dt_ext(dt: Any) -> str:
|
| 87 |
+
dtv = _dt_value(dt)
|
| 88 |
+
if dtv == DT.PNG.value:
|
| 89 |
+
return "png"
|
| 90 |
+
if dtv == DT.EXR.value:
|
| 91 |
+
return "exr"
|
| 92 |
+
if dtv == DT.TENSOR.value:
|
| 93 |
+
return "npy"
|
| 94 |
+
if dtv == DT.STR.value:
|
| 95 |
+
return "txt"
|
| 96 |
+
if dtv == DT.JSON.value:
|
| 97 |
+
return "json"
|
| 98 |
+
if dtv == DT.BYTES.value:
|
| 99 |
+
return "bin"
|
| 100 |
+
raise ValueError(f"Unhandled DT: {dt}, type: {type(dt)}")
|
| 101 |
+
|
| 102 |
+
|
| 103 |
+
def _schema_to_jsonable(schema: Dict[str, Any]) -> Dict[str, Any]:
|
| 104 |
+
"""Convert a schema containing DT enums into a JSON-serializable dict.
|
| 105 |
+
|
| 106 |
+
The expected input shape is a dict where values are either DT or nested dicts
|
| 107 |
+
(e.g., {"views": {"rgb": DT.PNG, ...}}). We convert any DT to its string value.
|
| 108 |
+
"""
|
| 109 |
+
|
| 110 |
+
def _conv(x: Any) -> Any:
|
| 111 |
+
if isinstance(x, DT):
|
| 112 |
+
return x.value
|
| 113 |
+
if isinstance(x, dict):
|
| 114 |
+
return {k: _conv(v) for k, v in x.items()}
|
| 115 |
+
return x
|
| 116 |
+
|
| 117 |
+
return {k: _conv(v) for k, v in schema.items()}
|
| 118 |
+
|
| 119 |
+
|
| 120 |
+
# =======================
|
| 121 |
+
# Encoders / Decoders I/O
|
| 122 |
+
# =======================
|
| 123 |
+
def _to_numpy(x: Any) -> np.ndarray:
|
| 124 |
+
if isinstance(x, np.ndarray):
|
| 125 |
+
return x
|
| 126 |
+
if torch.is_tensor(x):
|
| 127 |
+
return x.detach().cpu().numpy()
|
| 128 |
+
raise TypeError(f"Unsupported array/tensor type: {type(x)}")
|
| 129 |
+
|
| 130 |
+
|
| 131 |
+
def _encode_png_3ch(value: Any) -> bytes:
|
| 132 |
+
"""Encode to 3-channel uint8 PNG. Accepts PIL.Image, np.ndarray, or torch.Tensor."""
|
| 133 |
+
if isinstance(value, Image.Image):
|
| 134 |
+
img = value.convert("RGB")
|
| 135 |
+
arr = np.array(img, dtype=np.uint8)
|
| 136 |
+
else:
|
| 137 |
+
arr = _to_numpy(value)
|
| 138 |
+
# normalize layout
|
| 139 |
+
if arr.ndim == 3 and arr.shape[0] in (1, 3) and arr.shape[2] not in (1, 3):
|
| 140 |
+
# likely CHW -> HWC
|
| 141 |
+
arr = np.transpose(arr, (1, 2, 0))
|
| 142 |
+
if arr.ndim == 2:
|
| 143 |
+
arr = np.stack([arr, arr, arr], axis=-1)
|
| 144 |
+
if arr.ndim != 3 or arr.shape[-1] not in (1, 3):
|
| 145 |
+
raise ValueError(f"PNG expects HxWx{1|3}, got shape {arr.shape}")
|
| 146 |
+
if arr.shape[-1] == 1:
|
| 147 |
+
arr = np.repeat(arr, 3, axis=-1)
|
| 148 |
+
|
| 149 |
+
# dtype + range handling
|
| 150 |
+
if np.issubdtype(arr.dtype, np.floating):
|
| 151 |
+
arr = np.clip(arr, 0.0, 1.0)
|
| 152 |
+
arr = (arr * 255.0 + 0.5).astype(np.uint8)
|
| 153 |
+
elif arr.dtype != np.uint8:
|
| 154 |
+
arr = np.clip(arr, 0, 255).astype(np.uint8)
|
| 155 |
+
|
| 156 |
+
im = Image.fromarray(arr, mode="RGB")
|
| 157 |
+
buf = io.BytesIO()
|
| 158 |
+
im.save(buf, format="PNG", compress_level=4)
|
| 159 |
+
return buf.getvalue()
|
| 160 |
+
|
| 161 |
+
|
| 162 |
+
def _encode_exr(value: Any, compression: str = "PIZ") -> bytes:
|
| 163 |
+
"""
|
| 164 |
+
Encode depth (float16/float32) as single-channel EXR ("Z") with compression.
|
| 165 |
+
Prefers OpenEXR (allows setting compression = PIZ/ZIP/etc.) then imageio, else raises.
|
| 166 |
+
|
| 167 |
+
Expected shapes: HxW or HxWx1. Returns bytes.
|
| 168 |
+
"""
|
| 169 |
+
arr = _to_numpy(value)
|
| 170 |
+
if arr.ndim == 3 and arr.shape[-1] == 1:
|
| 171 |
+
arr = arr[..., 0]
|
| 172 |
+
if arr.ndim != 2:
|
| 173 |
+
raise ValueError(f"EXR expects HxW (single channel), got shape {arr.shape}")
|
| 174 |
+
arr = arr.astype(np.float32)
|
| 175 |
+
|
| 176 |
+
# Try OpenEXR first (full control on compression; best for high ratios)
|
| 177 |
+
import OpenEXR, Imath, tempfile, os
|
| 178 |
+
|
| 179 |
+
h, w = arr.shape
|
| 180 |
+
header = OpenEXR.Header(w, h)
|
| 181 |
+
comp = {
|
| 182 |
+
"NONE": Imath.Compression.NO_COMPRESSION,
|
| 183 |
+
"ZIP": Imath.Compression.ZIP_COMPRESSION,
|
| 184 |
+
"ZIPS": Imath.Compression.ZIPS_COMPRESSION,
|
| 185 |
+
"PIZ": Imath.Compression.PIZ_COMPRESSION,
|
| 186 |
+
"DWAA": Imath.Compression.DWAA_COMPRESSION,
|
| 187 |
+
"DWAB": Imath.Compression.DWAB_COMPRESSION,
|
| 188 |
+
"B44": Imath.Compression.B44_COMPRESSION,
|
| 189 |
+
"B44A": Imath.Compression.B44A_COMPRESSION,
|
| 190 |
+
}.get(compression.upper(), Imath.Compression.PIZ_COMPRESSION)
|
| 191 |
+
header["compression"] = Imath.Compression(comp)
|
| 192 |
+
header["channels"] = {"Z": Imath.Channel(Imath.PixelType(Imath.PixelType.FLOAT))}
|
| 193 |
+
with tempfile.NamedTemporaryFile(suffix=".exr", delete=False) as tmp:
|
| 194 |
+
outpath = tmp.name
|
| 195 |
+
try:
|
| 196 |
+
out = OpenEXR.OutputFile(outpath, header)
|
| 197 |
+
out.writePixels({"Z": arr.tobytes()})
|
| 198 |
+
out.close()
|
| 199 |
+
with open(outpath, "rb") as f:
|
| 200 |
+
return f.read()
|
| 201 |
+
finally:
|
| 202 |
+
os.remove(outpath)
|
| 203 |
+
|
| 204 |
+
|
| 205 |
+
def _decode_exr(data: bytes) -> np.ndarray:
|
| 206 |
+
"""
|
| 207 |
+
Decode EXR bytes → numpy float32 array (HxW or HxWxC).
|
| 208 |
+
Ensures the OpenEXR.InputFile handle is closed to avoid C-level memory leaks.
|
| 209 |
+
"""
|
| 210 |
+
import OpenEXR, Imath
|
| 211 |
+
|
| 212 |
+
exr = None
|
| 213 |
+
try:
|
| 214 |
+
with io.BytesIO(data) as f:
|
| 215 |
+
# Note: OpenEXR Python bindings support file-like objects.
|
| 216 |
+
exr = OpenEXR.InputFile(f)
|
| 217 |
+
header = exr.header()
|
| 218 |
+
dw = header["dataWindow"]
|
| 219 |
+
w, h = dw.max.x - dw.min.x + 1, dw.max.y - dw.min.y + 1
|
| 220 |
+
channels = list(header["channels"].keys())
|
| 221 |
+
pt = header["channels"][channels[0]].type
|
| 222 |
+
if pt == Imath.PixelType(Imath.PixelType.FLOAT):
|
| 223 |
+
np_dtype = np.float32
|
| 224 |
+
elif pt == Imath.PixelType(Imath.PixelType.HALF):
|
| 225 |
+
np_dtype = np.float16
|
| 226 |
+
elif pt == Imath.PixelType(Imath.PixelType.UINT):
|
| 227 |
+
np_dtype = np.uint32
|
| 228 |
+
else:
|
| 229 |
+
raise ValueError(f"Unsupported EXR pixel type: {pt}")
|
| 230 |
+
arrs = []
|
| 231 |
+
for c in channels:
|
| 232 |
+
ch_str = exr.channel(c)
|
| 233 |
+
arr = np.frombuffer(ch_str, dtype=np_dtype)
|
| 234 |
+
arr = arr.reshape((h, w))
|
| 235 |
+
arrs.append(arr)
|
| 236 |
+
if len(arrs) == 1:
|
| 237 |
+
return arrs[0]
|
| 238 |
+
return np.stack(arrs, axis=-1)
|
| 239 |
+
finally:
|
| 240 |
+
try:
|
| 241 |
+
if exr is not None:
|
| 242 |
+
exr.close()
|
| 243 |
+
except Exception:
|
| 244 |
+
# Swallow close errors; decoder already produced output
|
| 245 |
+
pass
|
| 246 |
+
|
| 247 |
+
|
| 248 |
+
def _encode_tensor(value: Any) -> bytes:
|
| 249 |
+
arr = _to_numpy(value)
|
| 250 |
+
buf = io.BytesIO()
|
| 251 |
+
np.save(buf, arr, allow_pickle=False)
|
| 252 |
+
return buf.getvalue()
|
| 253 |
+
|
| 254 |
+
|
| 255 |
+
def _encode_str(value: Any) -> bytes:
|
| 256 |
+
return str(value).encode("utf-8")
|
| 257 |
+
|
| 258 |
+
|
| 259 |
+
def _encode_json(value: Any) -> bytes:
|
| 260 |
+
return json.dumps(value).encode("utf-8")
|
| 261 |
+
|
| 262 |
+
|
| 263 |
+
def _encode_bytes(value: Any) -> bytes:
|
| 264 |
+
if isinstance(value, (bytes, bytearray, memoryview)):
|
| 265 |
+
return bytes(value)
|
| 266 |
+
raise TypeError("BYTES expects bytes-like input")
|
| 267 |
+
|
| 268 |
+
|
| 269 |
+
def _encode_by_dt(value: Any, dt: Any) -> bytes:
|
| 270 |
+
if value is None:
|
| 271 |
+
raise ValueError("Cannot encode None value")
|
| 272 |
+
dtv = _dt_value(dt)
|
| 273 |
+
if dtv == DT.PNG.value:
|
| 274 |
+
return _encode_png_3ch(value)
|
| 275 |
+
if dtv == DT.EXR.value:
|
| 276 |
+
return _encode_exr(value, compression="ZIP")
|
| 277 |
+
if dtv == DT.TENSOR.value:
|
| 278 |
+
return _encode_tensor(value)
|
| 279 |
+
if dtv == DT.STR.value:
|
| 280 |
+
return _encode_str(value)
|
| 281 |
+
if dtv == DT.JSON.value:
|
| 282 |
+
return _encode_json(value)
|
| 283 |
+
if dtv == DT.BYTES.value:
|
| 284 |
+
return _encode_bytes(value)
|
| 285 |
+
raise ValueError(f"Unhandled DT: {dt}")
|
| 286 |
+
|
| 287 |
+
|
| 288 |
+
# ============================
|
| 289 |
+
# (1) Partial builder (generic)
|
| 290 |
+
# ============================
|
| 291 |
+
def build_partial_dataset(
|
| 292 |
+
generate_data, # callable: gid -> dict with per-object fields and 'views' list of per-view dicts
|
| 293 |
+
gid_list: List[Any],
|
| 294 |
+
schema: Dict[
|
| 295 |
+
str, Any
|
| 296 |
+
], # same structure as generate_data output, except schema["views"] is dict of {key: DT}
|
| 297 |
+
root_dir: str,
|
| 298 |
+
run_name: Optional[str] = None,
|
| 299 |
+
part_dir: Optional[
|
| 300 |
+
str
|
| 301 |
+
] = None, # if given, overrides run_name and uses this dir directly
|
| 302 |
+
max_objects_per_shard: int = 64,
|
| 303 |
+
progress: bool = False,
|
| 304 |
+
) -> Dict[str, str]:
|
| 305 |
+
"""
|
| 306 |
+
Writes object-level WebDataset shards under {root_dir}/parts/{run}/objects-*.tar.
|
| 307 |
+
|
| 308 |
+
File naming (generic, type-driven):
|
| 309 |
+
- Per-object field 'K' (DT -> ext): 'obj_{K}.{ext}'
|
| 310 |
+
- Per-view field 'K' at index i: '{i:04d}.{K}.{ext}'
|
| 311 |
+
- Minimal header with counts: 'meta.json' (contains {'id': str(gid), 'num_views': int})
|
| 312 |
+
|
| 313 |
+
Nothing in this function assumes any concrete keys besides 'views'.
|
| 314 |
+
"""
|
| 315 |
+
if "views" not in schema or not isinstance(schema["views"], dict):
|
| 316 |
+
raise ValueError(
|
| 317 |
+
"Schema must contain a 'views' dict describing per-view fields and their DT."
|
| 318 |
+
)
|
| 319 |
+
|
| 320 |
+
run = run_name or f"part-{uuid.uuid4().hex[:8]}"
|
| 321 |
+
part_dir = part_dir or os.path.join(root_dir, "parts", run)
|
| 322 |
+
_ensure_dir(part_dir)
|
| 323 |
+
|
| 324 |
+
# persist the schema for provenance
|
| 325 |
+
with open(os.path.join(part_dir, "schema.json"), "w", encoding="utf-8") as f:
|
| 326 |
+
json.dump(_schema_to_jsonable(schema), f, indent=2)
|
| 327 |
+
|
| 328 |
+
shard_pattern = os.path.join(part_dir, "objects-%06d.tar")
|
| 329 |
+
shardlist_path = os.path.join(part_dir, "shards.txt")
|
| 330 |
+
created = []
|
| 331 |
+
|
| 332 |
+
obj_fields = {k: v for k, v in schema.items() if k != "views"}
|
| 333 |
+
view_fields = schema["views"]
|
| 334 |
+
|
| 335 |
+
def obj_key_for(gid: Any) -> str:
|
| 336 |
+
return _sanitize_key(str(gid))
|
| 337 |
+
|
| 338 |
+
with wds.ShardWriter(shard_pattern, maxcount=max_objects_per_shard) as sink:
|
| 339 |
+
if progress:
|
| 340 |
+
gid_list = tqdm(gid_list, desc=f"Building dataset {run}")
|
| 341 |
+
for gid in gid_list:
|
| 342 |
+
data = generate_data(gid)
|
| 343 |
+
if "views" not in data or not isinstance(data["views"], list):
|
| 344 |
+
raise ValueError(
|
| 345 |
+
"generate_data must return a dict with a 'views' list."
|
| 346 |
+
)
|
| 347 |
+
|
| 348 |
+
sample = {"__key__": obj_key_for(gid)}
|
| 349 |
+
# per-object fields
|
| 350 |
+
for k, dt in obj_fields.items():
|
| 351 |
+
if k == "views":
|
| 352 |
+
continue
|
| 353 |
+
if k in data and data[k] is not None:
|
| 354 |
+
ext = _dt_ext(dt)
|
| 355 |
+
sample[f"obj_{_sanitize_key(k)}.{ext}"] = _encode_by_dt(data[k], dt)
|
| 356 |
+
|
| 357 |
+
# per-view fields
|
| 358 |
+
num_views = len(data["views"])
|
| 359 |
+
for i, view in enumerate(data["views"]):
|
| 360 |
+
if not isinstance(view, dict):
|
| 361 |
+
raise ValueError("Each element of 'views' must be a dict.")
|
| 362 |
+
for vk, vdt in view_fields.items():
|
| 363 |
+
if vk in view and view[vk] is not None:
|
| 364 |
+
ext = _dt_ext(vdt)
|
| 365 |
+
name = f"{i:04d}.{_sanitize_key(vk)}.{ext}"
|
| 366 |
+
sample[name] = _encode_by_dt(view[vk], vdt)
|
| 367 |
+
|
| 368 |
+
# minimal meta
|
| 369 |
+
meta = {"id": sample["__key__"], "num_views": num_views}
|
| 370 |
+
sample["meta.json"] = json.dumps(meta).encode("utf-8")
|
| 371 |
+
|
| 372 |
+
sink.write(sample)
|
| 373 |
+
if getattr(sink, "fname", None) and (
|
| 374 |
+
not created or created[-1] != sink.fname
|
| 375 |
+
):
|
| 376 |
+
created.append(sink.fname)
|
| 377 |
+
|
| 378 |
+
# finalize shard list
|
| 379 |
+
tars = sorted(glob.glob(os.path.join(part_dir, "objects-*.tar")))
|
| 380 |
+
if len(tars) != len(created):
|
| 381 |
+
created = tars
|
| 382 |
+
with open(shardlist_path, "w", encoding="utf-8") as f:
|
| 383 |
+
for p in created:
|
| 384 |
+
f.write(p + "\n")
|
| 385 |
+
|
| 386 |
+
with open(os.path.join(part_dir, "manifest.json"), "w", encoding="utf-8") as f:
|
| 387 |
+
json.dump(
|
| 388 |
+
{
|
| 389 |
+
"kind": "webdataset-object-generic-v1",
|
| 390 |
+
"created": _timestamp(),
|
| 391 |
+
"run": run,
|
| 392 |
+
"num_shards": len(created),
|
| 393 |
+
"root": os.path.abspath(root_dir),
|
| 394 |
+
},
|
| 395 |
+
f,
|
| 396 |
+
indent=2,
|
| 397 |
+
)
|
| 398 |
+
|
| 399 |
+
return {"part_dir": part_dir, "shardlist": shardlist_path}
|
| 400 |
+
|
| 401 |
+
|
| 402 |
+
# ===========================
|
| 403 |
+
# (2) Merge partials (O(1) IO)
|
| 404 |
+
# ===========================
|
| 405 |
+
def merge_partials(
|
| 406 |
+
root_dir: str,
|
| 407 |
+
part_dirs: List[str],
|
| 408 |
+
out_name: str = "merged",
|
| 409 |
+
materialize: bool = False, # if True, hardlink (or copy) shards into a single subdir
|
| 410 |
+
) -> Dict[str, str]:
|
| 411 |
+
"""
|
| 412 |
+
Creates {root_dir}/{out_name}/shards.txt covering all shards from given parts.
|
| 413 |
+
If materialize=True, hardlinks/copies shards into {root_dir}/{out_name}/shards/ and points shards.txt there.
|
| 414 |
+
"""
|
| 415 |
+
out_dir = os.path.join(root_dir, out_name)
|
| 416 |
+
_ensure_dir(out_dir)
|
| 417 |
+
shards_txt = os.path.join(out_dir, "shards.txt")
|
| 418 |
+
|
| 419 |
+
all_shards: List[str] = []
|
| 420 |
+
for pd in part_dirs:
|
| 421 |
+
pd = os.path.abspath(pd)
|
| 422 |
+
txt = os.path.join(pd, "shards.txt")
|
| 423 |
+
if os.path.isfile(txt):
|
| 424 |
+
with open(txt, "r", encoding="utf-8") as f:
|
| 425 |
+
all_shards.extend([ln.strip() for ln in f if ln.strip()])
|
| 426 |
+
else:
|
| 427 |
+
all_shards.extend(sorted(glob.glob(os.path.join(pd, "objects-*.tar"))))
|
| 428 |
+
|
| 429 |
+
if materialize:
|
| 430 |
+
link_dir = os.path.join(out_dir, "shards")
|
| 431 |
+
_ensure_dir(link_dir)
|
| 432 |
+
linked = []
|
| 433 |
+
for i, src in enumerate(all_shards):
|
| 434 |
+
dst = os.path.join(link_dir, f"{i:06d}.tar")
|
| 435 |
+
try:
|
| 436 |
+
if os.path.exists(dst):
|
| 437 |
+
os.remove(dst)
|
| 438 |
+
os.link(src, dst) # fast if same filesystem
|
| 439 |
+
except OSError:
|
| 440 |
+
shutil.copy2(src, dst) # fallback across devices
|
| 441 |
+
linked.append(dst)
|
| 442 |
+
all_shards = linked
|
| 443 |
+
|
| 444 |
+
with open(shards_txt, "w", encoding="utf-8") as f:
|
| 445 |
+
for p in all_shards:
|
| 446 |
+
f.write(p + "\n")
|
| 447 |
+
|
| 448 |
+
# write a minimal manifest
|
| 449 |
+
with open(os.path.join(out_dir, "manifest.json"), "w", encoding="utf-8") as f:
|
| 450 |
+
json.dump(
|
| 451 |
+
{
|
| 452 |
+
"kind": "webdataset-merged",
|
| 453 |
+
"created": _timestamp(),
|
| 454 |
+
"num_shards": len(all_shards),
|
| 455 |
+
},
|
| 456 |
+
f,
|
| 457 |
+
indent=2,
|
| 458 |
+
)
|
| 459 |
+
|
| 460 |
+
# copy first available schema.json for convenience (optional)
|
| 461 |
+
for pd in part_dirs:
|
| 462 |
+
sch = os.path.join(pd, "schema.json")
|
| 463 |
+
if os.path.isfile(sch):
|
| 464 |
+
shutil.copy2(sch, os.path.join(out_dir, "schema.json"))
|
| 465 |
+
break
|
| 466 |
+
|
| 467 |
+
return {"dataset_dir": out_dir, "shardlist": shards_txt}
|
| 468 |
+
|
| 469 |
+
|
| 470 |
+
# =========================================
|
| 471 |
+
# (3) Loading: row-wise and object-wise views
|
| 472 |
+
# =========================================
|
| 473 |
+
def _resolve_urls(dataset_path: str) -> Union[str, List[str]]:
|
| 474 |
+
p = pathlib.Path(dataset_path)
|
| 475 |
+
if p.is_dir():
|
| 476 |
+
txt = p / "shards.txt"
|
| 477 |
+
if txt.exists():
|
| 478 |
+
with open(txt, "r", encoding="utf-8") as f:
|
| 479 |
+
return [ln.strip() for ln in f if ln.strip()]
|
| 480 |
+
tars = sorted(p.glob("*.tar"))
|
| 481 |
+
if tars:
|
| 482 |
+
return [str(x) for x in tars]
|
| 483 |
+
if p.suffix == ".txt" and p.exists():
|
| 484 |
+
with open(p, "r", encoding="utf-8") as f:
|
| 485 |
+
return [ln.strip() for ln in f if ln.strip()]
|
| 486 |
+
return dataset_path # brace/glob allowed by WebDataset
|
| 487 |
+
|
| 488 |
+
|
| 489 |
+
def build_mcvae_dataset(
|
| 490 |
+
dataset_path: str,
|
| 491 |
+
schema: Dict[str, Any],
|
| 492 |
+
size: int = 1024, # final image size (H=W=size)
|
| 493 |
+
shardshuffle: int = 1000,
|
| 494 |
+
decode: Union[str, Any] = "torch", # "pil" or "torch" or a handler
|
| 495 |
+
split_by_node: bool = True,
|
| 496 |
+
split_by_worker: bool = True,
|
| 497 |
+
):
|
| 498 |
+
"""
|
| 499 |
+
Returns an IterableDataset that yields per-view dicts.
|
| 500 |
+
Each item includes all per-object fields injected into the row.
|
| 501 |
+
Missing fields are omitted rather than set to None.
|
| 502 |
+
"""
|
| 503 |
+
if "views" not in schema or not isinstance(schema["views"], dict):
|
| 504 |
+
raise ValueError("Schema must contain a 'views' dict.")
|
| 505 |
+
|
| 506 |
+
urls = _resolve_urls(dataset_path)
|
| 507 |
+
obj_fields = {k: v for k, v in schema.items() if k != "views"}
|
| 508 |
+
view_fields = schema["views"]
|
| 509 |
+
|
| 510 |
+
# ---- 1) Pre-process (unpacking rows) ----
|
| 511 |
+
def _preprocess(sample: Dict[str, Any]) -> List[Dict[str, Any]]:
|
| 512 |
+
# meta.json is bytes (we didn't register a JSON handler) → parse explicitly
|
| 513 |
+
meta = sample["meta.json"]
|
| 514 |
+
gid = meta["id"]
|
| 515 |
+
n = int(meta["num_views"])
|
| 516 |
+
|
| 517 |
+
# per-object fields
|
| 518 |
+
per_object: Dict[str, Any] = {}
|
| 519 |
+
for k, dt in obj_fields.items():
|
| 520 |
+
ext = _dt_ext(dt)
|
| 521 |
+
name = f"obj_{_sanitize_key(k)}.{ext}"
|
| 522 |
+
if name in sample:
|
| 523 |
+
per_object[k] = sample[name]
|
| 524 |
+
|
| 525 |
+
# per-view fields
|
| 526 |
+
rows: List[Dict[str, Any]] = []
|
| 527 |
+
for idx in range(n):
|
| 528 |
+
v = {"id": gid, "view_index": idx}
|
| 529 |
+
v.update(per_object)
|
| 530 |
+
for vk, vdt in view_fields.items():
|
| 531 |
+
ext = _dt_ext(vdt)
|
| 532 |
+
vname = f"{idx:04d}.{_sanitize_key(vk)}.{ext}"
|
| 533 |
+
if vname in sample:
|
| 534 |
+
v[vk] = sample[vname]
|
| 535 |
+
rows.append(v)
|
| 536 |
+
return rows
|
| 537 |
+
|
| 538 |
+
# ---- 2) Post-process (resizing, dataset-specific tweaks) ----
|
| 539 |
+
def _postprocess(sample: Dict[str, Any]) -> Dict[str, Any]:
|
| 540 |
+
out: Dict[str, Any] = {}
|
| 541 |
+
for k, v in sample.items():
|
| 542 |
+
if isinstance(v, np.ndarray):
|
| 543 |
+
out[k] = torch.from_numpy(v.copy())
|
| 544 |
+
else:
|
| 545 |
+
out[k] = v
|
| 546 |
+
|
| 547 |
+
# Dataset-specific postprocessing
|
| 548 |
+
def _resize_(key: str):
|
| 549 |
+
out[key] = F.interpolate(
|
| 550 |
+
out[key].unsqueeze(0),
|
| 551 |
+
size=(size, size),
|
| 552 |
+
mode="bilinear",
|
| 553 |
+
align_corners=False,
|
| 554 |
+
).squeeze(0)
|
| 555 |
+
|
| 556 |
+
for key in ("albedo", "orm"):
|
| 557 |
+
_resize_(key)
|
| 558 |
+
out[key] = out[key] * 2 - 1
|
| 559 |
+
|
| 560 |
+
# ready-to-use tensors
|
| 561 |
+
# print(out['albedo'].shape, out['orm'].shape)
|
| 562 |
+
out["pixel_values"] = torch.cat(
|
| 563 |
+
[out["albedo"], out["orm"][1:, ...]], dim=0
|
| 564 |
+
) # (5,H,W)
|
| 565 |
+
|
| 566 |
+
# cleanup
|
| 567 |
+
for k in (
|
| 568 |
+
"prompt",
|
| 569 |
+
"prompt_embeds",
|
| 570 |
+
"pooled_prompt_embeds",
|
| 571 |
+
"elevation",
|
| 572 |
+
"azimuth",
|
| 573 |
+
"id",
|
| 574 |
+
"view_index",
|
| 575 |
+
"c2w",
|
| 576 |
+
"scale",
|
| 577 |
+
"pos",
|
| 578 |
+
"normal",
|
| 579 |
+
"depth",
|
| 580 |
+
):
|
| 581 |
+
out.pop(k, None)
|
| 582 |
+
|
| 583 |
+
return out
|
| 584 |
+
|
| 585 |
+
# For mcVAE training we don't use depth EXR; avoid decoding EXR entirely to reduce memory pressure
|
| 586 |
+
# and eliminate potential leaks from the EXR decoder. Keep PNG decoding for albedo/orm.
|
| 587 |
+
ds = (
|
| 588 |
+
wds.WebDataset(
|
| 589 |
+
urls,
|
| 590 |
+
nodesplitter=wds.split_by_node if split_by_node else None,
|
| 591 |
+
workersplitter=wds.split_by_worker if split_by_worker else None,
|
| 592 |
+
shardshuffle=shardshuffle,
|
| 593 |
+
)
|
| 594 |
+
.decode(
|
| 595 |
+
wds.imagehandler(decode),
|
| 596 |
+
)
|
| 597 |
+
.map(_preprocess)
|
| 598 |
+
.unlisted()
|
| 599 |
+
.map(_postprocess)
|
| 600 |
+
)
|
| 601 |
+
return ds
|
| 602 |
+
|
| 603 |
+
|
| 604 |
+
def build_mcdiff_dataset(
|
| 605 |
+
dataset_path: str,
|
| 606 |
+
schema: Dict[str, Any],
|
| 607 |
+
num_views: int,
|
| 608 |
+
size: int = 1024,
|
| 609 |
+
shardshuffle: int = 1000,
|
| 610 |
+
decode: Union[str, Any] = "torch",
|
| 611 |
+
split_by_node: bool = True,
|
| 612 |
+
split_by_worker: bool = True,
|
| 613 |
+
fixed_view_indices: Optional[List[int]] = None,
|
| 614 |
+
):
|
| 615 |
+
|
| 616 |
+
if "views" not in schema or not isinstance(schema["views"], dict):
|
| 617 |
+
raise ValueError("Schema must contain a 'views' dict.")
|
| 618 |
+
|
| 619 |
+
if fixed_view_indices is not None:
|
| 620 |
+
assert (
|
| 621 |
+
len(fixed_view_indices) == num_views
|
| 622 |
+
), "fixed_view_indices length must match num_views"
|
| 623 |
+
|
| 624 |
+
assert size % 16 == 0 and size <= 1024, "size must be multiple of 16 and <= 1024"
|
| 625 |
+
|
| 626 |
+
urls = _resolve_urls(dataset_path)
|
| 627 |
+
obj_fields = {k: v for k, v in schema.items() if k != "views"}
|
| 628 |
+
view_fields = schema["views"]
|
| 629 |
+
|
| 630 |
+
# ---- 1) Pre-sampling (view selection) ----
|
| 631 |
+
def _presample_filter(sample: Dict[str, Any]) -> Dict[str, Any]:
|
| 632 |
+
if "meta.json" not in sample:
|
| 633 |
+
return None
|
| 634 |
+
meta = json.loads(sample["meta.json"].decode("utf-8"))
|
| 635 |
+
n = int(meta["num_views"])
|
| 636 |
+
if not (1 <= num_views <= n):
|
| 637 |
+
return None
|
| 638 |
+
|
| 639 |
+
if fixed_view_indices is not None:
|
| 640 |
+
chosen = fixed_view_indices
|
| 641 |
+
else:
|
| 642 |
+
chosen = random.sample(
|
| 643 |
+
range(n), num_views
|
| 644 |
+
) # epoch마다 DataLoader 시드에 따라 바뀜
|
| 645 |
+
|
| 646 |
+
kept: Dict[str, Any] = {}
|
| 647 |
+
# keep metadata
|
| 648 |
+
kept["meta.json"] = sample["meta.json"]
|
| 649 |
+
if "__key__" in sample:
|
| 650 |
+
kept["__key__"] = sample["__key__"]
|
| 651 |
+
|
| 652 |
+
# keep per-object fields
|
| 653 |
+
for k, dt in obj_fields.items():
|
| 654 |
+
ext = _dt_ext(dt)
|
| 655 |
+
name = f"obj_{_sanitize_key(k)}.{ext}"
|
| 656 |
+
if name in sample:
|
| 657 |
+
kept[name] = sample[name]
|
| 658 |
+
|
| 659 |
+
# keep selected per-view fields
|
| 660 |
+
for idx in chosen:
|
| 661 |
+
for vk, vdt in view_fields.items():
|
| 662 |
+
ext = _dt_ext(vdt)
|
| 663 |
+
vname = f"{idx:04d}.{_sanitize_key(vk)}.{ext}"
|
| 664 |
+
if vname in sample:
|
| 665 |
+
kept[vname] = sample[vname]
|
| 666 |
+
|
| 667 |
+
# store chosen indices for later use
|
| 668 |
+
kept["_sel.json"] = json.dumps(chosen).encode("utf-8")
|
| 669 |
+
return kept
|
| 670 |
+
|
| 671 |
+
# ---- 2) Post-process (view stacking, resizing, dataset-specific tweaks) ----
|
| 672 |
+
def _postprocess(sample: Dict[str, Any]) -> Dict[str, Any]:
|
| 673 |
+
# restore selection order
|
| 674 |
+
chosen = sample["_sel.json"]
|
| 675 |
+
|
| 676 |
+
# per-object fields
|
| 677 |
+
out: Dict[str, Any] = {}
|
| 678 |
+
for k, dt in obj_fields.items():
|
| 679 |
+
ext = _dt_ext(dt)
|
| 680 |
+
name = f"obj_{_sanitize_key(k)}.{ext}"
|
| 681 |
+
if name in sample:
|
| 682 |
+
out[k] = sample[name]
|
| 683 |
+
|
| 684 |
+
# selected per-view fields
|
| 685 |
+
views: List[Dict[str, Any]] = []
|
| 686 |
+
for idx in chosen:
|
| 687 |
+
v: Dict[str, Any] = {}
|
| 688 |
+
for vk, vdt in view_fields.items():
|
| 689 |
+
ext = _dt_ext(vdt)
|
| 690 |
+
vname = f"{idx:04d}.{_sanitize_key(vk)}.{ext}"
|
| 691 |
+
if vname in sample:
|
| 692 |
+
v[vk] = sample[vname]
|
| 693 |
+
if isinstance(v[vk], np.ndarray):
|
| 694 |
+
v[vk] = torch.from_numpy(v[vk].copy())
|
| 695 |
+
v["view_index"] = idx
|
| 696 |
+
views.append(v)
|
| 697 |
+
|
| 698 |
+
if len(views) == 0:
|
| 699 |
+
return None
|
| 700 |
+
|
| 701 |
+
for k in views[0].keys():
|
| 702 |
+
arrs = [v[k] for v in views]
|
| 703 |
+
if isinstance(arrs[0], torch.Tensor):
|
| 704 |
+
out[k] = torch.stack(arrs, dim=0)
|
| 705 |
+
elif isinstance(arrs[0], np.ndarray):
|
| 706 |
+
out[k] = torch.from_numpy(np.stack(arrs, axis=0))
|
| 707 |
+
else:
|
| 708 |
+
out[k] = arrs # e.g., list of strings
|
| 709 |
+
|
| 710 |
+
# Dataset-specific postprocessing
|
| 711 |
+
|
| 712 |
+
# normal, depth, pos: (V,3,H,W); albedo, orm: (V,3,H,W) , c2w: (V,4,4)
|
| 713 |
+
# Always operate in single-channel mode - batched postprocessing will handle dual-channel
|
| 714 |
+
|
| 715 |
+
out["normal_fullres"] = out["normal"] * 2 - 1 # [-1,1], for CAA
|
| 716 |
+
|
| 717 |
+
mask = out["orm"][:, 0, :, :] > 0.5
|
| 718 |
+
out["depth"][~mask] = float("nan")
|
| 719 |
+
|
| 720 |
+
pos_raw = out["pos"] - 0.5 # [-0.5, 0.5], for pos_token
|
| 721 |
+
df = int(out["pos"].shape[2] // (size // 16))
|
| 722 |
+
mask_f = mask.float().unsqueeze(1) # (V,1,H,W)
|
| 723 |
+
|
| 724 |
+
# Weighted block average over valid pixels
|
| 725 |
+
pos_sum = F.avg_pool2d(
|
| 726 |
+
pos_raw * mask_f, kernel_size=df, stride=df, divisor_override=1
|
| 727 |
+
)
|
| 728 |
+
cnt = F.avg_pool2d(mask_f, kernel_size=df, stride=df, divisor_override=1)
|
| 729 |
+
pos_ds = pos_sum / cnt.clamp_min(1.0)
|
| 730 |
+
out["pos_token"] = pos_ds
|
| 731 |
+
|
| 732 |
+
def _resize_(key: str):
|
| 733 |
+
out[key] = F.interpolate(
|
| 734 |
+
out[key], size=(size, size), mode="bilinear", align_corners=False
|
| 735 |
+
)
|
| 736 |
+
|
| 737 |
+
for key in ("albedo", "orm", "pos", "normal"):
|
| 738 |
+
_resize_(key)
|
| 739 |
+
out[key] = out[key] * 2 - 1
|
| 740 |
+
|
| 741 |
+
# ready-to-use tensors (always single-channel mode)
|
| 742 |
+
out["pixel_values"] = torch.cat(
|
| 743 |
+
[out["albedo"], out["orm"][:, 1:, ...]], dim=1
|
| 744 |
+
) # (V,5,H,W)
|
| 745 |
+
|
| 746 |
+
out["cond_values"] = torch.cat([out["pos"], out["normal"]], dim=1) # (V,6,H,W)
|
| 747 |
+
|
| 748 |
+
out["scale"] = out["scale"][0] # common across views
|
| 749 |
+
out["w2c"] = torch.linalg.inv(out["c2w"]) # (V,4,4)
|
| 750 |
+
# albedo, orm, depth, pos, pos_token, normal, pixel_values, cond_values, w2c, normal_fullres
|
| 751 |
+
|
| 752 |
+
# cleanup
|
| 753 |
+
for k in ("prompt", "elevation", "azimuth"):
|
| 754 |
+
out.pop(k, None)
|
| 755 |
+
return out
|
| 756 |
+
|
| 757 |
+
# ---- 3) Build dataset ----
|
| 758 |
+
ds = (
|
| 759 |
+
wds.WebDataset(
|
| 760 |
+
urls,
|
| 761 |
+
nodesplitter=wds.split_by_node if split_by_node else None,
|
| 762 |
+
workersplitter=wds.split_by_worker if split_by_worker else None,
|
| 763 |
+
shardshuffle=shardshuffle,
|
| 764 |
+
)
|
| 765 |
+
.map(_presample_filter)
|
| 766 |
+
.decode(
|
| 767 |
+
wds.handle_extension("exr", _decode_exr),
|
| 768 |
+
wds.imagehandler(decode),
|
| 769 |
+
)
|
| 770 |
+
.map(_postprocess)
|
| 771 |
+
)
|
| 772 |
+
|
| 773 |
+
return ds
|
| 774 |
+
|
| 775 |
+
if __name__ == "__main__":
|
| 776 |
+
import argparse
|
| 777 |
+
|
| 778 |
+
parser = argparse.ArgumentParser(
|
| 779 |
+
description="Merge partial RGBMR dataset shards into a single dataset"
|
| 780 |
+
)
|
| 781 |
+
parser.add_argument(
|
| 782 |
+
"--root_dir",
|
| 783 |
+
type=str,
|
| 784 |
+
required=True,
|
| 785 |
+
help="Root directory where the merged dataset will be created"
|
| 786 |
+
)
|
| 787 |
+
parser.add_argument(
|
| 788 |
+
"--part_dirs",
|
| 789 |
+
type=str,
|
| 790 |
+
nargs="+",
|
| 791 |
+
required=True,
|
| 792 |
+
help="List of partial dataset directories to merge"
|
| 793 |
+
)
|
| 794 |
+
parser.add_argument(
|
| 795 |
+
"--out_name",
|
| 796 |
+
type=str,
|
| 797 |
+
default="merged",
|
| 798 |
+
help="Name of the output merged dataset directory (default: merged)"
|
| 799 |
+
)
|
| 800 |
+
parser.add_argument(
|
| 801 |
+
"--materialize",
|
| 802 |
+
action="store_true",
|
| 803 |
+
help="If set, hardlink/copy shards into a single directory instead of just listing paths"
|
| 804 |
+
)
|
| 805 |
+
|
| 806 |
+
args = parser.parse_args()
|
| 807 |
+
|
| 808 |
+
print(f"[{_timestamp()}] Starting merge operation...")
|
| 809 |
+
print(f" Root directory: {args.root_dir}")
|
| 810 |
+
print(f" Partial directories: {args.part_dirs}")
|
| 811 |
+
print(f" Output name: {args.out_name}")
|
| 812 |
+
print(f" Materialize: {args.materialize}")
|
| 813 |
+
|
| 814 |
+
result = merge_partials(
|
| 815 |
+
root_dir=args.root_dir,
|
| 816 |
+
part_dirs=args.part_dirs,
|
| 817 |
+
out_name=args.out_name,
|
| 818 |
+
materialize=args.materialize
|
| 819 |
+
)
|
| 820 |
+
|
| 821 |
+
print(f"\n[{_timestamp()}] Merge completed successfully!")
|
| 822 |
+
print(f" Dataset directory: {result['dataset_dir']}")
|
| 823 |
+
print(f" Shard list: {result['shardlist']}")
|
| 824 |
+
|
| 825 |
+
# Count total shards
|
| 826 |
+
with open(result['shardlist'], 'r') as f:
|
| 827 |
+
num_shards = len([line for line in f if line.strip()])
|
| 828 |
+
print(f" Total shards: {num_shards}")
|
| 829 |
+
|
| 830 |
+
|
| 831 |
+
|
| 832 |
+
'''
|
| 833 |
+
python data/rgbmr_dataset.py --root_dir /home/aaaaa/data/rgbmr_mv_web2/ \
|
| 834 |
+
--part_dirs \
|
| 835 |
+
/home/aaaaa/data/rgbmr_mv_web2/parts/render_00_of_07 \
|
| 836 |
+
/home/aaaaa/data/rgbmr_mv_web2/parts/render_01_of_07 \
|
| 837 |
+
/home/aaaaa/data/rgbmr_mv_web2/parts/render_02_of_07 \
|
| 838 |
+
/home/aaaaa/data/rgbmr_mv_web2/parts/render_03_of_07 \
|
| 839 |
+
/home/aaaaa/data/rgbmr_mv_web2/parts/render_04_of_07 \
|
| 840 |
+
/home/aaaaa/data/rgbmr_mv_web2/parts/render_05_of_07 \
|
| 841 |
+
/home/aaaaa/data/rgbmr_mv_web2/parts/render_06_of_07 \
|
| 842 |
+
--out_name merged --materialize
|
| 843 |
+
|
| 844 |
+
|
| 845 |
+
'''
|
home/ubuntu/aaaaa/data/rgbmr/debug_uv_mask.png
ADDED
|
home/ubuntu/aaaaa/data/rgbmr/filter_complex.py
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os, sys
|
| 2 |
+
import json
|
| 3 |
+
import glob
|
| 4 |
+
import numpy as np
|
| 5 |
+
import torch
|
| 6 |
+
import torch.nn.functional as F
|
| 7 |
+
from torchvision.transforms import ToTensor, ToPILImage
|
| 8 |
+
from scipy.ndimage import distance_transform_edt
|
| 9 |
+
from PIL import Image
|
| 10 |
+
from PIL import ImageDraw, ImageFont
|
| 11 |
+
from tqdm.auto import tqdm
|
| 12 |
+
|
| 13 |
+
def grid_images(images, ncols=4, labels=None):
|
| 14 |
+
nrows = (len(images) + ncols - 1) // ncols
|
| 15 |
+
w, h = images[0].size
|
| 16 |
+
grid = Image.new('RGB', (ncols * w, nrows * h))
|
| 17 |
+
for idx, img in enumerate(images):
|
| 18 |
+
row = idx // ncols
|
| 19 |
+
col = idx % ncols
|
| 20 |
+
grid.paste(img, (col * w, row * h))
|
| 21 |
+
|
| 22 |
+
if labels is not None:
|
| 23 |
+
assert len(labels) == ncols
|
| 24 |
+
grid_tmp = Image.new('RGB', (ncols * w, nrows * h + 20), (255, 255, 255))
|
| 25 |
+
grid_tmp.paste(grid, (0, 20))
|
| 26 |
+
grid = grid_tmp
|
| 27 |
+
|
| 28 |
+
# draw labels
|
| 29 |
+
font = ImageFont.load_default(size=16)
|
| 30 |
+
draw = ImageDraw.Draw(grid)
|
| 31 |
+
for col, label in enumerate(labels):
|
| 32 |
+
draw.text((col * w + 5, 0), label, fill=(0, 0, 0), font=font)
|
| 33 |
+
|
| 34 |
+
return grid
|
| 35 |
+
|
| 36 |
+
# ----------------- 내부 유틸 -----------------
|
| 37 |
+
def _erode_mask(mask: torch.Tensor, pixels: int) -> torch.Tensor:
|
| 38 |
+
"""
|
| 39 |
+
distance transform 기반 마스크 수축(AA 링 제거).
|
| 40 |
+
입력은 torch.Tensor([H,W] 또는 [1,H,W]) 혹은 numpy.ndarray([H,W]).
|
| 41 |
+
출력은 torch.bool 텐서([H,W]).
|
| 42 |
+
"""
|
| 43 |
+
# normalize input
|
| 44 |
+
m = mask
|
| 45 |
+
orig_device = m.device
|
| 46 |
+
m = m.squeeze().to(dtype=torch.float32) # [H,W]
|
| 47 |
+
if pixels <= 0:
|
| 48 |
+
return (m > 0.5).to(dtype=torch.bool, device=orig_device)
|
| 49 |
+
# EDT in numpy (CPU)
|
| 50 |
+
m_np = (m.detach().cpu().numpy() > 0.5)
|
| 51 |
+
d = distance_transform_edt(m_np)
|
| 52 |
+
out_np = (d > pixels)
|
| 53 |
+
out = torch.from_numpy(out_np).to(dtype=torch.bool, device=orig_device)
|
| 54 |
+
return out
|
| 55 |
+
|
| 56 |
+
def _mean_grad(x: torch.Tensor, mask: torch.Tensor) -> float:
|
| 57 |
+
"""평균 |∇| (Sobel 기반, torch 전용 경로).
|
| 58 |
+
- x: torch.Tensor([H,W] 또는 [1,H,W]) 혹은 np.ndarray([H,W])
|
| 59 |
+
- mask: torch.bool Tensor([H,W]) 또는 동일 shape의 numpy bool
|
| 60 |
+
반환: float
|
| 61 |
+
"""
|
| 62 |
+
# to torch tensors
|
| 63 |
+
xt = torch.from_numpy(x) if isinstance(x, np.ndarray) else x
|
| 64 |
+
mt = torch.from_numpy(mask) if isinstance(mask, np.ndarray) else mask
|
| 65 |
+
xt = xt.squeeze().to(dtype=torch.float32) # [H,W]
|
| 66 |
+
mt = mt.squeeze().to(dtype=torch.bool) # [H,W]
|
| 67 |
+
dev = xt.device
|
| 68 |
+
x4 = xt.unsqueeze(0).unsqueeze(0) # [1,1,H,W]
|
| 69 |
+
# Sobel kernels
|
| 70 |
+
kx = torch.tensor([[1, 0, -1],[2, 0, -2],[1, 0, -1]], device=dev, dtype=x4.dtype).view(1,1,3,3) / 8.0
|
| 71 |
+
ky = torch.tensor([[1, 2, 1],[0, 0, 0],[-1,-2,-1]], device=dev, dtype=x4.dtype).view(1,1,3,3) / 8.0
|
| 72 |
+
gx = F.conv2d(x4, kx, padding=1)
|
| 73 |
+
gy = F.conv2d(x4, ky, padding=1)
|
| 74 |
+
g = torch.sqrt(gx * gx + gy * gy + 1e-6).squeeze(0).squeeze(0) # [H,W]
|
| 75 |
+
vals = g[mt]
|
| 76 |
+
return float(vals.mean().item()) if vals.numel() else 0.0
|
| 77 |
+
|
| 78 |
+
# ----------------- 공개 API -----------------
|
| 79 |
+
def complexity_score_rm(
|
| 80 |
+
rough: torch.Tensor, metallic: torch.Tensor, mask: torch.Tensor,
|
| 81 |
+
erode_px: int = 2,
|
| 82 |
+
) -> tuple[float, float]:
|
| 83 |
+
"""
|
| 84 |
+
rough/metallic/마스크 -> (r_grad*100, m_grad*100).
|
| 85 |
+
입력은 torch.Tensor([H,W]) 또는 numpy.ndarray([H,W]); mask는 {0,1} 또는 bool.
|
| 86 |
+
"""
|
| 87 |
+
rmask = _erode_mask(mask, erode_px) # torch.bool [H,W]
|
| 88 |
+
|
| 89 |
+
r_grad = _mean_grad(rough, rmask)
|
| 90 |
+
m_grad = _mean_grad(metallic, rmask)
|
| 91 |
+
return r_grad * 100.0, m_grad * 100.0
|
| 92 |
+
|
| 93 |
+
def is_complex_rm(
|
| 94 |
+
rough: torch.Tensor, metallic: torch.Tensor, mask: torch.Tensor,
|
| 95 |
+
thres_avg=0.5, thres_r=0.05, thres_m=0.05, erode_px: int = 2
|
| 96 |
+
) -> bool:
|
| 97 |
+
"""
|
| 98 |
+
복잡 여부(bool) 반환.
|
| 99 |
+
- 평균 임계치와 채널별 최소 임계치로 판단.
|
| 100 |
+
"""
|
| 101 |
+
r_grad, m_grad = complexity_score_rm(rough, metallic, mask, erode_px=erode_px)
|
| 102 |
+
avg_grad = (r_grad + m_grad) / 2.0
|
| 103 |
+
return (avg_grad >= thres_avg) and (r_grad >= thres_r) and (m_grad >= thres_m)
|
| 104 |
+
|
| 105 |
+
to_tensor = ToTensor()
|
| 106 |
+
|
| 107 |
+
img_root = 'temp_outputs/'
|
| 108 |
+
# format: img_root/<object_id>/<albedo, metallic, roughness>/<00, 01>.png
|
| 109 |
+
|
| 110 |
+
object_ids = sorted(os.listdir(img_root))
|
| 111 |
+
# exclude id including 'warp'.
|
| 112 |
+
object_ids = [oid for oid in object_ids if 'warp' not in oid]
|
| 113 |
+
|
| 114 |
+
# filter folder having all required images
|
| 115 |
+
valid_ids = []
|
| 116 |
+
for obj_id in tqdm(object_ids):
|
| 117 |
+
required_paths = [
|
| 118 |
+
os.path.join(img_root, obj_id, 'albedo', '00.png'),
|
| 119 |
+
os.path.join(img_root, obj_id, 'albedo', '01.png'),
|
| 120 |
+
os.path.join(img_root, obj_id, 'metallic', '00.png'),
|
| 121 |
+
os.path.join(img_root, obj_id, 'metallic', '01.png'),
|
| 122 |
+
os.path.join(img_root, obj_id, 'roughness', '00.png'),
|
| 123 |
+
os.path.join(img_root, obj_id, 'roughness', '01.png'),
|
| 124 |
+
]
|
| 125 |
+
if all(os.path.isfile(p) for p in required_paths):
|
| 126 |
+
valid_ids.append(obj_id)
|
| 127 |
+
object_ids = valid_ids
|
| 128 |
+
|
| 129 |
+
print(f'Found {len(object_ids)} objects.')
|
| 130 |
+
|
| 131 |
+
complex_ids = []
|
| 132 |
+
for obj_id in tqdm(object_ids):
|
| 133 |
+
ok = False
|
| 134 |
+
for view in ['00', '01']:
|
| 135 |
+
albedo = Image.open(os.path.join(img_root, obj_id, 'albedo', f'{view}.png'))
|
| 136 |
+
metallic = Image.open(os.path.join(img_root, obj_id, 'metallic', f'{view}.png'))
|
| 137 |
+
roughness = Image.open(os.path.join(img_root, obj_id, 'roughness', f'{view}.png'))
|
| 138 |
+
# infer mask: background region have r=g=b=M=R=0.5.
|
| 139 |
+
albedo_t = to_tensor(albedo) # [3,H,W], float32, [0,1]
|
| 140 |
+
metallic_t = to_tensor(metallic)[0:1] # [1,H,W], float32, [0,1]
|
| 141 |
+
roughness_t = to_tensor(roughness)[0:1] # [1,H,W], float32, [0,1]
|
| 142 |
+
h, w = albedo_t.shape[1], albedo_t.shape[2]
|
| 143 |
+
rgbmr = torch.cat([
|
| 144 |
+
albedo_t, metallic_t, roughness_t
|
| 145 |
+
], dim=0) # [5,H,W]
|
| 146 |
+
mask_t = ~((rgbmr - 0.5).abs() < 2e-3).all(dim=0) # [H,W], bool
|
| 147 |
+
# complexity check
|
| 148 |
+
is_complex = is_complex_rm(
|
| 149 |
+
roughness_t.squeeze(0), metallic_t.squeeze(0), mask_t,
|
| 150 |
+
thres_avg=0.5, thres_r=0.05, thres_m=0.05, erode_px=5
|
| 151 |
+
)
|
| 152 |
+
if is_complex:
|
| 153 |
+
ok = True
|
| 154 |
+
break
|
| 155 |
+
if not ok:
|
| 156 |
+
continue
|
| 157 |
+
complex_ids.append(obj_id)
|
| 158 |
+
tqdm.write(f'Complex object: {obj_id}')
|
| 159 |
+
|
| 160 |
+
# sort
|
| 161 |
+
complex_ids = sorted(complex_ids)
|
| 162 |
+
|
| 163 |
+
# export complex ids
|
| 164 |
+
with open('complex_object_ids.json', 'w') as f:
|
| 165 |
+
json.dump(complex_ids, f, indent=2)
|
home/ubuntu/aaaaa/data/rgbmr/inference.py
ADDED
|
@@ -0,0 +1,1211 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Mesh-to-Textured-Mesh Inference Pipeline
|
| 3 |
+
Mesh (.glb/.obj) + text prompt -> shaded .glb
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import os
|
| 7 |
+
import tempfile
|
| 8 |
+
from pathlib import Path
|
| 9 |
+
from typing import Tuple, Dict, Any, Optional, List
|
| 10 |
+
import math
|
| 11 |
+
import json
|
| 12 |
+
import filelock
|
| 13 |
+
import hashlib
|
| 14 |
+
|
| 15 |
+
import numpy as np
|
| 16 |
+
import torch
|
| 17 |
+
import torch.nn as nn
|
| 18 |
+
import torch.nn.functional as F
|
| 19 |
+
from diffusers import FlowMatchEulerDiscreteScheduler, SD3Transformer2DModel, AutoencoderKL
|
| 20 |
+
from torchvision.transforms import ToPILImage
|
| 21 |
+
from PIL import Image
|
| 22 |
+
from omegaconf import OmegaConf
|
| 23 |
+
from peft import set_peft_model_state_dict
|
| 24 |
+
from safetensors.torch import load_file
|
| 25 |
+
|
| 26 |
+
from mcgen.utils.model_utils import load_offset_autoencoder, initialize_transformer_weights
|
| 27 |
+
from mcgen.mcdiff.attention_processor_lora import LoRAMessagePassingAttnProcessor
|
| 28 |
+
from mcgen.mcdiff.attention_utils import apply_custom_processors
|
| 29 |
+
from mcgen.utils.text_encoder_utils import load_text_ctx, encode_prompt
|
| 30 |
+
from mcgen.mcdiff.latents import generate_latents
|
| 31 |
+
from mcgen.utils.pipeline_texture import TexturePipeline, ModProcessConfig
|
| 32 |
+
from mcgen.utils.correspondence import build_corr_pairs, downscale_pairs_to_f2l, dilate_f2l, correspondence_psnr
|
| 33 |
+
from mcgen.utils.config import load_config
|
| 34 |
+
from tools.utils.mesh_utils import load_mesh, get_orthogonal_camera, NVDiffRastContextWrapper, render
|
| 35 |
+
from mcgen.utils.image_super_utils import imageSuperNet
|
| 36 |
+
from mcgen.utils.pipeline_utils import ViewProcessor
|
| 37 |
+
from mcgen.utils.uvwrap_utils import mesh_uv_wrap
|
| 38 |
+
from mcgen.utils.geometry_inpaint_utils import texture_inpaint
|
| 39 |
+
from DifferentiableRenderer.MeshRender import MeshRender
|
| 40 |
+
import trimesh
|
| 41 |
+
import copy
|
| 42 |
+
|
| 43 |
+
# =============================================================================
|
| 44 |
+
# Configuration
|
| 45 |
+
# =============================================================================
|
| 46 |
+
|
| 47 |
+
# Model paths
|
| 48 |
+
SD35_REPO = "stabilityai/stable-diffusion-3.5-medium"
|
| 49 |
+
TRANSFORMER_PARTIAL_WEIGHTS = "outputs/mcdiff_v1.9.5/checkpoint-40000/transformer/diffusion_pytorch_model.safetensors"
|
| 50 |
+
SD_VAE_PATH = "./vae_sd35"
|
| 51 |
+
MCVAE_CONFIG_PATH = "./configs/mcvae/config.json"
|
| 52 |
+
MCVAE_CKPT_PATH = "./outputs/mcvae_v1.8.1.pt"
|
| 53 |
+
MCVAE_OFFSET_MODE = True # If True, mcVAE encoder outputs offsets added to base mean/logvar. If False, directly predicts mean/logvar.
|
| 54 |
+
|
| 55 |
+
# Device & precision
|
| 56 |
+
DEVICE = "cuda"
|
| 57 |
+
DTYPE = torch.float16
|
| 58 |
+
|
| 59 |
+
# Image & view settings
|
| 60 |
+
HEIGHT = WIDTH = 512
|
| 61 |
+
TOKEN_HW = HEIGHT // 16
|
| 62 |
+
NUM_VIEWS = 6
|
| 63 |
+
ELEV_DEG = [0.0, 0.0, 0.0, 0.0, 89.99, -89.99]
|
| 64 |
+
AZIM_DEG = [0.0, 90.0, 180.0, 270.0, 0.0, 0.0]
|
| 65 |
+
# ELEV_DEG = [0.0, 0.0, 0.0, 0.0, 45, 45, 90.0, -90.0]
|
| 66 |
+
# AZIM_DEG = [0.0, 90.0, 180.0, 270.0, 45, 135, 0.0, 0.0]
|
| 67 |
+
|
| 68 |
+
# MaterialMVP azimuth convention (90 degree offset from mvadapter)
|
| 69 |
+
AZIM_DEG_MATERIALMVP = [angle + 90 for angle in AZIM_DEG]
|
| 70 |
+
|
| 71 |
+
# Inference defaults
|
| 72 |
+
GUIDANCE = 4.0
|
| 73 |
+
STEPS = 30
|
| 74 |
+
NEGATIVE_PROMPT = ""
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
# =============================================================================
|
| 78 |
+
# Utility Functions
|
| 79 |
+
# =============================================================================
|
| 80 |
+
|
| 81 |
+
def seed_everything(seed: int = 42):
|
| 82 |
+
"""Seed all random number generators for reproducibility."""
|
| 83 |
+
os.environ["PYTHONHASHSEED"] = str(seed)
|
| 84 |
+
np.random.seed(seed)
|
| 85 |
+
torch.manual_seed(seed)
|
| 86 |
+
torch.cuda.manual_seed_all(seed)
|
| 87 |
+
torch.backends.cudnn.deterministic = True
|
| 88 |
+
torch.backends.cudnn.benchmark = False
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
def _resize_to_pm1(x: torch.Tensor, *, height: int, width: int) -> torch.Tensor:
|
| 92 |
+
"""Resize to target resolution and normalize to [-1, 1]."""
|
| 93 |
+
x = F.interpolate(x, size=(height, width), mode="bilinear", align_corners=False)
|
| 94 |
+
return x * 2.0 - 1.0
|
| 95 |
+
|
| 96 |
+
|
| 97 |
+
def _expand_embeddings_for_views(pe: torch.Tensor, pooled: torch.Tensor, *, num_views: int) -> Tuple[torch.Tensor, torch.Tensor]:
|
| 98 |
+
"""Expand text embeddings from [1, seq, dim] to [num_views, seq, dim]."""
|
| 99 |
+
pe = pe.expand(1, -1, -1).unsqueeze(1).expand(-1, num_views, -1, -1).reshape(-1, pe.shape[1], pe.shape[2])
|
| 100 |
+
pooled = pooled.expand(1, -1).unsqueeze(1).expand(-1, num_views, -1).reshape(-1, pooled.shape[-1])
|
| 101 |
+
return pe, pooled
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
@torch.no_grad()
|
| 105 |
+
def _save_strip(tensor_bchw: torch.Tensor, path: str, nrow: int = 1) -> str:
|
| 106 |
+
"""Save grid of images from batched tensors in [-1, 1] range.
|
| 107 |
+
|
| 108 |
+
Args:
|
| 109 |
+
tensor_bchw: Batch of images in BCHW format, range [-1, 1]
|
| 110 |
+
path: Output path for saved image
|
| 111 |
+
nrow: Number of rows in grid. If 1, creates horizontal strip.
|
| 112 |
+
"""
|
| 113 |
+
|
| 114 |
+
to_pil = ToPILImage()
|
| 115 |
+
b, c, h, w = tensor_bchw.shape
|
| 116 |
+
|
| 117 |
+
nrow = max(1, nrow)
|
| 118 |
+
ncol = math.ceil(b / nrow)
|
| 119 |
+
|
| 120 |
+
canvas = Image.new('RGB', (ncol * w, nrow * h))
|
| 121 |
+
for i in range(b):
|
| 122 |
+
row = i // ncol
|
| 123 |
+
col = i % ncol
|
| 124 |
+
img = to_pil((tensor_bchw[i].clamp(-1, 1) * 0.5 + 0.5))
|
| 125 |
+
canvas.paste(img, (col * w, row * h))
|
| 126 |
+
|
| 127 |
+
os.makedirs(os.path.dirname(path), exist_ok=True)
|
| 128 |
+
canvas.save(path)
|
| 129 |
+
return path
|
| 130 |
+
|
| 131 |
+
|
| 132 |
+
def _update_cpsnr_json(json_path: str, gid: str, metrics: Dict[str, float]) -> None:
|
| 133 |
+
"""Thread-safe update of cpsnr.json file using file locking.
|
| 134 |
+
|
| 135 |
+
Args:
|
| 136 |
+
json_path: Path to cpsnr.json file
|
| 137 |
+
gid: Geometry ID (mesh filename stem)
|
| 138 |
+
metrics: Dictionary containing 'albedo', 'roughness', 'metallic', 'total' keys
|
| 139 |
+
"""
|
| 140 |
+
lock_path = json_path + ".lock"
|
| 141 |
+
lock = filelock.FileLock(lock_path, timeout=60)
|
| 142 |
+
|
| 143 |
+
try:
|
| 144 |
+
with lock:
|
| 145 |
+
# Read existing data
|
| 146 |
+
if os.path.exists(json_path):
|
| 147 |
+
with open(json_path, "r") as f:
|
| 148 |
+
data = json.load(f)
|
| 149 |
+
else:
|
| 150 |
+
data = {}
|
| 151 |
+
|
| 152 |
+
# Update with new metrics
|
| 153 |
+
data[gid] = metrics
|
| 154 |
+
|
| 155 |
+
# Write back atomically using temp file + rename
|
| 156 |
+
temp_path = json_path + ".tmp"
|
| 157 |
+
with open(temp_path, "w") as f:
|
| 158 |
+
json.dump(data, f, indent=2)
|
| 159 |
+
os.replace(temp_path, json_path)
|
| 160 |
+
finally:
|
| 161 |
+
# Clean up lock file if it exists and we can remove it
|
| 162 |
+
try:
|
| 163 |
+
if os.path.exists(lock_path):
|
| 164 |
+
os.remove(lock_path)
|
| 165 |
+
except OSError:
|
| 166 |
+
pass
|
| 167 |
+
|
| 168 |
+
|
| 169 |
+
def unproject_with_materialmvp(
|
| 170 |
+
mesh_path: str,
|
| 171 |
+
output_dir: str,
|
| 172 |
+
gid: str,
|
| 173 |
+
albedo_images: List[Image.Image],
|
| 174 |
+
mr_images: List[Image.Image],
|
| 175 |
+
*,
|
| 176 |
+
render_size: int = 512,
|
| 177 |
+
texture_size: int = 4096,
|
| 178 |
+
realesrgan_ckpt: str,
|
| 179 |
+
bake_exp: int = 2,
|
| 180 |
+
selected_view_weights: Optional[List[float]] = None,
|
| 181 |
+
) -> str:
|
| 182 |
+
"""Alternative unprojection backend using MeshRenderer and ViewProcessor.
|
| 183 |
+
|
| 184 |
+
This method uses the MaterialMVP approach with back-projection and baking,
|
| 185 |
+
using azimuth angles with a 90-degree offset from the mvadapter convention.
|
| 186 |
+
|
| 187 |
+
Args:
|
| 188 |
+
mesh_path: Path to input mesh
|
| 189 |
+
output_dir: Output directory
|
| 190 |
+
gid: Geometry ID (mesh filename stem)
|
| 191 |
+
albedo_images: List of albedo views (6 images)
|
| 192 |
+
mr_images: List of metallic-roughness views (6 images, ORM format)
|
| 193 |
+
render_size: Rendering resolution (default: 512)
|
| 194 |
+
texture_size: UV texture resolution (default: 4096)
|
| 195 |
+
realesrgan_ckpt: Path to RealESRGAN checkpoint
|
| 196 |
+
bake_exp: Baking exponent for view weighting
|
| 197 |
+
selected_view_weights: Optional list of per-view weights
|
| 198 |
+
|
| 199 |
+
Returns:
|
| 200 |
+
Path to saved GLB file
|
| 201 |
+
"""
|
| 202 |
+
# Initialize renderer
|
| 203 |
+
render_obj = MeshRender(
|
| 204 |
+
default_resolution=render_size,
|
| 205 |
+
texture_size=texture_size,
|
| 206 |
+
bake_mode="back_sample",
|
| 207 |
+
raster_mode="cr",
|
| 208 |
+
)
|
| 209 |
+
|
| 210 |
+
# Load and prepare mesh
|
| 211 |
+
mesh = trimesh.load(mesh_path)
|
| 212 |
+
mesh = mesh_uv_wrap(mesh)
|
| 213 |
+
render_obj.load_mesh(mesh=mesh)
|
| 214 |
+
|
| 215 |
+
# Initialize processors
|
| 216 |
+
view_processor = ViewProcessor(bake_exp, render_obj)
|
| 217 |
+
super_model = imageSuperNet(realesrgan_ckpt)
|
| 218 |
+
|
| 219 |
+
# Use MaterialMVP azimuth convention (90-degree offset)
|
| 220 |
+
selected_camera_azims = AZIM_DEG_MATERIALMVP
|
| 221 |
+
selected_camera_elevs = ELEV_DEG
|
| 222 |
+
|
| 223 |
+
if selected_view_weights is None:
|
| 224 |
+
selected_view_weights = [1, 1, 1, 1, 0.5, 0.5]
|
| 225 |
+
|
| 226 |
+
# Prepare images dictionary
|
| 227 |
+
multiviews_pbr = {
|
| 228 |
+
"albedo": albedo_images,
|
| 229 |
+
"mr": mr_images,
|
| 230 |
+
}
|
| 231 |
+
|
| 232 |
+
# Enhance images
|
| 233 |
+
enhance_images = {
|
| 234 |
+
"albedo": copy.deepcopy(multiviews_pbr["albedo"]),
|
| 235 |
+
"mr": copy.deepcopy(multiviews_pbr["mr"]),
|
| 236 |
+
}
|
| 237 |
+
|
| 238 |
+
for i in range(len(enhance_images["albedo"])):
|
| 239 |
+
enhance_images["albedo"][i] = super_model(enhance_images["albedo"][i])
|
| 240 |
+
enhance_images["mr"][i] = super_model(enhance_images["mr"][i])
|
| 241 |
+
|
| 242 |
+
# Resize to double render size for better quality
|
| 243 |
+
for i in range(len(enhance_images["albedo"])):
|
| 244 |
+
enhance_images["albedo"][i] = enhance_images["albedo"][i].resize(
|
| 245 |
+
(render_size * 2, render_size * 2), Image.LANCZOS
|
| 246 |
+
)
|
| 247 |
+
enhance_images["mr"][i] = enhance_images["mr"][i].resize(
|
| 248 |
+
(render_size * 2, render_size * 2), Image.LANCZOS
|
| 249 |
+
)
|
| 250 |
+
|
| 251 |
+
# Bake albedo texture
|
| 252 |
+
texture, mask = view_processor.bake_from_multiview(
|
| 253 |
+
enhance_images["albedo"],
|
| 254 |
+
selected_camera_elevs,
|
| 255 |
+
selected_camera_azims,
|
| 256 |
+
selected_view_weights
|
| 257 |
+
)
|
| 258 |
+
|
| 259 |
+
# Bake metallic-roughness texture
|
| 260 |
+
texture_mr, mask_mr = view_processor.bake_from_multiview(
|
| 261 |
+
enhance_images["mr"],
|
| 262 |
+
selected_camera_elevs,
|
| 263 |
+
selected_camera_azims,
|
| 264 |
+
selected_view_weights
|
| 265 |
+
)
|
| 266 |
+
|
| 267 |
+
# Convert baked masks to boolean tensors
|
| 268 |
+
mask_bool = (mask.squeeze(-1) > 0.5).to(torch.bool)
|
| 269 |
+
mask_mr_bool = (mask_mr.squeeze(-1) > 0.5).to(torch.bool)
|
| 270 |
+
|
| 271 |
+
# Apply geometry-aware inpainting for albedo texture
|
| 272 |
+
texture_inpainted = texture_inpaint(
|
| 273 |
+
texture,
|
| 274 |
+
mask_bool,
|
| 275 |
+
render_obj,
|
| 276 |
+
uv_mask_erode_iters=10,
|
| 277 |
+
baked_mask_erode_iters=2,
|
| 278 |
+
vertex_merge_tolerance=1e-5,
|
| 279 |
+
vertex_color_K=11,
|
| 280 |
+
)
|
| 281 |
+
render_obj.set_texture(texture_inpainted, force_set=True)
|
| 282 |
+
|
| 283 |
+
# Apply geometry-aware inpainting for metallic-roughness texture
|
| 284 |
+
texture_mr_inpainted = texture_inpaint(
|
| 285 |
+
texture_mr,
|
| 286 |
+
mask_mr_bool,
|
| 287 |
+
render_obj,
|
| 288 |
+
uv_mask_erode_iters=10,
|
| 289 |
+
baked_mask_erode_iters=2,
|
| 290 |
+
vertex_merge_tolerance=1e-5,
|
| 291 |
+
vertex_color_K=11,
|
| 292 |
+
)
|
| 293 |
+
render_obj.set_texture_mr(texture_mr_inpainted)
|
| 294 |
+
|
| 295 |
+
# Save mesh
|
| 296 |
+
os.makedirs(output_dir, exist_ok=True)
|
| 297 |
+
output_path = os.path.join(output_dir, f"{gid}.glb")
|
| 298 |
+
render_obj.save_mesh(output_path, downsample=False)
|
| 299 |
+
|
| 300 |
+
return output_path
|
| 301 |
+
|
| 302 |
+
# =============================================================================
|
| 303 |
+
# Rendering & Preprocessing
|
| 304 |
+
# =============================================================================
|
| 305 |
+
|
| 306 |
+
|
| 307 |
+
@torch.no_grad()
|
| 308 |
+
def render_views(
|
| 309 |
+
mesh_path: str,
|
| 310 |
+
*,
|
| 311 |
+
num_views: int,
|
| 312 |
+
height: int,
|
| 313 |
+
width: int,
|
| 314 |
+
device: str = DEVICE,
|
| 315 |
+
) -> Dict[str, Any]:
|
| 316 |
+
"""Render multi-view geometry attributes from mesh."""
|
| 317 |
+
ctx = NVDiffRastContextWrapper(device=device, context_type="cuda")
|
| 318 |
+
mesh = load_mesh(str(mesh_path), rescale=True, move_to_center=True, flip_uv=True, device=device)
|
| 319 |
+
if len(ELEV_DEG) != num_views or len(AZIM_DEG) != num_views:
|
| 320 |
+
raise ValueError("ELEV_DEG and AZIM_DEG presets must match num_views.")
|
| 321 |
+
|
| 322 |
+
cams = get_orthogonal_camera(
|
| 323 |
+
elevation_deg=ELEV_DEG,
|
| 324 |
+
azimuth_deg=AZIM_DEG,
|
| 325 |
+
distance=[1.0] * num_views,
|
| 326 |
+
left=-0.55,
|
| 327 |
+
right=0.55,
|
| 328 |
+
bottom=-0.55,
|
| 329 |
+
top=0.55,
|
| 330 |
+
device=device, dtype=torch.float32,
|
| 331 |
+
)
|
| 332 |
+
|
| 333 |
+
# Build 5-channel texture override: [rgb, roughness(0), roughness, metallic]
|
| 334 |
+
tex_ovr = torch.cat([mesh.texture, torch.zeros_like(mesh.roughness), mesh.roughness, mesh.metallic], dim=-1)
|
| 335 |
+
|
| 336 |
+
out = render(ctx, mesh, cams, height=height, width=width, render_attr=True, texture_override=tex_ovr)
|
| 337 |
+
|
| 338 |
+
# Extract and normalize geometry attributes
|
| 339 |
+
attr = out.attr
|
| 340 |
+
rgb = attr[..., :3].contiguous()
|
| 341 |
+
orm = attr[..., 3:6].contiguous()
|
| 342 |
+
orm[..., 0] = (orm[..., 0] < 0.25).float() # Binary occupancy mask
|
| 343 |
+
|
| 344 |
+
return {
|
| 345 |
+
"albedo": rgb,
|
| 346 |
+
"orm": orm,
|
| 347 |
+
"pos": out.pos + 0.5, # [-0.5, 0.5] -> [0, 1]
|
| 348 |
+
"normal": (out.normal + 1.0) * 0.5, # [-1, 1] -> [0, 1]
|
| 349 |
+
"depth": out.raw_depth,
|
| 350 |
+
"c2w": cams.c2w,
|
| 351 |
+
"scale": torch.tensor(1.1, device=device),
|
| 352 |
+
}
|
| 353 |
+
|
| 354 |
+
|
| 355 |
+
def preprocess_geometry(
|
| 356 |
+
rend: Dict[str, Any],
|
| 357 |
+
*,
|
| 358 |
+
device: str,
|
| 359 |
+
dtype: torch.dtype,
|
| 360 |
+
height: int,
|
| 361 |
+
width: int,
|
| 362 |
+
token_hw: int,
|
| 363 |
+
num_views: int,
|
| 364 |
+
use_caa: bool,
|
| 365 |
+
corr_dilate_iterations: int = 1,
|
| 366 |
+
use_global_pos: bool = False,
|
| 367 |
+
) -> Dict[str, torch.Tensor]:
|
| 368 |
+
"""Preprocess rendered geometry into model-ready format."""
|
| 369 |
+
# Convert to channel-first
|
| 370 |
+
albedo = rend["albedo"].permute(0, 3, 1, 2).contiguous()
|
| 371 |
+
orm = rend["orm"].permute(0, 3, 1, 2).contiguous()
|
| 372 |
+
pos = rend["pos"].permute(0, 3, 1, 2).contiguous()
|
| 373 |
+
normal = rend["normal"].permute(0, 3, 1, 2).contiguous()
|
| 374 |
+
depth = rend["depth"]
|
| 375 |
+
c2w = rend["c2w"]
|
| 376 |
+
scale = rend["scale"]
|
| 377 |
+
|
| 378 |
+
V, _, H_raw, W_raw = albedo.shape
|
| 379 |
+
assert V == num_views, f"Expected {num_views} views, got {V}"
|
| 380 |
+
|
| 381 |
+
# Normal in [-1, 1] for correspondence computation
|
| 382 |
+
normal_fullres = normal * 2.0 - 1.0
|
| 383 |
+
|
| 384 |
+
# Mask from occupancy channel
|
| 385 |
+
mask = orm[:, 0, :, :] > 0.5
|
| 386 |
+
depth = depth.clone()
|
| 387 |
+
depth[~mask] = float("nan")
|
| 388 |
+
|
| 389 |
+
# Position tokens via weighted averaging at token grid
|
| 390 |
+
pos_raw = pos - 0.5 # Convert to [-0.5, 0.5]
|
| 391 |
+
df = H_raw // token_hw
|
| 392 |
+
mask_f = mask.float().unsqueeze(1)
|
| 393 |
+
pos_sum = F.avg_pool2d(pos_raw * mask_f, kernel_size=df, stride=df, divisor_override=1)
|
| 394 |
+
cnt = F.avg_pool2d(mask_f, kernel_size=df, stride=df, divisor_override=1)
|
| 395 |
+
pos_token = (pos_sum / cnt.clamp_min(1.0)).to(device=device, dtype=dtype)
|
| 396 |
+
|
| 397 |
+
# Resize and normalize to [-1, 1]
|
| 398 |
+
albedo_r = _resize_to_pm1(albedo, height=height, width=width)
|
| 399 |
+
orm_r = _resize_to_pm1(orm, height=height, width=width)
|
| 400 |
+
pos_r = _resize_to_pm1(pos, height=height, width=width)
|
| 401 |
+
normal_r = _resize_to_pm1(normal, height=height, width=width)
|
| 402 |
+
|
| 403 |
+
# Condition values: position + normal (6 channels)
|
| 404 |
+
cond_values = torch.cat([pos_r, normal_r], dim=1)
|
| 405 |
+
|
| 406 |
+
# World-to-camera transformation
|
| 407 |
+
w2c = torch.linalg.inv(c2w).to(device=device, dtype=dtype)
|
| 408 |
+
|
| 409 |
+
corr_f2l = None
|
| 410 |
+
corr_f2l_highres = None
|
| 411 |
+
pos_delta = None
|
| 412 |
+
|
| 413 |
+
# Build correspondence pairs
|
| 414 |
+
corr_pairs = build_corr_pairs(
|
| 415 |
+
c2w, scale, depth, nor_w=normal_fullres.permute(0, 2, 3, 1),
|
| 416 |
+
depth_tol=0.01, angle_tol=10, angle_cam_tol=80,
|
| 417 |
+
)
|
| 418 |
+
|
| 419 |
+
# High resolution correspondence for c-PSNR evaluation
|
| 420 |
+
corr_high = downscale_pairs_to_f2l(corr_pairs, out_hw=height, device=device)
|
| 421 |
+
corr_f2l_highres = corr_high.unsqueeze(0) # (1, V*H*W, K) for later use
|
| 422 |
+
|
| 423 |
+
# Downscale to token resolution
|
| 424 |
+
corr_low = downscale_pairs_to_f2l(corr_pairs, out_hw=token_hw, device=device)
|
| 425 |
+
for _ in range(corr_dilate_iterations):
|
| 426 |
+
corr_low = dilate_f2l(corr_low, V=num_views, out_hw=token_hw)
|
| 427 |
+
|
| 428 |
+
# Convert to global indices (single batch)
|
| 429 |
+
corr_f2l = corr_low.unsqueeze(0) # (1, Lq, K)
|
| 430 |
+
B, Lq, K = corr_f2l.shape
|
| 431 |
+
X = Lq
|
| 432 |
+
base = torch.arange(B, device=corr_f2l.device).view(B, 1, 1) * X
|
| 433 |
+
corr_f2l = torch.where(corr_f2l >= 0, corr_f2l + base, corr_f2l)
|
| 434 |
+
corr_f2l = corr_f2l.reshape(B * Lq, K) # (M, K) where M = num_views * token_hw * token_hw
|
| 435 |
+
|
| 436 |
+
# Compute position deltas
|
| 437 |
+
M = num_views * token_hw * token_hw
|
| 438 |
+
|
| 439 |
+
# pos_token: (V, 3, token_hw, token_hw) -> (M, 3)
|
| 440 |
+
pos_w_flat = pos_token.view(num_views, 3, -1).permute(0, 2, 1).reshape(M, 3) # (M, 3)
|
| 441 |
+
|
| 442 |
+
# Get camera transforms for each query token
|
| 443 |
+
batch_ids = torch.arange(num_views, device=device).repeat_interleave(token_hw * token_hw) # (M,)
|
| 444 |
+
|
| 445 |
+
# Use identity matrix for global frame, or w2c for local query frame
|
| 446 |
+
if use_global_pos:
|
| 447 |
+
# Global frame: use identity matrix (no transformation)
|
| 448 |
+
Tq = torch.eye(4, device=device, dtype=w2c.dtype).unsqueeze(0).expand(M, -1, -1) # (M, 4, 4)
|
| 449 |
+
else:
|
| 450 |
+
# Local query frame: use w2c transformation
|
| 451 |
+
Tq = w2c[batch_ids] # (M, 4, 4)
|
| 452 |
+
|
| 453 |
+
# Transform query positions to camera space
|
| 454 |
+
ones_M = torch.ones(M, 1, device=device, dtype=pos_w_flat.dtype)
|
| 455 |
+
pq_h = torch.cat([pos_w_flat, ones_M], dim=-1).unsqueeze(-1) # (M, 4, 1)
|
| 456 |
+
pq_cam = (Tq @ pq_h).squeeze(-1)[..., :3] # (M, 3)
|
| 457 |
+
|
| 458 |
+
# Gather key positions and transform to camera space
|
| 459 |
+
gather_idx = corr_f2l.clamp(min=0, max=M - 1).reshape(-1) # (M*K,)
|
| 460 |
+
pk_world = pos_w_flat.index_select(0, gather_idx).view(M, K, 3) # (M, K, 3)
|
| 461 |
+
ones_MK = torch.ones(M, K, 1, device=device, dtype=pk_world.dtype)
|
| 462 |
+
pk_h = torch.cat([pk_world, ones_MK], dim=-1).unsqueeze(-1) # (M, K, 4, 1)
|
| 463 |
+
pk_cam = (Tq[:, None, :, :] @ pk_h).squeeze(-1)[..., :3] # (M, K, 3)
|
| 464 |
+
|
| 465 |
+
# Compute delta
|
| 466 |
+
pos_delta = pk_cam - pq_cam[:, None, :] # (M, K, 3)
|
| 467 |
+
|
| 468 |
+
return {
|
| 469 |
+
"cond_values": cond_values,
|
| 470 |
+
"pos_token": pos_token,
|
| 471 |
+
"w2c": w2c,
|
| 472 |
+
"corr_f2l": corr_f2l,
|
| 473 |
+
"corr_f2l_highres": corr_f2l_highres,
|
| 474 |
+
"pos_delta": pos_delta,
|
| 475 |
+
"decoded_albedo": albedo_r,
|
| 476 |
+
"decoded_orm": orm_r,
|
| 477 |
+
}
|
| 478 |
+
|
| 479 |
+
|
| 480 |
+
|
| 481 |
+
# =============================================================================
|
| 482 |
+
# Model Setup
|
| 483 |
+
# =============================================================================
|
| 484 |
+
|
| 485 |
+
|
| 486 |
+
|
| 487 |
+
def _apply_lora_processors(
|
| 488 |
+
transformer: SD3Transformer2DModel,
|
| 489 |
+
model_cfg,
|
| 490 |
+
*,
|
| 491 |
+
use_caa: bool,
|
| 492 |
+
use_rope: bool,
|
| 493 |
+
use_global_token: bool,
|
| 494 |
+
num_views: int,
|
| 495 |
+
num_domains: int,
|
| 496 |
+
) -> None:
|
| 497 |
+
"""Attach LoRA message-passing processors mirroring training setup."""
|
| 498 |
+
lcfg = getattr(model_cfg, "attn_lora", None)
|
| 499 |
+
if not lcfg or not getattr(lcfg, "enabled", False):
|
| 500 |
+
return
|
| 501 |
+
|
| 502 |
+
select = {"joint": True, "self": bool(getattr(lcfg, "apply_to_self", False))}
|
| 503 |
+
limits = {}
|
| 504 |
+
if getattr(lcfg, "limit_joint", None) is not None:
|
| 505 |
+
limits["joint"] = lcfg.limit_joint
|
| 506 |
+
if getattr(lcfg, "limit_self", None) is not None:
|
| 507 |
+
limits["self"] = lcfg.limit_self
|
| 508 |
+
|
| 509 |
+
def processor_factory(_name, _mod, query_dim, inner_dim, out_dim, num_heads, kind="joint"):
|
| 510 |
+
return LoRAMessagePassingAttnProcessor(
|
| 511 |
+
r_q=int(getattr(lcfg, "r_q", 8)),
|
| 512 |
+
r_k=int(getattr(lcfg, "r_k", 8)),
|
| 513 |
+
r_v=int(getattr(lcfg, "r_v", 8)),
|
| 514 |
+
alpha_q=float(getattr(lcfg, "alpha_q", getattr(lcfg, "r_q", 8))),
|
| 515 |
+
alpha_k=float(getattr(lcfg, "alpha_k", getattr(lcfg, "r_k", 8))),
|
| 516 |
+
alpha_v=float(getattr(lcfg, "alpha_v", getattr(lcfg, "r_v", 8))),
|
| 517 |
+
query_dim=query_dim,
|
| 518 |
+
inner_dim=inner_dim,
|
| 519 |
+
out_dim=out_dim,
|
| 520 |
+
num_heads=num_heads,
|
| 521 |
+
num_views=num_views,
|
| 522 |
+
num_domains=num_domains,
|
| 523 |
+
use_caa=use_caa,
|
| 524 |
+
use_rope=use_rope,
|
| 525 |
+
use_global_token=use_global_token,
|
| 526 |
+
kind=kind,
|
| 527 |
+
)
|
| 528 |
+
|
| 529 |
+
apply_custom_processors(
|
| 530 |
+
transformer,
|
| 531 |
+
model_cfg,
|
| 532 |
+
processor_factory,
|
| 533 |
+
select=select,
|
| 534 |
+
limits=limits if limits else None,
|
| 535 |
+
as_factory=True,
|
| 536 |
+
)
|
| 537 |
+
|
| 538 |
+
|
| 539 |
+
|
| 540 |
+
def _extend_pos_embed_in(transformer: SD3Transformer2DModel, extra_channels: int) -> None:
|
| 541 |
+
"""Extend transformer's pos_embed input channels by extra_channels."""
|
| 542 |
+
conv = transformer.pos_embed.proj
|
| 543 |
+
new_conv = nn.Conv2d(
|
| 544 |
+
in_channels=conv.in_channels + extra_channels,
|
| 545 |
+
out_channels=conv.out_channels,
|
| 546 |
+
kernel_size=conv.kernel_size, stride=conv.stride, padding=conv.padding,
|
| 547 |
+
dilation=conv.dilation, groups=conv.groups, padding_mode=conv.padding_mode,
|
| 548 |
+
device=conv.weight.device, dtype=conv.weight.dtype,
|
| 549 |
+
)
|
| 550 |
+
with torch.no_grad():
|
| 551 |
+
new_conv.weight.zero_()
|
| 552 |
+
new_conv.weight[:, :conv.in_channels].copy_(conv.weight)
|
| 553 |
+
new_conv.bias.copy_(conv.bias)
|
| 554 |
+
transformer.pos_embed.proj = new_conv
|
| 555 |
+
|
| 556 |
+
|
| 557 |
+
def _load_conditioning_conv(transformer: SD3Transformer2DModel, cond_path: Path) -> None:
|
| 558 |
+
if not cond_path.exists():
|
| 559 |
+
return
|
| 560 |
+
state = load_file(str(cond_path))
|
| 561 |
+
weight_key = "pos_embed.proj.weight"
|
| 562 |
+
bias_key = "pos_embed.proj.bias"
|
| 563 |
+
pe_proj = transformer.pos_embed.proj
|
| 564 |
+
if weight_key in state:
|
| 565 |
+
pe_proj.weight.data.copy_(state[weight_key].to(pe_proj.weight.device, dtype=pe_proj.weight.dtype))
|
| 566 |
+
if bias_key in state:
|
| 567 |
+
pe_proj.bias.data.copy_(state[bias_key].to(pe_proj.bias.device, dtype=pe_proj.bias.dtype))
|
| 568 |
+
|
| 569 |
+
|
| 570 |
+
def load_transformer_weights(
|
| 571 |
+
transformer: SD3Transformer2DModel,
|
| 572 |
+
weights_path: Optional[str],
|
| 573 |
+
*,
|
| 574 |
+
condition_channels: int,
|
| 575 |
+
) -> None:
|
| 576 |
+
if not weights_path:
|
| 577 |
+
return
|
| 578 |
+
path = Path(weights_path)
|
| 579 |
+
|
| 580 |
+
if path.is_dir():
|
| 581 |
+
full_dir = path / "transformer"
|
| 582 |
+
lora_path = path / "pytorch_lora_weights.safetensors"
|
| 583 |
+
cond_path = path / "pytorch_cond_conv_weights.safetensors"
|
| 584 |
+
|
| 585 |
+
if full_dir.is_dir():
|
| 586 |
+
initialize_transformer_weights(transformer, str(full_dir))
|
| 587 |
+
elif lora_path.exists():
|
| 588 |
+
lora_state = load_file(str(lora_path))
|
| 589 |
+
set_peft_model_state_dict(transformer, lora_state, adapter_name="default")
|
| 590 |
+
else:
|
| 591 |
+
# Fallback: attempt direct load from directory
|
| 592 |
+
initialize_transformer_weights(transformer, str(path))
|
| 593 |
+
|
| 594 |
+
if condition_channels > 0:
|
| 595 |
+
_load_conditioning_conv(transformer, cond_path)
|
| 596 |
+
return
|
| 597 |
+
|
| 598 |
+
initialize_transformer_weights(transformer, str(path))
|
| 599 |
+
|
| 600 |
+
|
| 601 |
+
def setup_transformer(
|
| 602 |
+
*,
|
| 603 |
+
base_model: str,
|
| 604 |
+
model_cfg,
|
| 605 |
+
device: str,
|
| 606 |
+
dtype: torch.dtype,
|
| 607 |
+
condition_channels: int,
|
| 608 |
+
use_caa: bool,
|
| 609 |
+
use_rope: bool,
|
| 610 |
+
use_global_token: bool,
|
| 611 |
+
use_dual_branch: bool,
|
| 612 |
+
num_views: int,
|
| 613 |
+
weights_path: Optional[str],
|
| 614 |
+
) -> Tuple[SD3Transformer2DModel, Any]:
|
| 615 |
+
"""Setup transformer with structural modifications and load weights."""
|
| 616 |
+
transformer = SD3Transformer2DModel.from_pretrained(base_model, subfolder="transformer")
|
| 617 |
+
|
| 618 |
+
num_domains = 2 if use_dual_branch else 1
|
| 619 |
+
print(f'use_dual_branch: {use_dual_branch}, use_caa: {use_caa}, use_rope: {use_rope}, use_global_token: {use_global_token}, num_views: {num_views}, num_domains: {num_domains}')
|
| 620 |
+
|
| 621 |
+
_apply_lora_processors(
|
| 622 |
+
transformer,
|
| 623 |
+
model_cfg,
|
| 624 |
+
use_caa=use_caa,
|
| 625 |
+
use_rope=use_rope,
|
| 626 |
+
use_global_token=use_global_token,
|
| 627 |
+
num_views=num_views,
|
| 628 |
+
num_domains=num_domains,
|
| 629 |
+
)
|
| 630 |
+
|
| 631 |
+
if condition_channels > 0:
|
| 632 |
+
_extend_pos_embed_in(transformer, condition_channels)
|
| 633 |
+
|
| 634 |
+
load_transformer_weights(transformer, weights_path, condition_channels=condition_channels)
|
| 635 |
+
|
| 636 |
+
transformer = transformer.to(device=device, dtype=dtype).eval().requires_grad_(False)
|
| 637 |
+
|
| 638 |
+
scheduler = FlowMatchEulerDiscreteScheduler.from_pretrained(base_model, subfolder="scheduler")
|
| 639 |
+
|
| 640 |
+
return transformer, scheduler
|
| 641 |
+
|
| 642 |
+
|
| 643 |
+
def load_vae_models(
|
| 644 |
+
*,
|
| 645 |
+
sd_vae_path: str,
|
| 646 |
+
mcvae_config_path: str,
|
| 647 |
+
mcvae_ckpt_path: Optional[str],
|
| 648 |
+
mcvae_offset_mode: bool,
|
| 649 |
+
device: str,
|
| 650 |
+
dtype: torch.dtype,
|
| 651 |
+
use_dual_branch: bool,
|
| 652 |
+
) -> Tuple[Any, Optional[Any]]:
|
| 653 |
+
"""Load required autoencoders depending on branch configuration."""
|
| 654 |
+
if use_dual_branch:
|
| 655 |
+
vae = AutoencoderKL.from_pretrained(sd_vae_path)
|
| 656 |
+
vae = vae.to(device=device, dtype=dtype).eval().requires_grad_(False)
|
| 657 |
+
return vae, None
|
| 658 |
+
|
| 659 |
+
vae, mcvae = load_offset_autoencoder(
|
| 660 |
+
sd_vae_path=sd_vae_path,
|
| 661 |
+
mcvae_config_path=mcvae_config_path,
|
| 662 |
+
mcvae_ckpt_path=mcvae_ckpt_path,
|
| 663 |
+
offset_mode=mcvae_offset_mode,
|
| 664 |
+
)
|
| 665 |
+
vae = vae.to(device=device, dtype=dtype).eval().requires_grad_(False)
|
| 666 |
+
mcvae = mcvae.to(device=device, dtype=dtype).eval().requires_grad_(False)
|
| 667 |
+
return vae, mcvae
|
| 668 |
+
|
| 669 |
+
|
| 670 |
+
def encode_conditions(
|
| 671 |
+
vae: Any,
|
| 672 |
+
cond_values: torch.Tensor,
|
| 673 |
+
*,
|
| 674 |
+
height: int,
|
| 675 |
+
width: int,
|
| 676 |
+
out_dtype: torch.dtype,
|
| 677 |
+
) -> torch.Tensor:
|
| 678 |
+
"""Encode 6-channel condition values into latent space."""
|
| 679 |
+
cond_values = cond_values.to(device=vae.device, dtype=vae.dtype)
|
| 680 |
+
lat_chunks = []
|
| 681 |
+
for c in range(0, cond_values.shape[1], 3):
|
| 682 |
+
posterior = vae.encode(cond_values[:, c:c+3]).latent_dist
|
| 683 |
+
lat_chunks.append(posterior.mean)
|
| 684 |
+
|
| 685 |
+
cond_latents = torch.cat(lat_chunks, dim=1)
|
| 686 |
+
cond_latents = (cond_latents - vae.config.shift_factor) * vae.config.scaling_factor
|
| 687 |
+
Cc = cond_latents.shape[1]
|
| 688 |
+
cond_latents = cond_latents.reshape(-1, Cc, height // 8, width // 8)
|
| 689 |
+
return cond_latents.to(dtype=out_dtype)
|
| 690 |
+
|
| 691 |
+
|
| 692 |
+
def encode_text_prompts(
|
| 693 |
+
prompt: str,
|
| 694 |
+
*,
|
| 695 |
+
device: str,
|
| 696 |
+
dtype: torch.dtype,
|
| 697 |
+
base_model: str,
|
| 698 |
+
num_views: int,
|
| 699 |
+
negative_prompt: str,
|
| 700 |
+
) -> Dict[str, torch.Tensor]:
|
| 701 |
+
"""Encode positive and negative text prompts, expanded for all views."""
|
| 702 |
+
text_ctx = load_text_ctx(device=device, dtype=dtype, sd_model_name=base_model)
|
| 703 |
+
|
| 704 |
+
pe, pooled = encode_prompt(text_ctx["encoders"], text_ctx["tokenizers"], prompt, max_sequence_length=77)
|
| 705 |
+
npe, npooled = encode_prompt(text_ctx["encoders"], text_ctx["tokenizers"], negative_prompt, max_sequence_length=77)
|
| 706 |
+
|
| 707 |
+
# Clean up text context
|
| 708 |
+
del text_ctx
|
| 709 |
+
if torch.cuda.is_available():
|
| 710 |
+
torch.cuda.empty_cache()
|
| 711 |
+
|
| 712 |
+
# Move to device and expand for all views
|
| 713 |
+
pe, pooled = pe.to(device=device, dtype=dtype), pooled.to(device=device, dtype=dtype)
|
| 714 |
+
npe, npooled = npe.to(device=device, dtype=dtype), npooled.to(device=device, dtype=dtype)
|
| 715 |
+
|
| 716 |
+
pe, pooled = _expand_embeddings_for_views(pe, pooled, num_views=num_views)
|
| 717 |
+
npe, npooled = _expand_embeddings_for_views(npe, npooled, num_views=num_views)
|
| 718 |
+
|
| 719 |
+
return {
|
| 720 |
+
"prompt_embeds": pe,
|
| 721 |
+
"pooled_prompt_embeds": pooled,
|
| 722 |
+
"negative_prompt_embeds": npe,
|
| 723 |
+
"negative_pooled_prompt_embeds": npooled,
|
| 724 |
+
}
|
| 725 |
+
|
| 726 |
+
|
| 727 |
+
# =============================================================================
|
| 728 |
+
# Main Inference Pipeline
|
| 729 |
+
# =============================================================================
|
| 730 |
+
|
| 731 |
+
@torch.no_grad()
|
| 732 |
+
def run(
|
| 733 |
+
mesh_path: str,
|
| 734 |
+
prompt: str,
|
| 735 |
+
output_dir: str,
|
| 736 |
+
*,
|
| 737 |
+
settings: Dict[str, Any],
|
| 738 |
+
keep_strips: bool = False,
|
| 739 |
+
seed: int = 42,
|
| 740 |
+
) -> str:
|
| 741 |
+
"""Mesh + prompt → textured GLB using configured MC-Diff pipeline."""
|
| 742 |
+
gid = Path(mesh_path).stem
|
| 743 |
+
# Use hashlib.md5 for deterministic seed generation (hash() is not deterministic across runs)
|
| 744 |
+
hash_input = f"{gid}_{seed}".encode('utf-8')
|
| 745 |
+
combined_seed = int(hashlib.md5(hash_input).hexdigest(), 16) % (2**32)
|
| 746 |
+
seed_everything(combined_seed)
|
| 747 |
+
|
| 748 |
+
device = settings["device"]
|
| 749 |
+
dtype = settings["dtype"]
|
| 750 |
+
height = settings["resolution"]
|
| 751 |
+
width = settings["resolution"]
|
| 752 |
+
num_views = settings["num_views"]
|
| 753 |
+
token_hw = height // 16
|
| 754 |
+
use_caa = settings["use_caa"]
|
| 755 |
+
use_rope = settings["use_rope"]
|
| 756 |
+
use_global_token = settings["use_global_token"]
|
| 757 |
+
use_global_pos = settings.get("use_global_pos", False)
|
| 758 |
+
use_dual_branch = settings["use_dual_branch"]
|
| 759 |
+
branch_factor = 2 if use_dual_branch else 1
|
| 760 |
+
|
| 761 |
+
rend = render_views(mesh_path, num_views=num_views, height=height, width=width, device=device)
|
| 762 |
+
preprocessed = preprocess_geometry(
|
| 763 |
+
rend,
|
| 764 |
+
device=device,
|
| 765 |
+
dtype=dtype,
|
| 766 |
+
height=height,
|
| 767 |
+
width=width,
|
| 768 |
+
token_hw=token_hw,
|
| 769 |
+
num_views=num_views,
|
| 770 |
+
use_caa=use_caa,
|
| 771 |
+
corr_dilate_iterations=settings["corr_dilate_iterations"],
|
| 772 |
+
use_global_pos=use_global_pos,
|
| 773 |
+
)
|
| 774 |
+
|
| 775 |
+
vae, mcvae = load_vae_models(
|
| 776 |
+
sd_vae_path=settings["sd_vae_path"],
|
| 777 |
+
mcvae_config_path=settings["mcvae_config_path"],
|
| 778 |
+
mcvae_ckpt_path=settings["mcvae_ckpt_path"],
|
| 779 |
+
mcvae_offset_mode=settings.get("mcvae_offset_mode", True),
|
| 780 |
+
device=device,
|
| 781 |
+
dtype=dtype,
|
| 782 |
+
use_dual_branch=use_dual_branch,
|
| 783 |
+
)
|
| 784 |
+
vae_shift, vae_scale = vae.config.shift_factor, vae.config.scaling_factor
|
| 785 |
+
|
| 786 |
+
cond_latents = None
|
| 787 |
+
condition_channels = 0
|
| 788 |
+
if settings.get("condition_channels_cfg", 0) > 0:
|
| 789 |
+
cond_latents = encode_conditions(
|
| 790 |
+
vae,
|
| 791 |
+
preprocessed["cond_values"],
|
| 792 |
+
height=height,
|
| 793 |
+
width=width,
|
| 794 |
+
out_dtype=dtype,
|
| 795 |
+
)
|
| 796 |
+
condition_channels = cond_latents.shape[1]
|
| 797 |
+
|
| 798 |
+
text_embeds = encode_text_prompts(
|
| 799 |
+
prompt,
|
| 800 |
+
device=device,
|
| 801 |
+
dtype=dtype,
|
| 802 |
+
base_model=settings["base_model"],
|
| 803 |
+
num_views=num_views,
|
| 804 |
+
negative_prompt=settings["negative_prompt"],
|
| 805 |
+
)
|
| 806 |
+
|
| 807 |
+
transformer, scheduler = setup_transformer(
|
| 808 |
+
base_model=settings["base_model"],
|
| 809 |
+
model_cfg=settings["model_cfg"],
|
| 810 |
+
device=device,
|
| 811 |
+
dtype=dtype,
|
| 812 |
+
condition_channels=condition_channels,
|
| 813 |
+
use_caa=use_caa,
|
| 814 |
+
use_rope=use_rope,
|
| 815 |
+
use_global_token=use_global_token,
|
| 816 |
+
use_dual_branch=use_dual_branch,
|
| 817 |
+
num_views=num_views,
|
| 818 |
+
weights_path=settings["weights_path"],
|
| 819 |
+
)
|
| 820 |
+
|
| 821 |
+
def _repeat_branch(tensor: Optional[torch.Tensor]) -> Optional[torch.Tensor]:
|
| 822 |
+
if tensor is None:
|
| 823 |
+
return None
|
| 824 |
+
first_dim = tensor.shape[0]
|
| 825 |
+
if first_dim == num_views:
|
| 826 |
+
tensor = tensor.reshape(1, num_views, *tensor.shape[1:])
|
| 827 |
+
elif first_dim == 1:
|
| 828 |
+
tensor = tensor.reshape(1, *tensor.shape[1:]).unsqueeze(1)
|
| 829 |
+
else:
|
| 830 |
+
return tensor
|
| 831 |
+
tensor = tensor.repeat(branch_factor, 1, *[1] * (tensor.dim() - 2))
|
| 832 |
+
return tensor.reshape(branch_factor * num_views, *tensor.shape[2:])
|
| 833 |
+
|
| 834 |
+
prompt_embeds = _repeat_branch(text_embeds["prompt_embeds"])
|
| 835 |
+
pooled_prompt_embeds = _repeat_branch(text_embeds["pooled_prompt_embeds"])
|
| 836 |
+
negative_prompt_embeds = _repeat_branch(text_embeds["negative_prompt_embeds"])
|
| 837 |
+
negative_pooled_prompt_embeds = _repeat_branch(text_embeds["negative_pooled_prompt_embeds"])
|
| 838 |
+
cond_latents_branch = _repeat_branch(cond_latents) if cond_latents is not None else None
|
| 839 |
+
|
| 840 |
+
# Prepare correspondence data for CAA
|
| 841 |
+
corr_lookups_branch = None
|
| 842 |
+
pos_delta_branch = None
|
| 843 |
+
if use_caa:
|
| 844 |
+
corr_f2l = preprocessed["corr_f2l"] # (M, K) where M = num_views * token_hw^2
|
| 845 |
+
pos_delta = preprocessed["pos_delta"] # (M, K, 3)
|
| 846 |
+
|
| 847 |
+
if corr_f2l is not None and pos_delta is not None:
|
| 848 |
+
# For dual branch, duplicate correspondence data
|
| 849 |
+
if use_dual_branch:
|
| 850 |
+
# corr_f2l needs to be adjusted for dual branch
|
| 851 |
+
# Each branch has its own set of indices
|
| 852 |
+
M, K = corr_f2l.shape
|
| 853 |
+
corr_branch_list = []
|
| 854 |
+
for b in range(branch_factor):
|
| 855 |
+
corr_b = corr_f2l + (b * M) # Offset indices for each branch
|
| 856 |
+
corr_branch_list.append(corr_b)
|
| 857 |
+
corr_lookups_branch = torch.cat(corr_branch_list, dim=0) # (branch_factor * M, K)
|
| 858 |
+
pos_delta_branch = pos_delta.repeat(branch_factor, 1, 1) # (branch_factor * M, K, 3)
|
| 859 |
+
else:
|
| 860 |
+
corr_lookups_branch = corr_f2l
|
| 861 |
+
pos_delta_branch = pos_delta
|
| 862 |
+
|
| 863 |
+
latents = generate_latents(
|
| 864 |
+
transformer=transformer,
|
| 865 |
+
noise_scheduler=scheduler,
|
| 866 |
+
prompt_embeds=prompt_embeds,
|
| 867 |
+
pooled_prompt_embeds=pooled_prompt_embeds,
|
| 868 |
+
negative_prompt_embeds=negative_prompt_embeds,
|
| 869 |
+
negative_pooled_prompt_embeds=negative_pooled_prompt_embeds,
|
| 870 |
+
height=height,
|
| 871 |
+
width=width,
|
| 872 |
+
num_inference_steps=settings["steps"],
|
| 873 |
+
guidance_scale=settings["guidance_scale"],
|
| 874 |
+
weight_dtype=dtype,
|
| 875 |
+
device=device,
|
| 876 |
+
condition_channels=condition_channels,
|
| 877 |
+
condition_latents=cond_latents_branch,
|
| 878 |
+
corr_lookups=corr_lookups_branch,
|
| 879 |
+
pos_delta=pos_delta_branch,
|
| 880 |
+
progress=True,
|
| 881 |
+
)
|
| 882 |
+
|
| 883 |
+
inv = (latents / vae_scale) + vae_shift
|
| 884 |
+
|
| 885 |
+
if use_dual_branch:
|
| 886 |
+
decoded = vae.decode(inv.to(device=vae.device, dtype=vae.dtype)).sample
|
| 887 |
+
decoded = decoded.view(branch_factor, num_views, 3, height, width)
|
| 888 |
+
albedo_pred = decoded[0]
|
| 889 |
+
orm_pred_full = decoded[1]
|
| 890 |
+
combined = torch.cat([albedo_pred, orm_pred_full[:, 1:, :, :]], dim=1)
|
| 891 |
+
combined_flat = combined.reshape(num_views, 5, height, width)
|
| 892 |
+
else:
|
| 893 |
+
inv_mc = inv.to(device=mcvae.device, dtype=mcvae.dtype)
|
| 894 |
+
decoded = mcvae.decode_aug(inv_mc).sample
|
| 895 |
+
combined_flat = decoded.reshape(num_views, 5, height, width)
|
| 896 |
+
albedo_pred = combined_flat[:, :3]
|
| 897 |
+
orm_pred_full = combined_flat[:, 3:]
|
| 898 |
+
|
| 899 |
+
# Compute c-PSNR if correspondence data is available
|
| 900 |
+
cpsnr_metrics = None
|
| 901 |
+
if preprocessed.get("corr_f2l_highres") is not None:
|
| 902 |
+
corr_high = preprocessed["corr_f2l_highres"] # (1, V*H*W, K)
|
| 903 |
+
|
| 904 |
+
# Convert to global indices (similar to training)
|
| 905 |
+
B, VHW, K = corr_high.shape
|
| 906 |
+
base = torch.arange(B, device=corr_high.device).view(B, 1, 1) * VHW
|
| 907 |
+
corr_high_global = torch.where(corr_high >= 0, corr_high + base, corr_high)
|
| 908 |
+
corr_high_global = corr_high_global.reshape(B * VHW, K) # (V*H*W, K)
|
| 909 |
+
|
| 910 |
+
# Prepare predictions in (B, C, H, W) format
|
| 911 |
+
pred_rgb = combined_flat[:, :3].unsqueeze(0) # (1, V, 3, H, W) -> need (V, 3, H, W)
|
| 912 |
+
pred_rgb = pred_rgb.squeeze(0) # (V, 3, H, W)
|
| 913 |
+
pred_mr = combined_flat[:, 3:].unsqueeze(0).squeeze(0) # (V, 2, H, W)
|
| 914 |
+
|
| 915 |
+
# Compute c-PSNR for each component
|
| 916 |
+
albedo_psnr, _, num_elements = correspondence_psnr(pred_rgb, corr_high_global, data_range=2.0)
|
| 917 |
+
roughness_psnr, _, _ = correspondence_psnr(pred_mr[:, 0:1], corr_high_global, data_range=2.0)
|
| 918 |
+
metallic_psnr, _, _ = correspondence_psnr(pred_mr[:, 1:2], corr_high_global, data_range=2.0)
|
| 919 |
+
|
| 920 |
+
# Combined 5-channel c-PSNR
|
| 921 |
+
pred_combined = combined_flat.unsqueeze(0).squeeze(0) # (V, 5, H, W)
|
| 922 |
+
total_psnr, _, _ = correspondence_psnr(pred_combined, corr_high_global, data_range=2.0)
|
| 923 |
+
|
| 924 |
+
cpsnr_metrics = {
|
| 925 |
+
"albedo": float(albedo_psnr.item()),
|
| 926 |
+
"roughness": float(roughness_psnr.item()),
|
| 927 |
+
"metallic": float(metallic_psnr.item()),
|
| 928 |
+
"total": float(total_psnr.item()),
|
| 929 |
+
"num_elements": num_elements,
|
| 930 |
+
}
|
| 931 |
+
|
| 932 |
+
dec_albedo = albedo_pred
|
| 933 |
+
if use_dual_branch:
|
| 934 |
+
dec_orm = torch.cat([
|
| 935 |
+
torch.full_like(orm_pred_full[:, :1], fill_value=-1.0),
|
| 936 |
+
orm_pred_full[:, 1:, :, :],
|
| 937 |
+
], dim=1)
|
| 938 |
+
else:
|
| 939 |
+
dec_orm = torch.cat([
|
| 940 |
+
torch.full_like(combined_flat[:, :1], fill_value=-1.0),
|
| 941 |
+
combined_flat[:, (3, 4)],
|
| 942 |
+
], dim=1)
|
| 943 |
+
|
| 944 |
+
os.makedirs(output_dir, exist_ok=True)
|
| 945 |
+
|
| 946 |
+
if keep_strips:
|
| 947 |
+
albedo_path = os.path.join(output_dir, f"{gid}_albedo_strip.png")
|
| 948 |
+
orm_path = os.path.join(output_dir, f"{gid}_orm_strip.png")
|
| 949 |
+
pos = preprocessed["cond_values"][:, :3].clone()
|
| 950 |
+
normal = preprocessed["cond_values"][:, 3:].clone()
|
| 951 |
+
cond_values_path = os.path.join(output_dir, f"{gid}_cond_values_strip.png")
|
| 952 |
+
cond_values_strip = torch.cat([(pos + 1.0) * 0.5, (normal + 1.0) * 0.5], dim=0).to("cpu")
|
| 953 |
+
_save_strip(cond_values_strip, cond_values_path, nrow=2)
|
| 954 |
+
else:
|
| 955 |
+
albedo_fd, albedo_path = tempfile.mkstemp(suffix="_albedo_strip.png")
|
| 956 |
+
orm_fd, orm_path = tempfile.mkstemp(suffix="_orm_strip.png")
|
| 957 |
+
os.close(albedo_fd)
|
| 958 |
+
os.close(orm_fd)
|
| 959 |
+
|
| 960 |
+
dec_albedo_cpu = dec_albedo.detach().to("cpu")
|
| 961 |
+
dec_orm_cpu = dec_orm.detach().to("cpu")
|
| 962 |
+
|
| 963 |
+
_save_strip(dec_albedo_cpu, albedo_path)
|
| 964 |
+
_save_strip(dec_orm_cpu, orm_path)
|
| 965 |
+
|
| 966 |
+
unproj_backend = settings.get("unproj_backend", "mvadapter")
|
| 967 |
+
|
| 968 |
+
if unproj_backend == "materialmvp":
|
| 969 |
+
# MaterialMVP backend: use MeshRenderer + ViewProcessor
|
| 970 |
+
# Convert strips to individual view images
|
| 971 |
+
albedo_strip_img = Image.open(albedo_path)
|
| 972 |
+
orm_strip_img = Image.open(orm_path)
|
| 973 |
+
|
| 974 |
+
strip_width = albedo_strip_img.width
|
| 975 |
+
strip_height = albedo_strip_img.height
|
| 976 |
+
view_width = strip_width // num_views
|
| 977 |
+
|
| 978 |
+
albedo_views = []
|
| 979 |
+
mr_views = []
|
| 980 |
+
|
| 981 |
+
for i in range(num_views):
|
| 982 |
+
left = i * view_width
|
| 983 |
+
right = (i + 1) * view_width
|
| 984 |
+
|
| 985 |
+
# Extract albedo view
|
| 986 |
+
albedo_view = albedo_strip_img.crop((left, 0, right, strip_height))
|
| 987 |
+
albedo_views.append(albedo_view)
|
| 988 |
+
|
| 989 |
+
# Extract MR from ORM (channels 1 and 2)
|
| 990 |
+
orm_view = orm_strip_img.crop((left, 0, right, strip_height))
|
| 991 |
+
mr_views.append(orm_view)
|
| 992 |
+
|
| 993 |
+
output_path = unproject_with_materialmvp(
|
| 994 |
+
mesh_path=mesh_path,
|
| 995 |
+
output_dir=output_dir,
|
| 996 |
+
gid=gid,
|
| 997 |
+
albedo_images=albedo_views,
|
| 998 |
+
mr_images=mr_views,
|
| 999 |
+
render_size=height,
|
| 1000 |
+
texture_size=settings["uv_size"],
|
| 1001 |
+
realesrgan_ckpt=settings["realesrgan_ckpt"],
|
| 1002 |
+
bake_exp=2,
|
| 1003 |
+
)
|
| 1004 |
+
|
| 1005 |
+
# Clean up temp files if needed
|
| 1006 |
+
if not keep_strips:
|
| 1007 |
+
for path in (albedo_path, orm_path):
|
| 1008 |
+
if path and os.path.exists(path):
|
| 1009 |
+
try:
|
| 1010 |
+
os.remove(path)
|
| 1011 |
+
except OSError:
|
| 1012 |
+
pass
|
| 1013 |
+
|
| 1014 |
+
else: # mvadapter (default)
|
| 1015 |
+
tex_pipeline = TexturePipeline(
|
| 1016 |
+
upscaler_ckpt_path=settings["realesrgan_ckpt"],
|
| 1017 |
+
inpaint_ckpt_path=settings["lama_ckpt"],
|
| 1018 |
+
device=device,
|
| 1019 |
+
)
|
| 1020 |
+
|
| 1021 |
+
try:
|
| 1022 |
+
result = tex_pipeline(
|
| 1023 |
+
mesh_path=mesh_path,
|
| 1024 |
+
save_dir=output_dir,
|
| 1025 |
+
save_name=gid,
|
| 1026 |
+
uv_unwarp=True,
|
| 1027 |
+
preprocess_mesh=False,
|
| 1028 |
+
move_to_center=True,
|
| 1029 |
+
uv_size=settings["uv_size"],
|
| 1030 |
+
base_color_path=albedo_path,
|
| 1031 |
+
orm_path=orm_path,
|
| 1032 |
+
base_color_process_config=ModProcessConfig(view_upscale=True, inpaint_mode="view"),
|
| 1033 |
+
orm_process_config=ModProcessConfig(view_upscale=True, inpaint_mode="view"),
|
| 1034 |
+
camera_elevation_deg=ELEV_DEG,
|
| 1035 |
+
camera_azimuth_deg=AZIM_DEG,
|
| 1036 |
+
)
|
| 1037 |
+
output_path = result.pbr_model_save_path
|
| 1038 |
+
finally:
|
| 1039 |
+
if not keep_strips:
|
| 1040 |
+
for path in (albedo_path, orm_path):
|
| 1041 |
+
if path and os.path.exists(path):
|
| 1042 |
+
try:
|
| 1043 |
+
os.remove(path)
|
| 1044 |
+
except OSError:
|
| 1045 |
+
pass
|
| 1046 |
+
|
| 1047 |
+
# Save c-PSNR metrics if computed
|
| 1048 |
+
if cpsnr_metrics is not None:
|
| 1049 |
+
cpsnr_json_path = os.path.join(output_dir, "cpsnr.json")
|
| 1050 |
+
_update_cpsnr_json(cpsnr_json_path, gid, cpsnr_metrics)
|
| 1051 |
+
|
| 1052 |
+
return output_path
|
| 1053 |
+
|
| 1054 |
+
|
| 1055 |
+
def build_inference_settings(args, cfg) -> Dict[str, Any]:
|
| 1056 |
+
"""Build inference settings from config file and CLI overrides.
|
| 1057 |
+
|
| 1058 |
+
Priority order (highest to lowest):
|
| 1059 |
+
1. CLI arguments (--num-views, --resolution, etc.) - for backwards compatibility
|
| 1060 |
+
2. Config file with CLI overrides (key=value format)
|
| 1061 |
+
|
| 1062 |
+
Args:
|
| 1063 |
+
args: Parsed argparse namespace with script-specific arguments
|
| 1064 |
+
cfg: OmegaConf config loaded with base-variant inheritance and CLI overrides
|
| 1065 |
+
"""
|
| 1066 |
+
model_cfg = cfg.model
|
| 1067 |
+
|
| 1068 |
+
# Apply CLI argument overrides to config (backwards compatibility)
|
| 1069 |
+
# These take precedence over --opts to maintain expected behavior
|
| 1070 |
+
if args.num_views is not None:
|
| 1071 |
+
model_cfg.num_views = args.num_views
|
| 1072 |
+
if args.resolution is not None:
|
| 1073 |
+
model_cfg.resolution = args.resolution
|
| 1074 |
+
if args.sd_vae_path is not None:
|
| 1075 |
+
model_cfg.sd_vae_path = args.sd_vae_path
|
| 1076 |
+
if args.mcvae_config is not None:
|
| 1077 |
+
model_cfg.mcvae_config = args.mcvae_config
|
| 1078 |
+
if args.mcvae_ckpt is not None:
|
| 1079 |
+
model_cfg.mcvae_ckpt = args.mcvae_ckpt
|
| 1080 |
+
|
| 1081 |
+
dtype_map = {
|
| 1082 |
+
"fp16": torch.float16,
|
| 1083 |
+
"bf16": torch.bfloat16,
|
| 1084 |
+
"fp32": torch.float32,
|
| 1085 |
+
}
|
| 1086 |
+
dtype = dtype_map.get(args.precision.lower(), DTYPE)
|
| 1087 |
+
|
| 1088 |
+
settings = {
|
| 1089 |
+
"device": args.device,
|
| 1090 |
+
"dtype": dtype,
|
| 1091 |
+
"resolution": int(model_cfg.resolution),
|
| 1092 |
+
"num_views": int(model_cfg.num_views),
|
| 1093 |
+
"use_caa": bool(getattr(model_cfg, "use_caa", True)),
|
| 1094 |
+
"use_rope": bool(getattr(model_cfg, "use_rope", False)),
|
| 1095 |
+
"use_global_token": bool(getattr(model_cfg, "use_global_token", False)),
|
| 1096 |
+
"use_global_pos": bool(getattr(model_cfg, "use_global_pos", False)),
|
| 1097 |
+
"use_dual_branch": bool(getattr(model_cfg, "use_dual_branch", False)),
|
| 1098 |
+
"corr_dilate_iterations": int(getattr(model_cfg, "corr_dilate_iterations", 2)),
|
| 1099 |
+
"condition_channels_cfg": int(getattr(model_cfg, "condition_channels", 0)),
|
| 1100 |
+
"base_model": model_cfg.pretrained_model_name_or_path,
|
| 1101 |
+
"model_cfg": model_cfg,
|
| 1102 |
+
"weights_path": args.weights or TRANSFORMER_PARTIAL_WEIGHTS,
|
| 1103 |
+
"sd_vae_path": model_cfg.sd_vae_path,
|
| 1104 |
+
"mcvae_config_path": model_cfg.mcvae_config,
|
| 1105 |
+
"mcvae_ckpt_path": model_cfg.mcvae_ckpt or MCVAE_CKPT_PATH,
|
| 1106 |
+
"mcvae_offset_mode": bool(getattr(model_cfg, "mcvae_offset_mode", True)),
|
| 1107 |
+
"negative_prompt": args.negative_prompt,
|
| 1108 |
+
"steps": args.steps,
|
| 1109 |
+
"guidance_scale": args.guidance,
|
| 1110 |
+
"realesrgan_ckpt": args.realesrgan_ckpt,
|
| 1111 |
+
"lama_ckpt": args.lama_ckpt,
|
| 1112 |
+
"uv_size": args.uv_size,
|
| 1113 |
+
"unproj_backend": args.unproj_backend,
|
| 1114 |
+
"config": cfg,
|
| 1115 |
+
}
|
| 1116 |
+
|
| 1117 |
+
return settings
|
| 1118 |
+
|
| 1119 |
+
|
| 1120 |
+
# =============================================================================
|
| 1121 |
+
# CLI Entry Point
|
| 1122 |
+
# =============================================================================
|
| 1123 |
+
|
| 1124 |
+
|
| 1125 |
+
|
| 1126 |
+
if __name__ == "__main__":
|
| 1127 |
+
import argparse
|
| 1128 |
+
|
| 1129 |
+
parser = argparse.ArgumentParser(
|
| 1130 |
+
description="Mesh + Prompt → Textured GLB",
|
| 1131 |
+
epilog="""
|
| 1132 |
+
Additional config overrides can be provided as key=value arguments:
|
| 1133 |
+
python inference.py --mesh model.glb --prompt "..." model.num_views=8 model.use_caa=false
|
| 1134 |
+
"""
|
| 1135 |
+
)
|
| 1136 |
+
parser.add_argument("--mesh", required=True, help="Path to input mesh (.glb or .obj)")
|
| 1137 |
+
parser.add_argument("--prompt", required=True, help="Text prompt for texture generation")
|
| 1138 |
+
parser.add_argument("--out", default="./temp_outputs", help="Output directory")
|
| 1139 |
+
parser.add_argument("--keep-strips", action="store_true", help="Keep intermediate albedo/ORM strip images")
|
| 1140 |
+
parser.add_argument("--guidance", type=float, default=GUIDANCE, help=f"Guidance scale (default: {GUIDANCE})")
|
| 1141 |
+
parser.add_argument("--steps", type=int, default=STEPS, help=f"Number of denoising steps (default: {STEPS})")
|
| 1142 |
+
parser.add_argument("--seed", type=int, default=42, help="Random seed (default: 42)")
|
| 1143 |
+
parser.add_argument("--negative-prompt", default=NEGATIVE_PROMPT, help="Negative prompt text")
|
| 1144 |
+
parser.add_argument("--device", default=DEVICE, help="Torch device to run inference on")
|
| 1145 |
+
parser.add_argument("--precision", default="fp16", choices=["fp16", "bf16", "fp32"], help="Computation precision")
|
| 1146 |
+
parser.add_argument("--num-views", type=int, default=None, help="Override number of camera views")
|
| 1147 |
+
parser.add_argument("--resolution", type=int, default=None, help="Override render resolution")
|
| 1148 |
+
parser.add_argument("--sd-vae-path", default=None, help="Override base SD VAE path")
|
| 1149 |
+
parser.add_argument("--mcvae-config", default=None, help="Override mcVAE config path")
|
| 1150 |
+
parser.add_argument("--mcvae-ckpt", default=None, help="Override mcVAE checkpoint path")
|
| 1151 |
+
parser.add_argument("--weights", default=None, help="Path to trained transformer weights (dir or file)")
|
| 1152 |
+
parser.add_argument("--realesrgan-ckpt", default="./checkpoints/RealESRGAN_x4plus.pth", help="RealESRGAN checkpoint path (x4plus for materialmvp, x2plus for mvadapter)")
|
| 1153 |
+
parser.add_argument("--lama-ckpt", default="./checkpoints/big-lama.pt", help="LaMa inpainting checkpoint path")
|
| 1154 |
+
parser.add_argument("--uv-size", type=int, default=4096, help="Final UV texture resolution")
|
| 1155 |
+
parser.add_argument("--config", default="configs/mcdiff/default.yaml", help="Config file (supports base-variant inheritance)")
|
| 1156 |
+
parser.add_argument("--unproj-backend", default="materialmvp", choices=["mvadapter", "materialmvp"],
|
| 1157 |
+
help="Unprojection backend: 'mvadapter' (default, uses TexturePipeline) or 'materialmvp' (uses MeshRenderer+ViewProcessor)")
|
| 1158 |
+
|
| 1159 |
+
# Parse known args (script-specific flags), unknown args will be config overrides (key=value)
|
| 1160 |
+
args, unknown = parser.parse_known_args()
|
| 1161 |
+
|
| 1162 |
+
# Load config with base-variant inheritance and CLI overrides
|
| 1163 |
+
cfg = load_config(args.config, cli_overrides=unknown)
|
| 1164 |
+
|
| 1165 |
+
# Build settings with both argparse args and loaded config
|
| 1166 |
+
settings = build_inference_settings(args, cfg)
|
| 1167 |
+
|
| 1168 |
+
output_path = run(
|
| 1169 |
+
mesh_path=args.mesh,
|
| 1170 |
+
prompt=args.prompt,
|
| 1171 |
+
output_dir=args.out,
|
| 1172 |
+
settings=settings,
|
| 1173 |
+
keep_strips=args.keep_strips,
|
| 1174 |
+
seed=args.seed,
|
| 1175 |
+
)
|
| 1176 |
+
|
| 1177 |
+
print(f"✓ Saved: {output_path}")
|
| 1178 |
+
|
| 1179 |
+
|
| 1180 |
+
'''
|
| 1181 |
+
python inference.py --keep-strips \
|
| 1182 |
+
--mesh /home/aaaaa/data/Arb-Objaverse/data/glb/000-132/21ec37b286474fedb43307f6f289269e.glb \
|
| 1183 |
+
--prompt "A wooden stool with a pink cushion and shiny, dark wood finish, detailed with tufting and decorative white buttons, suitable for interior design visualizations." \
|
| 1184 |
+
--config configs/mcdiff/dual_full.yaml \
|
| 1185 |
+
--weights outputs/mcdiff_v.dual_full/checkpoint-40000/transformer/diffusion_pytorch_model.safetensors \
|
| 1186 |
+
--out removethis/dual_full
|
| 1187 |
+
|
| 1188 |
+
python inference.py --keep-strips \
|
| 1189 |
+
--mesh /home/aaaaa/data/Arb-Objaverse/data/glb/000-132/21ec37b286474fedb43307f6f289269e.glb \
|
| 1190 |
+
--prompt "A wooden stool with a pink cushion and shiny, dark wood finish, detailed with tufting and decorative white buttons, suitable for interior design visualizations." \
|
| 1191 |
+
--config configs/mcdiff/single_caa.yaml \
|
| 1192 |
+
--weights outputs/mcdiff_v.single_caa/checkpoint-35000/transformer/diffusion_pytorch_model.safetensors \
|
| 1193 |
+
--out removethis/single_caa
|
| 1194 |
+
|
| 1195 |
+
|
| 1196 |
+
python inference.py --keep-strips \
|
| 1197 |
+
--mesh /home/aaaaa/data/Arb-Objaverse/data/glb/000-132/21ec37b286474fedb43307f6f289269e.glb \
|
| 1198 |
+
--prompt "A wooden stool with a pink cushion and shiny, dark wood finish, detailed with tufting and decorative white buttons, suitable for interior design visualizations." \
|
| 1199 |
+
--config configs/mcdiff/single_caa_global.yaml \
|
| 1200 |
+
--weights outputs/mcdiff_v.single_caa_global/checkpoint-5000/transformer/diffusion_pytorch_model.safetensors \
|
| 1201 |
+
--out removethis/single_caa_global
|
| 1202 |
+
|
| 1203 |
+
# With MaterialMVP unprojection backend:
|
| 1204 |
+
python inference.py --keep-strips \
|
| 1205 |
+
--mesh /home/aaaaa/data/Arb-Objaverse/data/glb/000-132/21ec37b286474fedb43307f6f289269e.glb \
|
| 1206 |
+
--prompt "A wooden stool with a pink cushion and shiny, dark wood finish, detailed with tufting and decorative white buttons, suitable for interior design visualizations." \
|
| 1207 |
+
--config configs/mcdiff/single_caa.yaml \
|
| 1208 |
+
--weights outputs/mcdiff_v.single_caa/checkpoint-35000/transformer/diffusion_pytorch_model.safetensors \
|
| 1209 |
+
--out removethis/single_caa_materialmvp \
|
| 1210 |
+
--unproj-backend materialmvp
|
| 1211 |
+
'''
|
home/ubuntu/aaaaa/data/rgbmr/inference_batch.py
ADDED
|
@@ -0,0 +1,658 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
run_infer_batch.py
|
| 4 |
+
|
| 5 |
+
Parallel runner for inference.py using queued GPU assignment.
|
| 6 |
+
- Reads GLB paths from a text file (one path per line; lines starting with '#' ignored).
|
| 7 |
+
- Reads GLB id (file stem) -> prompt mapping from a CSV file.
|
| 8 |
+
- For each GLB, looks up its stem in the CSV to get a text prompt.
|
| 9 |
+
- Schedules processes across provided CUDA devices with a fair queue.
|
| 10 |
+
- Applies a per-process timeout.
|
| 11 |
+
|
| 12 |
+
"""
|
| 13 |
+
|
| 14 |
+
import os
|
| 15 |
+
import sys
|
| 16 |
+
import csv
|
| 17 |
+
import shlex
|
| 18 |
+
import argparse
|
| 19 |
+
import asyncio
|
| 20 |
+
from typing import Dict, List, Tuple
|
| 21 |
+
|
| 22 |
+
# -----------------------------------------------------------------------------
|
| 23 |
+
# print utils (robust import with fallbacks)
|
| 24 |
+
# -----------------------------------------------------------------------------
|
| 25 |
+
from mcgen.utils.print_utils import (
|
| 26 |
+
print_header,
|
| 27 |
+
print_footer,
|
| 28 |
+
print_info,
|
| 29 |
+
print_error,
|
| 30 |
+
print_warning,
|
| 31 |
+
)
|
| 32 |
+
|
| 33 |
+
# -----------------------------------------------------------------------------
|
| 34 |
+
# Try to import shared helpers from the reference script; provide fallbacks if needed
|
| 35 |
+
# -----------------------------------------------------------------------------
|
| 36 |
+
from tools.render_eval_batch import (
|
| 37 |
+
print_box,
|
| 38 |
+
read_list_file,
|
| 39 |
+
ensure_exists,
|
| 40 |
+
quote,
|
| 41 |
+
run_tests,
|
| 42 |
+
deterministic_hdr_index,
|
| 43 |
+
build_blender_command,
|
| 44 |
+
default_script_path as default_render_script_path,
|
| 45 |
+
)
|
| 46 |
+
|
| 47 |
+
# -----------------------------------------------------------------------------
|
| 48 |
+
# Script-specific utilities
|
| 49 |
+
# -----------------------------------------------------------------------------
|
| 50 |
+
|
| 51 |
+
def default_infer_script_path() -> str:
|
| 52 |
+
here = os.path.dirname(os.path.abspath(__file__))
|
| 53 |
+
return os.path.join(here, "inference.py")
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
def default_infer_list_script_path() -> str:
|
| 57 |
+
here = os.path.dirname(os.path.abspath(__file__))
|
| 58 |
+
return os.path.join(here, "inference_list.py")
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
def stem_of(path: str) -> str:
|
| 62 |
+
return os.path.splitext(os.path.basename(path))[0]
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
def read_prompt_csv(path: str) -> Dict[str, str]:
|
| 66 |
+
"""Read headered CSV with columns: id, prompt."""
|
| 67 |
+
p = os.path.expanduser(path)
|
| 68 |
+
if not os.path.exists(p):
|
| 69 |
+
raise FileNotFoundError(f"Prompt CSV not found: {p}")
|
| 70 |
+
|
| 71 |
+
mapping: Dict[str, str] = {}
|
| 72 |
+
with open(p, "r", encoding="utf-8", newline="") as f:
|
| 73 |
+
for row in csv.DictReader(f):
|
| 74 |
+
rid = (row.get("id") or "").strip()
|
| 75 |
+
prompt = (row.get("prompt") or "").replace("\n", " ").strip()
|
| 76 |
+
if not rid:
|
| 77 |
+
continue
|
| 78 |
+
if not prompt:
|
| 79 |
+
print_warning(f"Empty prompt for id: {rid}; skipping")
|
| 80 |
+
continue
|
| 81 |
+
if rid in mapping:
|
| 82 |
+
print_warning(f"Duplicate id in CSV; last wins: {rid}")
|
| 83 |
+
mapping[rid] = prompt
|
| 84 |
+
return mapping
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
def build_infer_command(
|
| 88 |
+
python_exec: str,
|
| 89 |
+
script: str,
|
| 90 |
+
glb_path: str,
|
| 91 |
+
prompt: str,
|
| 92 |
+
out_dir: str,
|
| 93 |
+
extra_args: List[str] | None = None,
|
| 94 |
+
) -> str:
|
| 95 |
+
"""Build a shell command:
|
| 96 |
+
python_exec script --mesh GLB --prompt PROMPT --out OUT [extras...]
|
| 97 |
+
"""
|
| 98 |
+
parts = [
|
| 99 |
+
quote(python_exec),
|
| 100 |
+
quote(script),
|
| 101 |
+
"--mesh", quote(glb_path),
|
| 102 |
+
"--prompt", quote(prompt),
|
| 103 |
+
"--out", quote(out_dir),
|
| 104 |
+
]
|
| 105 |
+
if extra_args:
|
| 106 |
+
parts.extend(extra_args)
|
| 107 |
+
return " ".join(parts)
|
| 108 |
+
|
| 109 |
+
|
| 110 |
+
def build_infer_list_command(
|
| 111 |
+
python_exec: str,
|
| 112 |
+
script: str,
|
| 113 |
+
glb_paths: List[str],
|
| 114 |
+
prompts: List[str],
|
| 115 |
+
out_dir: str,
|
| 116 |
+
extra_args: List[str] | None = None,
|
| 117 |
+
) -> str:
|
| 118 |
+
"""Build a shell command for inference_list.py:
|
| 119 |
+
python_exec script --mesh GLB1 GLB2 ... --prompt P1 P2 ... --out OUT [extras...]
|
| 120 |
+
"""
|
| 121 |
+
parts = [
|
| 122 |
+
quote(python_exec),
|
| 123 |
+
quote(script),
|
| 124 |
+
"--mesh",
|
| 125 |
+
]
|
| 126 |
+
parts.extend([quote(p) for p in glb_paths])
|
| 127 |
+
parts.append("--prompt")
|
| 128 |
+
parts.extend([quote(p) for p in prompts])
|
| 129 |
+
parts.extend(["--out", quote(out_dir)])
|
| 130 |
+
if extra_args:
|
| 131 |
+
parts.extend(extra_args)
|
| 132 |
+
return " ".join(parts)
|
| 133 |
+
|
| 134 |
+
|
| 135 |
+
# -----------------------------------------------------------------------------
|
| 136 |
+
# CLI & main
|
| 137 |
+
# -----------------------------------------------------------------------------
|
| 138 |
+
|
| 139 |
+
def default_evaluate_script_path() -> str:
|
| 140 |
+
here = os.path.dirname(os.path.abspath(__file__))
|
| 141 |
+
return os.path.join(here, "tools", "evaluate.py")
|
| 142 |
+
|
| 143 |
+
|
| 144 |
+
def parse_args():
|
| 145 |
+
p = argparse.ArgumentParser(description="Parallel runner for inference.py (GLB generation) + rendering + evaluation")
|
| 146 |
+
p.add_argument("--glb_list", required=True, help="TXT file with GLB paths (one per line)")
|
| 147 |
+
p.add_argument("--prompt_csv", required=True, help="CSV file mapping GLB stem -> text prompt")
|
| 148 |
+
p.add_argument("--output_dir", required=True, help="Base output directory (passed to --out)")
|
| 149 |
+
|
| 150 |
+
# Parallel processing controls
|
| 151 |
+
p.add_argument("--devices", nargs="+", required=True, help="List of CUDA devices (e.g., 0 1 2)")
|
| 152 |
+
p.add_argument("--timeout", type=int, default=999999, help="Maximum runtime per command (seconds)")
|
| 153 |
+
|
| 154 |
+
# Optional QoL flags for inference
|
| 155 |
+
p.add_argument(
|
| 156 |
+
"--python_exec",
|
| 157 |
+
default=os.environ.get("PYTHON_EXEC", sys.executable),
|
| 158 |
+
help="Python interpreter to run inference (default: current Python or $PYTHON_EXEC)",
|
| 159 |
+
)
|
| 160 |
+
p.add_argument(
|
| 161 |
+
"--script",
|
| 162 |
+
default=default_infer_script_path(),
|
| 163 |
+
help="Path to inference.py (default: alongside this runner)",
|
| 164 |
+
)
|
| 165 |
+
p.add_argument(
|
| 166 |
+
"--extra_inference",
|
| 167 |
+
default="",
|
| 168 |
+
help="Extra args appended verbatim to the inference command",
|
| 169 |
+
)
|
| 170 |
+
p.add_argument(
|
| 171 |
+
"--group_inference",
|
| 172 |
+
action="store_true",
|
| 173 |
+
help="Use inference_list.py to process meshes in groups of 16 (faster due to shared model loading)",
|
| 174 |
+
)
|
| 175 |
+
p.add_argument(
|
| 176 |
+
"--group_size",
|
| 177 |
+
type=int,
|
| 178 |
+
default=16,
|
| 179 |
+
help="Number of meshes to process per group when --group_inference is enabled (default: 16)",
|
| 180 |
+
)
|
| 181 |
+
|
| 182 |
+
# Rendering options (required)
|
| 183 |
+
p.add_argument("--hdr_list", required=True, help="TXT file with HDR/HDRI paths (one per line) for rendering")
|
| 184 |
+
p.add_argument("--salt", default="", help="Salt string for deterministic HDR mapping (default: empty)")
|
| 185 |
+
p.add_argument(
|
| 186 |
+
"--blender",
|
| 187 |
+
default="/home/dbsghd363/blender-4.5.3-linux-x64/blender",
|
| 188 |
+
help="Path to Blender executable (default: /home/dbsghd363/blender-4.5.3-linux-x64/blender)",
|
| 189 |
+
)
|
| 190 |
+
p.add_argument(
|
| 191 |
+
"--render_script",
|
| 192 |
+
default=default_render_script_path(),
|
| 193 |
+
help="Path to render_eval.py (default: tools/render_eval.py)",
|
| 194 |
+
)
|
| 195 |
+
p.add_argument(
|
| 196 |
+
"--extra_render",
|
| 197 |
+
default="",
|
| 198 |
+
help="Extra args appended verbatim to the render command",
|
| 199 |
+
)
|
| 200 |
+
|
| 201 |
+
# Evaluation options (required)
|
| 202 |
+
p.add_argument("--ref_root", required=True, help="Reference root directory for evaluation")
|
| 203 |
+
p.add_argument(
|
| 204 |
+
"--evaluate_script",
|
| 205 |
+
default=default_evaluate_script_path(),
|
| 206 |
+
help="Path to evaluate.py (default: tools/evaluate.py)",
|
| 207 |
+
)
|
| 208 |
+
|
| 209 |
+
# Optional processing control flags
|
| 210 |
+
p.add_argument(
|
| 211 |
+
"--skip-vis",
|
| 212 |
+
action="store_true",
|
| 213 |
+
help="Skip visualization rendering (only perform inference, evaluation rendering, and metrics computation)",
|
| 214 |
+
)
|
| 215 |
+
|
| 216 |
+
return p.parse_args()
|
| 217 |
+
|
| 218 |
+
|
| 219 |
+
async def main_async():
|
| 220 |
+
args = parse_args()
|
| 221 |
+
|
| 222 |
+
# Inputs
|
| 223 |
+
glb_paths = ensure_exists(read_list_file(args.glb_list), "GLB")
|
| 224 |
+
if not glb_paths:
|
| 225 |
+
print_error("No valid GLB paths found.")
|
| 226 |
+
sys.exit(1)
|
| 227 |
+
|
| 228 |
+
prompt_map = read_prompt_csv(args.prompt_csv)
|
| 229 |
+
if not prompt_map:
|
| 230 |
+
print_error("No valid (id -> prompt) entries found in CSV.")
|
| 231 |
+
sys.exit(1)
|
| 232 |
+
|
| 233 |
+
# Prepare output
|
| 234 |
+
out_dir = os.path.expanduser(args.output_dir)
|
| 235 |
+
os.makedirs(out_dir, exist_ok=True)
|
| 236 |
+
|
| 237 |
+
# Create subdirectories for organized structure
|
| 238 |
+
glbs_dir = os.path.join(out_dir, "glbs")
|
| 239 |
+
eval_dir = os.path.join(out_dir, "eval")
|
| 240 |
+
vis_dir = os.path.join(out_dir, "vis")
|
| 241 |
+
os.makedirs(glbs_dir, exist_ok=True)
|
| 242 |
+
os.makedirs(eval_dir, exist_ok=True)
|
| 243 |
+
os.makedirs(vis_dir, exist_ok=True)
|
| 244 |
+
|
| 245 |
+
# Load HDR list for rendering
|
| 246 |
+
hdrs = ensure_exists(read_list_file(args.hdr_list), "HDR")
|
| 247 |
+
if not hdrs:
|
| 248 |
+
print_error("No valid HDR paths found.")
|
| 249 |
+
sys.exit(1)
|
| 250 |
+
|
| 251 |
+
# Build commands
|
| 252 |
+
infer_commands: List[str] = []
|
| 253 |
+
render_commands: List[str] = []
|
| 254 |
+
vis_commands: List[str] = []
|
| 255 |
+
extra_infer_args = [x for x in args.extra_inference.split() if x] if args.extra_inference else []
|
| 256 |
+
extra_render_args = [x for x in args.extra_render.split() if x] if args.extra_render else []
|
| 257 |
+
|
| 258 |
+
missing_prompts = 0
|
| 259 |
+
skip_vis = args.skip_vis
|
| 260 |
+
|
| 261 |
+
# Prepare data structures for group inference
|
| 262 |
+
pending_infer_items: List[Tuple[str, str]] = [] # [(glb_path, prompt), ...]
|
| 263 |
+
|
| 264 |
+
for glb in glb_paths:
|
| 265 |
+
sid = stem_of(glb)
|
| 266 |
+
output_glb_path = os.path.join(glbs_dir, f"{sid}.glb")
|
| 267 |
+
|
| 268 |
+
prompt = prompt_map.get(sid)
|
| 269 |
+
if not prompt:
|
| 270 |
+
print_warning(f"No prompt found for GLB stem '{sid}' (path: {glb}); skipping")
|
| 271 |
+
missing_prompts += 1
|
| 272 |
+
continue
|
| 273 |
+
|
| 274 |
+
# Build render command (output to eval_dir)
|
| 275 |
+
hdr_idx = deterministic_hdr_index(output_glb_path, len(hdrs), args.salt)
|
| 276 |
+
hdr = hdrs[hdr_idx]
|
| 277 |
+
render_cmd = build_blender_command(
|
| 278 |
+
blender=args.blender,
|
| 279 |
+
script=args.render_script,
|
| 280 |
+
glb_path=output_glb_path,
|
| 281 |
+
hdr_path=hdr,
|
| 282 |
+
out_dir=eval_dir,
|
| 283 |
+
extra_args=extra_render_args,
|
| 284 |
+
)
|
| 285 |
+
|
| 286 |
+
# Build visualization command (output to vis_dir)
|
| 287 |
+
vis_script = os.path.join(os.path.dirname(os.path.abspath(__file__)), "tools", "render_vis.py")
|
| 288 |
+
vis_cmd = build_blender_command(
|
| 289 |
+
blender=args.blender,
|
| 290 |
+
script=vis_script,
|
| 291 |
+
glb_path=output_glb_path,
|
| 292 |
+
hdr_path=hdr,
|
| 293 |
+
out_dir=vis_dir,
|
| 294 |
+
extra_args=[], # No extra args for vis rendering
|
| 295 |
+
)
|
| 296 |
+
|
| 297 |
+
# if the output for this GLB already exists, skip it
|
| 298 |
+
if not os.path.exists(output_glb_path):
|
| 299 |
+
pending_infer_items.append((glb, prompt))
|
| 300 |
+
|
| 301 |
+
# Check if rendering is already complete (20 PNG files in each subdir of eval_dir/gid/)
|
| 302 |
+
eval_gid_dir = os.path.join(eval_dir, sid)
|
| 303 |
+
render_complete = False
|
| 304 |
+
if os.path.exists(eval_gid_dir):
|
| 305 |
+
subdirs = [d for d in os.listdir(eval_gid_dir) if os.path.isdir(os.path.join(eval_gid_dir, d))]
|
| 306 |
+
if subdirs:
|
| 307 |
+
# Check if all subdirs have 20 PNG files
|
| 308 |
+
all_complete = True
|
| 309 |
+
for subdir in subdirs:
|
| 310 |
+
subdir_path = os.path.join(eval_gid_dir, subdir)
|
| 311 |
+
png_files = [f for f in os.listdir(subdir_path) if f.endswith('.png')]
|
| 312 |
+
if len(png_files) < 20:
|
| 313 |
+
all_complete = False
|
| 314 |
+
break
|
| 315 |
+
render_complete = all_complete
|
| 316 |
+
|
| 317 |
+
if not render_complete:
|
| 318 |
+
render_commands.append(render_cmd)
|
| 319 |
+
|
| 320 |
+
# Check if visualization is already complete (gid.mp4 exists in vis_dir)
|
| 321 |
+
# Only add vis commands if --skip-vis is not set
|
| 322 |
+
if not skip_vis:
|
| 323 |
+
vis_mp4_path = os.path.join(vis_dir, f"{sid}.mp4")
|
| 324 |
+
if not os.path.exists(vis_mp4_path):
|
| 325 |
+
vis_commands.append(vis_cmd)
|
| 326 |
+
|
| 327 |
+
# Informative mapping line (truncate very long prompts when printing)
|
| 328 |
+
p_preview = (prompt[:16] + "…") if len(prompt) > 16 else prompt
|
| 329 |
+
print_info(f"Mapped GLB -> Prompt: {glb} ==> \"{p_preview}\" -> Render with HDR[{hdr_idx}]")
|
| 330 |
+
|
| 331 |
+
# Build inference commands based on group_inference flag
|
| 332 |
+
if args.group_inference and pending_infer_items:
|
| 333 |
+
# Use inference_list.py with grouping
|
| 334 |
+
list_script = default_infer_list_script_path()
|
| 335 |
+
group_size = args.group_size
|
| 336 |
+
|
| 337 |
+
for i in range(0, len(pending_infer_items), group_size):
|
| 338 |
+
group = pending_infer_items[i:i + group_size]
|
| 339 |
+
group_glbs = [item[0] for item in group]
|
| 340 |
+
group_prompts = [item[1] for item in group]
|
| 341 |
+
|
| 342 |
+
infer_cmd = build_infer_list_command(
|
| 343 |
+
python_exec=args.python_exec,
|
| 344 |
+
script=list_script,
|
| 345 |
+
glb_paths=group_glbs,
|
| 346 |
+
prompts=group_prompts,
|
| 347 |
+
out_dir=glbs_dir,
|
| 348 |
+
extra_args=extra_infer_args,
|
| 349 |
+
)
|
| 350 |
+
infer_commands.append(infer_cmd)
|
| 351 |
+
|
| 352 |
+
print_info(f"Using grouped inference: {len(pending_infer_items)} meshes in {len(infer_commands)} group(s) of up to {group_size}")
|
| 353 |
+
else:
|
| 354 |
+
# Use individual inference.py calls
|
| 355 |
+
for glb, prompt in pending_infer_items:
|
| 356 |
+
infer_cmd = build_infer_command(
|
| 357 |
+
python_exec=args.python_exec,
|
| 358 |
+
script=args.script,
|
| 359 |
+
glb_path=glb,
|
| 360 |
+
prompt=prompt,
|
| 361 |
+
out_dir=glbs_dir,
|
| 362 |
+
extra_args=extra_infer_args,
|
| 363 |
+
)
|
| 364 |
+
infer_commands.append(infer_cmd)
|
| 365 |
+
|
| 366 |
+
if not args.group_inference:
|
| 367 |
+
print_info(f"Using individual inference: {len(infer_commands)} separate job(s)")
|
| 368 |
+
|
| 369 |
+
# Summary box
|
| 370 |
+
salt_info = f"Salt: '{args.salt}'\n" if args.salt else ""
|
| 371 |
+
vis_script = os.path.join(os.path.dirname(os.path.abspath(__file__)), "tools", "render_vis.py")
|
| 372 |
+
vis_info = f"Total vis jobs: {len(vis_commands)}\n" if not skip_vis else "Visualization: SKIPPED (--skip-vis)\n"
|
| 373 |
+
vis_script_info = f"Visualization script: {vis_script}\n" if not skip_vis else ""
|
| 374 |
+
|
| 375 |
+
# Group inference info
|
| 376 |
+
if args.group_inference:
|
| 377 |
+
infer_mode = f"Grouped inference (inference_list.py, group size: {args.group_size})\n"
|
| 378 |
+
infer_script_info = f"Inference script: {default_infer_list_script_path()}\n"
|
| 379 |
+
else:
|
| 380 |
+
infer_mode = "Individual inference (inference.py)\n"
|
| 381 |
+
infer_script_info = f"Inference script: {args.script}\n"
|
| 382 |
+
|
| 383 |
+
summary_text = (
|
| 384 |
+
f"Total inference jobs: {len(infer_commands)}\n"
|
| 385 |
+
+ infer_mode +
|
| 386 |
+
f"Total render jobs: {len(render_commands)}\n"
|
| 387 |
+
+ vis_info +
|
| 388 |
+
f"Devices: {args.devices}\n"
|
| 389 |
+
f"Timeout per job: {args.timeout}s\n"
|
| 390 |
+
f"Output dir (--out): {out_dir}\n"
|
| 391 |
+
f"Python: {args.python_exec}\n"
|
| 392 |
+
+ infer_script_info +
|
| 393 |
+
f"Rendering: {len(hdrs)} HDRs\n"
|
| 394 |
+
f"Render script: {args.render_script}\n"
|
| 395 |
+
+ vis_script_info +
|
| 396 |
+
f"Blender: {args.blender}\n"
|
| 397 |
+
+ salt_info +
|
| 398 |
+
f"Evaluation ref root: {args.ref_root}\n"
|
| 399 |
+
f"Evaluation script: {args.evaluate_script}\n"
|
| 400 |
+
+ (f"GLBs missing prompts: {missing_prompts}\n" if missing_prompts else "")
|
| 401 |
+
)
|
| 402 |
+
print_box(summary_text, title="Batch Configuration")
|
| 403 |
+
|
| 404 |
+
# Execute inference commands with queued device allocation
|
| 405 |
+
await run_tests(infer_commands, args.devices, args.timeout)
|
| 406 |
+
|
| 407 |
+
# Retry inference for failed GLBs (second attempt)
|
| 408 |
+
print_header("Checking for missing GLBs and retrying if needed")
|
| 409 |
+
retry_infer_items: List[Tuple[str, str]] = []
|
| 410 |
+
|
| 411 |
+
for glb in glb_paths:
|
| 412 |
+
sid = stem_of(glb)
|
| 413 |
+
output_glb_path = os.path.join(glbs_dir, f"{sid}.glb")
|
| 414 |
+
|
| 415 |
+
# Check if GLB was not created
|
| 416 |
+
if not os.path.exists(output_glb_path):
|
| 417 |
+
prompt = prompt_map.get(sid)
|
| 418 |
+
if prompt:
|
| 419 |
+
retry_infer_items.append((glb, prompt))
|
| 420 |
+
print_warning(f"GLB not found, will retry: {output_glb_path}")
|
| 421 |
+
|
| 422 |
+
if retry_infer_items:
|
| 423 |
+
retry_infer_commands: List[str] = []
|
| 424 |
+
|
| 425 |
+
if args.group_inference:
|
| 426 |
+
# Use inference_list.py with grouping for retry
|
| 427 |
+
list_script = default_infer_list_script_path()
|
| 428 |
+
group_size = min(args.group_size, 2) # Use smaller groups for retry
|
| 429 |
+
|
| 430 |
+
for i in range(0, len(retry_infer_items), group_size):
|
| 431 |
+
group = retry_infer_items[i:i + group_size]
|
| 432 |
+
group_glbs = [item[0] for item in group]
|
| 433 |
+
group_prompts = [item[1] for item in group]
|
| 434 |
+
|
| 435 |
+
infer_cmd = build_infer_list_command(
|
| 436 |
+
python_exec=args.python_exec,
|
| 437 |
+
script=list_script,
|
| 438 |
+
glb_paths=group_glbs,
|
| 439 |
+
prompts=group_prompts,
|
| 440 |
+
out_dir=glbs_dir,
|
| 441 |
+
extra_args=extra_infer_args,
|
| 442 |
+
)
|
| 443 |
+
retry_infer_commands.append(infer_cmd)
|
| 444 |
+
else:
|
| 445 |
+
# Use individual inference.py calls for retry
|
| 446 |
+
for glb, prompt in retry_infer_items:
|
| 447 |
+
infer_cmd = build_infer_command(
|
| 448 |
+
python_exec=args.python_exec,
|
| 449 |
+
script=args.script,
|
| 450 |
+
glb_path=glb,
|
| 451 |
+
prompt=prompt,
|
| 452 |
+
out_dir=glbs_dir,
|
| 453 |
+
extra_args=extra_infer_args,
|
| 454 |
+
)
|
| 455 |
+
retry_infer_commands.append(infer_cmd)
|
| 456 |
+
|
| 457 |
+
print_info(f"Retrying {len(retry_infer_items)} failed inference item(s) in {len(retry_infer_commands)} job(s)")
|
| 458 |
+
await run_tests(retry_infer_commands, args.devices, args.timeout)
|
| 459 |
+
else:
|
| 460 |
+
print_info("All GLBs generated successfully on first attempt")
|
| 461 |
+
print_footer()
|
| 462 |
+
|
| 463 |
+
# Execute render commands
|
| 464 |
+
print_header("Starting rendering phase")
|
| 465 |
+
await run_tests(render_commands, args.devices, args.timeout)
|
| 466 |
+
print_footer()
|
| 467 |
+
|
| 468 |
+
# Execute visualization commands (only if not skipped)
|
| 469 |
+
if not skip_vis:
|
| 470 |
+
print_header("Starting visualization rendering phase")
|
| 471 |
+
await run_tests(vis_commands, args.devices, args.timeout)
|
| 472 |
+
print_footer()
|
| 473 |
+
else:
|
| 474 |
+
print_info("Skipping visualization rendering (--skip-vis enabled)")
|
| 475 |
+
|
| 476 |
+
# After inference and rendering, collect all GLB files from glbs_dir and write their absolute paths
|
| 477 |
+
print_header("Collecting output GLB files")
|
| 478 |
+
glb_files = []
|
| 479 |
+
for root, dirs, files in os.walk(glbs_dir):
|
| 480 |
+
for file in files:
|
| 481 |
+
if file.endswith('.glb'):
|
| 482 |
+
abs_path = os.path.abspath(os.path.join(root, file))
|
| 483 |
+
glb_files.append(abs_path)
|
| 484 |
+
|
| 485 |
+
glb_files.sort() # Sort for consistency
|
| 486 |
+
print_footer()
|
| 487 |
+
|
| 488 |
+
# Write to glbs.txt
|
| 489 |
+
glbs_txt_path = os.path.join(out_dir, "glbs.txt")
|
| 490 |
+
with open(glbs_txt_path, 'w', encoding='utf-8') as f:
|
| 491 |
+
for glb_path in glb_files:
|
| 492 |
+
f.write(f"{glb_path}\n")
|
| 493 |
+
|
| 494 |
+
print_info(f"Found {len(glb_files)} GLB file(s)")
|
| 495 |
+
print_info(f"GLB paths written to: {glbs_txt_path}")
|
| 496 |
+
|
| 497 |
+
# Run evaluation (using eval_dir as method_root)
|
| 498 |
+
print_header("Starting evaluation phase")
|
| 499 |
+
metrics_csv_path = os.path.join(out_dir, "metrics.csv")
|
| 500 |
+
eval_cmd = " ".join([
|
| 501 |
+
quote(args.python_exec),
|
| 502 |
+
quote(args.evaluate_script),
|
| 503 |
+
"--ref_root", quote(args.ref_root),
|
| 504 |
+
"--method_root", quote(eval_dir),
|
| 505 |
+
"--prompts_csv", quote(args.prompt_csv),
|
| 506 |
+
"--out_csv", quote(metrics_csv_path),
|
| 507 |
+
])
|
| 508 |
+
|
| 509 |
+
# Set CUDA_VISIBLE_DEVICES to the user-specified GPU range
|
| 510 |
+
eval_env = os.environ.copy()
|
| 511 |
+
eval_env["CUDA_VISIBLE_DEVICES"] = ",".join(args.devices)
|
| 512 |
+
|
| 513 |
+
print_info(f"Running evaluation command with CUDA_VISIBLE_DEVICES={eval_env['CUDA_VISIBLE_DEVICES']}: {eval_cmd}")
|
| 514 |
+
eval_process = await asyncio.create_subprocess_shell(eval_cmd, env=eval_env)
|
| 515 |
+
await eval_process.wait()
|
| 516 |
+
print_footer()
|
| 517 |
+
|
| 518 |
+
if eval_process.returncode == 0:
|
| 519 |
+
print_info(f"Evaluation completed successfully")
|
| 520 |
+
print_info(f"Metrics saved to: {metrics_csv_path}")
|
| 521 |
+
else:
|
| 522 |
+
print_error(f"Evaluation failed with return code {eval_process.returncode}")
|
| 523 |
+
|
| 524 |
+
# Move cpsnr.json from glbs_dir to out_dir and process it
|
| 525 |
+
print_header("Processing CPSNR metrics")
|
| 526 |
+
cpsnr_source = os.path.join(glbs_dir, "cpsnr.json")
|
| 527 |
+
cpsnr_dest = os.path.join(out_dir, "cpsnr.json")
|
| 528 |
+
|
| 529 |
+
if os.path.exists(cpsnr_source):
|
| 530 |
+
import shutil
|
| 531 |
+
import json
|
| 532 |
+
import math
|
| 533 |
+
|
| 534 |
+
# Copy the cpsnr.json file
|
| 535 |
+
shutil.copyfile(cpsnr_source, cpsnr_dest)
|
| 536 |
+
print_info(f"Copied cpsnr.json from {cpsnr_source} to {cpsnr_dest}")
|
| 537 |
+
|
| 538 |
+
# Read and process cpsnr.json
|
| 539 |
+
with open(cpsnr_dest, 'r', encoding='utf-8') as f:
|
| 540 |
+
cpsnr_data = json.load(f)
|
| 541 |
+
|
| 542 |
+
# Calculate averages for each metric, excluding NaN values
|
| 543 |
+
# Also calculate weighted averages based on num_elements
|
| 544 |
+
metric_sums = {}
|
| 545 |
+
metric_counts = {}
|
| 546 |
+
metric_weighted_sums = {}
|
| 547 |
+
metric_total_weights = {}
|
| 548 |
+
|
| 549 |
+
for gid, metrics in cpsnr_data.items():
|
| 550 |
+
# Extract num_elements for this sample (used as weight)
|
| 551 |
+
num_elements = metrics.get("num_elements", None)
|
| 552 |
+
|
| 553 |
+
for metric_name, value in metrics.items():
|
| 554 |
+
# Skip num_elements itself in metric calculations
|
| 555 |
+
if metric_name == "num_elements":
|
| 556 |
+
continue
|
| 557 |
+
|
| 558 |
+
# Check if value is a valid number (not NaN, not None, not inf)
|
| 559 |
+
if value is not None and isinstance(value, (int, float)) and math.isfinite(value):
|
| 560 |
+
# Arithmetic mean (unweighted)
|
| 561 |
+
if metric_name not in metric_sums:
|
| 562 |
+
metric_sums[metric_name] = 0.0
|
| 563 |
+
metric_counts[metric_name] = 0
|
| 564 |
+
metric_sums[metric_name] += value
|
| 565 |
+
metric_counts[metric_name] += 1
|
| 566 |
+
|
| 567 |
+
# Weighted mean (weighted by num_elements)
|
| 568 |
+
if num_elements is not None and isinstance(num_elements, (int, float)) and num_elements > 0:
|
| 569 |
+
if metric_name not in metric_weighted_sums:
|
| 570 |
+
metric_weighted_sums[metric_name] = 0.0
|
| 571 |
+
metric_total_weights[metric_name] = 0.0
|
| 572 |
+
metric_weighted_sums[metric_name] += value * num_elements
|
| 573 |
+
metric_total_weights[metric_name] += num_elements
|
| 574 |
+
|
| 575 |
+
# Calculate averages
|
| 576 |
+
cpsnr_averages = {}
|
| 577 |
+
|
| 578 |
+
# Arithmetic mean
|
| 579 |
+
for metric_name in metric_sums:
|
| 580 |
+
if metric_counts[metric_name] > 0:
|
| 581 |
+
avg_value = metric_sums[metric_name] / metric_counts[metric_name]
|
| 582 |
+
cpsnr_averages[f"cpsnr_{metric_name}"] = avg_value
|
| 583 |
+
print_info(f"cpsnr_{metric_name}: {avg_value:.4f} (from {metric_counts[metric_name]} valid values)")
|
| 584 |
+
|
| 585 |
+
# Weighted mean
|
| 586 |
+
for metric_name in metric_weighted_sums:
|
| 587 |
+
if metric_total_weights[metric_name] > 0:
|
| 588 |
+
weighted_avg_value = metric_weighted_sums[metric_name] / metric_total_weights[metric_name]
|
| 589 |
+
cpsnr_averages[f"cpsnr_weighted_{metric_name}"] = weighted_avg_value
|
| 590 |
+
print_info(f"cpsnr_weighted_{metric_name}: {weighted_avg_value:.4f} (weighted by num_elements)")
|
| 591 |
+
|
| 592 |
+
# Append cpsnr averages to metrics.csv in (metric, value) format
|
| 593 |
+
if os.path.exists(metrics_csv_path) and cpsnr_averages:
|
| 594 |
+
# Append new cpsnr metrics as new rows
|
| 595 |
+
with open(metrics_csv_path, 'a', encoding='utf-8', newline='') as f:
|
| 596 |
+
writer = csv.writer(f)
|
| 597 |
+
for cpsnr_key, cpsnr_value in sorted(cpsnr_averages.items()):
|
| 598 |
+
writer.writerow([cpsnr_key, cpsnr_value])
|
| 599 |
+
|
| 600 |
+
print_info(f"Added {len(cpsnr_averages)} CPSNR metric(s) to {metrics_csv_path}")
|
| 601 |
+
else:
|
| 602 |
+
print_warning(f"Could not update metrics.csv with CPSNR data (file exists: {os.path.exists(metrics_csv_path)}, has data: {bool(cpsnr_averages)})")
|
| 603 |
+
else:
|
| 604 |
+
print_warning(f"cpsnr.json not found at {cpsnr_source}, skipping CPSNR processing")
|
| 605 |
+
|
| 606 |
+
print_footer()
|
| 607 |
+
|
| 608 |
+
def main():
|
| 609 |
+
try:
|
| 610 |
+
asyncio.run(main_async())
|
| 611 |
+
except KeyboardInterrupt:
|
| 612 |
+
print_warning("Interrupted by user (Ctrl+C). Exiting.")
|
| 613 |
+
|
| 614 |
+
|
| 615 |
+
if __name__ == "__main__":
|
| 616 |
+
main()
|
| 617 |
+
|
| 618 |
+
"""
|
| 619 |
+
EXAMPLE USAGE:
|
| 620 |
+
|
| 621 |
+
# Basic usage (individual inference.py calls):
|
| 622 |
+
METHOD=single_caa; ITER=40000; \
|
| 623 |
+
python inference_batch.py \
|
| 624 |
+
--glb_list /home/aaaaa/projects/cvpr2026/glbs_eval_ref.txt \
|
| 625 |
+
--prompt_csv /home/aaaaa/projects/cvpr2026/prompts_bs.csv \
|
| 626 |
+
--hdr_list /home/aaaaa/data/envmaps/hdr_list.txt \
|
| 627 |
+
--ref_root outputs_eval_omega/ref/ \
|
| 628 |
+
--output_dir ./outputs_eval_omega/glbs_${METHOD}_${ITER}/ \
|
| 629 |
+
--extra_inference "--guidance 4.0 --steps 30 --weights outputs/mcdiff_v.${METHOD}/checkpoint-${ITER}/transformer/diffusion_pytorch_model.safetensors --config configs/mcdiff/${METHOD}.yaml" \
|
| 630 |
+
--devices 0 --skip-vis
|
| 631 |
+
|
| 632 |
+
# Grouped inference (faster - uses inference_list.py with groups of 16):
|
| 633 |
+
METHOD=single_caa; ITER=40000; \
|
| 634 |
+
python inference_batch.py \
|
| 635 |
+
--glb_list /home/aaaaa/projects/cvpr2026/glbs_eval_ref.txt \
|
| 636 |
+
--prompt_csv /home/aaaaa/projects/cvpr2026/prompts_bs.csv \
|
| 637 |
+
--hdr_list /home/aaaaa/data/envmaps/hdr_list.txt \
|
| 638 |
+
--ref_root outputs_eval_omega/ref/ \
|
| 639 |
+
--output_dir ./outputs_eval_omega/glbs_${METHOD}_${ITER}/ \
|
| 640 |
+
--extra_inference "--guidance 4.0 --steps 30 --weights outputs/mcdiff_v.${METHOD}/checkpoint-${ITER}/transformer/diffusion_pytorch_model.safetensors --config configs/mcdiff/${METHOD}.yaml" \
|
| 641 |
+
--devices 0 --skip-vis \
|
| 642 |
+
--group_inference
|
| 643 |
+
|
| 644 |
+
# Grouped inference with custom group size:
|
| 645 |
+
METHOD=dual_full; ITER=40000; \
|
| 646 |
+
python inference_batch.py \
|
| 647 |
+
--glb_list /home/aaaaa/projects/cvpr2026/glbs_eval_ref.txt \
|
| 648 |
+
--prompt_csv /home/aaaaa/projects/cvpr2026/prompts_bs.csv \
|
| 649 |
+
--hdr_list /home/aaaaa/data/envmaps/hdr_list.txt \
|
| 650 |
+
--ref_root outputs_eval_omega/ref/ \
|
| 651 |
+
--output_dir ./outputs_eval_zenith/glbs_${METHOD}_${ITER}/ \
|
| 652 |
+
--extra_inference "--guidance 4.0 --steps 30 --weights outputs/mcdiff_v.${METHOD}/checkpoint-${ITER}/transformer/diffusion_pytorch_model.safetensors --config configs/mcdiff/${METHOD}.yaml" \
|
| 653 |
+
--devices 0 --skip-vis \
|
| 654 |
+
--group_inference
|
| 655 |
+
"""
|
| 656 |
+
|
| 657 |
+
# 40000 30000 20000 15000 10000 5000
|
| 658 |
+
# ours baseline1 baseline2
|
home/ubuntu/aaaaa/data/rgbmr/inference_list.py
ADDED
|
@@ -0,0 +1,1270 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Mesh-to-Textured-Mesh Batch Inference Pipeline
|
| 3 |
+
Multiple (mesh + prompt) pairs -> multiple textured .glb files
|
| 4 |
+
|
| 5 |
+
This script efficiently processes N mesh-prompt pairs by loading models once
|
| 6 |
+
and reusing them for all generations, avoiding redundant model loading overhead.
|
| 7 |
+
"""
|
| 8 |
+
|
| 9 |
+
import os
|
| 10 |
+
import tempfile
|
| 11 |
+
from pathlib import Path
|
| 12 |
+
from typing import Tuple, Dict, Any, Optional, List
|
| 13 |
+
import math
|
| 14 |
+
import json
|
| 15 |
+
import filelock
|
| 16 |
+
import hashlib
|
| 17 |
+
|
| 18 |
+
import numpy as np
|
| 19 |
+
import torch
|
| 20 |
+
import torch.nn as nn
|
| 21 |
+
import torch.nn.functional as F
|
| 22 |
+
from diffusers import FlowMatchEulerDiscreteScheduler, SD3Transformer2DModel, AutoencoderKL
|
| 23 |
+
from torchvision.transforms import ToPILImage
|
| 24 |
+
from PIL import Image
|
| 25 |
+
from omegaconf import OmegaConf
|
| 26 |
+
from peft import set_peft_model_state_dict
|
| 27 |
+
from safetensors.torch import load_file
|
| 28 |
+
|
| 29 |
+
from mcgen.utils.model_utils import load_offset_autoencoder, initialize_transformer_weights
|
| 30 |
+
from mcgen.mcdiff.attention_processor_lora import LoRAMessagePassingAttnProcessor
|
| 31 |
+
from mcgen.mcdiff.attention_utils import apply_custom_processors
|
| 32 |
+
from mcgen.utils.text_encoder_utils import load_text_ctx, encode_prompt
|
| 33 |
+
from mcgen.mcdiff.latents import generate_latents
|
| 34 |
+
from mcgen.utils.pipeline_texture import TexturePipeline, ModProcessConfig
|
| 35 |
+
from mcgen.utils.correspondence import build_corr_pairs, downscale_pairs_to_f2l, dilate_f2l, correspondence_psnr
|
| 36 |
+
from mcgen.utils.config import load_config
|
| 37 |
+
from tools.utils.mesh_utils import load_mesh, get_orthogonal_camera, NVDiffRastContextWrapper, render
|
| 38 |
+
from mcgen.utils.image_super_utils import imageSuperNet
|
| 39 |
+
from mcgen.utils.pipeline_utils import ViewProcessor
|
| 40 |
+
from mcgen.utils.uvwrap_utils import mesh_uv_wrap
|
| 41 |
+
from mcgen.utils.geometry_inpaint_utils import texture_inpaint
|
| 42 |
+
from DifferentiableRenderer.MeshRender import MeshRender
|
| 43 |
+
import trimesh
|
| 44 |
+
import copy
|
| 45 |
+
|
| 46 |
+
# =============================================================================
|
| 47 |
+
# Configuration
|
| 48 |
+
# =============================================================================
|
| 49 |
+
|
| 50 |
+
# Model paths
|
| 51 |
+
SD35_REPO = "stabilityai/stable-diffusion-3.5-medium"
|
| 52 |
+
TRANSFORMER_PARTIAL_WEIGHTS = "outputs/mcdiff_v1.9.5/checkpoint-40000/transformer/diffusion_pytorch_model.safetensors"
|
| 53 |
+
SD_VAE_PATH = "./vae_sd35"
|
| 54 |
+
MCVAE_CONFIG_PATH = "./configs/mcvae/config.json"
|
| 55 |
+
MCVAE_CKPT_PATH = "./outputs/mcvae_v1.8.1.pt"
|
| 56 |
+
|
| 57 |
+
# Device & precision
|
| 58 |
+
DEVICE = "cuda"
|
| 59 |
+
DTYPE = torch.float16
|
| 60 |
+
|
| 61 |
+
# Image & view settings
|
| 62 |
+
HEIGHT = WIDTH = 512
|
| 63 |
+
TOKEN_HW = HEIGHT // 16
|
| 64 |
+
NUM_VIEWS = 6
|
| 65 |
+
ELEV_DEG = [0.0, 0.0, 0.0, 0.0, 89.99, -89.99]
|
| 66 |
+
AZIM_DEG = [0.0, 90.0, 180.0, 270.0, 0.0, 0.0]
|
| 67 |
+
|
| 68 |
+
# MaterialMVP azimuth convention (90 degree offset from mvadapter)
|
| 69 |
+
AZIM_DEG_MATERIALMVP = [angle + 90 for angle in AZIM_DEG]
|
| 70 |
+
|
| 71 |
+
# Inference defaults
|
| 72 |
+
GUIDANCE = 4.0
|
| 73 |
+
STEPS = 30
|
| 74 |
+
NEGATIVE_PROMPT = ""
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
# =============================================================================
|
| 78 |
+
# Utility Functions
|
| 79 |
+
# =============================================================================
|
| 80 |
+
|
| 81 |
+
def seed_everything(seed: int = 42):
|
| 82 |
+
"""Seed all random number generators for reproducibility."""
|
| 83 |
+
os.environ["PYTHONHASHSEED"] = str(seed)
|
| 84 |
+
np.random.seed(seed)
|
| 85 |
+
torch.manual_seed(seed)
|
| 86 |
+
torch.cuda.manual_seed_all(seed)
|
| 87 |
+
torch.backends.cudnn.deterministic = True
|
| 88 |
+
torch.backends.cudnn.benchmark = False
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
def _resize_to_pm1(x: torch.Tensor, *, height: int, width: int) -> torch.Tensor:
|
| 92 |
+
"""Resize to target resolution and normalize to [-1, 1]."""
|
| 93 |
+
x = F.interpolate(x, size=(height, width), mode="bilinear", align_corners=False)
|
| 94 |
+
return x * 2.0 - 1.0
|
| 95 |
+
|
| 96 |
+
|
| 97 |
+
def _expand_embeddings_for_views(pe: torch.Tensor, pooled: torch.Tensor, *, num_views: int) -> Tuple[torch.Tensor, torch.Tensor]:
|
| 98 |
+
"""Expand text embeddings from [1, seq, dim] to [num_views, seq, dim]."""
|
| 99 |
+
pe = pe.expand(1, -1, -1).unsqueeze(1).expand(-1, num_views, -1, -1).reshape(-1, pe.shape[1], pe.shape[2])
|
| 100 |
+
pooled = pooled.expand(1, -1).unsqueeze(1).expand(-1, num_views, -1).reshape(-1, pooled.shape[-1])
|
| 101 |
+
return pe, pooled
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
@torch.no_grad()
|
| 105 |
+
def _save_strip(tensor_bchw: torch.Tensor, path: str, nrow: int = 1) -> str:
|
| 106 |
+
"""Save grid of images from batched tensors in [-1, 1] range.
|
| 107 |
+
|
| 108 |
+
Args:
|
| 109 |
+
tensor_bchw: Batch of images in BCHW format, range [-1, 1]
|
| 110 |
+
path: Output path for saved image
|
| 111 |
+
nrow: Number of rows in grid. If 1, creates horizontal strip.
|
| 112 |
+
"""
|
| 113 |
+
|
| 114 |
+
to_pil = ToPILImage()
|
| 115 |
+
b, c, h, w = tensor_bchw.shape
|
| 116 |
+
|
| 117 |
+
nrow = max(1, nrow)
|
| 118 |
+
ncol = math.ceil(b / nrow)
|
| 119 |
+
|
| 120 |
+
canvas = Image.new('RGB', (ncol * w, nrow * h))
|
| 121 |
+
for i in range(b):
|
| 122 |
+
row = i // ncol
|
| 123 |
+
col = i % ncol
|
| 124 |
+
img = to_pil((tensor_bchw[i].clamp(-1, 1) * 0.5 + 0.5))
|
| 125 |
+
canvas.paste(img, (col * w, row * h))
|
| 126 |
+
|
| 127 |
+
os.makedirs(os.path.dirname(path), exist_ok=True)
|
| 128 |
+
canvas.save(path)
|
| 129 |
+
return path
|
| 130 |
+
|
| 131 |
+
|
| 132 |
+
def _update_cpsnr_json(json_path: str, gid: str, metrics: Dict[str, float]) -> None:
|
| 133 |
+
"""Thread-safe update of cpsnr.json file using file locking.
|
| 134 |
+
|
| 135 |
+
Args:
|
| 136 |
+
json_path: Path to cpsnr.json file
|
| 137 |
+
gid: Geometry ID (mesh filename stem)
|
| 138 |
+
metrics: Dictionary containing 'albedo', 'roughness', 'metallic', 'total' keys
|
| 139 |
+
"""
|
| 140 |
+
lock_path = json_path + ".lock"
|
| 141 |
+
lock = filelock.FileLock(lock_path, timeout=60)
|
| 142 |
+
|
| 143 |
+
try:
|
| 144 |
+
with lock:
|
| 145 |
+
# Read existing data
|
| 146 |
+
if os.path.exists(json_path):
|
| 147 |
+
with open(json_path, "r") as f:
|
| 148 |
+
data = json.load(f)
|
| 149 |
+
else:
|
| 150 |
+
data = {}
|
| 151 |
+
|
| 152 |
+
# Update with new metrics
|
| 153 |
+
data[gid] = metrics
|
| 154 |
+
|
| 155 |
+
# Write back atomically using temp file + rename
|
| 156 |
+
temp_path = json_path + ".tmp"
|
| 157 |
+
with open(temp_path, "w") as f:
|
| 158 |
+
json.dump(data, f, indent=2)
|
| 159 |
+
os.replace(temp_path, json_path)
|
| 160 |
+
finally:
|
| 161 |
+
# Clean up lock file if it exists and we can remove it
|
| 162 |
+
try:
|
| 163 |
+
if os.path.exists(lock_path):
|
| 164 |
+
os.remove(lock_path)
|
| 165 |
+
except OSError:
|
| 166 |
+
pass
|
| 167 |
+
|
| 168 |
+
|
| 169 |
+
# =============================================================================
|
| 170 |
+
# Reusable Model Context (loaded once, used for all meshes)
|
| 171 |
+
# =============================================================================
|
| 172 |
+
|
| 173 |
+
class UnprojectionContext:
|
| 174 |
+
"""Manages reusable models for unprojection to avoid repeated loading."""
|
| 175 |
+
|
| 176 |
+
def __init__(
|
| 177 |
+
self,
|
| 178 |
+
*,
|
| 179 |
+
render_size: int = 512,
|
| 180 |
+
texture_size: int = 4096,
|
| 181 |
+
realesrgan_ckpt: str,
|
| 182 |
+
device: str = "cuda",
|
| 183 |
+
):
|
| 184 |
+
self.render_size = render_size
|
| 185 |
+
self.texture_size = texture_size
|
| 186 |
+
self.device = device
|
| 187 |
+
|
| 188 |
+
# Load super-resolution model once
|
| 189 |
+
self.super_model = imageSuperNet(realesrgan_ckpt)
|
| 190 |
+
|
| 191 |
+
def unproject_with_materialmvp(
|
| 192 |
+
self,
|
| 193 |
+
mesh_path: str,
|
| 194 |
+
output_dir: str,
|
| 195 |
+
gid: str,
|
| 196 |
+
albedo_images: List[Image.Image],
|
| 197 |
+
mr_images: List[Image.Image],
|
| 198 |
+
*,
|
| 199 |
+
bake_exp: int = 2,
|
| 200 |
+
selected_view_weights: Optional[List[float]] = None,
|
| 201 |
+
) -> str:
|
| 202 |
+
"""Unproject multi-view images to textured mesh using MaterialMVP approach.
|
| 203 |
+
|
| 204 |
+
Uses pre-loaded super-resolution model from context.
|
| 205 |
+
|
| 206 |
+
Args:
|
| 207 |
+
mesh_path: Path to input mesh
|
| 208 |
+
output_dir: Output directory
|
| 209 |
+
gid: Geometry ID (mesh filename stem)
|
| 210 |
+
albedo_images: List of albedo views (6 images)
|
| 211 |
+
mr_images: List of metallic-roughness views (6 images, ORM format)
|
| 212 |
+
bake_exp: Baking exponent for view weighting
|
| 213 |
+
selected_view_weights: Optional list of per-view weights
|
| 214 |
+
|
| 215 |
+
Returns:
|
| 216 |
+
Path to saved GLB file
|
| 217 |
+
"""
|
| 218 |
+
# Initialize renderer (lightweight, can be created per mesh)
|
| 219 |
+
render_obj = MeshRender(
|
| 220 |
+
default_resolution=self.render_size,
|
| 221 |
+
texture_size=self.texture_size,
|
| 222 |
+
bake_mode="back_sample",
|
| 223 |
+
raster_mode="cr",
|
| 224 |
+
)
|
| 225 |
+
|
| 226 |
+
# Load and prepare mesh
|
| 227 |
+
mesh = trimesh.load(mesh_path)
|
| 228 |
+
mesh = mesh_uv_wrap(mesh)
|
| 229 |
+
render_obj.load_mesh(mesh=mesh)
|
| 230 |
+
|
| 231 |
+
# Initialize view processor
|
| 232 |
+
view_processor = ViewProcessor(bake_exp, render_obj)
|
| 233 |
+
|
| 234 |
+
# Use MaterialMVP azimuth convention (90-degree offset)
|
| 235 |
+
selected_camera_azims = AZIM_DEG_MATERIALMVP
|
| 236 |
+
selected_camera_elevs = ELEV_DEG
|
| 237 |
+
|
| 238 |
+
if selected_view_weights is None:
|
| 239 |
+
selected_view_weights = [1, 1, 1, 1, 0.5, 0.5]
|
| 240 |
+
|
| 241 |
+
# Prepare images dictionary
|
| 242 |
+
multiviews_pbr = {
|
| 243 |
+
"albedo": albedo_images,
|
| 244 |
+
"mr": mr_images,
|
| 245 |
+
}
|
| 246 |
+
|
| 247 |
+
# Enhance images using pre-loaded super model
|
| 248 |
+
enhance_images = {
|
| 249 |
+
"albedo": copy.deepcopy(multiviews_pbr["albedo"]),
|
| 250 |
+
"mr": copy.deepcopy(multiviews_pbr["mr"]),
|
| 251 |
+
}
|
| 252 |
+
|
| 253 |
+
for i in range(len(enhance_images["albedo"])):
|
| 254 |
+
enhance_images["albedo"][i] = self.super_model(enhance_images["albedo"][i])
|
| 255 |
+
enhance_images["mr"][i] = self.super_model(enhance_images["mr"][i])
|
| 256 |
+
|
| 257 |
+
# Resize to double render size for better quality
|
| 258 |
+
for i in range(len(enhance_images["albedo"])):
|
| 259 |
+
enhance_images["albedo"][i] = enhance_images["albedo"][i].resize(
|
| 260 |
+
(self.render_size * 2, self.render_size * 2), Image.LANCZOS
|
| 261 |
+
)
|
| 262 |
+
enhance_images["mr"][i] = enhance_images["mr"][i].resize(
|
| 263 |
+
(self.render_size * 2, self.render_size * 2), Image.LANCZOS
|
| 264 |
+
)
|
| 265 |
+
|
| 266 |
+
# Bake albedo texture
|
| 267 |
+
texture, mask = view_processor.bake_from_multiview(
|
| 268 |
+
enhance_images["albedo"],
|
| 269 |
+
selected_camera_elevs,
|
| 270 |
+
selected_camera_azims,
|
| 271 |
+
selected_view_weights
|
| 272 |
+
)
|
| 273 |
+
|
| 274 |
+
# Bake metallic-roughness texture
|
| 275 |
+
texture_mr, mask_mr = view_processor.bake_from_multiview(
|
| 276 |
+
enhance_images["mr"],
|
| 277 |
+
selected_camera_elevs,
|
| 278 |
+
selected_camera_azims,
|
| 279 |
+
selected_view_weights
|
| 280 |
+
)
|
| 281 |
+
|
| 282 |
+
# Convert baked masks to boolean tensors
|
| 283 |
+
mask_bool = (mask.squeeze(-1) > 0.5).to(torch.bool)
|
| 284 |
+
mask_mr_bool = (mask_mr.squeeze(-1) > 0.5).to(torch.bool)
|
| 285 |
+
|
| 286 |
+
# Apply geometry-aware inpainting for albedo texture
|
| 287 |
+
texture_inpainted = texture_inpaint(
|
| 288 |
+
texture,
|
| 289 |
+
mask_bool,
|
| 290 |
+
render_obj,
|
| 291 |
+
uv_mask_erode_iters=10,
|
| 292 |
+
baked_mask_erode_iters=2,
|
| 293 |
+
vertex_merge_tolerance=1e-5,
|
| 294 |
+
vertex_color_K=11,
|
| 295 |
+
)
|
| 296 |
+
render_obj.set_texture(texture_inpainted, force_set=True)
|
| 297 |
+
|
| 298 |
+
# Apply geometry-aware inpainting for metallic-roughness texture
|
| 299 |
+
texture_mr_inpainted = texture_inpaint(
|
| 300 |
+
texture_mr,
|
| 301 |
+
mask_mr_bool,
|
| 302 |
+
render_obj,
|
| 303 |
+
uv_mask_erode_iters=10,
|
| 304 |
+
baked_mask_erode_iters=2,
|
| 305 |
+
vertex_merge_tolerance=1e-5,
|
| 306 |
+
vertex_color_K=11,
|
| 307 |
+
)
|
| 308 |
+
render_obj.set_texture_mr(texture_mr_inpainted)
|
| 309 |
+
|
| 310 |
+
# Save mesh
|
| 311 |
+
os.makedirs(output_dir, exist_ok=True)
|
| 312 |
+
output_path = os.path.join(output_dir, f"{gid}.glb")
|
| 313 |
+
render_obj.save_mesh(output_path, downsample=False)
|
| 314 |
+
|
| 315 |
+
return output_path
|
| 316 |
+
|
| 317 |
+
|
| 318 |
+
class InferenceContext:
|
| 319 |
+
"""Manages all pre-loaded models for efficient batch inference."""
|
| 320 |
+
|
| 321 |
+
def __init__(self, settings: Dict[str, Any]):
|
| 322 |
+
self.settings = settings
|
| 323 |
+
self.device = settings["device"]
|
| 324 |
+
self.dtype = settings["dtype"]
|
| 325 |
+
|
| 326 |
+
# Load VAE models once
|
| 327 |
+
self.vae, self.mcvae = self._load_vae_models()
|
| 328 |
+
self.vae_shift = self.vae.config.shift_factor
|
| 329 |
+
self.vae_scale = self.vae.config.scaling_factor
|
| 330 |
+
|
| 331 |
+
# Load transformer and scheduler once
|
| 332 |
+
self.transformer, self.scheduler = self._setup_transformer()
|
| 333 |
+
|
| 334 |
+
# Load text encoders once
|
| 335 |
+
self.text_ctx = self._load_text_ctx()
|
| 336 |
+
|
| 337 |
+
# Initialize unprojection context if using materialmvp backend
|
| 338 |
+
self.unproj_ctx = None
|
| 339 |
+
if settings.get("unproj_backend") == "materialmvp":
|
| 340 |
+
self.unproj_ctx = UnprojectionContext(
|
| 341 |
+
render_size=settings["resolution"],
|
| 342 |
+
texture_size=settings["uv_size"],
|
| 343 |
+
realesrgan_ckpt=settings["realesrgan_ckpt"],
|
| 344 |
+
device=self.device,
|
| 345 |
+
)
|
| 346 |
+
|
| 347 |
+
# Initialize texture pipeline if using mvadapter backend
|
| 348 |
+
self.tex_pipeline = None
|
| 349 |
+
if settings.get("unproj_backend") == "mvadapter":
|
| 350 |
+
self.tex_pipeline = TexturePipeline(
|
| 351 |
+
upscaler_ckpt_path=settings["realesrgan_ckpt"],
|
| 352 |
+
inpaint_ckpt_path=settings["lama_ckpt"],
|
| 353 |
+
device=self.device,
|
| 354 |
+
)
|
| 355 |
+
|
| 356 |
+
def _load_vae_models(self):
|
| 357 |
+
"""Load VAE and optionally mcVAE."""
|
| 358 |
+
use_dual_branch = self.settings["use_dual_branch"]
|
| 359 |
+
|
| 360 |
+
if use_dual_branch:
|
| 361 |
+
vae = AutoencoderKL.from_pretrained(self.settings["sd_vae_path"])
|
| 362 |
+
vae = vae.to(device=self.device, dtype=self.dtype).eval().requires_grad_(False)
|
| 363 |
+
return vae, None
|
| 364 |
+
|
| 365 |
+
vae, mcvae = load_offset_autoencoder(
|
| 366 |
+
sd_vae_path=self.settings["sd_vae_path"],
|
| 367 |
+
mcvae_config_path=self.settings["mcvae_config_path"],
|
| 368 |
+
mcvae_ckpt_path=self.settings["mcvae_ckpt_path"],
|
| 369 |
+
offset_mode=self.settings.get("mcvae_offset_mode", True),
|
| 370 |
+
)
|
| 371 |
+
vae = vae.to(device=self.device, dtype=self.dtype).eval().requires_grad_(False)
|
| 372 |
+
mcvae = mcvae.to(device=self.device, dtype=self.dtype).eval().requires_grad_(False)
|
| 373 |
+
return vae, mcvae
|
| 374 |
+
|
| 375 |
+
def _setup_transformer(self):
|
| 376 |
+
"""Setup transformer with structural modifications and load weights."""
|
| 377 |
+
transformer = SD3Transformer2DModel.from_pretrained(
|
| 378 |
+
self.settings["base_model"],
|
| 379 |
+
subfolder="transformer"
|
| 380 |
+
)
|
| 381 |
+
|
| 382 |
+
model_cfg = self.settings["model_cfg"]
|
| 383 |
+
num_views = self.settings["num_views"]
|
| 384 |
+
use_caa = self.settings["use_caa"]
|
| 385 |
+
use_rope = self.settings["use_rope"]
|
| 386 |
+
use_global_token = self.settings["use_global_token"]
|
| 387 |
+
use_dual_branch = self.settings["use_dual_branch"]
|
| 388 |
+
condition_channels = self.settings["condition_channels_cfg"]
|
| 389 |
+
|
| 390 |
+
num_domains = 2 if use_dual_branch else 1
|
| 391 |
+
print(f'use_dual_branch: {use_dual_branch}, use_caa: {use_caa}, use_rope: {use_rope}, use_global_token: {use_global_token}, num_views: {num_views}, num_domains: {num_domains}')
|
| 392 |
+
|
| 393 |
+
# Apply LoRA processors
|
| 394 |
+
self._apply_lora_processors(
|
| 395 |
+
transformer,
|
| 396 |
+
model_cfg,
|
| 397 |
+
use_caa=use_caa,
|
| 398 |
+
use_rope=use_rope,
|
| 399 |
+
use_global_token=use_global_token,
|
| 400 |
+
num_views=num_views,
|
| 401 |
+
num_domains=num_domains,
|
| 402 |
+
)
|
| 403 |
+
|
| 404 |
+
# Extend position embedding input channels if needed
|
| 405 |
+
if condition_channels > 0:
|
| 406 |
+
self._extend_pos_embed_in(transformer, condition_channels)
|
| 407 |
+
|
| 408 |
+
# Load weights
|
| 409 |
+
self._load_transformer_weights(
|
| 410 |
+
transformer,
|
| 411 |
+
self.settings["weights_path"],
|
| 412 |
+
condition_channels=condition_channels,
|
| 413 |
+
)
|
| 414 |
+
|
| 415 |
+
transformer = transformer.to(device=self.device, dtype=self.dtype).eval().requires_grad_(False)
|
| 416 |
+
|
| 417 |
+
scheduler = FlowMatchEulerDiscreteScheduler.from_pretrained(
|
| 418 |
+
self.settings["base_model"],
|
| 419 |
+
subfolder="scheduler"
|
| 420 |
+
)
|
| 421 |
+
|
| 422 |
+
return transformer, scheduler
|
| 423 |
+
|
| 424 |
+
def _apply_lora_processors(
|
| 425 |
+
self,
|
| 426 |
+
transformer: SD3Transformer2DModel,
|
| 427 |
+
model_cfg,
|
| 428 |
+
*,
|
| 429 |
+
use_caa: bool,
|
| 430 |
+
use_rope: bool,
|
| 431 |
+
use_global_token: bool,
|
| 432 |
+
num_views: int,
|
| 433 |
+
num_domains: int,
|
| 434 |
+
) -> None:
|
| 435 |
+
"""Attach LoRA message-passing processors."""
|
| 436 |
+
lcfg = getattr(model_cfg, "attn_lora", None)
|
| 437 |
+
if not lcfg or not getattr(lcfg, "enabled", False):
|
| 438 |
+
return
|
| 439 |
+
|
| 440 |
+
select = {"joint": True, "self": bool(getattr(lcfg, "apply_to_self", False))}
|
| 441 |
+
limits = {}
|
| 442 |
+
if getattr(lcfg, "limit_joint", None) is not None:
|
| 443 |
+
limits["joint"] = lcfg.limit_joint
|
| 444 |
+
if getattr(lcfg, "limit_self", None) is not None:
|
| 445 |
+
limits["self"] = lcfg.limit_self
|
| 446 |
+
|
| 447 |
+
def processor_factory(_name, _mod, query_dim, inner_dim, out_dim, num_heads, kind="joint"):
|
| 448 |
+
return LoRAMessagePassingAttnProcessor(
|
| 449 |
+
r_q=int(getattr(lcfg, "r_q", 8)),
|
| 450 |
+
r_k=int(getattr(lcfg, "r_k", 8)),
|
| 451 |
+
r_v=int(getattr(lcfg, "r_v", 8)),
|
| 452 |
+
alpha_q=float(getattr(lcfg, "alpha_q", getattr(lcfg, "r_q", 8))),
|
| 453 |
+
alpha_k=float(getattr(lcfg, "alpha_k", getattr(lcfg, "r_k", 8))),
|
| 454 |
+
alpha_v=float(getattr(lcfg, "alpha_v", getattr(lcfg, "r_v", 8))),
|
| 455 |
+
query_dim=query_dim,
|
| 456 |
+
inner_dim=inner_dim,
|
| 457 |
+
out_dim=out_dim,
|
| 458 |
+
num_heads=num_heads,
|
| 459 |
+
num_views=num_views,
|
| 460 |
+
num_domains=num_domains,
|
| 461 |
+
use_caa=use_caa,
|
| 462 |
+
use_rope=use_rope,
|
| 463 |
+
use_global_token=use_global_token,
|
| 464 |
+
kind=kind,
|
| 465 |
+
)
|
| 466 |
+
|
| 467 |
+
apply_custom_processors(
|
| 468 |
+
transformer,
|
| 469 |
+
model_cfg,
|
| 470 |
+
processor_factory,
|
| 471 |
+
select=select,
|
| 472 |
+
limits=limits if limits else None,
|
| 473 |
+
as_factory=True,
|
| 474 |
+
)
|
| 475 |
+
|
| 476 |
+
def _extend_pos_embed_in(self, transformer: SD3Transformer2DModel, extra_channels: int) -> None:
|
| 477 |
+
"""Extend transformer's pos_embed input channels."""
|
| 478 |
+
conv = transformer.pos_embed.proj
|
| 479 |
+
new_conv = nn.Conv2d(
|
| 480 |
+
in_channels=conv.in_channels + extra_channels,
|
| 481 |
+
out_channels=conv.out_channels,
|
| 482 |
+
kernel_size=conv.kernel_size, stride=conv.stride, padding=conv.padding,
|
| 483 |
+
dilation=conv.dilation, groups=conv.groups, padding_mode=conv.padding_mode,
|
| 484 |
+
device=conv.weight.device, dtype=conv.weight.dtype,
|
| 485 |
+
)
|
| 486 |
+
with torch.no_grad():
|
| 487 |
+
new_conv.weight.zero_()
|
| 488 |
+
new_conv.weight[:, :conv.in_channels].copy_(conv.weight)
|
| 489 |
+
new_conv.bias.copy_(conv.bias)
|
| 490 |
+
transformer.pos_embed.proj = new_conv
|
| 491 |
+
|
| 492 |
+
def _load_conditioning_conv(self, transformer: SD3Transformer2DModel, cond_path: Path) -> None:
|
| 493 |
+
"""Load conditioning convolution weights if available."""
|
| 494 |
+
if not cond_path.exists():
|
| 495 |
+
return
|
| 496 |
+
state = load_file(str(cond_path))
|
| 497 |
+
weight_key = "pos_embed.proj.weight"
|
| 498 |
+
bias_key = "pos_embed.proj.bias"
|
| 499 |
+
pe_proj = transformer.pos_embed.proj
|
| 500 |
+
if weight_key in state:
|
| 501 |
+
pe_proj.weight.data.copy_(state[weight_key].to(pe_proj.weight.device, dtype=pe_proj.weight.dtype))
|
| 502 |
+
if bias_key in state:
|
| 503 |
+
pe_proj.bias.data.copy_(state[bias_key].to(pe_proj.bias.device, dtype=pe_proj.bias.dtype))
|
| 504 |
+
|
| 505 |
+
def _load_transformer_weights(
|
| 506 |
+
self,
|
| 507 |
+
transformer: SD3Transformer2DModel,
|
| 508 |
+
weights_path: Optional[str],
|
| 509 |
+
*,
|
| 510 |
+
condition_channels: int,
|
| 511 |
+
) -> None:
|
| 512 |
+
"""Load transformer weights from checkpoint."""
|
| 513 |
+
if not weights_path:
|
| 514 |
+
return
|
| 515 |
+
path = Path(weights_path)
|
| 516 |
+
|
| 517 |
+
if path.is_dir():
|
| 518 |
+
full_dir = path / "transformer"
|
| 519 |
+
lora_path = path / "pytorch_lora_weights.safetensors"
|
| 520 |
+
cond_path = path / "pytorch_cond_conv_weights.safetensors"
|
| 521 |
+
|
| 522 |
+
if full_dir.is_dir():
|
| 523 |
+
initialize_transformer_weights(transformer, str(full_dir))
|
| 524 |
+
elif lora_path.exists():
|
| 525 |
+
lora_state = load_file(str(lora_path))
|
| 526 |
+
set_peft_model_state_dict(transformer, lora_state, adapter_name="default")
|
| 527 |
+
else:
|
| 528 |
+
initialize_transformer_weights(transformer, str(path))
|
| 529 |
+
|
| 530 |
+
if condition_channels > 0:
|
| 531 |
+
self._load_conditioning_conv(transformer, cond_path)
|
| 532 |
+
return
|
| 533 |
+
|
| 534 |
+
initialize_transformer_weights(transformer, str(path))
|
| 535 |
+
|
| 536 |
+
def _load_text_ctx(self):
|
| 537 |
+
"""Load text encoding context (tokenizers and encoders)."""
|
| 538 |
+
return load_text_ctx(
|
| 539 |
+
device=self.device,
|
| 540 |
+
dtype=self.dtype,
|
| 541 |
+
sd_model_name=self.settings["base_model"]
|
| 542 |
+
)
|
| 543 |
+
|
| 544 |
+
def encode_text_prompts(self, prompt: str, negative_prompt: str) -> Dict[str, torch.Tensor]:
|
| 545 |
+
"""Encode text prompts using pre-loaded text context."""
|
| 546 |
+
num_views = self.settings["num_views"]
|
| 547 |
+
|
| 548 |
+
pe, pooled = encode_prompt(
|
| 549 |
+
self.text_ctx["encoders"],
|
| 550 |
+
self.text_ctx["tokenizers"],
|
| 551 |
+
prompt,
|
| 552 |
+
max_sequence_length=77
|
| 553 |
+
)
|
| 554 |
+
npe, npooled = encode_prompt(
|
| 555 |
+
self.text_ctx["encoders"],
|
| 556 |
+
self.text_ctx["tokenizers"],
|
| 557 |
+
negative_prompt,
|
| 558 |
+
max_sequence_length=77
|
| 559 |
+
)
|
| 560 |
+
|
| 561 |
+
# Move to device and expand for all views
|
| 562 |
+
pe, pooled = pe.to(device=self.device, dtype=self.dtype), pooled.to(device=self.device, dtype=self.dtype)
|
| 563 |
+
npe, npooled = npe.to(device=self.device, dtype=self.dtype), npooled.to(device=self.device, dtype=self.dtype)
|
| 564 |
+
|
| 565 |
+
pe, pooled = _expand_embeddings_for_views(pe, pooled, num_views=num_views)
|
| 566 |
+
npe, npooled = _expand_embeddings_for_views(npe, npooled, num_views=num_views)
|
| 567 |
+
|
| 568 |
+
return {
|
| 569 |
+
"prompt_embeds": pe,
|
| 570 |
+
"pooled_prompt_embeds": pooled,
|
| 571 |
+
"negative_prompt_embeds": npe,
|
| 572 |
+
"negative_pooled_prompt_embeds": npooled,
|
| 573 |
+
}
|
| 574 |
+
|
| 575 |
+
def encode_conditions(self, cond_values: torch.Tensor) -> torch.Tensor:
|
| 576 |
+
"""Encode 6-channel condition values into latent space."""
|
| 577 |
+
height = self.settings["resolution"]
|
| 578 |
+
width = self.settings["resolution"]
|
| 579 |
+
|
| 580 |
+
cond_values = cond_values.to(device=self.vae.device, dtype=self.vae.dtype)
|
| 581 |
+
lat_chunks = []
|
| 582 |
+
for c in range(0, cond_values.shape[1], 3):
|
| 583 |
+
posterior = self.vae.encode(cond_values[:, c:c+3]).latent_dist
|
| 584 |
+
lat_chunks.append(posterior.mean)
|
| 585 |
+
|
| 586 |
+
cond_latents = torch.cat(lat_chunks, dim=1)
|
| 587 |
+
cond_latents = (cond_latents - self.vae_shift) * self.vae_scale
|
| 588 |
+
Cc = cond_latents.shape[1]
|
| 589 |
+
cond_latents = cond_latents.reshape(-1, Cc, height // 8, width // 8)
|
| 590 |
+
return cond_latents.to(dtype=self.dtype)
|
| 591 |
+
|
| 592 |
+
|
| 593 |
+
# =============================================================================
|
| 594 |
+
# Rendering & Preprocessing
|
| 595 |
+
# =============================================================================
|
| 596 |
+
|
| 597 |
+
@torch.no_grad()
|
| 598 |
+
def render_views(
|
| 599 |
+
mesh_path: str,
|
| 600 |
+
*,
|
| 601 |
+
num_views: int,
|
| 602 |
+
height: int,
|
| 603 |
+
width: int,
|
| 604 |
+
device: str = DEVICE,
|
| 605 |
+
) -> Dict[str, Any]:
|
| 606 |
+
"""Render multi-view geometry attributes from mesh."""
|
| 607 |
+
ctx = NVDiffRastContextWrapper(device=device, context_type="cuda")
|
| 608 |
+
mesh = load_mesh(str(mesh_path), rescale=True, move_to_center=True, flip_uv=True, device=device)
|
| 609 |
+
if len(ELEV_DEG) != num_views or len(AZIM_DEG) != num_views:
|
| 610 |
+
raise ValueError("ELEV_DEG and AZIM_DEG presets must match num_views.")
|
| 611 |
+
|
| 612 |
+
cams = get_orthogonal_camera(
|
| 613 |
+
elevation_deg=ELEV_DEG,
|
| 614 |
+
azimuth_deg=AZIM_DEG,
|
| 615 |
+
distance=[1.0] * num_views,
|
| 616 |
+
left=-0.55,
|
| 617 |
+
right=0.55,
|
| 618 |
+
bottom=-0.55,
|
| 619 |
+
top=0.55,
|
| 620 |
+
device=device, dtype=torch.float32,
|
| 621 |
+
)
|
| 622 |
+
|
| 623 |
+
# Build 5-channel texture override: [rgb, roughness(0), roughness, metallic]
|
| 624 |
+
tex_ovr = torch.cat([mesh.texture, torch.zeros_like(mesh.roughness), mesh.roughness, mesh.metallic], dim=-1)
|
| 625 |
+
|
| 626 |
+
out = render(ctx, mesh, cams, height=height, width=width, render_attr=True, texture_override=tex_ovr)
|
| 627 |
+
|
| 628 |
+
# Extract and normalize geometry attributes
|
| 629 |
+
attr = out.attr
|
| 630 |
+
rgb = attr[..., :3].contiguous()
|
| 631 |
+
orm = attr[..., 3:6].contiguous()
|
| 632 |
+
orm[..., 0] = (orm[..., 0] < 0.25).float() # Binary occupancy mask
|
| 633 |
+
|
| 634 |
+
return {
|
| 635 |
+
"albedo": rgb,
|
| 636 |
+
"orm": orm,
|
| 637 |
+
"pos": out.pos + 0.5, # [-0.5, 0.5] -> [0, 1]
|
| 638 |
+
"normal": (out.normal + 1.0) * 0.5, # [-1, 1] -> [0, 1]
|
| 639 |
+
"depth": out.raw_depth,
|
| 640 |
+
"c2w": cams.c2w,
|
| 641 |
+
"scale": torch.tensor(1.1, device=device),
|
| 642 |
+
}
|
| 643 |
+
|
| 644 |
+
|
| 645 |
+
def preprocess_geometry(
|
| 646 |
+
rend: Dict[str, Any],
|
| 647 |
+
*,
|
| 648 |
+
device: str,
|
| 649 |
+
dtype: torch.dtype,
|
| 650 |
+
height: int,
|
| 651 |
+
width: int,
|
| 652 |
+
token_hw: int,
|
| 653 |
+
num_views: int,
|
| 654 |
+
use_caa: bool,
|
| 655 |
+
corr_dilate_iterations: int = 1,
|
| 656 |
+
use_global_pos: bool = False,
|
| 657 |
+
) -> Dict[str, torch.Tensor]:
|
| 658 |
+
"""Preprocess rendered geometry into model-ready format."""
|
| 659 |
+
# Convert to channel-first
|
| 660 |
+
albedo = rend["albedo"].permute(0, 3, 1, 2).contiguous()
|
| 661 |
+
orm = rend["orm"].permute(0, 3, 1, 2).contiguous()
|
| 662 |
+
pos = rend["pos"].permute(0, 3, 1, 2).contiguous()
|
| 663 |
+
normal = rend["normal"].permute(0, 3, 1, 2).contiguous()
|
| 664 |
+
depth = rend["depth"]
|
| 665 |
+
c2w = rend["c2w"]
|
| 666 |
+
scale = rend["scale"]
|
| 667 |
+
|
| 668 |
+
V, _, H_raw, W_raw = albedo.shape
|
| 669 |
+
assert V == num_views, f"Expected {num_views} views, got {V}"
|
| 670 |
+
|
| 671 |
+
# Normal in [-1, 1] for correspondence computation
|
| 672 |
+
normal_fullres = normal * 2.0 - 1.0
|
| 673 |
+
|
| 674 |
+
# Mask from occupancy channel
|
| 675 |
+
mask = orm[:, 0, :, :] > 0.5
|
| 676 |
+
depth = depth.clone()
|
| 677 |
+
depth[~mask] = float("nan")
|
| 678 |
+
|
| 679 |
+
# Position tokens via weighted averaging at token grid
|
| 680 |
+
pos_raw = pos - 0.5 # Convert to [-0.5, 0.5]
|
| 681 |
+
df = H_raw // token_hw
|
| 682 |
+
mask_f = mask.float().unsqueeze(1)
|
| 683 |
+
pos_sum = F.avg_pool2d(pos_raw * mask_f, kernel_size=df, stride=df, divisor_override=1)
|
| 684 |
+
cnt = F.avg_pool2d(mask_f, kernel_size=df, stride=df, divisor_override=1)
|
| 685 |
+
pos_token = (pos_sum / cnt.clamp_min(1.0)).to(device=device, dtype=dtype)
|
| 686 |
+
|
| 687 |
+
# Resize and normalize to [-1, 1]
|
| 688 |
+
albedo_r = _resize_to_pm1(albedo, height=height, width=width)
|
| 689 |
+
orm_r = _resize_to_pm1(orm, height=height, width=width)
|
| 690 |
+
pos_r = _resize_to_pm1(pos, height=height, width=width)
|
| 691 |
+
normal_r = _resize_to_pm1(normal, height=height, width=width)
|
| 692 |
+
|
| 693 |
+
# Condition values: position + normal (6 channels)
|
| 694 |
+
cond_values = torch.cat([pos_r, normal_r], dim=1)
|
| 695 |
+
|
| 696 |
+
# World-to-camera transformation
|
| 697 |
+
w2c = torch.linalg.inv(c2w).to(device=device, dtype=dtype)
|
| 698 |
+
|
| 699 |
+
corr_f2l = None
|
| 700 |
+
corr_f2l_highres = None
|
| 701 |
+
pos_delta = None
|
| 702 |
+
|
| 703 |
+
# Build correspondence pairs
|
| 704 |
+
corr_pairs = build_corr_pairs(
|
| 705 |
+
c2w, scale, depth, nor_w=normal_fullres.permute(0, 2, 3, 1),
|
| 706 |
+
depth_tol=0.01, angle_tol=10, angle_cam_tol=80,
|
| 707 |
+
)
|
| 708 |
+
|
| 709 |
+
# High resolution correspondence for c-PSNR evaluation
|
| 710 |
+
corr_high = downscale_pairs_to_f2l(corr_pairs, out_hw=height, device=device)
|
| 711 |
+
corr_f2l_highres = corr_high.unsqueeze(0) # (1, V*H*W, K) for later use
|
| 712 |
+
|
| 713 |
+
# Downscale to token resolution
|
| 714 |
+
corr_low = downscale_pairs_to_f2l(corr_pairs, out_hw=token_hw, device=device)
|
| 715 |
+
for _ in range(corr_dilate_iterations):
|
| 716 |
+
corr_low = dilate_f2l(corr_low, V=num_views, out_hw=token_hw)
|
| 717 |
+
|
| 718 |
+
# Convert to global indices (single batch)
|
| 719 |
+
corr_f2l = corr_low.unsqueeze(0) # (1, Lq, K)
|
| 720 |
+
B, Lq, K = corr_f2l.shape
|
| 721 |
+
X = Lq
|
| 722 |
+
base = torch.arange(B, device=corr_f2l.device).view(B, 1, 1) * X
|
| 723 |
+
corr_f2l = torch.where(corr_f2l >= 0, corr_f2l + base, corr_f2l)
|
| 724 |
+
corr_f2l = corr_f2l.reshape(B * Lq, K) # (M, K) where M = num_views * token_hw * token_hw
|
| 725 |
+
|
| 726 |
+
# Compute position deltas
|
| 727 |
+
M = num_views * token_hw * token_hw
|
| 728 |
+
|
| 729 |
+
# pos_token: (V, 3, token_hw, token_hw) -> (M, 3)
|
| 730 |
+
pos_w_flat = pos_token.view(num_views, 3, -1).permute(0, 2, 1).reshape(M, 3) # (M, 3)
|
| 731 |
+
|
| 732 |
+
# Get camera transforms for each query token
|
| 733 |
+
batch_ids = torch.arange(num_views, device=device).repeat_interleave(token_hw * token_hw) # (M,)
|
| 734 |
+
|
| 735 |
+
# Use identity matrix for global frame, or w2c for local query frame
|
| 736 |
+
if use_global_pos:
|
| 737 |
+
# Global frame: use identity matrix (no transformation)
|
| 738 |
+
Tq = torch.eye(4, device=device, dtype=w2c.dtype).unsqueeze(0).expand(M, -1, -1) # (M, 4, 4)
|
| 739 |
+
else:
|
| 740 |
+
# Local query frame: use w2c transformation
|
| 741 |
+
Tq = w2c[batch_ids] # (M, 4, 4)
|
| 742 |
+
|
| 743 |
+
# Transform query positions to camera space
|
| 744 |
+
ones_M = torch.ones(M, 1, device=device, dtype=pos_w_flat.dtype)
|
| 745 |
+
pq_h = torch.cat([pos_w_flat, ones_M], dim=-1).unsqueeze(-1) # (M, 4, 1)
|
| 746 |
+
pq_cam = (Tq @ pq_h).squeeze(-1)[..., :3] # (M, 3)
|
| 747 |
+
|
| 748 |
+
# Gather key positions and transform to camera space
|
| 749 |
+
gather_idx = corr_f2l.clamp(min=0, max=M - 1).reshape(-1) # (M*K,)
|
| 750 |
+
pk_world = pos_w_flat.index_select(0, gather_idx).view(M, K, 3) # (M, K, 3)
|
| 751 |
+
ones_MK = torch.ones(M, K, 1, device=device, dtype=pk_world.dtype)
|
| 752 |
+
pk_h = torch.cat([pk_world, ones_MK], dim=-1).unsqueeze(-1) # (M, K, 4, 1)
|
| 753 |
+
pk_cam = (Tq[:, None, :, :] @ pk_h).squeeze(-1)[..., :3] # (M, K, 3)
|
| 754 |
+
|
| 755 |
+
# Compute delta
|
| 756 |
+
pos_delta = pk_cam - pq_cam[:, None, :] # (M, K, 3)
|
| 757 |
+
|
| 758 |
+
return {
|
| 759 |
+
"cond_values": cond_values,
|
| 760 |
+
"pos_token": pos_token,
|
| 761 |
+
"w2c": w2c,
|
| 762 |
+
"corr_f2l": corr_f2l,
|
| 763 |
+
"corr_f2l_highres": corr_f2l_highres,
|
| 764 |
+
"pos_delta": pos_delta,
|
| 765 |
+
"decoded_albedo": albedo_r,
|
| 766 |
+
"decoded_orm": orm_r,
|
| 767 |
+
}
|
| 768 |
+
|
| 769 |
+
|
| 770 |
+
# =============================================================================
|
| 771 |
+
# Main Inference Function (using pre-loaded models)
|
| 772 |
+
# =============================================================================
|
| 773 |
+
|
| 774 |
+
@torch.no_grad()
|
| 775 |
+
def run_single(
|
| 776 |
+
mesh_path: str,
|
| 777 |
+
prompt: str,
|
| 778 |
+
output_dir: str,
|
| 779 |
+
context: InferenceContext,
|
| 780 |
+
*,
|
| 781 |
+
keep_strips: bool = False,
|
| 782 |
+
seed: int = 42,
|
| 783 |
+
) -> str:
|
| 784 |
+
"""Run inference on a single mesh using pre-loaded models from context.
|
| 785 |
+
|
| 786 |
+
This function produces identical results to running inference.py individually,
|
| 787 |
+
but reuses models from the context to avoid repeated loading.
|
| 788 |
+
"""
|
| 789 |
+
gid = Path(mesh_path).stem
|
| 790 |
+
settings = context.settings
|
| 791 |
+
|
| 792 |
+
# Generate deterministic seed per mesh
|
| 793 |
+
hash_input = f"{gid}_{seed}".encode('utf-8')
|
| 794 |
+
combined_seed = int(hashlib.md5(hash_input).hexdigest(), 16) % (2**32)
|
| 795 |
+
seed_everything(combined_seed)
|
| 796 |
+
|
| 797 |
+
device = settings["device"]
|
| 798 |
+
dtype = settings["dtype"]
|
| 799 |
+
height = settings["resolution"]
|
| 800 |
+
width = settings["resolution"]
|
| 801 |
+
num_views = settings["num_views"]
|
| 802 |
+
token_hw = height // 16
|
| 803 |
+
use_caa = settings["use_caa"]
|
| 804 |
+
use_rope = settings["use_rope"]
|
| 805 |
+
use_global_token = settings["use_global_token"]
|
| 806 |
+
use_global_pos = settings.get("use_global_pos", False)
|
| 807 |
+
use_dual_branch = settings["use_dual_branch"]
|
| 808 |
+
branch_factor = 2 if use_dual_branch else 1
|
| 809 |
+
|
| 810 |
+
# Render and preprocess geometry
|
| 811 |
+
rend = render_views(mesh_path, num_views=num_views, height=height, width=width, device=device)
|
| 812 |
+
preprocessed = preprocess_geometry(
|
| 813 |
+
rend,
|
| 814 |
+
device=device,
|
| 815 |
+
dtype=dtype,
|
| 816 |
+
height=height,
|
| 817 |
+
width=width,
|
| 818 |
+
token_hw=token_hw,
|
| 819 |
+
num_views=num_views,
|
| 820 |
+
use_caa=use_caa,
|
| 821 |
+
corr_dilate_iterations=settings["corr_dilate_iterations"],
|
| 822 |
+
use_global_pos=use_global_pos,
|
| 823 |
+
)
|
| 824 |
+
|
| 825 |
+
# Encode conditions if needed
|
| 826 |
+
cond_latents = None
|
| 827 |
+
condition_channels = 0
|
| 828 |
+
if settings.get("condition_channels_cfg", 0) > 0:
|
| 829 |
+
cond_latents = context.encode_conditions(preprocessed["cond_values"])
|
| 830 |
+
condition_channels = cond_latents.shape[1]
|
| 831 |
+
|
| 832 |
+
# Encode text prompts using pre-loaded text context
|
| 833 |
+
text_embeds = context.encode_text_prompts(prompt, settings["negative_prompt"])
|
| 834 |
+
|
| 835 |
+
def _repeat_branch(tensor: Optional[torch.Tensor]) -> Optional[torch.Tensor]:
|
| 836 |
+
if tensor is None:
|
| 837 |
+
return None
|
| 838 |
+
first_dim = tensor.shape[0]
|
| 839 |
+
if first_dim == num_views:
|
| 840 |
+
tensor = tensor.reshape(1, num_views, *tensor.shape[1:])
|
| 841 |
+
elif first_dim == 1:
|
| 842 |
+
tensor = tensor.reshape(1, *tensor.shape[1:]).unsqueeze(1)
|
| 843 |
+
else:
|
| 844 |
+
return tensor
|
| 845 |
+
tensor = tensor.repeat(branch_factor, 1, *[1] * (tensor.dim() - 2))
|
| 846 |
+
return tensor.reshape(branch_factor * num_views, *tensor.shape[2:])
|
| 847 |
+
|
| 848 |
+
prompt_embeds = _repeat_branch(text_embeds["prompt_embeds"])
|
| 849 |
+
pooled_prompt_embeds = _repeat_branch(text_embeds["pooled_prompt_embeds"])
|
| 850 |
+
negative_prompt_embeds = _repeat_branch(text_embeds["negative_prompt_embeds"])
|
| 851 |
+
negative_pooled_prompt_embeds = _repeat_branch(text_embeds["negative_pooled_prompt_embeds"])
|
| 852 |
+
cond_latents_branch = _repeat_branch(cond_latents) if cond_latents is not None else None
|
| 853 |
+
|
| 854 |
+
# Prepare correspondence data for CAA
|
| 855 |
+
corr_lookups_branch = None
|
| 856 |
+
pos_delta_branch = None
|
| 857 |
+
if use_caa:
|
| 858 |
+
corr_f2l = preprocessed["corr_f2l"]
|
| 859 |
+
pos_delta = preprocessed["pos_delta"]
|
| 860 |
+
|
| 861 |
+
if corr_f2l is not None and pos_delta is not None:
|
| 862 |
+
if use_dual_branch:
|
| 863 |
+
M, K = corr_f2l.shape
|
| 864 |
+
corr_branch_list = []
|
| 865 |
+
for b in range(branch_factor):
|
| 866 |
+
corr_b = corr_f2l + (b * M)
|
| 867 |
+
corr_branch_list.append(corr_b)
|
| 868 |
+
corr_lookups_branch = torch.cat(corr_branch_list, dim=0)
|
| 869 |
+
pos_delta_branch = pos_delta.repeat(branch_factor, 1, 1)
|
| 870 |
+
else:
|
| 871 |
+
corr_lookups_branch = corr_f2l
|
| 872 |
+
pos_delta_branch = pos_delta
|
| 873 |
+
|
| 874 |
+
# Generate latents using pre-loaded transformer
|
| 875 |
+
latents = generate_latents(
|
| 876 |
+
transformer=context.transformer,
|
| 877 |
+
noise_scheduler=context.scheduler,
|
| 878 |
+
prompt_embeds=prompt_embeds,
|
| 879 |
+
pooled_prompt_embeds=pooled_prompt_embeds,
|
| 880 |
+
negative_prompt_embeds=negative_prompt_embeds,
|
| 881 |
+
negative_pooled_prompt_embeds=negative_pooled_prompt_embeds,
|
| 882 |
+
height=height,
|
| 883 |
+
width=width,
|
| 884 |
+
num_inference_steps=settings["steps"],
|
| 885 |
+
guidance_scale=settings["guidance_scale"],
|
| 886 |
+
weight_dtype=dtype,
|
| 887 |
+
device=device,
|
| 888 |
+
condition_channels=condition_channels,
|
| 889 |
+
condition_latents=cond_latents_branch,
|
| 890 |
+
corr_lookups=corr_lookups_branch,
|
| 891 |
+
pos_delta=pos_delta_branch,
|
| 892 |
+
progress=True,
|
| 893 |
+
)
|
| 894 |
+
|
| 895 |
+
# Decode using pre-loaded VAE/mcVAE
|
| 896 |
+
inv = (latents / context.vae_scale) + context.vae_shift
|
| 897 |
+
|
| 898 |
+
if use_dual_branch:
|
| 899 |
+
decoded = context.vae.decode(inv.to(device=context.vae.device, dtype=context.vae.dtype)).sample
|
| 900 |
+
decoded = decoded.view(branch_factor, num_views, 3, height, width)
|
| 901 |
+
albedo_pred = decoded[0]
|
| 902 |
+
orm_pred_full = decoded[1]
|
| 903 |
+
combined = torch.cat([albedo_pred, orm_pred_full[:, 1:, :, :]], dim=1)
|
| 904 |
+
combined_flat = combined.reshape(num_views, 5, height, width)
|
| 905 |
+
else:
|
| 906 |
+
inv_mc = inv.to(device=context.mcvae.device, dtype=context.mcvae.dtype)
|
| 907 |
+
decoded = context.mcvae.decode_aug(inv_mc).sample
|
| 908 |
+
combined_flat = decoded.reshape(num_views, 5, height, width)
|
| 909 |
+
albedo_pred = combined_flat[:, :3]
|
| 910 |
+
orm_pred_full = combined_flat[:, 3:]
|
| 911 |
+
|
| 912 |
+
# Compute c-PSNR metrics
|
| 913 |
+
cpsnr_metrics = None
|
| 914 |
+
if preprocessed.get("corr_f2l_highres") is not None:
|
| 915 |
+
corr_high = preprocessed["corr_f2l_highres"]
|
| 916 |
+
|
| 917 |
+
B, VHW, K = corr_high.shape
|
| 918 |
+
base = torch.arange(B, device=corr_high.device).view(B, 1, 1) * VHW
|
| 919 |
+
corr_high_global = torch.where(corr_high >= 0, corr_high + base, corr_high)
|
| 920 |
+
corr_high_global = corr_high_global.reshape(B * VHW, K)
|
| 921 |
+
|
| 922 |
+
pred_rgb = combined_flat[:, :3].unsqueeze(0).squeeze(0)
|
| 923 |
+
pred_mr = combined_flat[:, 3:].unsqueeze(0).squeeze(0)
|
| 924 |
+
|
| 925 |
+
albedo_psnr, _, num_elements = correspondence_psnr(pred_rgb, corr_high_global, data_range=2.0)
|
| 926 |
+
roughness_psnr, _, _ = correspondence_psnr(pred_mr[:, 0:1], corr_high_global, data_range=2.0)
|
| 927 |
+
metallic_psnr, _, _ = correspondence_psnr(pred_mr[:, 1:2], corr_high_global, data_range=2.0)
|
| 928 |
+
|
| 929 |
+
pred_combined = combined_flat.unsqueeze(0).squeeze(0)
|
| 930 |
+
total_psnr, _, _ = correspondence_psnr(pred_combined, corr_high_global, data_range=2.0)
|
| 931 |
+
|
| 932 |
+
cpsnr_metrics = {
|
| 933 |
+
"albedo": float(albedo_psnr.item()),
|
| 934 |
+
"roughness": float(roughness_psnr.item()),
|
| 935 |
+
"metallic": float(metallic_psnr.item()),
|
| 936 |
+
"total": float(total_psnr.item()),
|
| 937 |
+
"num_elements": num_elements,
|
| 938 |
+
}
|
| 939 |
+
|
| 940 |
+
dec_albedo = albedo_pred
|
| 941 |
+
if use_dual_branch:
|
| 942 |
+
dec_orm = torch.cat([
|
| 943 |
+
torch.full_like(orm_pred_full[:, :1], fill_value=-1.0),
|
| 944 |
+
orm_pred_full[:, 1:, :, :],
|
| 945 |
+
], dim=1)
|
| 946 |
+
else:
|
| 947 |
+
dec_orm = torch.cat([
|
| 948 |
+
torch.full_like(combined_flat[:, :1], fill_value=-1.0),
|
| 949 |
+
combined_flat[:, (3, 4)],
|
| 950 |
+
], dim=1)
|
| 951 |
+
|
| 952 |
+
os.makedirs(output_dir, exist_ok=True)
|
| 953 |
+
|
| 954 |
+
# Save or create temporary strips
|
| 955 |
+
if keep_strips:
|
| 956 |
+
albedo_path = os.path.join(output_dir, f"{gid}_albedo_strip.png")
|
| 957 |
+
orm_path = os.path.join(output_dir, f"{gid}_orm_strip.png")
|
| 958 |
+
pos = preprocessed["cond_values"][:, :3].clone()
|
| 959 |
+
normal = preprocessed["cond_values"][:, 3:].clone()
|
| 960 |
+
cond_values_path = os.path.join(output_dir, f"{gid}_cond_values_strip.png")
|
| 961 |
+
cond_values_strip = torch.cat([(pos + 1.0) * 0.5, (normal + 1.0) * 0.5], dim=0).to("cpu")
|
| 962 |
+
_save_strip(cond_values_strip, cond_values_path, nrow=2)
|
| 963 |
+
else:
|
| 964 |
+
albedo_fd, albedo_path = tempfile.mkstemp(suffix="_albedo_strip.png")
|
| 965 |
+
orm_fd, orm_path = tempfile.mkstemp(suffix="_orm_strip.png")
|
| 966 |
+
os.close(albedo_fd)
|
| 967 |
+
os.close(orm_fd)
|
| 968 |
+
|
| 969 |
+
dec_albedo_cpu = dec_albedo.detach().to("cpu")
|
| 970 |
+
dec_orm_cpu = dec_orm.detach().to("cpu")
|
| 971 |
+
|
| 972 |
+
_save_strip(dec_albedo_cpu, albedo_path)
|
| 973 |
+
_save_strip(dec_orm_cpu, orm_path)
|
| 974 |
+
|
| 975 |
+
# Unprojection
|
| 976 |
+
unproj_backend = settings.get("unproj_backend", "mvadapter")
|
| 977 |
+
|
| 978 |
+
if unproj_backend == "materialmvp":
|
| 979 |
+
# Use pre-loaded unprojection context
|
| 980 |
+
albedo_strip_img = Image.open(albedo_path)
|
| 981 |
+
orm_strip_img = Image.open(orm_path)
|
| 982 |
+
|
| 983 |
+
strip_width = albedo_strip_img.width
|
| 984 |
+
strip_height = albedo_strip_img.height
|
| 985 |
+
view_width = strip_width // num_views
|
| 986 |
+
|
| 987 |
+
albedo_views = []
|
| 988 |
+
mr_views = []
|
| 989 |
+
|
| 990 |
+
for i in range(num_views):
|
| 991 |
+
left = i * view_width
|
| 992 |
+
right = (i + 1) * view_width
|
| 993 |
+
|
| 994 |
+
albedo_view = albedo_strip_img.crop((left, 0, right, strip_height))
|
| 995 |
+
albedo_views.append(albedo_view)
|
| 996 |
+
|
| 997 |
+
orm_view = orm_strip_img.crop((left, 0, right, strip_height))
|
| 998 |
+
mr_views.append(orm_view)
|
| 999 |
+
|
| 1000 |
+
output_path = context.unproj_ctx.unproject_with_materialmvp(
|
| 1001 |
+
mesh_path=mesh_path,
|
| 1002 |
+
output_dir=output_dir,
|
| 1003 |
+
gid=gid,
|
| 1004 |
+
albedo_images=albedo_views,
|
| 1005 |
+
mr_images=mr_views,
|
| 1006 |
+
bake_exp=2,
|
| 1007 |
+
)
|
| 1008 |
+
|
| 1009 |
+
if not keep_strips:
|
| 1010 |
+
for path in (albedo_path, orm_path):
|
| 1011 |
+
if path and os.path.exists(path):
|
| 1012 |
+
try:
|
| 1013 |
+
os.remove(path)
|
| 1014 |
+
except OSError:
|
| 1015 |
+
pass
|
| 1016 |
+
|
| 1017 |
+
else: # mvadapter (default)
|
| 1018 |
+
try:
|
| 1019 |
+
result = context.tex_pipeline(
|
| 1020 |
+
mesh_path=mesh_path,
|
| 1021 |
+
save_dir=output_dir,
|
| 1022 |
+
save_name=gid,
|
| 1023 |
+
uv_unwarp=True,
|
| 1024 |
+
preprocess_mesh=False,
|
| 1025 |
+
move_to_center=True,
|
| 1026 |
+
uv_size=settings["uv_size"],
|
| 1027 |
+
base_color_path=albedo_path,
|
| 1028 |
+
orm_path=orm_path,
|
| 1029 |
+
base_color_process_config=ModProcessConfig(view_upscale=True, inpaint_mode="view"),
|
| 1030 |
+
orm_process_config=ModProcessConfig(view_upscale=True, inpaint_mode="view"),
|
| 1031 |
+
camera_elevation_deg=ELEV_DEG,
|
| 1032 |
+
camera_azimuth_deg=AZIM_DEG,
|
| 1033 |
+
)
|
| 1034 |
+
output_path = result.pbr_model_save_path
|
| 1035 |
+
finally:
|
| 1036 |
+
if not keep_strips:
|
| 1037 |
+
for path in (albedo_path, orm_path):
|
| 1038 |
+
if path and os.path.exists(path):
|
| 1039 |
+
try:
|
| 1040 |
+
os.remove(path)
|
| 1041 |
+
except OSError:
|
| 1042 |
+
pass
|
| 1043 |
+
|
| 1044 |
+
# Save c-PSNR metrics
|
| 1045 |
+
if cpsnr_metrics is not None:
|
| 1046 |
+
cpsnr_json_path = os.path.join(output_dir, "cpsnr.json")
|
| 1047 |
+
_update_cpsnr_json(cpsnr_json_path, gid, cpsnr_metrics)
|
| 1048 |
+
|
| 1049 |
+
return output_path
|
| 1050 |
+
|
| 1051 |
+
|
| 1052 |
+
# =============================================================================
|
| 1053 |
+
# Batch Processing
|
| 1054 |
+
# =============================================================================
|
| 1055 |
+
|
| 1056 |
+
def run_batch(
|
| 1057 |
+
mesh_paths: List[str],
|
| 1058 |
+
prompts: List[str],
|
| 1059 |
+
output_dir: str,
|
| 1060 |
+
settings: Dict[str, Any],
|
| 1061 |
+
*,
|
| 1062 |
+
keep_strips: bool = False,
|
| 1063 |
+
seed: int = 42,
|
| 1064 |
+
) -> List[str]:
|
| 1065 |
+
"""Process multiple mesh-prompt pairs efficiently.
|
| 1066 |
+
|
| 1067 |
+
Models are loaded once and reused for all pairs.
|
| 1068 |
+
|
| 1069 |
+
Args:
|
| 1070 |
+
mesh_paths: List of paths to input meshes
|
| 1071 |
+
prompts: List of text prompts (must match length of mesh_paths)
|
| 1072 |
+
output_dir: Output directory for all results
|
| 1073 |
+
settings: Inference settings dictionary
|
| 1074 |
+
keep_strips: Whether to keep intermediate strip images
|
| 1075 |
+
seed: Base random seed
|
| 1076 |
+
|
| 1077 |
+
Returns:
|
| 1078 |
+
List of output GLB paths
|
| 1079 |
+
"""
|
| 1080 |
+
if len(mesh_paths) != len(prompts):
|
| 1081 |
+
raise ValueError(f"Number of meshes ({len(mesh_paths)}) must match number of prompts ({len(prompts)})")
|
| 1082 |
+
|
| 1083 |
+
print(f"Starting batch inference for {len(mesh_paths)} mesh-prompt pairs...")
|
| 1084 |
+
print(f"Loading models once for reuse...")
|
| 1085 |
+
|
| 1086 |
+
# Initialize context with all pre-loaded models
|
| 1087 |
+
context = InferenceContext(settings)
|
| 1088 |
+
|
| 1089 |
+
print(f"✓ Models loaded successfully")
|
| 1090 |
+
print()
|
| 1091 |
+
|
| 1092 |
+
output_paths = []
|
| 1093 |
+
|
| 1094 |
+
for idx, (mesh_path, prompt) in enumerate(zip(mesh_paths, prompts), 1):
|
| 1095 |
+
print(f"[{idx}/{len(mesh_paths)}] Processing: {Path(mesh_path).name}")
|
| 1096 |
+
print(f" Prompt: {prompt}")
|
| 1097 |
+
|
| 1098 |
+
try:
|
| 1099 |
+
output_path = run_single(
|
| 1100 |
+
mesh_path=mesh_path,
|
| 1101 |
+
prompt=prompt,
|
| 1102 |
+
output_dir=output_dir,
|
| 1103 |
+
context=context,
|
| 1104 |
+
keep_strips=keep_strips,
|
| 1105 |
+
seed=seed,
|
| 1106 |
+
)
|
| 1107 |
+
output_paths.append(output_path)
|
| 1108 |
+
print(f" ✓ Saved: {output_path}")
|
| 1109 |
+
except Exception as e:
|
| 1110 |
+
print(f" ✗ Error: {e}")
|
| 1111 |
+
output_paths.append(None)
|
| 1112 |
+
|
| 1113 |
+
print()
|
| 1114 |
+
|
| 1115 |
+
# Summary
|
| 1116 |
+
successful = sum(1 for p in output_paths if p is not None)
|
| 1117 |
+
print(f"Batch processing complete: {successful}/{len(mesh_paths)} successful")
|
| 1118 |
+
|
| 1119 |
+
return output_paths
|
| 1120 |
+
|
| 1121 |
+
|
| 1122 |
+
def build_inference_settings(args, cfg) -> Dict[str, Any]:
|
| 1123 |
+
"""Build inference settings from config file and CLI overrides."""
|
| 1124 |
+
model_cfg = cfg.model
|
| 1125 |
+
|
| 1126 |
+
# Apply CLI argument overrides
|
| 1127 |
+
if args.num_views is not None:
|
| 1128 |
+
model_cfg.num_views = args.num_views
|
| 1129 |
+
if args.resolution is not None:
|
| 1130 |
+
model_cfg.resolution = args.resolution
|
| 1131 |
+
if args.sd_vae_path is not None:
|
| 1132 |
+
model_cfg.sd_vae_path = args.sd_vae_path
|
| 1133 |
+
if args.mcvae_config is not None:
|
| 1134 |
+
model_cfg.mcvae_config = args.mcvae_config
|
| 1135 |
+
if args.mcvae_ckpt is not None:
|
| 1136 |
+
model_cfg.mcvae_ckpt = args.mcvae_ckpt
|
| 1137 |
+
|
| 1138 |
+
dtype_map = {
|
| 1139 |
+
"fp16": torch.float16,
|
| 1140 |
+
"bf16": torch.bfloat16,
|
| 1141 |
+
"fp32": torch.float32,
|
| 1142 |
+
}
|
| 1143 |
+
dtype = dtype_map.get(args.precision.lower(), DTYPE)
|
| 1144 |
+
|
| 1145 |
+
settings = {
|
| 1146 |
+
"device": args.device,
|
| 1147 |
+
"dtype": dtype,
|
| 1148 |
+
"resolution": int(model_cfg.resolution),
|
| 1149 |
+
"num_views": int(model_cfg.num_views),
|
| 1150 |
+
"use_caa": bool(getattr(model_cfg, "use_caa", True)),
|
| 1151 |
+
"use_rope": bool(getattr(model_cfg, "use_rope", False)),
|
| 1152 |
+
"use_global_token": bool(getattr(model_cfg, "use_global_token", False)),
|
| 1153 |
+
"use_global_pos": bool(getattr(model_cfg, "use_global_pos", False)),
|
| 1154 |
+
"use_dual_branch": bool(getattr(model_cfg, "use_dual_branch", False)),
|
| 1155 |
+
"corr_dilate_iterations": int(getattr(model_cfg, "corr_dilate_iterations", 2)),
|
| 1156 |
+
"condition_channels_cfg": int(getattr(model_cfg, "condition_channels", 0)),
|
| 1157 |
+
"base_model": model_cfg.pretrained_model_name_or_path,
|
| 1158 |
+
"model_cfg": model_cfg,
|
| 1159 |
+
"weights_path": args.weights or TRANSFORMER_PARTIAL_WEIGHTS,
|
| 1160 |
+
"sd_vae_path": model_cfg.sd_vae_path,
|
| 1161 |
+
"mcvae_config_path": model_cfg.mcvae_config,
|
| 1162 |
+
"mcvae_ckpt_path": model_cfg.mcvae_ckpt or MCVAE_CKPT_PATH,
|
| 1163 |
+
"mcvae_offset_mode": bool(getattr(model_cfg, "mcvae_offset_mode", True)),
|
| 1164 |
+
"negative_prompt": args.negative_prompt,
|
| 1165 |
+
"steps": args.steps,
|
| 1166 |
+
"guidance_scale": args.guidance,
|
| 1167 |
+
"realesrgan_ckpt": args.realesrgan_ckpt,
|
| 1168 |
+
"lama_ckpt": args.lama_ckpt,
|
| 1169 |
+
"uv_size": args.uv_size,
|
| 1170 |
+
"unproj_backend": args.unproj_backend,
|
| 1171 |
+
"config": cfg,
|
| 1172 |
+
}
|
| 1173 |
+
|
| 1174 |
+
return settings
|
| 1175 |
+
|
| 1176 |
+
|
| 1177 |
+
# =============================================================================
|
| 1178 |
+
# CLI Entry Point
|
| 1179 |
+
# =============================================================================
|
| 1180 |
+
|
| 1181 |
+
if __name__ == "__main__":
|
| 1182 |
+
import argparse
|
| 1183 |
+
|
| 1184 |
+
parser = argparse.ArgumentParser(
|
| 1185 |
+
description="Batch Mesh + Prompt → Textured GLB (efficient multi-sample processing)",
|
| 1186 |
+
epilog="""
|
| 1187 |
+
Examples:
|
| 1188 |
+
# Single mesh-prompt pair (same as inference.py)
|
| 1189 |
+
python inference_list.py --mesh model.glb --prompt "wooden chair"
|
| 1190 |
+
|
| 1191 |
+
# Multiple mesh-prompt pairs
|
| 1192 |
+
python inference_list.py --mesh 1.glb 2.glb 3.glb --prompt "p1" "p2" "p3"
|
| 1193 |
+
|
| 1194 |
+
Additional config overrides can be provided as key=value arguments:
|
| 1195 |
+
python inference_list.py --mesh a.glb b.glb --prompt "p1" "p2" model.num_views=8
|
| 1196 |
+
"""
|
| 1197 |
+
)
|
| 1198 |
+
parser.add_argument("--mesh", nargs="+", required=True, help="Path(s) to input mesh(es) (.glb or .obj)")
|
| 1199 |
+
parser.add_argument("--prompt", nargs="+", required=True, help="Text prompt(s) for texture generation (must match number of meshes)")
|
| 1200 |
+
parser.add_argument("--out", default="./temp_outputs", help="Output directory")
|
| 1201 |
+
parser.add_argument("--keep-strips", action="store_true", help="Keep intermediate albedo/ORM strip images")
|
| 1202 |
+
parser.add_argument("--guidance", type=float, default=GUIDANCE, help=f"Guidance scale (default: {GUIDANCE})")
|
| 1203 |
+
parser.add_argument("--steps", type=int, default=STEPS, help=f"Number of denoising steps (default: {STEPS})")
|
| 1204 |
+
parser.add_argument("--seed", type=int, default=42, help="Random seed (default: 42)")
|
| 1205 |
+
parser.add_argument("--negative-prompt", default=NEGATIVE_PROMPT, help="Negative prompt text")
|
| 1206 |
+
parser.add_argument("--device", default=DEVICE, help="Torch device to run inference on")
|
| 1207 |
+
parser.add_argument("--precision", default="fp16", choices=["fp16", "bf16", "fp32"], help="Computation precision")
|
| 1208 |
+
parser.add_argument("--num-views", type=int, default=None, help="Override number of camera views")
|
| 1209 |
+
parser.add_argument("--resolution", type=int, default=None, help="Override render resolution")
|
| 1210 |
+
parser.add_argument("--sd-vae-path", default=None, help="Override base SD VAE path")
|
| 1211 |
+
parser.add_argument("--mcvae-config", default=None, help="Override mcVAE config path")
|
| 1212 |
+
parser.add_argument("--mcvae-ckpt", default=None, help="Override mcVAE checkpoint path")
|
| 1213 |
+
parser.add_argument("--weights", default=None, help="Path to trained transformer weights (dir or file)")
|
| 1214 |
+
parser.add_argument("--realesrgan-ckpt", default="./checkpoints/RealESRGAN_x2plus.pth", help="RealESRGAN checkpoint path")
|
| 1215 |
+
parser.add_argument("--lama-ckpt", default="./checkpoints/big-lama.pt", help="LaMa inpainting checkpoint path")
|
| 1216 |
+
parser.add_argument("--uv-size", type=int, default=4096, help="Final UV texture resolution")
|
| 1217 |
+
parser.add_argument("--config", default="configs/mcdiff/default.yaml", help="Config file (supports base-variant inheritance)")
|
| 1218 |
+
parser.add_argument("--unproj-backend", default="mvadapter", choices=["mvadapter", "materialmvp"],
|
| 1219 |
+
help="Unprojection backend")
|
| 1220 |
+
|
| 1221 |
+
args, unknown = parser.parse_known_args()
|
| 1222 |
+
|
| 1223 |
+
# Validate mesh and prompt counts match
|
| 1224 |
+
if len(args.mesh) != len(args.prompt):
|
| 1225 |
+
parser.error(f"Number of --mesh arguments ({len(args.mesh)}) must match number of --prompt arguments ({len(args.prompt)})")
|
| 1226 |
+
|
| 1227 |
+
# Load config
|
| 1228 |
+
cfg = load_config(args.config, cli_overrides=unknown)
|
| 1229 |
+
|
| 1230 |
+
# Build settings
|
| 1231 |
+
settings = build_inference_settings(args, cfg)
|
| 1232 |
+
|
| 1233 |
+
# Run batch processing
|
| 1234 |
+
output_paths = run_batch(
|
| 1235 |
+
mesh_paths=args.mesh,
|
| 1236 |
+
prompts=args.prompt,
|
| 1237 |
+
output_dir=args.out,
|
| 1238 |
+
settings=settings,
|
| 1239 |
+
keep_strips=args.keep_strips,
|
| 1240 |
+
seed=args.seed,
|
| 1241 |
+
)
|
| 1242 |
+
|
| 1243 |
+
print("\n" + "="*60)
|
| 1244 |
+
print("Final Results:")
|
| 1245 |
+
print("="*60)
|
| 1246 |
+
for mesh_path, prompt, output_path in zip(args.mesh, args.prompt, output_paths):
|
| 1247 |
+
status = "✓" if output_path else "✗"
|
| 1248 |
+
print(f"{status} {Path(mesh_path).name}")
|
| 1249 |
+
if output_path:
|
| 1250 |
+
print(f" → {output_path}")
|
| 1251 |
+
print("="*60)
|
| 1252 |
+
|
| 1253 |
+
|
| 1254 |
+
'''
|
| 1255 |
+
# Single mesh (backward compatible with inference.py)
|
| 1256 |
+
python inference_list.py \
|
| 1257 |
+
--mesh /home/aaaaa/data/Arb-Objaverse/data/glb/000-132/21ec37b286474fedb43307f6f289269e.glb \
|
| 1258 |
+
--prompt "A wooden stool with a pink cushion" \
|
| 1259 |
+
--config configs/mcdiff/single_caa.yaml \
|
| 1260 |
+
--weights outputs/mcdiff_v.single_caa/checkpoint-35000/transformer/diffusion_pytorch_model.safetensors \
|
| 1261 |
+
--out temp_outputs_list
|
| 1262 |
+
|
| 1263 |
+
# Multiple meshes (efficient batch processing)
|
| 1264 |
+
python inference_list.py \
|
| 1265 |
+
--mesh mesh1.glb mesh2.glb mesh3.glb \
|
| 1266 |
+
--prompt "prompt 1" "prompt 2" "prompt 3" \
|
| 1267 |
+
--config configs/mcdiff/single_caa.yaml \
|
| 1268 |
+
--weights outputs/mcdiff_v.single_caa/checkpoint-35000/transformer/diffusion_pytorch_model.safetensors \
|
| 1269 |
+
--out temp_outputs_list_batch
|
| 1270 |
+
'''
|
home/ubuntu/aaaaa/data/rgbmr/latent_vis/base.png
ADDED
|
home/ubuntu/aaaaa/data/rgbmr/latent_vis/full.png
ADDED
|
home/ubuntu/aaaaa/data/rgbmr/latent_vis/offset.png
ADDED
|